Official cwtch.im peer and server implementations. https://cwtch.im
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

174 lines
4.4 KiB

  1. package storage
  2. import (
  3. "cwtch.im/cwtch/model"
  4. "encoding/json"
  5. "fmt"
  6. "git.openprivacy.ca/openprivacy/libricochet-go/log"
  7. "io/ioutil"
  8. "os"
  9. "path"
  10. "sync"
  11. )
  12. const (
  13. fileStorePartitions = 16
  14. bytesPerFile = 15 * 1024
  15. )
  16. // streamStore is a file-backed implementation of StreamStore using an in memory buffer of ~16KB and a rotating set of files
  17. type streamStore struct {
  18. password string
  19. storeDirectory string
  20. filenameBase string
  21. // Buffer is used just for current file to write to
  22. messages []model.Message
  23. bufferByteCount int
  24. lock sync.Mutex
  25. }
  26. // StreamStore provides a stream like interface to encrypted storage
  27. type StreamStore interface {
  28. Write(message model.Message)
  29. WriteN(messages []model.Message)
  30. Read() []model.Message
  31. Delete()
  32. ChangePassword(newpass string)
  33. }
  34. // NewStreamStore returns an initialized StreamStore ready for reading and writing
  35. func NewStreamStore(directory string, filenameBase string, password string) (store StreamStore) {
  36. ss := &streamStore{storeDirectory: directory, filenameBase: filenameBase, password: password}
  37. os.Mkdir(ss.storeDirectory, 0700)
  38. ss.initBuffer()
  39. ss.initBufferFromStorage()
  40. return ss
  41. }
  42. func (ss *streamStore) initBuffer() {
  43. ss.messages = []model.Message{}
  44. ss.bufferByteCount = 0
  45. }
  46. func (ss *streamStore) initBufferFromStorage() error {
  47. filename := fmt.Sprintf("%s.%d", ss.filenameBase, 0)
  48. bytes, _ := readEncryptedFile(ss.storeDirectory, filename, ss.password)
  49. msgs := []model.Message{}
  50. err := json.Unmarshal([]byte(bytes), &msgs)
  51. if err != nil {
  52. return err
  53. }
  54. for _, message := range msgs {
  55. ss.updateBuffer(message)
  56. }
  57. return nil
  58. }
  59. func (ss *streamStore) updateBuffer(m model.Message) {
  60. ss.messages = append(ss.messages, m)
  61. ss.bufferByteCount += (model.MessageBaseSize * 1.5) + len(m.Message)
  62. }
  63. func (ss *streamStore) updateFile() error {
  64. msgs, err := json.Marshal(ss.messages)
  65. if err != nil {
  66. log.Errorf("Failed to marshal group messages %v\n", err)
  67. }
  68. // ENCRYPT
  69. key, salt, _ := createKey(ss.password)
  70. encryptedMsgs, err := encryptFileData(msgs, key)
  71. if err != nil {
  72. log.Errorf("Failed to encrypt messages: %v\n", err)
  73. return err
  74. }
  75. encryptedMsgs = append(salt[:], encryptedMsgs...)
  76. ioutil.WriteFile(path.Join(ss.storeDirectory, fmt.Sprintf("%s.%d", ss.filenameBase, 0)), encryptedMsgs, 0700)
  77. return nil
  78. }
  79. func (ss *streamStore) rotateFileStore() {
  80. os.Remove(path.Join(ss.storeDirectory, fmt.Sprintf("%s.%d", ss.filenameBase, fileStorePartitions-1)))
  81. for i := fileStorePartitions - 2; i >= 0; i-- {
  82. 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)))
  83. }
  84. }
  85. // Delete deletes all the files associated with this streamStore
  86. func (ss *streamStore) Delete() {
  87. for i := fileStorePartitions - 1; i >= 0; i-- {
  88. os.Remove(path.Join(ss.storeDirectory, fmt.Sprintf("%s.%d", ss.filenameBase, i)))
  89. }
  90. }
  91. // Read returns all messages from the backing file (not the buffer, which is jsut for writing to the current file)
  92. func (ss *streamStore) Read() (messages []model.Message) {
  93. ss.lock.Lock()
  94. defer ss.lock.Unlock()
  95. resp := []model.Message{}
  96. for i := fileStorePartitions - 1; i >= 0; i-- {
  97. filename := fmt.Sprintf("%s.%d", ss.filenameBase, i)
  98. bytes, err := readEncryptedFile(ss.storeDirectory, filename, ss.password)
  99. if err != nil {
  100. continue
  101. }
  102. msgs := []model.Message{}
  103. json.Unmarshal([]byte(bytes), &msgs)
  104. resp = append(resp, msgs...)
  105. }
  106. // 2019.10.10 "Acknowledged" & "ReceivedByServer" are added to the struct, populate it as true for old ones without
  107. for i := 0; i < len(resp) && (resp[i].Acknowledged == false && resp[i].ReceivedByServer == false); i++ {
  108. resp[i].Acknowledged = true
  109. resp[i].ReceivedByServer = true
  110. }
  111. return resp
  112. }
  113. // Write adds a GroupMessage to the store
  114. func (ss *streamStore) Write(m model.Message) {
  115. ss.lock.Lock()
  116. defer ss.lock.Unlock()
  117. ss.updateBuffer(m)
  118. ss.updateFile()
  119. if ss.bufferByteCount > bytesPerFile {
  120. log.Debugf("rotating log file")
  121. ss.rotateFileStore()
  122. ss.initBuffer()
  123. }
  124. }
  125. func (ss *streamStore) WriteN(messages []model.Message) {
  126. ss.lock.Lock()
  127. defer ss.lock.Unlock()
  128. for _, m := range messages {
  129. ss.updateBuffer(m)
  130. if ss.bufferByteCount > bytesPerFile {
  131. ss.updateFile()
  132. log.Debugf("rotating log file")
  133. ss.rotateFileStore()
  134. ss.initBuffer()
  135. }
  136. }
  137. }
  138. func (ss *streamStore) ChangePassword(newpass string) {}