2
0

rowcodec.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. package storage
  2. import (
  3. "encoding/binary"
  4. "errors"
  5. "fmt"
  6. "math"
  7. "sort"
  8. "github.com/goccy/go-json"
  9. )
  10. // rowMagic prefixes every versioned binary row value. It is chosen so it can
  11. // never be the first bytes of a legacy JSON row (which always begins with '{',
  12. // '[', '"', a digit, 't', 'f', or 'n'), so decodeRow can disambiguate the two
  13. // encodings unambiguously.
  14. const rowMagic = "PZSQLROW"
  15. // rowVersion is the format version. It must be bumped whenever the binary
  16. // layout changes in a way that would make old bytes undecodable.
  17. const rowVersion = 1
  18. // rowHeaderLen is the fixed size of the binary header: magic + version + count.
  19. const rowHeaderLen = len(rowMagic) + 1 + 4
  20. // maxRowFieldLen caps the encoded length of a field name or a variable-length
  21. // value (string, bytes, json.Number). It is far larger than any value the KV
  22. // layer can return (64 MiB), so it only ever rejects adversarial lengths.
  23. const maxRowFieldLen = 1 << 30
  24. // minFieldEncodedSize is the smallest possible on-disk size of a single field:
  25. // a 4-byte name length, an empty name, and a 1-byte type tag.
  26. const minFieldEncodedSize = 4 + 1
  27. // Value type tags. A tag occupies one byte and precedes the value payload.
  28. const (
  29. tagNil = 0x00
  30. tagFalse = 0x01
  31. tagTrue = 0x02
  32. tagInt = 0x03 // signed integer, normalized to int64
  33. tagUint = 0x04 // unsigned integer, normalized to uint64
  34. tagFloat32 = 0x05
  35. tagFloat64 = 0x06
  36. tagString = 0x07
  37. tagBytes = 0x08
  38. tagNumber = 0x09 // json.Number, preserved verbatim as decimal bytes
  39. )
  40. var errMalformedRow = errors.New("malformed row encoding")
  41. // encodeRow serializes a row into a deterministic, compact, versioned binary
  42. // value. Field names are sorted so identical rows always encode to identical
  43. // bytes. If any value cannot be represented exactly in the binary format
  44. // (e.g. a slice, map, struct, or time), the entire row is encoded as legacy
  45. // JSON instead so no data is lost.
  46. func encodeRow(row Row) ([]byte, error) {
  47. if len(row) > math.MaxUint32 {
  48. return nil, fmt.Errorf("row has too many fields")
  49. }
  50. names := make([]string, 0, len(row))
  51. for name := range row {
  52. if len(name) > maxRowFieldLen {
  53. return nil, fmt.Errorf("row field name is too long")
  54. }
  55. names = append(names, name)
  56. }
  57. sort.Strings(names)
  58. // Encode values first; fall back to JSON if any is unrepresentable.
  59. encoded := make([][]byte, len(names))
  60. for i, name := range names {
  61. enc, ok := encodeValue(row[name])
  62. if !ok {
  63. return json.Marshal(row)
  64. }
  65. encoded[i] = enc
  66. }
  67. buf := make([]byte, 0, rowHeaderLen+len(names)*8)
  68. buf = append(buf, rowMagic...)
  69. buf = append(buf, rowVersion)
  70. buf = appendU32(buf, uint32(len(names)))
  71. for i, name := range names {
  72. buf = appendU32(buf, uint32(len(name)))
  73. buf = append(buf, name...)
  74. buf = append(buf, encoded[i]...)
  75. }
  76. return buf, nil
  77. }
  78. // encodeValue returns the type tag plus payload for v, and reports whether v
  79. // can be represented exactly. All Go integer widths are normalized to their
  80. // fixed-width equivalents; every other supported type is self-describing.
  81. func encodeValue(v interface{}) ([]byte, bool) {
  82. switch t := v.(type) {
  83. case nil:
  84. return []byte{tagNil}, true
  85. case bool:
  86. if t {
  87. return []byte{tagTrue}, true
  88. }
  89. return []byte{tagFalse}, true
  90. case int:
  91. return appendU64([]byte{tagInt}, uint64(int64(t))), true
  92. case int8:
  93. return appendU64([]byte{tagInt}, uint64(int64(t))), true
  94. case int16:
  95. return appendU64([]byte{tagInt}, uint64(int64(t))), true
  96. case int32:
  97. return appendU64([]byte{tagInt}, uint64(int64(t))), true
  98. case int64:
  99. return appendU64([]byte{tagInt}, uint64(t)), true
  100. case uint:
  101. return appendU64([]byte{tagUint}, uint64(t)), true
  102. case uint8:
  103. return appendU64([]byte{tagUint}, uint64(t)), true
  104. case uint16:
  105. return appendU64([]byte{tagUint}, uint64(t)), true
  106. case uint32:
  107. return appendU64([]byte{tagUint}, uint64(t)), true
  108. case uint64:
  109. return appendU64([]byte{tagUint}, t), true
  110. case uintptr:
  111. return appendU64([]byte{tagUint}, uint64(t)), true
  112. case float32:
  113. var b [5]byte
  114. b[0] = tagFloat32
  115. binary.LittleEndian.PutUint32(b[1:], math.Float32bits(t))
  116. return b[:], true
  117. case float64:
  118. var b [9]byte
  119. b[0] = tagFloat64
  120. binary.LittleEndian.PutUint64(b[1:], math.Float64bits(t))
  121. return b[:], true
  122. case string:
  123. if len(t) > maxRowFieldLen {
  124. return nil, false
  125. }
  126. return appendBytesField([]byte{tagString}, []byte(t)), true
  127. case []byte:
  128. if len(t) > maxRowFieldLen {
  129. return nil, false
  130. }
  131. return appendBytesField([]byte{tagBytes}, t), true
  132. case json.Number:
  133. if len(t) > maxRowFieldLen {
  134. return nil, false
  135. }
  136. return appendBytesField([]byte{tagNumber}, []byte(string(t))), true
  137. default:
  138. return nil, false
  139. }
  140. }
  141. // decodeRow decodes a row value in either the versioned binary format or the
  142. // legacy untagged JSON format. The two are distinguished solely by the magic
  143. // prefix: bytes carrying the magic are always parsed as binary and never fall
  144. // back to JSON, while anything else is parsed as legacy JSON for backward
  145. // compatibility.
  146. func decodeRow(data []byte) (Row, error) {
  147. if len(data) >= len(rowMagic) && string(data[:len(rowMagic)]) == rowMagic {
  148. return decodeBinaryRow(data)
  149. }
  150. var row Row
  151. if err := json.Unmarshal(data, &row); err != nil {
  152. return nil, err
  153. }
  154. return row, nil
  155. }
  156. // decodeBinaryRow parses a versioned binary row, validating the magic,
  157. // version, field count, per-field length bounds, and that no trailing bytes
  158. // remain once every field has been consumed.
  159. func decodeBinaryRow(data []byte) (Row, error) {
  160. if len(data) < rowHeaderLen {
  161. return nil, errMalformedRow
  162. }
  163. if string(data[:len(rowMagic)]) != rowMagic {
  164. return nil, errMalformedRow
  165. }
  166. version := data[len(rowMagic)]
  167. if version != rowVersion {
  168. return nil, fmt.Errorf("%w: unsupported version %d", errMalformedRow, version)
  169. }
  170. count := binary.LittleEndian.Uint32(data[len(rowMagic)+1 : len(rowMagic)+5])
  171. pos := rowHeaderLen
  172. remaining := len(data) - pos
  173. // Reject impossible field counts up front so a hostile count cannot drive
  174. // an unbounded loop: every field occupies at least minFieldEncodedSize.
  175. if uint64(count)*minFieldEncodedSize > uint64(remaining) {
  176. return nil, errMalformedRow
  177. }
  178. row := make(Row, count)
  179. for i := uint32(0); i < count; i++ {
  180. if remaining < 4 {
  181. return nil, errMalformedRow
  182. }
  183. nameLen := binary.LittleEndian.Uint32(data[pos : pos+4])
  184. pos += 4
  185. remaining -= 4
  186. if nameLen > maxRowFieldLen || uint64(nameLen) > uint64(remaining) {
  187. return nil, errMalformedRow
  188. }
  189. name := string(data[pos : pos+int(nameLen)])
  190. pos += int(nameLen)
  191. remaining -= int(nameLen)
  192. if remaining < 1 {
  193. return nil, errMalformedRow
  194. }
  195. tag := data[pos]
  196. pos++
  197. remaining--
  198. value, n, err := decodeValue(tag, data[pos:])
  199. if err != nil {
  200. return nil, err
  201. }
  202. pos += n
  203. remaining -= n
  204. row[name] = value
  205. }
  206. if pos != len(data) {
  207. return nil, errMalformedRow
  208. }
  209. return row, nil
  210. }
  211. // decodeValue decodes a single tagged value from data, returning the value and
  212. // the number of payload bytes consumed. Variable-length payloads are validated
  213. // against both the global cap and the actual remaining input.
  214. func decodeValue(tag byte, data []byte) (interface{}, int, error) {
  215. switch tag {
  216. case tagNil:
  217. return nil, 0, nil
  218. case tagFalse:
  219. return false, 0, nil
  220. case tagTrue:
  221. return true, 0, nil
  222. case tagInt:
  223. if len(data) < 8 {
  224. return nil, 0, errMalformedRow
  225. }
  226. return int64(binary.LittleEndian.Uint64(data[:8])), 8, nil
  227. case tagUint:
  228. if len(data) < 8 {
  229. return nil, 0, errMalformedRow
  230. }
  231. return binary.LittleEndian.Uint64(data[:8]), 8, nil
  232. case tagFloat32:
  233. if len(data) < 4 {
  234. return nil, 0, errMalformedRow
  235. }
  236. return math.Float32frombits(binary.LittleEndian.Uint32(data[:4])), 4, nil
  237. case tagFloat64:
  238. if len(data) < 8 {
  239. return nil, 0, errMalformedRow
  240. }
  241. return math.Float64frombits(binary.LittleEndian.Uint64(data[:8])), 8, nil
  242. case tagString, tagBytes, tagNumber:
  243. if len(data) < 4 {
  244. return nil, 0, errMalformedRow
  245. }
  246. l := binary.LittleEndian.Uint32(data[:4])
  247. if l > maxRowFieldLen || uint64(l) > uint64(len(data)-4) {
  248. return nil, 0, errMalformedRow
  249. }
  250. content := data[4 : 4+int(l)]
  251. switch tag {
  252. case tagString:
  253. return string(content), 4 + int(l), nil
  254. case tagBytes:
  255. return append([]byte(nil), content...), 4 + int(l), nil
  256. case tagNumber:
  257. return json.Number(string(content)), 4 + int(l), nil
  258. }
  259. }
  260. return nil, 0, fmt.Errorf("%w: unknown tag %d", errMalformedRow, tag)
  261. }
  262. func appendU32(dst []byte, v uint32) []byte {
  263. var b [4]byte
  264. binary.LittleEndian.PutUint32(b[:], v)
  265. return append(dst, b[:]...)
  266. }
  267. func appendU64(dst []byte, v uint64) []byte {
  268. var b [8]byte
  269. binary.LittleEndian.PutUint64(b[:], v)
  270. return append(dst, b[:]...)
  271. }
  272. func appendBytesField(dst []byte, data []byte) []byte {
  273. dst = appendU32(dst, uint32(len(data)))
  274. return append(dst, data...)
  275. }