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"
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"
)
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-04 21:44:21 +00:00
SendMessageToPeer ( 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
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-04 21:44:21 +00:00
// TODO: Would be nice if ProtocolEngine did not need to explictly 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 { }
err := proto . Unmarshal ( data , cpp )
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 )
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 ) {
return cp . Profile . StartGroup ( server )
}
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.
func ( cp * cwtchPeer ) StartGroupWithMessage ( server string , initialMessage [ ] byte ) ( string , [ ] byte , error ) {
return cp . Profile . StartGroupWithMessage ( server , initialMessage )
}
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 )
}
// 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-04 21:44:21 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . PeerRequest , map [ string ] string { "Onion" : onion } ) )
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 {
cp . eventBus . Publish ( event . NewEvent ( event . InvitePeerToGroup , map [ string ] string { "Onion" : onion , "Invite" : string ( invite ) } ) )
}
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-04 21:44:21 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . JoinServer , map [ string ] string { "Onion" : onion } ) )
2018-03-09 20:44:13 +00:00
}
2018-05-16 21:31:06 +00:00
// SendMessageToGroup attemps 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 {
cp . eventBus . Publish ( event . NewEvent ( event . SendMessageToGroup , map [ string ] string { "Server" : group . GroupServer , "Ciphertext" : string ( ct ) , "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-04 21:44:21 +00:00
func ( cp * cwtchPeer ) SendMessageToPeer ( onion string , message string ) {
cp . eventBus . Publish ( event . NewEvent ( event . SendMessageToPeer , map [ string ] string { "Peer" : onion , "Message" : message } ) )
}
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-04 21:44:21 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . BlockPeer , map [ string ] string { "Onion" : 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-04 21:44:21 +00:00
cp . eventBus . Publish ( event . NewEvent ( event . ProtocolEngineStartListen , map [ string ] 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 :
ok , groupID , _ := cp . Profile . AttemptDecryption ( [ ] byte ( ev . Data [ "Ciphertext" ] ) , [ ] byte ( ev . Data [ "Signature" ] ) )
if ok {
cp . eventBus . Publish ( event . NewEvent ( event . NewMessageFromGroup , map [ string ] string { "GroupID" : groupID } ) )
}
case event . NewGroupInvite :
var groupInvite protocol . GroupChatInvite
proto . Unmarshal ( [ ] byte ( ev . Data [ "GroupInvite" ] ) , & groupInvite )
cp . Profile . ProcessInvite ( & groupInvite , ev . Data [ "Onion" ] )
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
}
return
}
}
2018-10-04 19:15:03 +00:00
}