1package kv
2
3import (
4 "encoding/binary"
5 "errors"
6 "hash/crc32"
7 "math"
8 "sort"
9)
10
11var ErrCorrupt = errors.New("kv: corrupt store file")
12
13var fileMagic = [4]byte{'V', 'E', 'V', 'K'}
14
15const (
16 fileVersion uint16 = 3
17 filePrefixLen = 4 + 2 + 4
18 entryPrefixLen = 4 + 4
19 crcLen = 4
20)
21
22// encodeFile writes magic | version | count | (keyLen key valLen val)* | crc32.
23func encodeFile(data map[string][]byte) []byte {
24 keys := make([]string, 0, len(data))
25 for key := range data {
26 keys = append(keys, key)
27 }
28 sort.Strings(keys)
29
30 size := filePrefixLen + crcLen
31 for _, key := range keys {
32 size += entryPrefixLen + len(key) + len(data[key])
33 }
34 buf := make([]byte, 0, size)
35 buf = append(buf, fileMagic[:]...)
36 buf = binary.BigEndian.AppendUint16(buf, fileVersion)
37 buf = binary.BigEndian.AppendUint32(buf, uint32(len(keys)))
38 for _, key := range keys {
39 value := data[key]
40 buf = binary.BigEndian.AppendUint32(buf, uint32(len(key)))
41 buf = append(buf, key...)
42 buf = binary.BigEndian.AppendUint32(buf, uint32(len(value)))
43 buf = append(buf, value...)
44 }
45 return appendCRC(buf)
46}
47
48func appendCRC(payload []byte) []byte {
49 return binary.BigEndian.AppendUint32(payload, crc32.ChecksumIEEE(payload))
50}
51
52// decodeFile validates the single trailing CRC and rejects every malformed
53// existing file. Callers must not replace or truncate a file that fails here.
54func decodeFile(raw []byte) (map[string][]byte, error) {
55 if len(raw) < filePrefixLen+crcLen {
56 return nil, ErrCorrupt
57 }
58 payload := raw[:len(raw)-crcLen]
59 wantCRC := binary.BigEndian.Uint32(raw[len(raw)-crcLen:])
60 if crc32.ChecksumIEEE(payload) != wantCRC {
61 return nil, ErrCorrupt
62 }
63 if string(payload[:4]) != string(fileMagic[:]) || binary.BigEndian.Uint16(payload[4:6]) != fileVersion {
64 return nil, ErrCorrupt
65 }
66
67 count := binary.BigEndian.Uint32(payload[6:10])
68 off := filePrefixLen
69 data := make(map[string][]byte)
70 for range count {
71 keyLen, next, ok := decodeLength(payload, off)
72 if !ok {
73 return nil, ErrCorrupt
74 }
75 off = next
76 if keyLen > uint64(len(payload)-off) {
77 return nil, ErrCorrupt
78 }
79 key := string(payload[off : off+int(keyLen)])
80 off += int(keyLen)
81 valueLen, next, ok := decodeLength(payload, off)
82 if !ok {
83 return nil, ErrCorrupt
84 }
85 off = next
86 if valueLen > uint64(len(payload)-off) {
87 return nil, ErrCorrupt
88 }
89 if _, duplicate := data[key]; duplicate {
90 return nil, ErrCorrupt
91 }
92 data[key] = append([]byte(nil), payload[off:off+int(valueLen)]...)
93 off += int(valueLen)
94 }
95 if off != len(payload) {
96 return nil, ErrCorrupt
97 }
98 return data, nil
99}
100
101func decodeLength(payload []byte, off int) (uint64, int, bool) {
102 if off < 0 || len(payload)-off < 4 {
103 return 0, off, false
104 }
105 length := uint64(binary.BigEndian.Uint32(payload[off : off+4]))
106 if length > uint64(math.MaxInt) {
107 return 0, off, false
108 }
109 return length, off + 4, true
110}