2018-03-11 18:49:10 +00:00
|
|
|
package peer
|
2018-03-09 20:44:13 +00:00
|
|
|
|
|
|
|
import (
|
2021-10-15 19:38:22 +00:00
|
|
|
"cwtch.im/cwtch/model/constants"
|
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"
|
2021-09-30 00:57:13 +00:00
|
|
|
"fmt"
|
2021-11-09 23:47:33 +00:00
|
|
|
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
|
|
|
"git.openprivacy.ca/openprivacy/connectivity"
|
2021-09-30 00:57:13 +00:00
|
|
|
"runtime"
|
2020-03-07 07:41:00 +00:00
|
|
|
"strconv"
|
2018-06-09 07:49:18 +00:00
|
|
|
"strings"
|
2018-03-09 20:44:13 +00:00
|
|
|
"sync"
|
2019-01-22 19:11:25 +00:00
|
|
|
"time"
|
2021-09-30 00:57:13 +00:00
|
|
|
|
|
|
|
"cwtch.im/cwtch/event"
|
|
|
|
"cwtch.im/cwtch/model"
|
|
|
|
"cwtch.im/cwtch/model/attr"
|
|
|
|
"cwtch.im/cwtch/protocol/connections"
|
|
|
|
"cwtch.im/cwtch/protocol/files"
|
|
|
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
|
|
|
"git.openprivacy.ca/openprivacy/log"
|
2018-03-09 20:44:13 +00:00
|
|
|
)
|
|
|
|
|
2021-09-29 20:52:48 +00:00
|
|
|
const lastKnownSignature = "LastKnowSignature"
|
2021-05-07 23:16:22 +00:00
|
|
|
|
2019-10-18 23:56:10 +00:00
|
|
|
var autoHandleableEvents = map[event.Type]bool{event.EncryptedGroupMessage: true, event.PeerStateChange: true,
|
|
|
|
event.ServerStateChange: true, event.NewGroupInvite: true, event.NewMessageFromPeer: true,
|
2021-05-28 08:45:59 +00:00
|
|
|
event.PeerAcknowledgement: true, event.PeerError: true, event.SendMessageToPeerError: true, event.SendMessageToGroupError: true,
|
2021-09-30 00:57:13 +00:00
|
|
|
event.NewGetValMessageFromPeer: true, event.NewRetValMessageFromPeer: true, event.ProtocolEngineStopped: true, event.RetryServerRequest: true,
|
|
|
|
event.ManifestSizeReceived: true, event.ManifestReceived: true}
|
2019-09-19 23:14:35 +00:00
|
|
|
|
2020-10-22 23:21:33 +00:00
|
|
|
// DefaultEventsToHandle specifies which events will be subscribed to
|
|
|
|
// when a peer has its Init() function called
|
2020-10-03 22:13:06 +00:00
|
|
|
var DefaultEventsToHandle = []event.Type{
|
|
|
|
event.EncryptedGroupMessage,
|
|
|
|
event.NewMessageFromPeer,
|
|
|
|
event.PeerAcknowledgement,
|
|
|
|
event.NewGroupInvite,
|
|
|
|
event.PeerError,
|
|
|
|
event.SendMessageToGroupError,
|
|
|
|
event.NewGetValMessageFromPeer,
|
2021-04-13 22:12:12 +00:00
|
|
|
event.ProtocolEngineStopped,
|
2021-04-28 19:47:55 +00:00
|
|
|
event.RetryServerRequest,
|
2020-10-03 22:13:06 +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 {
|
2021-04-13 22:12:12 +00:00
|
|
|
Profile *model.Profile
|
|
|
|
mutex sync.Mutex
|
|
|
|
shutdown bool
|
|
|
|
listenStatus bool
|
2021-11-09 23:47:33 +00:00
|
|
|
storage *CwtchProfileStorage
|
|
|
|
|
|
|
|
state map[string]connections.ConnectionState
|
2019-01-04 21:44:21 +00:00
|
|
|
|
2019-08-14 20:56:45 +00:00
|
|
|
queue event.Queue
|
2019-06-05 20:40:55 +00:00
|
|
|
eventBus event.Manager
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// GenerateProtocolEngine
|
|
|
|
// Status: New in 1.5
|
|
|
|
func (cp *cwtchPeer) GenerateProtocolEngine(acn connectivity.ACN, bus event.Manager) connections.Engine {
|
|
|
|
return connections.NewProtocolEngine(cp.GetIdentity(), cp.Profile.Ed25519PrivateKey, acn, bus, cp.Profile.ContactsAuthorizations())
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetIdentity
|
|
|
|
// Status: New in 1.5
|
|
|
|
func (cp *cwtchPeer) GetIdentity() primitives.Identity {
|
|
|
|
return primitives.InitializeIdentity("", &cp.Profile.Ed25519PrivateKey, &cp.Profile.Ed25519PublicKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendScopedZonedGetValToContact
|
|
|
|
// Status: No change in 1.5
|
2021-10-07 22:40:25 +00:00
|
|
|
func (cp *cwtchPeer) SendScopedZonedGetValToContact(handle string, scope attr.Scope, zone attr.Zone, path string) {
|
2021-10-30 00:44:16 +00:00
|
|
|
ev := event.NewEventList(event.SendGetValMessageToPeer, event.RemotePeer, handle, event.Scope, string(scope), event.Path, string(zone.ConstructZonedPath(path)))
|
|
|
|
cp.eventBus.Publish(ev)
|
2021-10-07 22:40:25 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// GetScopedZonedAttribute
|
|
|
|
// Status: Ready for 1.5
|
2021-10-07 22:40:25 +00:00
|
|
|
func (cp *cwtchPeer) GetScopedZonedAttribute(scope attr.Scope, zone attr.Zone, key string) (string, bool) {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
2021-10-08 17:59:32 +00:00
|
|
|
scopedZonedKey := scope.ConstructScopedZonedPath(zone.ConstructZonedPath(key))
|
2021-10-07 22:40:25 +00:00
|
|
|
|
|
|
|
log.Debugf("looking up attribute %v %v %v (%v)", scope, zone, key, scopedZonedKey)
|
2021-11-09 23:47:33 +00:00
|
|
|
value, err := cp.storage.LoadProfileKeyValue(TypeAttribute, scopedZonedKey.ToString())
|
2021-10-07 22:40:25 +00:00
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
log.Debugf("found [%v] %v", string(value), err)
|
|
|
|
if err != nil {
|
|
|
|
return "", false
|
2021-10-07 22:40:25 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
return string(value), true
|
2021-10-07 22:40:25 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// SetScopedZonedAttribute
|
|
|
|
// Status: Ready for 1.5
|
2021-10-07 22:40:25 +00:00
|
|
|
func (cp *cwtchPeer) SetScopedZonedAttribute(scope attr.Scope, zone attr.Zone, key string, value string) {
|
|
|
|
cp.mutex.Lock()
|
2021-11-09 23:47:33 +00:00
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
|
2021-10-08 17:59:32 +00:00
|
|
|
scopedZonedKey := scope.ConstructScopedZonedPath(zone.ConstructZonedPath(key))
|
2021-10-07 22:40:25 +00:00
|
|
|
log.Debugf("storing attribute: %v = %v", scopedZonedKey, value)
|
2021-11-09 23:47:33 +00:00
|
|
|
err := cp.storage.StoreProfileKeyValue(TypeAttribute, scopedZonedKey.ToString(), []byte(value))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("error setting attribute %v")
|
|
|
|
}
|
2021-10-07 22:40:25 +00:00
|
|
|
}
|
|
|
|
|
2021-09-30 00:57:13 +00:00
|
|
|
// SendMessage is a higher level that merges sending messages to contacts and group handles
|
|
|
|
// If you try to send a message to a handle that doesn't exist, malformed or an incorrect type then
|
|
|
|
// this function will error
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
2021-09-30 00:57:13 +00:00
|
|
|
func (cp *cwtchPeer) SendMessage(handle string, message string) error {
|
2021-10-30 00:44:16 +00:00
|
|
|
|
|
|
|
var ev event.Event
|
2021-09-30 00:57:13 +00:00
|
|
|
// Group Handles are always 32 bytes in length, but we forgo any further testing here
|
2021-10-30 00:44:16 +00:00
|
|
|
// and delegate the group existence check to EncryptMessageToGroup
|
2021-09-30 00:57:13 +00:00
|
|
|
if len(handle) == 32 {
|
2021-11-09 23:47:33 +00:00
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
2021-10-30 00:44:16 +00:00
|
|
|
group := cp.Profile.GetGroup(handle)
|
|
|
|
if group == nil {
|
|
|
|
return errors.New("invalid group id")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Group adds it's own sent message to timeline
|
|
|
|
ct, sig, err := cp.Profile.EncryptMessageToGroup(message, handle)
|
|
|
|
|
|
|
|
// Group does not exist or some other unrecoverable error...
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ev = event.NewEvent(event.SendMessageToGroup, map[event.Field]string{event.GroupID: handle, event.GroupServer: group.GroupServer, event.Ciphertext: base64.StdEncoding.EncodeToString(ct), event.Signature: base64.StdEncoding.EncodeToString(sig)})
|
2021-09-30 00:57:13 +00:00
|
|
|
} else if tor.IsValidHostname(handle) {
|
|
|
|
// We assume we are sending to a Contact.
|
2021-11-09 23:47:33 +00:00
|
|
|
contact, exists := cp.FetchConversationInfo(handle)
|
2021-10-30 00:44:16 +00:00
|
|
|
ev = event.NewEvent(event.SendMessageToPeer, map[event.Field]string{event.RemotePeer: handle, event.Data: message})
|
|
|
|
// If the contact exists replace the event id wih the index of this message in the contacts timeline...
|
|
|
|
// Otherwise assume we don't log the message in the timeline...
|
2021-11-09 23:47:33 +00:00
|
|
|
if exists != nil {
|
|
|
|
//ev.EventID = strconv.Itoa(contact.Timeline.Len())
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
cp.storage.InsertMessage(contact.ID, 0, message, model.Attributes{"ack": event.False, "sent": time.Now().String()})
|
2021-10-30 00:44:16 +00:00
|
|
|
}
|
|
|
|
// Regardless we publish the send message to peer event for the protocol engine to execute on...
|
2021-09-30 00:57:13 +00:00
|
|
|
// We assume this is always successful as it is always valid to attempt to
|
|
|
|
// Contact a valid hostname
|
2021-10-30 00:44:16 +00:00
|
|
|
} else {
|
|
|
|
return errors.New("malformed handle type")
|
2021-09-30 00:57:13 +00:00
|
|
|
}
|
2021-10-30 00:44:16 +00:00
|
|
|
|
|
|
|
cp.eventBus.Publish(ev)
|
|
|
|
return nil
|
2021-09-30 00:57:13 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// UpdateMessageFlags
|
|
|
|
// Status: TODO
|
2021-06-08 22:29:04 +00:00
|
|
|
func (cp *cwtchPeer) UpdateMessageFlags(handle string, mIdx int, flags uint64) {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
log.Debugf("Updating Flags for %v %v %v", handle, mIdx, flags)
|
|
|
|
cp.Profile.UpdateMessageFlags(handle, mIdx, flags)
|
|
|
|
cp.eventBus.Publish(event.NewEvent(event.UpdateMessageFlags, map[event.Field]string{event.Handle: handle, event.Index: strconv.Itoa(mIdx), event.Flags: strconv.FormatUint(flags, 2)}))
|
|
|
|
}
|
|
|
|
|
2021-04-06 21:22:36 +00:00
|
|
|
// BlockUnknownConnections will auto disconnect from connections if authentication doesn't resolve a hostname
|
|
|
|
// known to peer.
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: Ready for 1.5
|
2021-04-06 21:22:36 +00:00
|
|
|
func (cp *cwtchPeer) BlockUnknownConnections() {
|
|
|
|
cp.eventBus.Publish(event.NewEvent(event.BlockUnknownPeers, map[event.Field]string{}))
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllowUnknownConnections will permit connections from unknown contacts.
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: Ready for 1.5
|
2021-04-06 21:22:36 +00:00
|
|
|
func (cp *cwtchPeer) AllowUnknownConnections() {
|
|
|
|
cp.eventBus.Publish(event.NewEvent(event.AllowUnknownPeers, map[event.Field]string{}))
|
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// NewProfileWithEncryptedStorage instantiates a new Cwtch Profile from encrypted storage
|
|
|
|
func NewProfileWithEncryptedStorage(name string, cps *CwtchProfileStorage) CwtchPeer {
|
|
|
|
cp := new(cwtchPeer)
|
|
|
|
cp.shutdown = false
|
|
|
|
cp.storage = cps
|
|
|
|
cp.state = make(map[string]connections.ConnectionState)
|
|
|
|
cp.Profile = model.GenerateNewProfile(name)
|
2021-03-29 18:53:02 +00:00
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// Store all the Necessary Base Attributes In The Database
|
|
|
|
cp.storage.StoreProfileKeyValue(TypeAttribute, attr.PublicScope.ConstructScopedZonedPath(attr.ProfileZone.ConstructZonedPath(constants.Name)).ToString(), []byte(name))
|
|
|
|
cp.storage.StoreProfileKeyValue(TypePrivateKey, "Ed25519PrivateKey", cp.Profile.Ed25519PrivateKey)
|
|
|
|
cp.storage.StoreProfileKeyValue(TypePublicKey, "Ed25519PublicKey", cp.Profile.Ed25519PublicKey)
|
2021-09-30 00:57:13 +00:00
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
return cp
|
2018-06-19 22:28:44 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// FromEncryptedStorage loads an existing Profile from Encrypted Storage
|
|
|
|
func FromEncryptedStorage(cps *CwtchProfileStorage) CwtchPeer {
|
2018-06-23 23:56:51 +00:00
|
|
|
cp := new(cwtchPeer)
|
2018-11-10 22:14:12 +00:00
|
|
|
cp.shutdown = false
|
2021-11-09 23:47:33 +00:00
|
|
|
cp.storage = cps
|
|
|
|
cp.state = make(map[string]connections.ConnectionState)
|
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.
|
2021-11-09 23:47:33 +00:00
|
|
|
// Deprecated - Only to be used for importing new profiles
|
|
|
|
func FromProfile(profile *model.Profile, cps *CwtchProfileStorage) CwtchPeer {
|
2018-10-06 03:50:55 +00:00
|
|
|
cp := new(cwtchPeer)
|
|
|
|
cp.Profile = profile
|
2019-09-19 23:14:35 +00:00
|
|
|
cp.shutdown = false
|
2021-11-09 23:47:33 +00:00
|
|
|
cp.storage = cps
|
|
|
|
|
|
|
|
// Store all the Necessary Base Attributes In The Database
|
|
|
|
cp.storage.StoreProfileKeyValue(TypeAttribute, "public.profile.name", []byte(profile.Name))
|
|
|
|
cp.storage.StoreProfileKeyValue(TypePrivateKey, "Ed25519PrivateKey", cp.Profile.Ed25519PrivateKey)
|
|
|
|
cp.storage.StoreProfileKeyValue(TypePublicKey, "Ed25519PublicKey", cp.Profile.Ed25519PublicKey)
|
|
|
|
|
2018-10-06 03:50:55 +00:00
|
|
|
return cp
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2018-10-06 03:50:55 +00:00
|
|
|
// Init instantiates a cwtchPeer
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: Ready for 1.5
|
2019-06-21 21:50:43 +00:00
|
|
|
func (cp *cwtchPeer) Init(eventBus event.Manager) {
|
2020-10-03 22:13:06 +00:00
|
|
|
cp.InitForEvents(eventBus, DefaultEventsToHandle)
|
2021-10-15 19:38:22 +00:00
|
|
|
|
|
|
|
// Upgrade the Cwtch Peer if necessary
|
|
|
|
// It would be nice to do these checks in the storage engine itself, but it is easier to do them here
|
|
|
|
// rather than duplicating the logic to construct/reconstruct attributes in storage engine...
|
2021-10-26 21:48:26 +00:00
|
|
|
// TODO: Remove these checks after Cwtch ~1.5 storage engine is implemented
|
|
|
|
if _, exists := cp.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name); !exists {
|
|
|
|
// If public.profile.name does not exist, and we have an existing public.name then:
|
|
|
|
// set public.profile.name from public.name
|
|
|
|
// set local.profile.name from public.name
|
|
|
|
if name, exists := cp.Profile.GetAttribute(attr.GetPublicScope(constants.Name)); exists {
|
|
|
|
cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, name)
|
2021-10-15 19:38:22 +00:00
|
|
|
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, name)
|
|
|
|
} else {
|
2021-10-26 21:48:26 +00:00
|
|
|
// Otherwise check if local.name exists and set it from that
|
|
|
|
// If not, then check the very old unzoned, unscoped name.
|
|
|
|
// If not, then set directly from Profile.Name...
|
|
|
|
if name, exists := cp.Profile.GetAttribute(attr.GetLocalScope(constants.Name)); exists {
|
|
|
|
cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, name)
|
|
|
|
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, name)
|
|
|
|
} else if name, exists := cp.Profile.GetAttribute(constants.Name); exists {
|
|
|
|
cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, name)
|
|
|
|
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, name)
|
|
|
|
} else {
|
|
|
|
// Profile.Name is very deprecated at this point...
|
|
|
|
cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, cp.Profile.Name)
|
|
|
|
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, cp.Profile.Name)
|
|
|
|
}
|
2021-10-15 19:38:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-26 21:48:26 +00:00
|
|
|
// At this point we can safely assume that public.profile.name exists
|
|
|
|
localName, _ := cp.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name)
|
|
|
|
publicName, _ := cp.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
|
|
|
|
|
|
|
if localName != publicName {
|
|
|
|
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, publicName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// At this point we can safely assume that public.profile.name exists AND is consistent with
|
|
|
|
// local.profile.name - regardless of whatever Cwtch version we have upgraded from. This will
|
|
|
|
// be important after Cwtch 1.5 when we purge all previous references to local.profile.name and
|
|
|
|
// profile-> name - and remove all name processing code from libcwtch-go.
|
|
|
|
|
2021-10-15 19:38:22 +00:00
|
|
|
// If local.profile.tag does not exist then set it from deprecated GetAttribute
|
|
|
|
if _, exists := cp.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Tag); !exists {
|
|
|
|
if tag, exists := cp.Profile.GetAttribute(constants.Tag); exists {
|
|
|
|
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Tag, tag)
|
|
|
|
} else {
|
|
|
|
// Assume a default password, which will allow the older profile to have it's password reset by the UI
|
|
|
|
cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Tag, constants.ProfileTypeV1DefaultPassword)
|
|
|
|
}
|
|
|
|
}
|
2020-10-03 22:13:06 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// InitForEvents
|
|
|
|
// Status: Ready for 1.5
|
2020-10-03 22:13:06 +00:00
|
|
|
func (cp *cwtchPeer) InitForEvents(eventBus event.Manager, toBeHandled []event.Type) {
|
2019-08-14 20:56:45 +00:00
|
|
|
cp.queue = event.NewQueue()
|
2019-01-04 21:44:21 +00:00
|
|
|
go cp.eventHandler()
|
|
|
|
|
|
|
|
cp.eventBus = eventBus
|
2020-10-03 22:13:06 +00:00
|
|
|
cp.AutoHandleEvents(toBeHandled)
|
2019-09-19 23:14:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AutoHandleEvents sets an event (if able) to be handled by this peer
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: Ready for 1.5
|
2019-09-19 23:14:35 +00:00
|
|
|
func (cp *cwtchPeer) AutoHandleEvents(events []event.Type) {
|
|
|
|
for _, ev := range events {
|
|
|
|
if _, exists := autoHandleableEvents[ev]; exists {
|
|
|
|
cp.eventBus.Subscribe(ev, cp.queue)
|
|
|
|
} else {
|
|
|
|
log.Errorf("Peer asked to autohandle event it cannot: %v\n", ev)
|
|
|
|
}
|
|
|
|
}
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2021-04-28 19:47:55 +00:00
|
|
|
// ImportGroup initializes a group from an imported source rather than a peer invite
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
2021-05-03 23:32:48 +00:00
|
|
|
func (cp *cwtchPeer) ImportGroup(exportedInvite string) (string, error) {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
gid, err := cp.Profile.ProcessInvite(exportedInvite)
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
cp.eventBus.Publish(event.NewEvent(event.NewGroup, map[event.Field]string{event.GroupID: gid, event.GroupInvite: exportedInvite}))
|
2018-06-09 07:49:18 +00:00
|
|
|
}
|
2021-05-03 23:32:48 +00:00
|
|
|
|
|
|
|
return gid, err
|
2018-06-09 07:49:18 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// NewContactConversation create a new p2p conversation with the given acl applied to the handle.
|
|
|
|
func (cp *cwtchPeer) NewContactConversation(handle string, acl model.AccessControl, accepted bool) error {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
return cp.storage.NewConversation(handle, model.Attributes{event.SaveHistoryKey: event.DeleteHistoryDefault}, model.AccessControlList{handle: acl}, accepted)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AcceptConversation looks up a conversation by `handle` and sets the Accepted status to `true`
|
|
|
|
// This will cause Cwtch to auto connect to this conversation on start up
|
|
|
|
func (cp *cwtchPeer) AcceptConversation(handle string) error {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
return cp.storage.AcceptConversation(handle)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FetchConversationInfo returns information about the given conversation referenced by the handle
|
|
|
|
func (cp *cwtchPeer) FetchConversationInfo(handle string) (*model.Conversation, error) {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
return cp.storage.GetConversation(handle)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteConversation purges all data about the conversation, including message timelines, referenced by the handle
|
|
|
|
func (cp *cwtchPeer) DeleteConversation(handle string) error {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
return cp.storage.DeleteConversation(handle)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetConversationAttribute sets the conversation attribute at path to value
|
|
|
|
func (cp *cwtchPeer) SetConversationAttribute(handle string, path attr.ScopedZonedPath, value string) error {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
return cp.storage.SetConversationAttribute(handle, path, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetConversationAttribute is a shortcut method for retrieving the value of a given path
|
|
|
|
func (cp *cwtchPeer) GetConversationAttribute(handle string, path attr.ScopedZonedPath) (string, error) {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
ci, err := cp.storage.GetConversation(handle)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
val, exists := ci.Attributes[path.ToString()]
|
|
|
|
if !exists {
|
|
|
|
return "", fmt.Errorf("%v does not exist for conversation %v", path.ToString(), handle)
|
|
|
|
}
|
|
|
|
return val, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cp *cwtchPeer) GetChannelMessage(conversation int, channel int, id int) (string, model.Attributes, error) {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
return cp.storage.GetChannelMessage(conversation, channel, id)
|
|
|
|
}
|
|
|
|
|
2018-06-09 07:49:18 +00:00
|
|
|
// ExportGroup serializes a group invite so it can be given offline
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
2018-06-19 22:28:44 +00:00
|
|
|
func (cp *cwtchPeer) ExportGroup(groupID string) (string, error) {
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
2019-10-31 21:39:31 +00:00
|
|
|
group := cp.Profile.GetGroup(groupID)
|
2018-06-09 07:49:18 +00:00
|
|
|
if group != nil {
|
2021-05-03 23:32:48 +00:00
|
|
|
return group.Invite()
|
2018-06-09 07:49:18 +00:00
|
|
|
}
|
|
|
|
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.
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
2021-05-03 23:32:48 +00:00
|
|
|
func (cp *cwtchPeer) StartGroup(server string) (string, string, error) {
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Lock()
|
2021-05-03 23:32:48 +00:00
|
|
|
groupID, invite, err := cp.Profile.StartGroup(server)
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Unlock()
|
2019-02-03 01:18:33 +00:00
|
|
|
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{
|
2021-05-28 08:45:59 +00:00
|
|
|
event.GroupID: groupID,
|
|
|
|
event.GroupServer: group.GroupServer,
|
|
|
|
event.GroupInvite: invite,
|
|
|
|
// Needed for Storage Engine...
|
2019-02-03 01:18:33 +00:00
|
|
|
event.Data: string(jsobj),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.Errorf("error creating group: %v", err)
|
|
|
|
}
|
2021-05-03 23:32:48 +00:00
|
|
|
return groupID, invite, err
|
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.
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
2018-06-19 22:28:44 +00:00
|
|
|
func (cp *cwtchPeer) GetGroups() []string {
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
2018-06-19 22:28:44 +00:00
|
|
|
return cp.Profile.GetGroups()
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetGroup returns a pointer to a specific group, nil if no group exists.
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
2018-06-19 22:28:44 +00:00
|
|
|
func (cp *cwtchPeer) GetGroup(groupID string) *model.Group {
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
2019-10-31 21:39:31 +00:00
|
|
|
return cp.Profile.GetGroup(groupID)
|
2018-06-19 22:28:44 +00:00
|
|
|
}
|
|
|
|
|
2020-07-23 21:40:44 +00:00
|
|
|
// AddServer takes in a serialized server specification (a bundle of related keys) and adds a contact for the
|
|
|
|
// server assuming there are no errors and the contact doesn't already exist.
|
|
|
|
// TODO in the future this function should also integrate with a trust provider to validate the key bundle.
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
2020-07-22 17:14:32 +00:00
|
|
|
func (cp *cwtchPeer) AddServer(serverSpecification string) error {
|
2021-11-09 23:47:33 +00:00
|
|
|
//// This confirms that the server did at least sign the bundle
|
|
|
|
//keyBundle, err := model.DeserializeAndVerify([]byte(serverSpecification))
|
|
|
|
//if err != nil {
|
|
|
|
// return err
|
|
|
|
//}
|
|
|
|
//log.Debugf("Got new key bundle %v", keyBundle.Serialize())
|
|
|
|
//
|
|
|
|
//// TODO if the key bundle is incomplete then error out. In the future we may allow servers to attest to new
|
|
|
|
//// keys or subsets of keys, but for now they must commit only to a complete set of keys required for Cwtch Groups
|
|
|
|
//// (that way we can be assured that the keybundle we store is a valid one)
|
|
|
|
//if !keyBundle.HasKeyType(model.KeyTypeTokenOnion) || !keyBundle.HasKeyType(model.KeyTypeServerOnion) || !keyBundle.HasKeyType(model.KeyTypePrivacyPass) {
|
|
|
|
// return errors.New("keybundle is incomplete")
|
|
|
|
//}
|
|
|
|
//
|
|
|
|
//if keyBundle.HasKeyType(model.KeyTypeServerOnion) {
|
|
|
|
// onionKey, _ := keyBundle.GetKey(model.KeyTypeServerOnion)
|
|
|
|
// onion := string(onionKey)
|
|
|
|
//
|
|
|
|
// // Add the contact if we don't already have it
|
|
|
|
// if cp.GetContact(onion) == nil {
|
|
|
|
// decodedPub, _ := base32.StdEncoding.DecodeString(strings.ToUpper(onion))
|
|
|
|
// ab := keyBundle.AttributeBundle()
|
|
|
|
// pp := &model.PublicProfile{Name: onion, Ed25519PublicKey: decodedPub, Authorization: model.AuthUnknown, Onion: onion, Attributes: ab}
|
|
|
|
//
|
|
|
|
// // The only part of this function that actually modifies the profile...
|
|
|
|
// cp.mutex.Lock()
|
|
|
|
// cp.Profile.AddContact(onion, pp)
|
|
|
|
// cp.mutex.Unlock()
|
|
|
|
//
|
|
|
|
// pd, _ := json.Marshal(pp)
|
|
|
|
//
|
|
|
|
// // Sync the Storage Engine
|
|
|
|
// cp.eventBus.Publish(event.NewEvent(event.PeerCreated, map[event.Field]string{
|
|
|
|
// event.Data: string(pd),
|
|
|
|
// event.RemotePeer: onion,
|
|
|
|
// }))
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // At this point we know the server exists
|
|
|
|
// server := cp.GetContact(onion)
|
|
|
|
// ab := keyBundle.AttributeBundle()
|
|
|
|
//
|
|
|
|
// // Check server bundle for consistency if we have different keys stored than in the tofu bundle then we
|
|
|
|
// // abort...
|
|
|
|
// for k, v := range ab {
|
|
|
|
// val, exists := server.GetAttribute(k)
|
|
|
|
// if exists {
|
|
|
|
// if val != v {
|
|
|
|
// // this is inconsistent!
|
|
|
|
// return model.InconsistentKeyBundleError
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// // we haven't seen this key associated with the server before
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Store the key bundle for the server so we can reconstruct a tofubundle invite
|
|
|
|
// cp.SetContactAttribute(onion, string(model.BundleType), serverSpecification)
|
|
|
|
//
|
|
|
|
// // If we have gotten to this point we can assume this is a safe key bundle signed by the
|
|
|
|
// // server with no conflicting keys. So we are going to publish all the keys
|
|
|
|
// for k, v := range ab {
|
|
|
|
// log.Debugf("Server (%v) has %v key %v", onion, k, v)
|
|
|
|
// cp.SetContactAttribute(onion, k, v)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return nil
|
|
|
|
//}
|
|
|
|
return nil
|
2018-06-19 22:28:44 +00:00
|
|
|
}
|
|
|
|
|
2020-12-17 01:40:03 +00:00
|
|
|
// GetServers returns an unordered list of servers
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
2020-12-17 01:40:03 +00:00
|
|
|
func (cp *cwtchPeer) GetServers() []string {
|
|
|
|
var servers []string
|
|
|
|
return servers
|
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// GetOnion
|
|
|
|
// Status: Deprecated in 1.5
|
2020-02-03 18:46:15 +00:00
|
|
|
func (cp *cwtchPeer) GetOnion() string {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
return cp.Profile.Onion
|
|
|
|
}
|
2021-11-09 23:47:33 +00:00
|
|
|
|
|
|
|
// GetPeerState
|
|
|
|
// Status: TODO
|
2020-02-03 18:46:15 +00:00
|
|
|
func (cp *cwtchPeer) GetPeerState(onion string) (connections.ConnectionState, bool) {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
if peer, ok := cp.Profile.Contacts[onion]; ok {
|
2021-03-19 21:20:22 +00:00
|
|
|
return connections.ConnectionStateToType()[peer.State], true
|
2020-02-03 18:46:15 +00:00
|
|
|
}
|
|
|
|
return connections.DISCONNECTED, false
|
2019-05-15 20:12:11 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// GetGroupState
|
|
|
|
// Status: TODO
|
2020-02-03 18:46:15 +00:00
|
|
|
func (cp *cwtchPeer) GetGroupState(groupid string) (connections.ConnectionState, bool) {
|
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
if group, ok := cp.Profile.Groups[groupid]; ok {
|
2021-03-19 21:20:22 +00:00
|
|
|
return connections.ConnectionStateToType()[group.State], true
|
2020-02-03 18:46:15 +00:00
|
|
|
}
|
|
|
|
return connections.DISCONNECTED, false
|
2019-05-15 20:12:11 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// PeerWithOnion initiates a request to the Protocol Engine to set up Cwtch Session with a given tor v3 onion
|
|
|
|
// address.
|
2019-07-17 19:10:52 +00:00
|
|
|
func (cp *cwtchPeer) PeerWithOnion(onion string) {
|
2019-01-21 20:08:03 +00:00
|
|
|
cp.eventBus.Publish(event.NewEvent(event.PeerRequest, map[event.Field]string{event.RemotePeer: onion}))
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// InviteOnionToGroup kicks off the invite process
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
2018-06-19 22:28:44 +00:00
|
|
|
func (cp *cwtchPeer) InviteOnionToGroup(onion string, groupid string) error {
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Lock()
|
2019-10-31 21:39:31 +00:00
|
|
|
group := cp.Profile.GetGroup(groupid)
|
2019-01-04 21:44:21 +00:00
|
|
|
if group == nil {
|
2021-05-04 19:23:25 +00:00
|
|
|
cp.mutex.Unlock()
|
2019-01-04 21:44:21 +00:00
|
|
|
return errors.New("invalid group id")
|
2018-03-15 20:53:22 +00:00
|
|
|
}
|
2021-05-03 23:32:48 +00:00
|
|
|
invite, err := group.Invite()
|
|
|
|
cp.mutex.Unlock()
|
2019-01-04 21:44:21 +00:00
|
|
|
if err == nil {
|
2021-10-30 00:44:16 +00:00
|
|
|
err = cp.SendMessage(onion, 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
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
2020-09-21 21:26:28 +00:00
|
|
|
func (cp *cwtchPeer) JoinServer(onion string) error {
|
2021-11-09 23:47:33 +00:00
|
|
|
//if cp.GetContact(onion) != nil {
|
|
|
|
// tokenY, yExists := cp.GetContact(onion).GetAttribute(string(model.KeyTypePrivacyPass))
|
|
|
|
// tokenOnion, onionExists := cp.GetContact(onion).GetAttribute(string(model.KeyTypeTokenOnion))
|
|
|
|
// if yExists && onionExists {
|
|
|
|
// signature, exists := cp.GetContactAttribute(onion, lastKnownSignature)
|
|
|
|
// if !exists {
|
|
|
|
// signature = base64.StdEncoding.EncodeToString([]byte{})
|
|
|
|
// }
|
|
|
|
// cp.eventBus.Publish(event.NewEvent(event.JoinServer, map[event.Field]string{event.GroupServer: onion, event.ServerTokenY: tokenY, event.ServerTokenOnion: tokenOnion, event.Signature: signature}))
|
|
|
|
// return nil
|
|
|
|
// }
|
|
|
|
//}
|
2020-09-21 21:26:28 +00:00
|
|
|
return errors.New("no keys found for server connection")
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2021-06-01 18:34:35 +00:00
|
|
|
// ResyncServer completely tears down and resyncs a new server connection with the given onion address
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
2021-06-01 18:34:35 +00:00
|
|
|
func (cp *cwtchPeer) ResyncServer(onion string) error {
|
2021-11-09 23:47:33 +00:00
|
|
|
//if cp.GetContact(onion) != nil {
|
|
|
|
// tokenY, yExists := cp.GetContact(onion).GetAttribute(string(model.KeyTypePrivacyPass))
|
|
|
|
// tokenOnion, onionExists := cp.GetContact(onion).GetAttribute(string(model.KeyTypeTokenOnion))
|
|
|
|
// if yExists && onionExists {
|
|
|
|
// signature := base64.StdEncoding.EncodeToString([]byte{})
|
|
|
|
// cp.eventBus.Publish(event.NewEvent(event.JoinServer, map[event.Field]string{event.GroupServer: onion, event.ServerTokenY: tokenY, event.ServerTokenOnion: tokenOnion, event.Signature: signature}))
|
|
|
|
// return nil
|
|
|
|
// }
|
|
|
|
//}
|
2021-06-01 18:34:35 +00:00
|
|
|
return errors.New("no keys found for server connection")
|
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// SendGetValToPeer
|
|
|
|
// Status: Ready for 1.5
|
2020-03-07 07:41:00 +00:00
|
|
|
func (cp *cwtchPeer) SendGetValToPeer(onion string, scope string, path string) {
|
2021-10-30 00:44:16 +00:00
|
|
|
ev := event.NewEventList(event.SendGetValMessageToPeer, event.RemotePeer, onion, event.Scope, scope, event.Path, path)
|
|
|
|
cp.eventBus.Publish(ev)
|
2020-03-07 07:41:00 +00:00
|
|
|
}
|
|
|
|
|
2021-10-30 00:44:16 +00:00
|
|
|
// Listen makes the peer open a listening port to accept incoming connections (and be detectably online)
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: Ready for 1.5
|
2018-11-10 22:14:12 +00:00
|
|
|
func (cp *cwtchPeer) Listen() {
|
2021-04-13 22:12:12 +00:00
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
2021-06-02 18:34:57 +00:00
|
|
|
if !cp.listenStatus {
|
2021-04-13 22:12:12 +00:00
|
|
|
log.Infof("cwtchPeer Listen sending ProtocolEngineStartListen\n")
|
|
|
|
cp.listenStatus = true
|
|
|
|
cp.eventBus.Publish(event.NewEvent(event.ProtocolEngineStartListen, map[event.Field]string{event.Onion: cp.Profile.Onion}))
|
|
|
|
}
|
2021-06-02 18:34:57 +00:00
|
|
|
// else protocol engine is already listening
|
|
|
|
|
2018-03-09 20:44:13 +00:00
|
|
|
}
|
|
|
|
|
2020-10-29 21:22:33 +00:00
|
|
|
// StartPeersConnections attempts to connect to peer connections
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: Ready for 1.5
|
2019-05-15 20:12:11 +00:00
|
|
|
func (cp *cwtchPeer) StartPeersConnections() {
|
2021-11-09 23:47:33 +00:00
|
|
|
//for _, contact := range cp.GetContacts() {
|
|
|
|
// if !cp.GetContact(contact).IsServer() {
|
|
|
|
// cp.PeerWithOnion(contact)
|
|
|
|
// }
|
|
|
|
//}
|
2020-10-29 21:22:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// StartServerConnections attempts to connect to all server connections
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: Ready for 1.5
|
2020-10-29 21:22:33 +00:00
|
|
|
func (cp *cwtchPeer) StartServerConnections() {
|
2021-11-09 23:47:33 +00:00
|
|
|
//for _, contact := range cp.GetContacts() {
|
|
|
|
// if cp.GetContact(contact).IsServer() {
|
|
|
|
// err := cp.JoinServer(contact)
|
|
|
|
// if err != nil {
|
|
|
|
// // Almost certainly a programming error so print it..
|
|
|
|
// log.Errorf("error joining server %v", err)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//}
|
2019-10-31 21:39:31 +00:00
|
|
|
}
|
|
|
|
|
2018-06-15 16:21:07 +00:00
|
|
|
// Shutdown kills all connections and cleans up all goroutines for the peer
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: Ready for 1.5
|
2018-06-19 22:28:44 +00:00
|
|
|
func (cp *cwtchPeer) Shutdown() {
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Lock()
|
|
|
|
defer cp.mutex.Unlock()
|
2018-11-10 22:14:12 +00:00
|
|
|
cp.shutdown = true
|
2019-01-04 21:44:21 +00:00
|
|
|
cp.queue.Shutdown()
|
2021-11-09 23:47:33 +00:00
|
|
|
if cp.storage != nil {
|
|
|
|
cp.storage.Close()
|
|
|
|
}
|
2018-05-30 18:42:17 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// Status: TODO
|
|
|
|
func (cp *cwtchPeer) storeMessage(handle string, message string, sent time.Time) error {
|
|
|
|
// TOOD maybe atomize this?
|
|
|
|
ci, err := cp.FetchConversationInfo(handle)
|
|
|
|
if err != nil {
|
|
|
|
err := cp.NewContactConversation(handle, model.DefaultP2PAccessControl(), false)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ci, err = cp.FetchConversationInfo(handle)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-11-12 21:17:06 +00:00
|
|
|
}
|
2020-10-03 23:09:47 +00:00
|
|
|
cp.mutex.Lock()
|
2021-11-09 23:47:33 +00:00
|
|
|
defer cp.mutex.Unlock()
|
|
|
|
return cp.storage.InsertMessage(ci.ID, 0, message, model.Attributes{"ack": event.True, "sent": sent.String()})
|
2020-10-03 23:09:47 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
// ShareFile begins hosting the given serialized manifest
|
|
|
|
// Status: Ready for 1.5
|
2021-09-30 00:57:13 +00:00
|
|
|
func (cp *cwtchPeer) ShareFile(fileKey string, serializedManifest string) {
|
|
|
|
cp.eventBus.Publish(event.NewEvent(event.ShareManifest, map[event.Field]string{event.FileKey: fileKey, event.SerializedManifest: serializedManifest}))
|
|
|
|
}
|
|
|
|
|
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 {
|
2019-09-19 23:14:35 +00:00
|
|
|
/***** Default auto handled events *****/
|
2021-04-13 22:12:12 +00:00
|
|
|
case event.ProtocolEngineStopped:
|
|
|
|
cp.mutex.Lock()
|
|
|
|
cp.listenStatus = false
|
|
|
|
log.Infof("Protocol engine for %v has stopped listening", cp.Profile.Onion)
|
|
|
|
cp.mutex.Unlock()
|
2019-01-04 21:44:21 +00:00
|
|
|
case event.EncryptedGroupMessage:
|
2019-10-18 23:56:10 +00:00
|
|
|
// If successful, a side effect is the message is added to the group's timeline
|
2021-05-07 23:16:22 +00:00
|
|
|
|
2021-03-29 18:53:02 +00:00
|
|
|
ciphertext, _ := base64.StdEncoding.DecodeString(ev.Data[event.Ciphertext])
|
|
|
|
signature, _ := base64.StdEncoding.DecodeString(ev.Data[event.Signature])
|
2021-06-01 18:34:35 +00:00
|
|
|
|
|
|
|
// SECURITY NOTE: A malicious server could insert posts such that everyone always has a different lastKnownSignature
|
|
|
|
// However the server can always replace **all** messages in an attempt to track users
|
|
|
|
// This is mitigated somewhat by resync events which do wipe things entire.
|
|
|
|
// The security of cwtch groups are also not dependent on the servers inability to uniquely tag connections (as long as
|
|
|
|
// it learns nothing else about each connection).
|
2021-06-24 01:29:23 +00:00
|
|
|
// store the base64 encoded signature for later use
|
2021-11-09 23:47:33 +00:00
|
|
|
cp.SetConversationAttribute(ev.Data[event.GroupServer], lastKnownSignature, ev.Data[event.Signature])
|
2021-06-01 18:34:35 +00:00
|
|
|
|
2021-05-07 23:16:22 +00:00
|
|
|
cp.mutex.Lock()
|
2021-10-30 00:44:16 +00:00
|
|
|
ok, groupID, message, index := cp.Profile.AttemptDecryption(ciphertext, signature)
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Unlock()
|
2021-10-30 00:44:16 +00:00
|
|
|
if ok && index > -1 {
|
|
|
|
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: base64.StdEncoding.EncodeToString(message.Signature), event.PreviousSignature: base64.StdEncoding.EncodeToString(message.PreviousMessageSig), event.RemotePeer: message.PeerID, event.Index: strconv.Itoa(index)}))
|
2019-01-04 21:44:21 +00:00
|
|
|
}
|
2019-09-19 23:14:35 +00:00
|
|
|
|
2021-05-14 18:26:04 +00:00
|
|
|
// The group has been compromised
|
|
|
|
if !ok && groupID != "" {
|
|
|
|
if cp.Profile.GetGroup(groupID).IsCompromised {
|
|
|
|
cp.eventBus.Publish(event.NewEvent(event.GroupCompromised, map[event.Field]string{event.GroupID: groupID}))
|
|
|
|
}
|
|
|
|
}
|
2019-10-18 23:56:10 +00:00
|
|
|
case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data
|
2020-10-03 23:49:05 +00:00
|
|
|
ts, _ := time.Parse(time.RFC3339Nano, ev.Data[event.TimestampReceived])
|
2021-10-08 17:59:32 +00:00
|
|
|
cp.storeMessage(ev.Data[event.RemotePeer], ev.Data[event.Data], ts)
|
2019-10-18 23:56:10 +00:00
|
|
|
|
|
|
|
case event.PeerAcknowledgement:
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Lock()
|
2021-05-03 18:35:35 +00:00
|
|
|
idx := cp.Profile.AckSentMessageToPeer(ev.Data[event.RemotePeer], ev.Data[event.EventID])
|
|
|
|
edata := ev.Data
|
|
|
|
edata[event.Index] = strconv.Itoa(idx)
|
|
|
|
cp.eventBus.Publish(event.NewEvent(event.IndexedAcknowledgement, edata))
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Unlock()
|
2019-10-18 23:56:10 +00:00
|
|
|
|
|
|
|
case event.SendMessageToGroupError:
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Lock()
|
2021-05-08 19:03:09 +00:00
|
|
|
signature, _ := base64.StdEncoding.DecodeString(ev.Data[event.Signature])
|
|
|
|
cp.Profile.AddGroupSentMessageError(ev.Data[event.GroupID], signature, ev.Data[event.Error])
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Unlock()
|
2019-10-18 23:56:10 +00:00
|
|
|
|
|
|
|
case event.SendMessageToPeerError:
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Lock()
|
2021-05-26 17:07:08 +00:00
|
|
|
idx := cp.Profile.ErrorSentMessageToPeer(ev.Data[event.RemotePeer], ev.Data[event.EventID], ev.Data[event.Error])
|
|
|
|
edata := ev.Data
|
|
|
|
edata[event.Index] = strconv.Itoa(idx)
|
|
|
|
cp.eventBus.Publish(event.NewEvent(event.IndexedFailure, edata))
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Unlock()
|
2021-04-28 19:47:55 +00:00
|
|
|
case event.RetryServerRequest:
|
|
|
|
// Automated Join Server Request triggered by a plugin.
|
2021-05-07 23:16:22 +00:00
|
|
|
log.Debugf("profile received an automated retry event for %v", ev.Data[event.GroupServer])
|
2021-10-30 00:44:16 +00:00
|
|
|
err := cp.JoinServer(ev.Data[event.GroupServer])
|
2021-10-31 19:16:50 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("error joining server... %v", err)
|
2021-10-30 00:44:16 +00:00
|
|
|
}
|
2020-03-07 07:41:00 +00:00
|
|
|
case event.NewGetValMessageFromPeer:
|
|
|
|
onion := ev.Data[event.RemotePeer]
|
|
|
|
scope := ev.Data[event.Scope]
|
|
|
|
path := ev.Data[event.Path]
|
|
|
|
|
2021-10-07 22:40:25 +00:00
|
|
|
log.Debugf("NewGetValMessageFromPeer for %v.%v from %v\n", scope, path, onion)
|
2020-03-07 07:41:00 +00:00
|
|
|
|
2021-11-09 23:47:33 +00:00
|
|
|
conversationInfo, _ := cp.FetchConversationInfo(onion)
|
|
|
|
if conversationInfo != nil && conversationInfo.Accepted {
|
2021-10-07 22:40:25 +00:00
|
|
|
scope := attr.IntoScope(scope)
|
|
|
|
if scope.IsPublic() || scope.IsConversation() {
|
2021-10-15 20:43:02 +00:00
|
|
|
zone, zpath := attr.ParseZone(path)
|
|
|
|
val, exists := cp.GetScopedZonedAttribute(scope, zone, zpath)
|
|
|
|
|
|
|
|
// NOTE: Temporary Override because UI currently wipes names if it can't find them...
|
|
|
|
if !exists && zone == attr.UnknownZone && path == constants.Name {
|
|
|
|
val, exists = cp.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:17:06 +00:00
|
|
|
resp := event.NewEvent(event.SendRetValMessageToPeer, map[event.Field]string{event.RemotePeer: onion, event.Exists: strconv.FormatBool(exists)})
|
|
|
|
resp.EventID = ev.EventID
|
|
|
|
if exists {
|
|
|
|
resp.Data[event.Data] = val
|
|
|
|
} else {
|
|
|
|
resp.Data[event.Data] = ""
|
|
|
|
}
|
|
|
|
log.Debugf("Responding with SendRetValMessageToPeer exists:%v data: %v\n", exists, val)
|
|
|
|
|
|
|
|
cp.eventBus.Publish(resp)
|
2020-03-07 07:41:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-30 00:44:16 +00:00
|
|
|
/***** Non default but requestable handleable events *****/
|
2020-04-17 00:00:17 +00:00
|
|
|
|
2021-09-30 00:57:13 +00:00
|
|
|
case event.ManifestReceived:
|
|
|
|
log.Debugf("Manifest Received Event!: %v", ev)
|
|
|
|
handle := ev.Data[event.Handle]
|
|
|
|
fileKey := ev.Data[event.FileKey]
|
|
|
|
serializedManifest := ev.Data[event.SerializedManifest]
|
|
|
|
|
2021-10-07 22:40:25 +00:00
|
|
|
manifestFilePath, exists := cp.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%v.manifest", fileKey))
|
2021-09-30 00:57:13 +00:00
|
|
|
if exists {
|
2021-10-07 22:40:25 +00:00
|
|
|
downloadFilePath, exists := cp.GetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fileKey)
|
2021-09-30 00:57:13 +00:00
|
|
|
if exists {
|
|
|
|
log.Debugf("downloading manifest to %v, file to %v", manifestFilePath, downloadFilePath)
|
|
|
|
var manifest files.Manifest
|
|
|
|
err := json.Unmarshal([]byte(serializedManifest), &manifest)
|
|
|
|
if err == nil {
|
|
|
|
manifest.Title = manifest.FileName
|
|
|
|
manifest.FileName = downloadFilePath
|
|
|
|
log.Debugf("saving manifest")
|
|
|
|
err = manifest.Save(manifestFilePath)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("could not save manifest: %v", err)
|
|
|
|
} else {
|
|
|
|
tempFile := ""
|
|
|
|
if runtime.GOOS == "android" {
|
|
|
|
tempFile = manifestFilePath[0 : len(manifestFilePath)-len(".manifest")]
|
|
|
|
log.Debugf("derived android temp path: %v", tempFile)
|
|
|
|
}
|
|
|
|
cp.eventBus.Publish(event.NewEvent(event.ManifestSaved, map[event.Field]string{
|
|
|
|
event.FileKey: fileKey,
|
|
|
|
event.Handle: handle,
|
|
|
|
event.SerializedManifest: string(manifest.Serialize()),
|
|
|
|
event.TempFile: tempFile,
|
|
|
|
event.NameSuggestion: manifest.Title,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.Errorf("error saving manifest: %v", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.Errorf("found manifest path but not download path for %v", fileKey)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.Errorf("no download path found for manifest: %v", fileKey)
|
|
|
|
}
|
|
|
|
|
2020-03-07 07:41:00 +00:00
|
|
|
case event.NewRetValMessageFromPeer:
|
|
|
|
onion := ev.Data[event.RemotePeer]
|
|
|
|
scope := ev.Data[event.Scope]
|
|
|
|
path := ev.Data[event.Path]
|
|
|
|
val := ev.Data[event.Data]
|
|
|
|
exists, _ := strconv.ParseBool(ev.Data[event.Exists])
|
|
|
|
log.Debugf("NewRetValMessageFromPeer %v %v%v %v %v\n", onion, scope, path, exists, val)
|
|
|
|
if exists {
|
2021-10-07 22:40:25 +00:00
|
|
|
|
|
|
|
// Handle File Sharing Metadata
|
|
|
|
// TODO This probably should be broken out to it's own code..
|
|
|
|
zone, path := attr.ParseZone(path)
|
|
|
|
if attr.Scope(scope).IsConversation() && zone == attr.FilesharingZone && strings.HasSuffix(path, ".manifest.size") {
|
|
|
|
fileKey := strings.Replace(path, ".manifest.size", "", 1)
|
|
|
|
size, err := strconv.Atoi(val)
|
|
|
|
// if size is valid and below the maximum size for a manifest
|
|
|
|
// this is to prevent malicious sharers from using large amounts of memory when distributing
|
|
|
|
// a manifest as we reconstruct this in-memory
|
|
|
|
if err == nil && size < files.MaxManifestSize {
|
|
|
|
cp.eventBus.Publish(event.NewEvent(event.ManifestSizeReceived, map[event.Field]string{event.FileKey: fileKey, event.ManifestSize: val, event.Handle: onion}))
|
2021-09-30 00:57:13 +00:00
|
|
|
} else {
|
2021-10-07 22:40:25 +00:00
|
|
|
cp.eventBus.Publish(event.NewEvent(event.ManifestError, map[event.Field]string{event.FileKey: fileKey, event.Handle: onion}))
|
2021-09-30 00:57:13 +00:00
|
|
|
}
|
2020-03-07 07:41:00 +00:00
|
|
|
}
|
2021-10-07 22:40:25 +00:00
|
|
|
|
|
|
|
// Allow public profile parameters to be added as peer specific attributes...
|
|
|
|
if attr.Scope(scope).IsPublic() && zone == attr.ProfileZone {
|
2021-11-09 23:47:33 +00:00
|
|
|
cp.SetConversationAttribute(onion, attr.Scope(scope).ConstructScopedZonedPath(zone.ConstructZonedPath(path)), val)
|
2021-10-07 22:40:25 +00:00
|
|
|
}
|
2020-03-07 07:41:00 +00:00
|
|
|
}
|
2019-05-15 20:12:11 +00:00
|
|
|
case event.PeerStateChange:
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Lock()
|
2021-11-09 23:47:33 +00:00
|
|
|
cp.state[ev.Data[event.RemotePeer]] = connections.ConnectionStateToType()[ev.Data[event.ConnectionState]]
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Unlock()
|
2019-05-15 20:12:11 +00:00
|
|
|
case event.ServerStateChange:
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Lock()
|
2021-11-09 23:47:33 +00:00
|
|
|
cp.state[ev.Data[event.GroupServer]] = connections.ConnectionStateToType()[ev.Data[event.ConnectionState]]
|
2020-02-03 18:46:15 +00:00
|
|
|
cp.mutex.Unlock()
|
2021-04-28 19:47:55 +00:00
|
|
|
|
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
|
|
|
}
|