2018-03-11 18:49:10 +00:00
package peer
2018-03-09 20:44:13 +00:00
import (
2019-01-04 21:44:21 +00:00
"cwtch.im/cwtch/event"
2018-05-28 18:05:06 +00:00
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/protocol"
2019-01-04 21:44:21 +00:00
"cwtch.im/cwtch/protocol/connections"
2018-06-09 07:49:18 +00:00
"encoding/base64"
2019-02-03 01:18:33 +00:00
"encoding/json"
2018-03-30 21:16:51 +00:00
"errors"
2018-11-10 22:14:12 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
2018-10-05 03:18:34 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
2019-01-13 23:48:17 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/log"
2018-10-05 03:18:34 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
2018-06-24 18:37:11 +00:00
"github.com/golang/protobuf/proto"
2018-06-09 07:49:18 +00:00
"golang.org/x/crypto/ed25519"
"strings"
2018-03-09 20:44:13 +00:00
"sync"
2019-01-22 19:11:25 +00:00
"time"
2018-03-09 20:44:13 +00:00
)
2018-10-06 03:50:55 +00:00
// cwtchPeer manages incoming and outgoing connections and all processing for a Cwtch cwtchPeer
2018-06-19 22:28:44 +00:00
type cwtchPeer struct {
2019-01-04 21:44:21 +00:00
Profile * model . Profile
mutex sync . Mutex
shutdown bool
started bool
engine * connections . Engine
queue * event . Queue
eventBus * event . Manager
2018-03-09 20:44:13 +00:00
}
2018-09-21 18:53:10 +00:00
// CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
2018-06-19 22:28:44 +00:00
// directly implement a cwtchPeer.
2018-09-21 18:53:10 +00:00
type CwtchPeer interface {
2019-01-04 21:44:21 +00:00
Init ( connectivity . ACN , * event . Manager )
2018-10-04 19:15:03 +00:00
PeerWithOnion ( string ) * connections . PeerPeerConnection
2018-06-19 22:28:44 +00:00
InviteOnionToGroup ( string , string ) error
2019-01-15 20:59:54 +00:00
SendMessageToPeer ( string , string ) string
2018-06-19 22:28:44 +00:00
TrustPeer ( string ) error
BlockPeer ( string ) error
AcceptInvite ( string ) error
RejectInvite ( string )
JoinServer ( string )
SendMessageToGroup ( string , string ) error
GetProfile ( ) * model . Profile
GetPeers ( ) map [ string ] connections . ConnectionState
GetServers ( ) map [ string ] connections . ConnectionState
StartGroup ( string ) ( string , [ ] byte , error )
ImportGroup ( string ) ( string , error )
ExportGroup ( string ) ( string , error )
GetGroup ( string ) * model . Group
GetGroups ( ) [ ] string
2019-02-03 01:18:33 +00:00
AddContact ( nick , onion string , publickey [ ] byte , trusted bool )
2018-06-19 22:28:44 +00:00
GetContacts ( ) [ ] string
GetContact ( string ) * model . PublicProfile
2018-11-22 00:08:47 +00:00
IsStarted ( ) bool
2018-11-10 22:14:12 +00:00
Listen ( )
2018-06-19 22:28:44 +00:00
Shutdown ( )
}
2018-06-24 18:59:42 +00:00
// NewCwtchPeer creates and returns a new cwtchPeer with the given name.
2018-10-06 03:50:55 +00:00
func NewCwtchPeer ( name string ) CwtchPeer {
2018-06-23 23:56:51 +00:00
cp := new ( cwtchPeer )
2018-05-01 20:44:45 +00:00
cp . Profile = model . GenerateNewProfile ( name )
2018-11-10 22:14:12 +00:00
cp . shutdown = false
2018-10-06 03:50:55 +00:00
return cp
2018-10-15 00:59:53 +00:00
}
2018-10-06 03:50:55 +00:00
// FromProfile generates a new peer from a profile.
func FromProfile ( profile * model . Profile ) CwtchPeer {
cp := new ( cwtchPeer )
cp . Profile = profile
return cp
2018-03-09 20:44:13 +00:00
}
2018-10-06 03:50:55 +00:00
// Init instantiates a cwtchPeer
2019-01-04 21:44:21 +00:00
func ( cp * cwtchPeer ) Init ( acn connectivity . ACN , eventBus * event . Manager ) {
cp . queue = event . NewEventQueue ( 100 )
go cp . eventHandler ( )
cp . eventBus = eventBus
cp . eventBus . Subscribe ( event . EncryptedGroupMessage , cp . queue . EventChannel )
cp . eventBus . Subscribe ( event . NewGroupInvite , cp . queue . EventChannel )
2019-01-08 18:58:01 +00:00
// Calculate a list of Peers who have been Blocked.
blockedPeers := [ ] string { }
for _ , contact := range cp . Profile . GetContacts ( ) {
c , _ := cp . Profile . GetContact ( contact )
if c . Blocked {
blockedPeers = append ( blockedPeers , c . Onion )
}
}
2019-01-28 20:09:25 +00:00
// TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key.
2019-01-08 18:58:01 +00:00
cp . engine = connections . NewProtocolEngine ( cp . Profile . Ed25519PrivateKey , acn , eventBus , blockedPeers )
2019-01-04 21:44:21 +00:00
cp . engine . Identity = identity . InitializeV3 ( cp . Profile . Name , & cp . Profile . Ed25519PrivateKey , & cp . Profile . Ed25519PublicKey )
2018-03-09 20:44:13 +00:00
}
2018-06-09 07:49:18 +00:00
// ImportGroup intializes a group from an imported source rather than a peer invite
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) ImportGroup ( exportedInvite string ) ( groupID string , err error ) {
2018-10-05 03:18:34 +00:00
if strings . HasPrefix ( exportedInvite , "torv3" ) {
data , err := base64 . StdEncoding . DecodeString ( exportedInvite [ 5 + 44 : ] )
2018-06-09 07:49:18 +00:00
if err == nil {
cpp := & protocol . CwtchPeerPacket { }
2019-01-19 23:16:38 +00:00
err = proto . Unmarshal ( data , cpp )
2018-06-09 07:49:18 +00:00
if err == nil {
2018-10-05 03:18:34 +00:00
pk , err := base64 . StdEncoding . DecodeString ( exportedInvite [ 5 : 5 + 44 ] )
2018-06-09 07:49:18 +00:00
if err == nil {
edpk := ed25519 . PublicKey ( pk )
2018-10-05 03:18:34 +00:00
onion := utils . GetTorV3Hostname ( edpk )
2018-06-19 22:38:22 +00:00
cp . Profile . AddContact ( onion , & model . PublicProfile { Name : "" , Ed25519PublicKey : edpk , Trusted : true , Blocked : false , Onion : onion } )
2018-06-09 07:49:18 +00:00
cp . Profile . ProcessInvite ( cpp . GetGroupChatInvite ( ) , onion )
2019-02-11 18:50:39 +00:00
jsobj , err := json . Marshal ( cpp . GetGroupChatInvite ( ) )
2019-02-03 01:18:33 +00:00
if err == nil {
2019-02-11 18:50:39 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . NewGroupInvite , map [ event . Field ] string {
event . GroupInvite : string ( jsobj ) ,
2019-02-03 01:18:33 +00:00
} ) )
} else {
log . Errorf ( "error serializing group: %v" , err )
}
2018-06-09 07:49:18 +00:00
return cpp . GroupChatInvite . GetGroupName ( ) , nil
}
}
}
} else {
err = errors . New ( "unsupported exported group type" )
}
return
}
// ExportGroup serializes a group invite so it can be given offline
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) ExportGroup ( groupID string ) ( string , error ) {
2018-06-09 07:49:18 +00:00
group := cp . Profile . GetGroupByGroupID ( groupID )
if group != nil {
2018-10-03 05:46:00 +00:00
invite , err := group . Invite ( group . GetInitialMessage ( ) )
2018-06-09 07:49:18 +00:00
if err == nil {
2018-10-05 03:18:34 +00:00
exportedInvite := "torv3" + base64 . StdEncoding . EncodeToString ( cp . Profile . Ed25519PublicKey ) + base64 . StdEncoding . EncodeToString ( invite )
2018-06-09 07:49:18 +00:00
return exportedInvite , err
}
}
return "" , errors . New ( "group id could not be found" )
}
2018-06-19 22:28:44 +00:00
// StartGroup create a new group linked to the given server and returns the group ID, an invite or an error.
func ( cp * cwtchPeer ) StartGroup ( server string ) ( string , [ ] byte , error ) {
2019-02-03 01:18:33 +00:00
return cp . StartGroupWithMessage ( server , [ ] byte { } )
2018-06-19 22:28:44 +00:00
}
2018-09-21 18:02:46 +00:00
// StartGroupWithMessage create a new group linked to the given server and returns the group ID, an invite or an error.
2019-02-03 01:18:33 +00:00
func ( cp * cwtchPeer ) StartGroupWithMessage ( server string , initialMessage [ ] byte ) ( groupID string , invite [ ] byte , err error ) {
groupID , invite , err = cp . Profile . StartGroupWithMessage ( server , initialMessage )
if err == nil {
group := cp . GetGroup ( groupID )
jsobj , err := json . Marshal ( group )
2019-02-03 03:24:42 +00:00
if err == nil {
2019-02-03 01:18:33 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . GroupCreated , map [ event . Field ] string {
event . Data : string ( jsobj ) ,
} ) )
}
} else {
log . Errorf ( "error creating group: %v" , err )
}
return
2018-09-21 18:02:46 +00:00
}
2018-06-19 22:28:44 +00:00
// GetGroups returns an unordered list of all group IDs.
func ( cp * cwtchPeer ) GetGroups ( ) [ ] string {
return cp . Profile . GetGroups ( )
}
// GetGroup returns a pointer to a specific group, nil if no group exists.
func ( cp * cwtchPeer ) GetGroup ( groupID string ) * model . Group {
return cp . Profile . GetGroupByGroupID ( groupID )
}
2019-02-03 01:18:33 +00:00
func ( cp * cwtchPeer ) AddContact ( nick , onion string , publickey [ ] byte , trusted bool ) {
2019-02-11 21:40:20 +00:00
pp := & model . PublicProfile { Name : nick , Ed25519PublicKey : publickey , Trusted : trusted , Blocked : false , Onion : onion , Attributes : map [ string ] string { "nick" : nick } }
2019-02-03 01:18:33 +00:00
cp . Profile . AddContact ( onion , pp )
2019-02-04 19:39:58 +00:00
pd , _ := json . Marshal ( pp )
2019-02-03 01:18:33 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . PeerCreated , map [ event . Field ] string {
2019-02-04 19:39:58 +00:00
event . Data : string ( pd ) ,
2019-02-03 01:18:33 +00:00
event . RemotePeer : onion ,
} ) )
}
2018-06-19 22:28:44 +00:00
// GetContacts returns an unordered list of onions
func ( cp * cwtchPeer ) GetContacts ( ) [ ] string {
return cp . Profile . GetContacts ( )
}
// GetContact returns a given contact, nil is no such contact exists
func ( cp * cwtchPeer ) GetContact ( onion string ) * model . PublicProfile {
contact , _ := cp . Profile . GetContact ( onion )
return contact
}
2018-10-06 03:50:55 +00:00
// GetProfile returns the profile associated with this cwtchPeer.
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) GetProfile ( ) * model . Profile {
return cp . Profile
}
// PeerWithOnion is the entry point for cwtchPeer relationships
2018-10-04 19:15:03 +00:00
func ( cp * cwtchPeer ) PeerWithOnion ( onion string ) * connections . PeerPeerConnection {
2019-01-21 20:08:03 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . PeerRequest , map [ event . Field ] string { event . RemotePeer : onion } ) )
2019-01-04 21:44:21 +00:00
return nil
2018-03-09 20:44:13 +00:00
}
// InviteOnionToGroup kicks off the invite process
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) InviteOnionToGroup ( onion string , groupid string ) error {
2018-05-16 20:53:09 +00:00
group := cp . Profile . GetGroupByGroupID ( groupid )
2019-01-04 21:44:21 +00:00
if group == nil {
return errors . New ( "invalid group id" )
2018-03-15 20:53:22 +00:00
}
2018-03-09 20:44:13 +00:00
2019-01-04 21:44:21 +00:00
invite , err := group . Invite ( group . InitialMessage )
if err == nil {
2019-01-21 20:08:03 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . InvitePeerToGroup , map [ event . Field ] string { event . RemotePeer : onion , event . GroupInvite : string ( invite ) } ) )
2019-01-04 21:44:21 +00:00
}
return err
2018-03-30 21:16:51 +00:00
}
2018-05-16 21:31:06 +00:00
// JoinServer manages a new server connection with the given onion address
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) JoinServer ( onion string ) {
2019-01-21 20:08:03 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . JoinServer , map [ event . Field ] string { event . GroupServer : onion } ) )
2018-03-09 20:44:13 +00:00
}
2019-01-28 20:09:25 +00:00
// SendMessageToGroup attempts to sent the given message to the given group id.
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) SendMessageToGroup ( groupid string , message string ) error {
2018-05-16 20:53:09 +00:00
group := cp . Profile . GetGroupByGroupID ( groupid )
2018-05-03 04:12:45 +00:00
if group == nil {
2019-01-04 21:44:21 +00:00
return errors . New ( "invalid group id" )
2018-05-09 19:09:00 +00:00
}
2018-06-22 18:11:23 +00:00
ct , sig , err := cp . Profile . EncryptMessageToGroup ( message , groupid )
2019-01-04 21:44:21 +00:00
if err == nil {
2019-01-21 20:08:03 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . SendMessageToGroup , map [ event . Field ] string { event . GroupServer : group . GroupServer , event . Ciphertext : string ( ct ) , event . Signature : string ( sig ) } ) )
2018-03-30 21:16:51 +00:00
}
2019-01-04 21:44:21 +00:00
2018-05-09 19:09:00 +00:00
return err
2018-03-09 20:44:13 +00:00
}
2019-01-15 20:59:54 +00:00
func ( cp * cwtchPeer ) SendMessageToPeer ( onion string , message string ) string {
2019-01-21 20:08:03 +00:00
event := event . NewEvent ( event . SendMessageToPeer , map [ event . Field ] string { event . RemotePeer : onion , event . Data : message } )
2019-01-15 20:59:54 +00:00
cp . eventBus . Publish ( event )
return event . EventID
2019-01-04 21:44:21 +00:00
}
2018-05-16 21:31:06 +00:00
// GetPeers returns a list of peer connections.
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) GetPeers ( ) map [ string ] connections . ConnectionState {
2019-01-04 21:44:21 +00:00
return cp . engine . GetPeers ( )
2018-05-01 20:44:45 +00:00
}
2018-05-16 21:31:06 +00:00
// GetServers returns a list of server connections
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) GetServers ( ) map [ string ] connections . ConnectionState {
2019-01-04 21:44:21 +00:00
return cp . engine . GetServers ( )
2018-05-03 04:12:45 +00:00
}
2018-05-16 21:31:06 +00:00
// TrustPeer sets an existing peer relationship to trusted
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) TrustPeer ( peer string ) error {
2018-05-30 17:41:02 +00:00
err := cp . Profile . TrustPeer ( peer )
if err == nil {
cp . PeerWithOnion ( peer )
2018-05-03 19:23:02 +00:00
}
2018-05-30 17:41:02 +00:00
return err
2018-05-03 19:23:02 +00:00
}
2018-05-16 21:31:06 +00:00
// BlockPeer blocks an existing peer relationship.
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) BlockPeer ( peer string ) error {
2018-05-30 17:41:02 +00:00
err := cp . Profile . BlockPeer ( peer )
2019-01-21 20:08:03 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . BlockPeer , map [ event . Field ] string { event . RemotePeer : peer } ) )
2018-05-30 17:41:02 +00:00
return err
2018-05-03 19:23:02 +00:00
}
2018-05-16 21:31:06 +00:00
// AcceptInvite accepts a given existing group invite
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) AcceptInvite ( groupID string ) error {
2018-05-30 17:41:02 +00:00
return cp . Profile . AcceptInvite ( groupID )
2018-05-03 04:12:45 +00:00
}
2018-05-16 21:31:06 +00:00
// RejectInvite rejects a given group invite.
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) RejectInvite ( groupID string ) {
2018-05-30 17:41:02 +00:00
cp . Profile . RejectInvite ( groupID )
}
2018-11-10 22:14:12 +00:00
func ( cp * cwtchPeer ) Listen ( ) {
2019-01-21 18:47:07 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . ProtocolEngineStartListen , map [ event . Field ] string { } ) )
2018-03-09 20:44:13 +00:00
}
2018-06-15 16:21:07 +00:00
// Shutdown kills all connections and cleans up all goroutines for the peer
2018-06-19 22:28:44 +00:00
func ( cp * cwtchPeer ) Shutdown ( ) {
2018-11-10 22:14:12 +00:00
cp . shutdown = true
2019-01-04 21:44:21 +00:00
cp . engine . Shutdown ( )
cp . queue . Shutdown ( )
2018-05-30 18:42:17 +00:00
}
2018-11-22 00:08:47 +00:00
// IsStarted returns true if Listen() has successfully been run before on this connection (ever). TODO: we will need to properly unset this flag on error if we want to support resumption in the future
func ( cp * cwtchPeer ) IsStarted ( ) bool {
return cp . started
}
2019-01-04 21:44:21 +00:00
// eventHandler process events from other subsystems
func ( cp * cwtchPeer ) eventHandler ( ) {
for {
ev := cp . queue . Next ( )
switch ev . EventType {
case event . EncryptedGroupMessage :
2019-02-03 03:24:42 +00:00
ok , groupID , message , seen := cp . Profile . AttemptDecryption ( [ ] byte ( ev . Data [ event . Ciphertext ] ) , [ ] byte ( ev . Data [ event . Signature ] ) )
if ok && ! seen {
2019-02-04 01:55:13 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . NewMessageFromGroup , map [ event . Field ] string { event . TimestampReceived : message . Received . Format ( time . RFC3339Nano ) , event . TimestampSent : message . Timestamp . Format ( time . RFC3339Nano ) , event . Data : message . Message , event . GroupID : groupID , event . Signature : string ( message . Signature ) , event . PreviousSignature : string ( message . PreviousMessageSig ) , event . RemotePeer : message . PeerID } ) )
2019-01-04 21:44:21 +00:00
}
case event . NewGroupInvite :
var groupInvite protocol . GroupChatInvite
2019-02-11 18:50:39 +00:00
proto . Unmarshal ( [ ] byte ( ev . Data [ event . GroupInvite ] ) , & groupInvite )
2019-01-21 20:08:03 +00:00
cp . Profile . ProcessInvite ( & groupInvite , ev . Data [ event . RemotePeer ] )
2019-01-04 21:44:21 +00:00
default :
if ev . EventType != "" {
2019-01-13 23:48:17 +00:00
log . Errorf ( "peer event handler received an event it was not subscribed for: %v" , ev . EventType )
2019-01-04 21:44:21 +00:00
}
2019-01-21 20:08:03 +00:00
return
2019-01-04 21:44:21 +00:00
}
}
2018-10-04 19:15:03 +00:00
}