cwtch/peer/cwtch_peer.go

331 lines
11 KiB
Go
Raw Normal View History

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"
"encoding/base64"
2019-02-03 01:18:33 +00:00
"encoding/json"
2018-03-30 21:16:51 +00:00
"errors"
"git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
2018-10-05 03:18:34 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/identity"
"git.openprivacy.ca/openprivacy/libricochet-go/log"
2018-10-05 03:18:34 +00:00
"git.openprivacy.ca/openprivacy/libricochet-go/utils"
"github.com/golang/protobuf/proto"
"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
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
}
// CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
// directly implement a cwtchPeer.
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
InviteOnionToGroup(string, string) error
SendMessageToPeer(string, string) string
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)
GetContacts() []string
GetContact(string) *model.PublicProfile
2018-11-22 00:08:47 +00:00
IsStarted() bool
Listen()
Shutdown()
}
// NewCwtchPeer creates and returns a new cwtchPeer with the given name.
2018-10-06 03:50:55 +00:00
func NewCwtchPeer(name string) CwtchPeer {
cp := new(cwtchPeer)
cp.Profile = model.GenerateNewProfile(name)
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
}
// ImportGroup intializes a group from an imported source rather than a peer invite
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:])
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])
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})
cp.Profile.ProcessInvite(cpp.GetGroupChatInvite(), onion)
2019-02-03 01:18:33 +00:00
jsobj, err := json.Marshal(cp.GetGroup(cpp.GroupChatInvite.GetGroupName()))
if err == nil {
cp.eventBus.Publish(event.NewEvent(event.GroupCreated, map[event.Field]string{
event.Data: string(jsobj),
}))
} else {
log.Errorf("error serializing group: %v", err)
}
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
func (cp *cwtchPeer) ExportGroup(groupID string) (string, error) {
group := cp.Profile.GetGroupByGroupID(groupID)
if group != nil {
2018-10-03 05:46:00 +00:00
invite, err := group.Invite(group.GetInitialMessage())
if err == nil {
2018-10-05 03:18:34 +00:00
exportedInvite := "torv3" + base64.StdEncoding.EncodeToString(cp.Profile.Ed25519PublicKey) + base64.StdEncoding.EncodeToString(invite)
return exportedInvite, err
}
}
return "", errors.New("group id could not be found")
}
// 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{})
}
// 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)
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
}
// 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) {
pp := &model.PublicProfile{Name: nick, Ed25519PublicKey: publickey, Trusted: trusted, Blocked: false, Onion: onion, Attributes: map[string]string{"name": nick}}
cp.GetProfile().Contacts[onion] = pp
cp.Profile.AddContact(onion, pp)
cp.eventBus.Publish(event.NewEvent(event.PeerCreated, map[event.Field]string{
event.RemotePeer: onion,
}))
}
// 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.
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
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-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
}
// JoinServer manages a new server connection with the given onion address
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.
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")
}
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
return err
2018-03-09 20:44:13 +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})
cp.eventBus.Publish(event)
return event.EventID
2019-01-04 21:44:21 +00:00
}
// GetPeers returns a list of peer connections.
func (cp *cwtchPeer) GetPeers() map[string]connections.ConnectionState {
2019-01-04 21:44:21 +00:00
return cp.engine.GetPeers()
}
// GetServers returns a list of server connections
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
}
// TrustPeer sets an existing peer relationship to trusted
func (cp *cwtchPeer) TrustPeer(peer string) error {
err := cp.Profile.TrustPeer(peer)
if err == nil {
cp.PeerWithOnion(peer)
}
return err
}
// BlockPeer blocks an existing peer relationship.
func (cp *cwtchPeer) BlockPeer(peer string) error {
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}))
return err
}
// AcceptInvite accepts a given existing group invite
func (cp *cwtchPeer) AcceptInvite(groupID string) error {
return cp.Profile.AcceptInvite(groupID)
2018-05-03 04:12:45 +00:00
}
// RejectInvite rejects a given group invite.
func (cp *cwtchPeer) RejectInvite(groupID string) {
cp.Profile.RejectInvite(groupID)
}
func (cp *cwtchPeer) Listen() {
cp.eventBus.Publish(event.NewEvent(event.ProtocolEngineStartListen, map[event.Field]string{}))
2018-03-09 20:44:13 +00:00
}
// Shutdown kills all connections and cleans up all goroutines for the peer
func (cp *cwtchPeer) Shutdown() {
cp.shutdown = true
2019-01-04 21:44:21 +00:00
cp.engine.Shutdown()
cp.queue.Shutdown()
}
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, 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
proto.Unmarshal([]byte(ev.Data["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 != "" {
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
}