2020-01-13 22:11:00 +00:00
|
|
|
|
package v1
|
2019-01-29 20:56:59 +00:00
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"cwtch.im/cwtch/model"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
2020-02-10 22:09:24 +00:00
|
|
|
|
"git.openprivacy.ca/openprivacy/log"
|
2019-01-29 20:56:59 +00:00
|
|
|
|
"io/ioutil"
|
2021-06-21 22:52:29 +00:00
|
|
|
|
"math"
|
2019-01-29 20:56:59 +00:00
|
|
|
|
"os"
|
|
|
|
|
"path"
|
|
|
|
|
"sync"
|
|
|
|
|
)
|
|
|
|
|
|
2021-06-21 22:52:29 +00:00
|
|
|
|
// This number is larger that the recommend chunk size of libsodium secretbox by an order of magnitude.
|
|
|
|
|
// Since this code is not performance-sensitive (and is unlikely to gain any significant performance benefit from
|
|
|
|
|
// cache-efficient chunking) this size isn’t currently a concern.
|
|
|
|
|
// TODO: revise and evaluate better storage options after beta”
|
2019-01-29 20:56:59 +00:00
|
|
|
|
const (
|
2021-06-21 22:52:29 +00:00
|
|
|
|
fileStorePartitions = 128
|
|
|
|
|
bytesPerFile = 128 * 1024
|
2019-01-29 20:56:59 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// streamStore is a file-backed implementation of StreamStore using an in memory buffer of ~16KB and a rotating set of files
|
|
|
|
|
type streamStore struct {
|
2020-01-13 22:11:00 +00:00
|
|
|
|
key [32]byte
|
2019-01-29 20:56:59 +00:00
|
|
|
|
|
|
|
|
|
storeDirectory string
|
|
|
|
|
filenameBase string
|
|
|
|
|
|
2019-07-19 17:27:50 +00:00
|
|
|
|
// Buffer is used just for current file to write to
|
2019-01-29 20:56:59 +00:00
|
|
|
|
messages []model.Message
|
|
|
|
|
bufferByteCount int
|
|
|
|
|
|
|
|
|
|
lock sync.Mutex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// StreamStore provides a stream like interface to encrypted storage
|
|
|
|
|
type StreamStore interface {
|
|
|
|
|
Write(message model.Message)
|
2019-12-12 20:21:14 +00:00
|
|
|
|
WriteN(messages []model.Message)
|
2019-01-29 20:56:59 +00:00
|
|
|
|
Read() []model.Message
|
2019-08-07 05:27:11 +00:00
|
|
|
|
Delete()
|
2019-01-29 20:56:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewStreamStore returns an initialized StreamStore ready for reading and writing
|
2020-01-13 22:11:00 +00:00
|
|
|
|
func NewStreamStore(directory string, filenameBase string, key [32]byte) (store StreamStore) {
|
|
|
|
|
ss := &streamStore{storeDirectory: directory, filenameBase: filenameBase, key: key}
|
2019-01-29 20:56:59 +00:00
|
|
|
|
os.Mkdir(ss.storeDirectory, 0700)
|
|
|
|
|
|
|
|
|
|
ss.initBuffer()
|
|
|
|
|
ss.initBufferFromStorage()
|
|
|
|
|
|
|
|
|
|
return ss
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ss *streamStore) initBuffer() {
|
|
|
|
|
ss.messages = []model.Message{}
|
|
|
|
|
ss.bufferByteCount = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ss *streamStore) initBufferFromStorage() error {
|
2019-02-04 04:32:22 +00:00
|
|
|
|
filename := fmt.Sprintf("%s.%d", ss.filenameBase, 0)
|
|
|
|
|
|
2020-06-25 18:21:10 +00:00
|
|
|
|
bytes, _ := ReadEncryptedFile(ss.storeDirectory, filename, ss.key)
|
2019-01-29 20:56:59 +00:00
|
|
|
|
|
2019-02-04 04:32:22 +00:00
|
|
|
|
msgs := []model.Message{}
|
2019-03-25 18:55:04 +00:00
|
|
|
|
err := json.Unmarshal([]byte(bytes), &msgs)
|
2019-02-04 04:32:22 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
2019-01-29 20:56:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-04 04:32:22 +00:00
|
|
|
|
for _, message := range msgs {
|
|
|
|
|
ss.updateBuffer(message)
|
|
|
|
|
}
|
2019-01-29 20:56:59 +00:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ss *streamStore) updateBuffer(m model.Message) {
|
|
|
|
|
ss.messages = append(ss.messages, m)
|
2021-06-21 22:52:29 +00:00
|
|
|
|
ss.bufferByteCount += int(math.Ceil(model.MessageBaseSize*1.5)) + len(m.Message)
|
2019-01-29 20:56:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ss *streamStore) updateFile() error {
|
|
|
|
|
msgs, err := json.Marshal(ss.messages)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Failed to marshal group messages %v\n", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ENCRYPT
|
2020-06-25 18:21:10 +00:00
|
|
|
|
encryptedMsgs, err := EncryptFileData(msgs, ss.key)
|
2019-01-29 20:56:59 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Failed to encrypt messages: %v\n", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-24 02:25:24 +00:00
|
|
|
|
ioutil.WriteFile(path.Join(ss.storeDirectory, fmt.Sprintf("%s.%d", ss.filenameBase, 0)), encryptedMsgs, 0600)
|
2019-01-29 20:56:59 +00:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ss *streamStore) rotateFileStore() {
|
|
|
|
|
os.Remove(path.Join(ss.storeDirectory, fmt.Sprintf("%s.%d", ss.filenameBase, fileStorePartitions-1)))
|
|
|
|
|
|
|
|
|
|
for i := fileStorePartitions - 2; i >= 0; i-- {
|
|
|
|
|
os.Rename(path.Join(ss.storeDirectory, fmt.Sprintf("%s.%d", ss.filenameBase, i)), path.Join(ss.storeDirectory, fmt.Sprintf("%s.%d", ss.filenameBase, i+1)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-07 05:27:11 +00:00
|
|
|
|
// Delete deletes all the files associated with this streamStore
|
|
|
|
|
func (ss *streamStore) Delete() {
|
|
|
|
|
for i := fileStorePartitions - 1; i >= 0; i-- {
|
|
|
|
|
os.Remove(path.Join(ss.storeDirectory, fmt.Sprintf("%s.%d", ss.filenameBase, i)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-08 18:29:33 +00:00
|
|
|
|
// Read returns all messages from the backing file (not the buffer, for writing to the current file)
|
2019-01-29 20:56:59 +00:00
|
|
|
|
func (ss *streamStore) Read() (messages []model.Message) {
|
|
|
|
|
ss.lock.Lock()
|
|
|
|
|
defer ss.lock.Unlock()
|
|
|
|
|
|
|
|
|
|
resp := []model.Message{}
|
|
|
|
|
|
|
|
|
|
for i := fileStorePartitions - 1; i >= 0; i-- {
|
|
|
|
|
filename := fmt.Sprintf("%s.%d", ss.filenameBase, i)
|
|
|
|
|
|
2020-06-25 18:21:10 +00:00
|
|
|
|
bytes, err := ReadEncryptedFile(ss.storeDirectory, filename, ss.key)
|
2019-01-29 20:56:59 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
msgs := []model.Message{}
|
2019-07-19 17:27:50 +00:00
|
|
|
|
json.Unmarshal([]byte(bytes), &msgs)
|
|
|
|
|
resp = append(resp, msgs...)
|
2019-01-29 20:56:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resp
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-12 20:21:14 +00:00
|
|
|
|
// Write adds a GroupMessage to the store
|
2019-01-29 20:56:59 +00:00
|
|
|
|
func (ss *streamStore) Write(m model.Message) {
|
|
|
|
|
ss.lock.Lock()
|
|
|
|
|
defer ss.lock.Unlock()
|
|
|
|
|
ss.updateBuffer(m)
|
|
|
|
|
ss.updateFile()
|
|
|
|
|
|
|
|
|
|
if ss.bufferByteCount > bytesPerFile {
|
2019-02-04 04:32:22 +00:00
|
|
|
|
log.Debugf("rotating log file")
|
2019-01-29 20:56:59 +00:00
|
|
|
|
ss.rotateFileStore()
|
|
|
|
|
ss.initBuffer()
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-12 20:21:14 +00:00
|
|
|
|
|
|
|
|
|
func (ss *streamStore) WriteN(messages []model.Message) {
|
|
|
|
|
ss.lock.Lock()
|
|
|
|
|
defer ss.lock.Unlock()
|
|
|
|
|
|
2020-01-13 22:11:00 +00:00
|
|
|
|
log.Infof("WriteN %v messages\n", len(messages))
|
|
|
|
|
i := 0
|
2019-12-12 20:21:14 +00:00
|
|
|
|
for _, m := range messages {
|
|
|
|
|
ss.updateBuffer(m)
|
2020-01-13 22:11:00 +00:00
|
|
|
|
i++
|
2019-12-12 20:21:14 +00:00
|
|
|
|
|
|
|
|
|
if ss.bufferByteCount > bytesPerFile {
|
|
|
|
|
ss.updateFile()
|
|
|
|
|
ss.rotateFileStore()
|
|
|
|
|
ss.initBuffer()
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-01-13 22:11:00 +00:00
|
|
|
|
ss.updateFile()
|
2019-12-12 20:21:14 +00:00
|
|
|
|
}
|