Skip to content

Commit 1c65442

Browse files
feat(GODT-2385): Add file header to store files
File header consists of a simple id string and a 32bit integer which stores the version.
1 parent c8f22fc commit 1c65442

2 files changed

Lines changed: 61 additions & 8 deletions

File tree

store/disk.go

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/aes"
66
"crypto/cipher"
77
"crypto/rand"
8+
"encoding/binary"
89
"errors"
910
"fmt"
1011
"io"
@@ -50,7 +51,8 @@ func NewOnDiskStore(path string, pass []byte, opt ...Option) (Store, error) {
5051
return store, nil
5152
}
5253

53-
const BlockSize = 64 * 4096
54+
const blockSize = 64 * 4096
55+
const storeVersion = uint32(1)
5456

5557
func (c *onDiskStore) Set(messageID imap.InternalMessageID, in io.Reader) error {
5658
if err := os.MkdirAll(c.path, 0o700); err != nil {
@@ -76,6 +78,12 @@ func (c *onDiskStore) Set(messageID imap.InternalMessageID, in io.Reader) error
7678

7779
defer file.Close()
7880

81+
if written, err := file.Write(storeHeaderBytes); err != nil {
82+
return err
83+
} else if written != len(storeHeaderBytes) {
84+
return fmt.Errorf("failed to write store header to file")
85+
}
86+
7987
reader, writer := io.Pipe()
8088
defer writer.Close()
8189

@@ -91,9 +99,9 @@ func (c *onDiskStore) Set(messageID imap.InternalMessageID, in io.Reader) error
9199
}()
92100

93101
encryptionOverhead := c.gcm.Overhead()
94-
encryptedBlockSized := getEncryptedBlockSize(c.gcm, BlockSize)
102+
encryptedBlockSized := getEncryptedBlockSize(c.gcm, blockSize)
95103

96-
compressedBlock := make([]byte, BlockSize)
104+
compressedBlock := make([]byte, blockSize)
97105
encryptedBlock := make([]byte, encryptedBlockSized)
98106

99107
// Write nonce to file.
@@ -103,8 +111,8 @@ func (c *onDiskStore) Set(messageID imap.InternalMessageID, in io.Reader) error
103111

104112
// Write encrypted blocks.
105113
for {
106-
// Read at least BlockSize from the compressor.
107-
bytesRead, err := io.ReadAtLeast(reader, compressedBlock, BlockSize)
114+
// Read at least blockSize from the compressor.
115+
bytesRead, err := io.ReadAtLeast(reader, compressedBlock, blockSize)
108116
if err != nil {
109117
if errors.Is(err, io.EOF) {
110118
break
@@ -144,8 +152,17 @@ func (c *onDiskStore) Get(messageID imap.InternalMessageID) ([]byte, error) {
144152

145153
var fileSize int64
146154

155+
header := make([]byte, len(storeHeaderBytes))
156+
if _, err := io.ReadFull(file, header); err != nil {
157+
return nil, err
158+
}
159+
160+
if !bytes.Equal(header, storeHeaderBytes) {
161+
return nil, fmt.Errorf("file is not a valid store file")
162+
}
163+
147164
if stat, err := file.Stat(); err == nil {
148-
fileSize = stat.Size()
165+
fileSize = stat.Size() - int64(len(storeHeaderBytes))
149166
}
150167

151168
nonce := make([]byte, c.gcm.NonceSize())
@@ -158,13 +175,13 @@ func (c *onDiskStore) Get(messageID imap.InternalMessageID) ([]byte, error) {
158175
reader, writer := io.Pipe()
159176

160177
encryptionOverhead := c.gcm.Overhead()
161-
encryptedBlockSize := getEncryptedBlockSize(c.gcm, BlockSize)
178+
encryptedBlockSize := getEncryptedBlockSize(c.gcm, blockSize)
162179

163180
go func() {
164181
defer writer.Close()
165182

166183
readBuffer := make([]byte, encryptedBlockSize)
167-
decryptBuffer := make([]byte, BlockSize)
184+
decryptBuffer := make([]byte, blockSize)
168185
totalBytesRead := 0
169186

170187
for {
@@ -282,3 +299,15 @@ func (*OnDiskStoreBuilder) Delete(path, userID string) error {
282299
func getEncryptedBlockSize(aead cipher.AEAD, blockSize int) int {
283300
return blockSize + aead.Overhead()
284301
}
302+
303+
func makeGluonHeaderBytes() []byte {
304+
const StoreHeaderID = "GLUON-CACHE"
305+
306+
version := make([]byte, 4)
307+
308+
binary.LittleEndian.PutUint32(version, storeVersion)
309+
310+
return append([]byte(StoreHeaderID), version...)
311+
}
312+
313+
var storeHeaderBytes = makeGluonHeaderBytes()

store/store_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package store_test
33
import (
44
"bytes"
55
"math/rand"
6+
"os"
7+
"path/filepath"
68
"runtime"
79
"testing"
810

@@ -58,6 +60,28 @@ func BenchmarkStoreRead(t *testing.B) {
5860
}
5961
}
6062

63+
func TestStoreReadFailsIfHeaderDoesNotMatch(t *testing.T) {
64+
storeDir := t.TempDir()
65+
store, err := store.NewOnDiskStore(
66+
storeDir,
67+
[]byte("pass"),
68+
store.WithSemaphore(store.NewSemaphore(runtime.NumCPU())),
69+
)
70+
require.NoError(t, err)
71+
72+
id := imap.NewInternalMessageID()
73+
// Generate dummy file
74+
{
75+
data := make([]byte, 15*1024)
76+
_, err := rand.Read(data) //nolint:gosec
77+
require.NoError(t, err)
78+
require.NoError(t, os.WriteFile(filepath.Join(storeDir, id.String()), data, 0o600))
79+
}
80+
81+
_, err = store.Get(id)
82+
require.Error(t, err)
83+
}
84+
6185
func TestOnDiskStore(t *testing.T) {
6286
store, err := store.NewOnDiskStore(
6387
t.TempDir(),

0 commit comments

Comments
 (0)