profile and peer messaging refactor. Profiles once again store timelines for peers, should be used as canonical timeline by frontend UI
the build was successful
Details
the build was successful
Details
This commit is contained in:
parent
832c4c28d5
commit
77d26d3877
|
@ -86,7 +86,6 @@ func (ac *applicationClient) newPeer(localID, password string, reload bool) {
|
||||||
if reload {
|
if reload {
|
||||||
ac.bridge.Write(&event.IPCMessage{Dest: DestApp, Message: event.NewEventList(event.ReloadPeer, event.Identity, profile.Onion)})
|
ac.bridge.Write(&event.IPCMessage{Dest: DestApp, Message: event.NewEventList(event.ReloadPeer, event.Identity, profile.Onion)})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePeer messages the service to create a new Peer with the given name
|
// CreatePeer messages the service to create a new Peer with the given name
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,7 +1,7 @@
|
||||||
module cwtch.im/cwtch
|
module cwtch.im/cwtch
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cwtch.im/tapir v0.1.10
|
cwtch.im/tapir v0.1.11
|
||||||
git.openprivacy.ca/openprivacy/libricochet-go v1.0.6
|
git.openprivacy.ca/openprivacy/libricochet-go v1.0.6
|
||||||
github.com/c-bata/go-prompt v0.2.3
|
github.com/c-bata/go-prompt v0.2.3
|
||||||
github.com/golang/protobuf v1.3.2
|
github.com/golang/protobuf v1.3.2
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -1,5 +1,7 @@
|
||||||
cwtch.im/tapir v0.1.10 h1:V+TkmwXNd6gySZqlVw468wMYEkmDwMSyvhkkpOfUw7w=
|
cwtch.im/tapir v0.1.10 h1:V+TkmwXNd6gySZqlVw468wMYEkmDwMSyvhkkpOfUw7w=
|
||||||
cwtch.im/tapir v0.1.10/go.mod h1:EuRYdVrwijeaGBQ4OijDDRHf7R2MDSypqHkSl5DxI34=
|
cwtch.im/tapir v0.1.10/go.mod h1:EuRYdVrwijeaGBQ4OijDDRHf7R2MDSypqHkSl5DxI34=
|
||||||
|
cwtch.im/tapir v0.1.11 h1:JLm1MIYq4VXKzhj68+P8OuVPllAU9U6G0DtUor2fbc4=
|
||||||
|
cwtch.im/tapir v0.1.11/go.mod h1:EuRYdVrwijeaGBQ4OijDDRHf7R2MDSypqHkSl5DxI34=
|
||||||
git.openprivacy.ca/openprivacy/libricochet-go v1.0.4 h1:GWLMJ5jBSIC/gFXzdbbeVz7fIAn2FTgW8+wBci6/3Ek=
|
git.openprivacy.ca/openprivacy/libricochet-go v1.0.4 h1:GWLMJ5jBSIC/gFXzdbbeVz7fIAn2FTgW8+wBci6/3Ek=
|
||||||
git.openprivacy.ca/openprivacy/libricochet-go v1.0.4/go.mod h1:yMSG1gBaP4f1U+RMZXN85d29D39OK5s8aTpyVRoH5FY=
|
git.openprivacy.ca/openprivacy/libricochet-go v1.0.4/go.mod h1:yMSG1gBaP4f1U+RMZXN85d29D39OK5s8aTpyVRoH5FY=
|
||||||
git.openprivacy.ca/openprivacy/libricochet-go v1.0.6 h1:5o4K2qn3otEE1InC5v5CzU0yL7Wl7DhVp4s8H3K6mXY=
|
git.openprivacy.ca/openprivacy/libricochet-go v1.0.6 h1:5o4K2qn3otEE1InC5v5CzU0yL7Wl7DhVp4s8H3K6mXY=
|
||||||
|
|
|
@ -112,11 +112,32 @@ func (g *Group) AddSentMessage(message *protocol.DecryptedGroupMessage, sig []by
|
||||||
Signature: sig,
|
Signature: sig,
|
||||||
PeerID: message.GetOnion(),
|
PeerID: message.GetOnion(),
|
||||||
PreviousMessageSig: message.GetPreviousMessageSig(),
|
PreviousMessageSig: message.GetPreviousMessageSig(),
|
||||||
|
ReceivedByServer: false,
|
||||||
}
|
}
|
||||||
g.unacknowledgedMessages = append(g.unacknowledgedMessages, timelineMessage)
|
g.unacknowledgedMessages = append(g.unacknowledgedMessages, timelineMessage)
|
||||||
return timelineMessage
|
return timelineMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrorSentMessage removes a sent message from the unacknowledged list and sets its error flag if found, otherwise returns false
|
||||||
|
func (g *Group) ErrorSentMessage(sig []byte, error string) bool {
|
||||||
|
g.lock.Lock()
|
||||||
|
defer g.lock.Unlock()
|
||||||
|
var message *Message
|
||||||
|
|
||||||
|
// Delete the message from the unack'd buffer if it exists
|
||||||
|
for i, unAckedMessage := range g.unacknowledgedMessages {
|
||||||
|
if compareSignatures(unAckedMessage.Signature, sig) {
|
||||||
|
message = &unAckedMessage
|
||||||
|
g.unacknowledgedMessages = append(g.unacknowledgedMessages[:i], g.unacknowledgedMessages[i+1:]...)
|
||||||
|
|
||||||
|
message.Error = error
|
||||||
|
g.Timeline.Insert(message)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// AddMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
|
// AddMessage takes a DecryptedGroupMessage and adds it to the Groups Timeline
|
||||||
func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte) (*Message, bool) {
|
func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte) (*Message, bool) {
|
||||||
|
|
||||||
|
@ -138,6 +159,8 @@ func (g *Group) AddMessage(message *protocol.DecryptedGroupMessage, sig []byte)
|
||||||
Signature: sig,
|
Signature: sig,
|
||||||
PeerID: message.GetOnion(),
|
PeerID: message.GetOnion(),
|
||||||
PreviousMessageSig: message.GetPreviousMessageSig(),
|
PreviousMessageSig: message.GetPreviousMessageSig(),
|
||||||
|
ReceivedByServer: true,
|
||||||
|
Error: "",
|
||||||
}
|
}
|
||||||
seen := g.Timeline.Insert(timelineMessage)
|
seen := g.Timeline.Insert(timelineMessage)
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,9 @@ type Message struct {
|
||||||
Message string
|
Message string
|
||||||
Signature []byte
|
Signature []byte
|
||||||
PreviousMessageSig []byte
|
PreviousMessageSig []byte
|
||||||
|
ReceivedByServer bool // messages sent to a server
|
||||||
|
Acknowledged bool // peer to peer
|
||||||
|
Error string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessageBaseSize is a rough estimate of the base number of bytes the struct uses before strings are populated
|
// MessageBaseSize is a rough estimate of the base number of bytes the struct uses before strings are populated
|
||||||
|
|
|
@ -19,16 +19,17 @@ import (
|
||||||
|
|
||||||
// PublicProfile is a local copy of a CwtchIdentity
|
// PublicProfile is a local copy of a CwtchIdentity
|
||||||
type PublicProfile struct {
|
type PublicProfile struct {
|
||||||
Name string
|
Name string
|
||||||
Ed25519PublicKey ed25519.PublicKey
|
Ed25519PublicKey ed25519.PublicKey
|
||||||
Trusted bool
|
Trusted bool
|
||||||
Blocked bool
|
Blocked bool
|
||||||
Onion string
|
Onion string
|
||||||
Attributes map[string]string
|
Attributes map[string]string
|
||||||
//Timeline Timeline `json:"-"` // TODO: cache recent messages for client
|
Timeline Timeline `json:"-"`
|
||||||
LocalID string // used by storage engine
|
LocalID string // used by storage engine
|
||||||
State string `json:"-"`
|
State string `json:"-"`
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
|
unacknowledgedMessages map[string]Message
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile encapsulates all the attributes necessary to be a Cwtch Peer.
|
// Profile encapsulates all the attributes necessary to be a Cwtch Peer.
|
||||||
|
@ -53,6 +54,7 @@ func (p *PublicProfile) init() {
|
||||||
if p.Attributes == nil {
|
if p.Attributes == nil {
|
||||||
p.Attributes = make(map[string]string)
|
p.Attributes = make(map[string]string)
|
||||||
}
|
}
|
||||||
|
p.unacknowledgedMessages = make(map[string]Message)
|
||||||
p.LocalID = generateRandomID()
|
p.LocalID = generateRandomID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,25 +121,84 @@ func (p *Profile) RejectInvite(groupID string) {
|
||||||
p.lock.Unlock()
|
p.lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// AddSentMessageToContactTimeline allows the saving of a message sent via a direct connection chat to the profile.
|
||||||
|
func (p *Profile) AddSentMessageToContactTimeline(onion string, messageTxt string, sent time.Time, eventID string) *Message {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
contact, ok := p.Contacts[onion]
|
||||||
|
if ok {
|
||||||
|
now := time.Now()
|
||||||
|
sig := p.SignMessage(onion + messageTxt + sent.String() + now.String())
|
||||||
|
|
||||||
|
message := &Message{PeerID: p.Onion, Message: messageTxt, Timestamp: sent, Received: now, Signature: sig, Acknowledged: false}
|
||||||
|
if contact.unacknowledgedMessages == nil {
|
||||||
|
contact.unacknowledgedMessages = make(map[string]Message)
|
||||||
|
}
|
||||||
|
contact.unacknowledgedMessages[eventID] = *message
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddMessageToContactTimeline allows the saving of a message sent via a direct connection chat to the profile.
|
// AddMessageToContactTimeline allows the saving of a message sent via a direct connection chat to the profile.
|
||||||
func (p *Profile) AddMessageToContactTimeline(onion string, fromMe bool, message string, sent time.Time) {
|
func (p *Profile) AddMessageToContactTimeline(onion string, messageTxt string, sent time.Time) (message *Message) {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
contact, ok := p.Contacts[onion]
|
contact, ok := p.Contacts[onion]
|
||||||
|
|
||||||
// We don't really need a Signature here, but we use it to maintain order
|
// We don't really need a Signature here, but we use it to maintain order
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
sig := p.SignMessage(onion + message + sent.String() + now.String())
|
sig := p.SignMessage(onion + messageTxt + sent.String() + now.String())
|
||||||
if ok {
|
if ok {
|
||||||
if fromMe {
|
message = &Message{PeerID: onion, Message: messageTxt, Timestamp: sent, Received: now, Signature: sig, Acknowledged: true}
|
||||||
contact.Timeline.Insert(&Message{PeerID: p.Onion, Message: message, Timestamp: sent, Received: now, Signature: sig})
|
contact.Timeline.Insert(message)
|
||||||
} else {
|
}
|
||||||
contact.Timeline.Insert(&Message{PeerID: onion, Message: message, Timestamp: sent, Received: now, Signature: sig})
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorSentMessageToPeer sets a sent message's error message and removes it from the unacknowledged list
|
||||||
|
func (p *Profile) ErrorSentMessageToPeer(onion string, eventID string, error string) {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
contact, ok := p.Contacts[onion]
|
||||||
|
if ok {
|
||||||
|
message, ok := contact.unacknowledgedMessages[eventID]
|
||||||
|
if ok {
|
||||||
|
message.Error = error
|
||||||
|
contact.Timeline.Insert(&message) // TODO: do we want a non timeline.Insert way to handle errors
|
||||||
|
delete(contact.unacknowledgedMessages, eventID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AckSentMessageToPeer sets mesage to a peer as acknowledged
|
||||||
|
func (p *Profile) AckSentMessageToPeer(onion string, eventID string) {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
contact, ok := p.Contacts[onion]
|
||||||
|
if ok {
|
||||||
|
message, ok := contact.unacknowledgedMessages[eventID]
|
||||||
|
if ok {
|
||||||
|
message.Acknowledged = true
|
||||||
|
contact.Timeline.Insert(&message)
|
||||||
|
delete(contact.unacknowledgedMessages, eventID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddGroupSentMessageError searches matching groups for the message by sig and marks it as an error
|
||||||
|
func (p *Profile) AddGroupSentMessageError(groupServer string, signature string, error string) {
|
||||||
|
for _, group := range p.Groups {
|
||||||
|
if group.GroupServer == groupServer {
|
||||||
|
if group.ErrorSentMessage([]byte(signature), error) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// AcceptInvite accepts a group invite
|
// AcceptInvite accepts a group invite
|
||||||
func (p *Profile) AcceptInvite(groupID string) (err error) {
|
func (p *Profile) AcceptInvite(groupID string) (err error) {
|
||||||
|
@ -335,6 +396,7 @@ func (p *Profile) AddGroup(group *Group) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttemptDecryption takes a ciphertext and signature and attempts to decrypt it under known groups.
|
// AttemptDecryption takes a ciphertext and signature and attempts to decrypt it under known groups.
|
||||||
|
// If successful, adds the message to the group's timeline
|
||||||
func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) (bool, string, *Message, bool) {
|
func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) (bool, string, *Message, bool) {
|
||||||
for _, group := range p.Groups {
|
for _, group := range p.Groups {
|
||||||
success, dgm := group.DecryptMessage(ciphertext)
|
success, dgm := group.DecryptMessage(ciphertext)
|
||||||
|
|
|
@ -14,7 +14,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var autoHandleableEvents = map[event.Type]bool{event.EncryptedGroupMessage: true, event.PeerStateChange: true, event.ServerStateChange: true, event.NewGroupInvite: true}
|
var autoHandleableEvents = map[event.Type]bool{event.EncryptedGroupMessage: true, event.PeerStateChange: true,
|
||||||
|
event.ServerStateChange: true, event.NewGroupInvite: true, event.NewMessageFromPeer: true,
|
||||||
|
event.PeerAcknowledgement: true, event.PeerError: true, event.SendMessageToGroupError: true}
|
||||||
|
|
||||||
// cwtchPeer manages incoming and outgoing connections and all processing for a Cwtch cwtchPeer
|
// cwtchPeer manages incoming and outgoing connections and all processing for a Cwtch cwtchPeer
|
||||||
type cwtchPeer struct {
|
type cwtchPeer struct {
|
||||||
|
@ -90,7 +92,7 @@ func (cp *cwtchPeer) Init(eventBus event.Manager) {
|
||||||
go cp.eventHandler()
|
go cp.eventHandler()
|
||||||
|
|
||||||
cp.eventBus = eventBus
|
cp.eventBus = eventBus
|
||||||
cp.AutoHandleEvents([]event.Type{event.EncryptedGroupMessage})
|
cp.AutoHandleEvents([]event.Type{event.EncryptedGroupMessage, event.NewMessageFromPeer, event.PeerAcknowledgement, event.PeerError, event.SendMessageToGroupError})
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutoHandleEvents sets an event (if able) to be handled by this peer
|
// AutoHandleEvents sets an event (if able) to be handled by this peer
|
||||||
|
@ -262,6 +264,9 @@ func (cp *cwtchPeer) SendMessageToGroupTracked(groupid string, message string) (
|
||||||
func (cp *cwtchPeer) SendMessageToPeer(onion string, message string) string {
|
func (cp *cwtchPeer) SendMessageToPeer(onion string, message string) string {
|
||||||
event := event.NewEvent(event.SendMessageToPeer, map[event.Field]string{event.RemotePeer: onion, event.Data: message})
|
event := event.NewEvent(event.SendMessageToPeer, map[event.Field]string{event.RemotePeer: onion, event.Data: message})
|
||||||
cp.eventBus.Publish(event)
|
cp.eventBus.Publish(event)
|
||||||
|
|
||||||
|
cp.Profile.AddSentMessageToContactTimeline(onion, message, time.Now(), event.EventID)
|
||||||
|
|
||||||
return event.EventID
|
return event.EventID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,11 +351,25 @@ func (cp *cwtchPeer) eventHandler() {
|
||||||
/***** Default auto handled events *****/
|
/***** Default auto handled events *****/
|
||||||
|
|
||||||
case event.EncryptedGroupMessage:
|
case event.EncryptedGroupMessage:
|
||||||
|
// If successful, a side effect is the message is added to the group's timeline
|
||||||
ok, groupID, message, seen := cp.Profile.AttemptDecryption([]byte(ev.Data[event.Ciphertext]), []byte(ev.Data[event.Signature]))
|
ok, groupID, message, seen := cp.Profile.AttemptDecryption([]byte(ev.Data[event.Ciphertext]), []byte(ev.Data[event.Signature]))
|
||||||
if ok && !seen {
|
if ok && !seen {
|
||||||
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}))
|
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}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data
|
||||||
|
ts, _ := time.Parse(time.RFC3339Nano, ev.Data[event.TimestampReceived])
|
||||||
|
cp.Profile.AddMessageToContactTimeline(ev.Data[event.RemotePeer], ev.Data[event.Data], ts)
|
||||||
|
|
||||||
|
case event.PeerAcknowledgement:
|
||||||
|
cp.Profile.AckSentMessageToPeer(ev.Data[event.RemotePeer], ev.Data[event.EventID])
|
||||||
|
|
||||||
|
case event.SendMessageToGroupError:
|
||||||
|
cp.Profile.AddGroupSentMessageError(ev.Data[event.GroupServer], ev.Data[event.Signature], ev.Data[event.Error])
|
||||||
|
|
||||||
|
case event.SendMessageToPeerError:
|
||||||
|
cp.Profile.ErrorSentMessageToPeer(ev.Data[event.RemotePeer], ev.Data[event.EventID], ev.Data[event.Error])
|
||||||
|
|
||||||
/***** Non default but requestable handlable events *****/
|
/***** Non default but requestable handlable events *****/
|
||||||
|
|
||||||
case event.NewGroupInvite:
|
case event.NewGroupInvite:
|
||||||
|
|
|
@ -142,7 +142,7 @@ func (e *engine) eventHandler() {
|
||||||
}
|
}
|
||||||
err := e.sendMessageToPeer(ev.EventID, ev.Data[event.RemotePeer], context, []byte(ev.Data[event.Data]))
|
err := e.sendMessageToPeer(ev.EventID, ev.Data[event.RemotePeer], context, []byte(ev.Data[event.Data]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.eventManager.Publish(event.NewEvent(event.SendMessageToPeerError, map[event.Field]string{event.RemotePeer: ev.Data[event.RemotePeer], event.Signature: ev.EventID, event.Error: "peer is offline or the connection has yet to finalize"}))
|
e.eventManager.Publish(event.NewEvent(event.SendMessageToPeerError, map[event.Field]string{event.RemotePeer: ev.Data[event.RemotePeer], event.EventID: ev.EventID, event.Error: "peer is offline or the connection has yet to finalize"}))
|
||||||
}
|
}
|
||||||
case event.UnblockPeer:
|
case event.UnblockPeer:
|
||||||
// We simply remove the peer from our blocklist
|
// We simply remove the peer from our blocklist
|
||||||
|
|
|
@ -78,7 +78,6 @@ func (pa PeerApp) listen() {
|
||||||
pa.MessageHandler(pa.connection.Hostname(), peerMessage.Context, peerMessage.Data)
|
pa.MessageHandler(pa.connection.Hostname(), peerMessage.Context, peerMessage.Data)
|
||||||
|
|
||||||
// Acknowledge the message
|
// Acknowledge the message
|
||||||
// TODO Should this be in the ui?
|
|
||||||
pa.SendMessage(PeerMessage{peerMessage.ID, event.ContextAck, []byte{}})
|
pa.SendMessage(PeerMessage{peerMessage.ID, event.ContextAck, []byte{}})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -129,6 +129,12 @@ func (ss *streamStore) Read() (messages []model.Message) {
|
||||||
resp = append(resp, msgs...)
|
resp = append(resp, msgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2019.10.10 "Acknowledged" & "ReceivedByServer" are added to the struct, populate it as true for old ones without
|
||||||
|
for i := 0; i < len(resp) && (resp[i].Acknowledged == false && resp[i].ReceivedByServer == false); i++ {
|
||||||
|
resp[i].Acknowledged = true
|
||||||
|
resp[i].ReceivedByServer = true
|
||||||
|
}
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue