cwtch/model/message.go

149 lines
3.7 KiB
Go
Raw Normal View History

2018-03-09 20:44:13 +00:00
package model
import (
"encoding/base64"
"sort"
"sync"
2018-03-09 20:44:13 +00:00
"time"
)
2019-02-03 01:18:33 +00:00
// Timeline encapsulates a collection of ordered Messages, and a mechanism to access them
2018-05-16 20:53:09 +00:00
// in a threadsafe manner.
type Timeline struct {
2019-02-03 01:18:33 +00:00
Messages []Message
SignedGroupID []byte
2018-05-28 17:44:47 +00:00
lock sync.Mutex
// a cache to allow quick checks for existing messages...
signatureCache map[string]bool
}
2018-03-15 16:33:26 +00:00
// Message is a local representation of a given message sent over a group chat channel.
2018-03-09 20:44:13 +00:00
type Message struct {
Timestamp time.Time
Received time.Time
PeerID string
Message string
Signature []byte
PreviousMessageSig []byte
ReceivedByServer bool // messages sent to a server
Acknowledged bool // peer to peer
Error string `json:",omitempty"`
2021-06-08 22:29:04 +00:00
// Application specific flags, useful for storing small amounts of metadata
Flags uint64
}
// MessageBaseSize 2021.06 byte size of an *empty* message json serialized
const MessageBaseSize float64 = 463
// compareSignatures checks if a and b are equal. Note: this function does
// not need to be constant time - in fact it is better that it is not as it's only main use
// is in sorting timeline state consistently.
func compareSignatures(a []byte, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
// GetMessages returns a copy of the entire timeline
2018-05-20 19:58:16 +00:00
func (t *Timeline) GetMessages() []Message {
2018-05-20 18:29:46 +00:00
t.lock.Lock()
2019-02-03 01:18:33 +00:00
messages := make([]Message, len(t.Messages))
copy(messages[:], t.Messages[:])
2018-05-20 18:29:46 +00:00
t.lock.Unlock()
2018-05-20 19:58:16 +00:00
return messages
2018-05-20 18:29:46 +00:00
}
// GetCopy returns a duplicate of the Timeline
func (t *Timeline) GetCopy() *Timeline {
t.lock.Lock()
defer t.lock.Unlock()
newt := &Timeline{}
// initialize the timeline and copy the message over...
newt.SetMessages(t.Messages)
return newt
}
2019-02-03 01:18:33 +00:00
// SetMessages sets the Messages of this timeline. Only to be used in loading/initialization
func (t *Timeline) SetMessages(messages []Message) {
t.lock.Lock()
defer t.lock.Unlock()
t.init()
2019-02-03 01:18:33 +00:00
t.Messages = messages
for _, message := range t.Messages {
t.signatureCache[base64.StdEncoding.EncodeToString(message.Signature)] = true
}
}
2018-05-16 20:53:09 +00:00
// Len gets the length of the timeline
2018-05-20 18:29:46 +00:00
func (t *Timeline) Len() int {
2019-02-03 01:18:33 +00:00
return len(t.Messages)
}
2018-05-16 20:53:09 +00:00
2019-02-03 01:18:33 +00:00
// Swap swaps 2 Messages on the timeline.
2018-05-20 18:29:46 +00:00
func (t *Timeline) Swap(i, j int) {
2019-02-03 01:18:33 +00:00
t.Messages[i], t.Messages[j] = t.Messages[j], t.Messages[i]
}
2018-05-16 20:53:09 +00:00
2019-02-03 01:18:33 +00:00
// Less checks 2 Messages (i and j) in the timeline and returns true if i occurred before j, else false
2018-05-20 18:29:46 +00:00
func (t *Timeline) Less(i, j int) bool {
2019-02-03 01:18:33 +00:00
if t.Messages[i].Timestamp.Before(t.Messages[j].Timestamp) {
2018-12-03 19:12:34 +00:00
return true
}
// Short circuit false if j is before i, signature checks will give a wrong order in this case.
2019-02-03 01:18:33 +00:00
if t.Messages[j].Timestamp.Before(t.Messages[i].Timestamp) {
return false
}
2019-02-03 01:18:33 +00:00
if compareSignatures(t.Messages[i].PreviousMessageSig, t.SignedGroupID) {
return true
}
2019-02-03 01:18:33 +00:00
if compareSignatures(t.Messages[i].Signature, t.Messages[j].PreviousMessageSig) {
return true
}
return false
}
2018-12-03 19:12:34 +00:00
// Sort sorts the timeline in a canonical order.
func (t *Timeline) Sort() {
t.lock.Lock()
defer t.lock.Unlock()
2018-12-03 19:12:34 +00:00
sort.Sort(t)
}
// Insert a message into the timeline in a thread safe way.
2018-10-05 03:18:34 +00:00
func (t *Timeline) Insert(mi *Message) bool {
t.lock.Lock()
defer t.lock.Unlock()
2018-05-03 04:12:45 +00:00
// assert timeline is initialized
t.init()
_, exists := t.signatureCache[base64.StdEncoding.EncodeToString(mi.Signature)]
if exists {
return true
2018-05-03 04:12:45 +00:00
}
2019-02-03 01:18:33 +00:00
t.Messages = append(t.Messages, *mi)
t.signatureCache[base64.StdEncoding.EncodeToString(mi.Signature)] = true
2018-10-05 03:18:34 +00:00
return false
2018-03-09 20:44:13 +00:00
}
func (t *Timeline) init() {
// only allow this setting once...
if t.signatureCache == nil {
t.signatureCache = make(map[string]bool)
}
}