2
0

testkv.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. // Package testkv provides an in-memory PizzaKV-compatible server for tests. It
  2. // speaks the PKBFI wire protocol over a real TCP listener so production
  3. // KVPool/KVClient code paths can be exercised without a separate binary.
  4. package testkv
  5. import (
  6. "bufio"
  7. "hash/crc32"
  8. "io"
  9. "net"
  10. "sort"
  11. "strings"
  12. "sync"
  13. "testing"
  14. "time"
  15. "github.com/danfragoso/pizzasql-next/pkg/storage"
  16. )
  17. const (
  18. headerMagic = "PKBF"
  19. headerVersion = 1
  20. headerSize = 32
  21. opPing = 1
  22. opGet = 3
  23. opPut = 4
  24. opDelete = 5
  25. opExists = 6
  26. opMultiGet = 7
  27. opBatchWrite = 8
  28. opScanOpen = 9
  29. opScanNext = 10
  30. opScanClose = 11
  31. opCompareBatch = 12
  32. batchPut = 1
  33. batchDelete = 2
  34. statusOK = 0
  35. statusNotFound = 1
  36. statusError = 2
  37. )
  38. var crcTable = crc32.MakeTable(crc32.Castagnoli)
  39. type scan struct {
  40. keys []string
  41. offset int
  42. limit uint32
  43. keysOnly bool
  44. }
  45. // Server is an in-memory KV server.
  46. type Server struct {
  47. mu sync.Mutex
  48. data map[string][]byte
  49. lsns map[string]uint64
  50. nextLSN uint64
  51. ln net.Listener
  52. wg sync.WaitGroup
  53. closed bool
  54. scans map[uint64]*scan
  55. nextScan uint64
  56. }
  57. // New starts an in-memory KV server on an ephemeral TCP port.
  58. func New(t testing.TB) *Server {
  59. t.Helper()
  60. ln, err := net.Listen("tcp", "127.0.0.1:0")
  61. if err != nil {
  62. t.Fatal(err)
  63. }
  64. s := &Server{
  65. data: make(map[string][]byte),
  66. lsns: make(map[string]uint64),
  67. ln: ln,
  68. scans: make(map[uint64]*scan),
  69. }
  70. s.wg.Add(1)
  71. go s.acceptLoop()
  72. t.Cleanup(func() { s.Close() })
  73. return s
  74. }
  75. // Addr returns the server's listen address.
  76. func (s *Server) Addr() string { return s.ln.Addr().String() }
  77. // Pool creates a KV pool connected to the server.
  78. func (s *Server) Pool(size int) *storage.KVPool {
  79. pool, err := storage.NewKVPool(s.Addr(), size, 5*time.Second)
  80. if err != nil {
  81. panic(err)
  82. }
  83. return pool
  84. }
  85. // Close stops the server.
  86. func (s *Server) Close() {
  87. s.mu.Lock()
  88. if s.closed {
  89. s.mu.Unlock()
  90. return
  91. }
  92. s.closed = true
  93. s.mu.Unlock()
  94. _ = s.ln.Close()
  95. s.wg.Wait()
  96. }
  97. func (s *Server) acceptLoop() {
  98. defer s.wg.Done()
  99. for {
  100. conn, err := s.ln.Accept()
  101. if err != nil {
  102. return
  103. }
  104. s.wg.Add(1)
  105. go func() {
  106. defer s.wg.Done()
  107. s.handle(conn)
  108. }()
  109. }
  110. }
  111. func (s *Server) handle(conn net.Conn) {
  112. defer conn.Close()
  113. r := bufio.NewReader(conn)
  114. for {
  115. opcode, _, requestID, payload, err := readFrame(r)
  116. if err != nil {
  117. return
  118. }
  119. body := s.execute(opcode, payload)
  120. if _, err := conn.Write(encodeResponse(opcode, requestID, body)); err != nil {
  121. return
  122. }
  123. }
  124. }
  125. func (s *Server) execute(opcode uint16, payload []byte) []byte {
  126. switch opcode {
  127. case opPing:
  128. return statusOKBody(nil)
  129. case opGet:
  130. key, ok := parseOneKey(payload)
  131. if !ok {
  132. return errorBody("InvalidPayload")
  133. }
  134. s.mu.Lock()
  135. v, found := s.data[string(key)]
  136. lsn := s.lsns[string(key)]
  137. s.mu.Unlock()
  138. if !found {
  139. return statusBody(statusNotFound, nil)
  140. }
  141. body := make([]byte, 14+len(v))
  142. putU16(body[0:2], statusOK)
  143. putU64(body[2:10], lsn)
  144. putU32(body[10:14], uint32(len(v)))
  145. copy(body[14:], v)
  146. return body
  147. case opPut:
  148. key, value, ok := parsePut(payload)
  149. if !ok {
  150. return errorBody("InvalidPayload")
  151. }
  152. s.mu.Lock()
  153. s.nextLSN++
  154. lsn := s.nextLSN
  155. s.data[string(key)] = append([]byte(nil), value...)
  156. s.lsns[string(key)] = lsn
  157. s.mu.Unlock()
  158. body := make([]byte, 10)
  159. putU16(body[0:2], statusOK)
  160. putU64(body[2:10], lsn)
  161. return body
  162. case opDelete:
  163. key, ok := parseOneKey(payload)
  164. if !ok {
  165. return errorBody("InvalidPayload")
  166. }
  167. s.mu.Lock()
  168. _, found := s.data[string(key)]
  169. delete(s.data, string(key))
  170. delete(s.lsns, string(key))
  171. s.mu.Unlock()
  172. body := make([]byte, 3)
  173. putU16(body[0:2], statusOK)
  174. if found {
  175. body[2] = 1
  176. }
  177. return body
  178. case opExists:
  179. key, ok := parseOneKey(payload)
  180. if !ok {
  181. return errorBody("InvalidPayload")
  182. }
  183. s.mu.Lock()
  184. _, found := s.data[string(key)]
  185. s.mu.Unlock()
  186. body := make([]byte, 3)
  187. putU16(body[0:2], statusOK)
  188. if found {
  189. body[2] = 1
  190. }
  191. return body
  192. case opMultiGet:
  193. keys, ok := parseMultiGet(payload)
  194. if !ok {
  195. return errorBody("InvalidPayload")
  196. }
  197. body := make([]byte, 6)
  198. putU16(body[0:2], statusOK)
  199. putU32(body[2:6], uint32(len(keys)))
  200. s.mu.Lock()
  201. for _, key := range keys {
  202. v, found := s.data[string(key)]
  203. if !found {
  204. body = append(body, make([]byte, 16)...)
  205. continue
  206. }
  207. entry := make([]byte, 16+len(v))
  208. entry[0] = 1
  209. putU32(entry[4:8], uint32(len(v)))
  210. putU64(entry[8:16], s.lsns[string(key)])
  211. copy(entry[16:], v)
  212. body = append(body, entry...)
  213. }
  214. s.mu.Unlock()
  215. return body
  216. case opBatchWrite:
  217. ops, ok := parseBatchOps(payload)
  218. if !ok {
  219. return errorBody("InvalidPayload")
  220. }
  221. s.mu.Lock()
  222. s.nextLSN++
  223. lsn := s.nextLSN
  224. applyOps(s.data, s.lsns, ops, lsn)
  225. s.mu.Unlock()
  226. body := make([]byte, 10)
  227. putU16(body[0:2], statusOK)
  228. putU64(body[2:10], lsn)
  229. return body
  230. case opCompareBatch:
  231. checks, ops, ok := parseCompareBatch(payload)
  232. if !ok {
  233. return errorBody("InvalidPayload")
  234. }
  235. s.mu.Lock()
  236. committed := true
  237. for _, c := range checks {
  238. if c.LSN == 0 {
  239. if _, found := s.data[string(c.Key)]; found {
  240. committed = false
  241. break
  242. }
  243. } else if s.lsns[string(c.Key)] != c.LSN {
  244. committed = false
  245. break
  246. }
  247. }
  248. var lsn uint64
  249. if committed {
  250. s.nextLSN++
  251. lsn = s.nextLSN
  252. applyOps(s.data, s.lsns, ops, lsn)
  253. }
  254. s.mu.Unlock()
  255. body := make([]byte, 18)
  256. putU16(body[0:2], statusOK)
  257. if committed {
  258. body[2] = 1
  259. }
  260. putU64(body[10:18], lsn)
  261. return body
  262. case opScanOpen:
  263. includeValues, limit, prefix, ok := parseScanOpen(payload)
  264. if !ok {
  265. return errorBody("InvalidPayload")
  266. }
  267. s.mu.Lock()
  268. keys := make([]string, 0)
  269. for k := range s.data {
  270. if strings.HasPrefix(k, string(prefix)) {
  271. keys = append(keys, k)
  272. }
  273. }
  274. sort.Strings(keys)
  275. s.nextScan++
  276. id := s.nextScan
  277. s.scans[id] = &scan{keys: keys, limit: limit, keysOnly: !includeValues}
  278. s.mu.Unlock()
  279. body := make([]byte, 10)
  280. putU16(body[0:2], statusOK)
  281. putU64(body[2:10], id)
  282. return body
  283. case opScanNext:
  284. id, ok := parseScanID(payload)
  285. if !ok {
  286. return errorBody("InvalidPayload")
  287. }
  288. s.mu.Lock()
  289. sc := s.scans[id]
  290. if sc == nil {
  291. s.mu.Unlock()
  292. return errorBody("ScanNotFound")
  293. }
  294. remaining := len(sc.keys) - sc.offset
  295. count := int(sc.limit)
  296. if count > remaining {
  297. count = remaining
  298. }
  299. end := sc.offset + count
  300. body := make([]byte, 10)
  301. putU16(body[0:2], statusOK)
  302. if end >= len(sc.keys) {
  303. body[2] = 1
  304. }
  305. putU32(body[6:10], uint32(count))
  306. for _, key := range sc.keys[sc.offset:end] {
  307. value := s.data[key]
  308. if sc.keysOnly {
  309. value = nil
  310. }
  311. entry := make([]byte, 16+len(key)+len(value))
  312. putU32(entry[0:4], uint32(len(key)))
  313. putU32(entry[4:8], uint32(len(value)))
  314. putU64(entry[8:16], s.lsns[key])
  315. copy(entry[16:], key)
  316. copy(entry[16+len(key):], value)
  317. body = append(body, entry...)
  318. }
  319. sc.offset = end
  320. s.mu.Unlock()
  321. return body
  322. case opScanClose:
  323. id, ok := parseScanID(payload)
  324. if !ok {
  325. return errorBody("InvalidPayload")
  326. }
  327. s.mu.Lock()
  328. delete(s.scans, id)
  329. s.mu.Unlock()
  330. body := make([]byte, 3)
  331. putU16(body[0:2], statusOK)
  332. body[2] = 1
  333. return body
  334. default:
  335. return errorBody("UnknownOpcode")
  336. }
  337. }
  338. func applyOps(data map[string][]byte, lsns map[string]uint64, ops []storage.BatchOp, lsn uint64) {
  339. for _, op := range ops {
  340. if op.Op == batchPut {
  341. data[string(op.Key)] = append([]byte(nil), op.Value...)
  342. lsns[string(op.Key)] = lsn
  343. } else {
  344. delete(data, string(op.Key))
  345. delete(lsns, string(op.Key))
  346. }
  347. }
  348. }
  349. // ── wire helpers ────────────────────────────────────────────────────────────
  350. func crc32c(p []byte) uint32 { return crc32.Checksum(p, crcTable) }
  351. func putU16(b []byte, v uint16) { b[0] = byte(v); b[1] = byte(v >> 8) }
  352. func putU32(b []byte, v uint32) {
  353. b[0] = byte(v)
  354. b[1] = byte(v >> 8)
  355. b[2] = byte(v >> 16)
  356. b[3] = byte(v >> 24)
  357. }
  358. func putU64(b []byte, v uint64) {
  359. b[0] = byte(v)
  360. b[1] = byte(v >> 8)
  361. b[2] = byte(v >> 16)
  362. b[3] = byte(v >> 24)
  363. b[4] = byte(v >> 32)
  364. b[5] = byte(v >> 40)
  365. b[6] = byte(v >> 48)
  366. b[7] = byte(v >> 56)
  367. }
  368. func getU16(b []byte) uint16 { return uint16(b[0]) | uint16(b[1])<<8 }
  369. func getU32(b []byte) uint32 {
  370. return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
  371. }
  372. func getU64(b []byte) uint64 {
  373. return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
  374. uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
  375. }
  376. func encodeFrame(opcode, flags uint16, requestID uint64, payload []byte) []byte {
  377. frame := make([]byte, headerSize+len(payload))
  378. copy(frame[0:4], headerMagic)
  379. putU16(frame[4:6], headerVersion)
  380. putU16(frame[8:10], opcode)
  381. putU16(frame[10:12], flags)
  382. putU64(frame[12:20], requestID)
  383. putU32(frame[20:24], uint32(len(payload)))
  384. putU32(frame[24:28], crc32c(payload))
  385. putU32(frame[28:32], crc32c(frame[0:32]))
  386. copy(frame[32:], payload)
  387. return frame
  388. }
  389. func encodeResponse(opcode uint16, requestID uint64, body []byte) []byte {
  390. return encodeFrame(opcode|0x8000, 1, requestID, body)
  391. }
  392. func readFrame(r *bufio.Reader) (uint16, uint16, uint64, []byte, error) {
  393. var header [headerSize]byte
  394. if _, err := io.ReadFull(r, header[:]); err != nil {
  395. return 0, 0, 0, nil, err
  396. }
  397. payloadLen := getU32(header[20:24])
  398. payload := make([]byte, payloadLen)
  399. if _, err := io.ReadFull(r, payload); err != nil {
  400. return 0, 0, 0, nil, err
  401. }
  402. return getU16(header[8:10]), getU16(header[10:12]), getU64(header[12:20]), payload, nil
  403. }
  404. func statusOKBody(p []byte) []byte {
  405. body := make([]byte, 2+len(p))
  406. putU16(body[0:2], statusOK)
  407. copy(body[2:], p)
  408. return body
  409. }
  410. func statusBody(status uint16, p []byte) []byte {
  411. body := make([]byte, 2+len(p))
  412. putU16(body[0:2], status)
  413. copy(body[2:], p)
  414. return body
  415. }
  416. func errorBody(msg string) []byte {
  417. body := make([]byte, 2+len(msg))
  418. putU16(body[0:2], statusError)
  419. copy(body[2:], msg)
  420. return body
  421. }
  422. func parseOneKey(p []byte) ([]byte, bool) {
  423. if len(p) < 4 {
  424. return nil, false
  425. }
  426. l := getU32(p[0:4])
  427. if 4+int(l) != len(p) {
  428. return nil, false
  429. }
  430. return p[4:], true
  431. }
  432. func parsePut(p []byte) ([]byte, []byte, bool) {
  433. if len(p) < 8 {
  434. return nil, nil, false
  435. }
  436. kl := getU32(p[0:4])
  437. vl := getU32(p[4:8])
  438. if 8+int(kl)+int(vl) != len(p) {
  439. return nil, nil, false
  440. }
  441. return p[8 : 8+kl], p[8+kl:], true
  442. }
  443. func parseMultiGet(p []byte) ([][]byte, bool) {
  444. if len(p) < 4 {
  445. return nil, false
  446. }
  447. n := getU32(p[0:4])
  448. keys := make([][]byte, 0, n)
  449. pos := 4
  450. for i := uint32(0); i < n; i++ {
  451. if len(p)-pos < 4 {
  452. return nil, false
  453. }
  454. l := getU32(p[pos : pos+4])
  455. pos += 4
  456. if len(p)-pos < int(l) {
  457. return nil, false
  458. }
  459. keys = append(keys, p[pos:pos+int(l)])
  460. pos += int(l)
  461. }
  462. return keys, pos == len(p)
  463. }
  464. func parseBatchOps(p []byte) ([]storage.BatchOp, bool) {
  465. if len(p) < 8 {
  466. return nil, false
  467. }
  468. n := getU32(p[0:4])
  469. ml := getU32(p[4:8])
  470. pos := 8 + int(ml)
  471. ops := make([]storage.BatchOp, 0, n)
  472. for i := uint32(0); i < n; i++ {
  473. if len(p)-pos < 12 {
  474. return nil, false
  475. }
  476. op := p[pos]
  477. kl := getU32(p[pos+4 : pos+8])
  478. vl := getU32(p[pos+8 : pos+12])
  479. pos += 12
  480. if op != batchPut && op != batchDelete {
  481. return nil, false
  482. }
  483. if len(p)-pos < int(kl)+int(vl) {
  484. return nil, false
  485. }
  486. key := p[pos : pos+int(kl)]
  487. pos += int(kl)
  488. value := p[pos : pos+int(vl)]
  489. pos += int(vl)
  490. ops = append(ops, storage.BatchOp{Op: op, Key: key, Value: value})
  491. }
  492. return ops, pos == len(p)
  493. }
  494. func parseCompareBatch(p []byte) ([]storage.CompareCheck, []storage.BatchOp, bool) {
  495. if len(p) < 16 {
  496. return nil, nil, false
  497. }
  498. nc := getU32(p[0:4])
  499. no := getU32(p[4:8])
  500. ml := getU32(p[8:12])
  501. pos := 16
  502. checks := make([]storage.CompareCheck, 0, nc)
  503. for i := uint32(0); i < nc; i++ {
  504. if len(p)-pos < 16 {
  505. return nil, nil, false
  506. }
  507. kl := getU32(p[pos : pos+4])
  508. lsn := getU64(p[pos+8 : pos+16])
  509. pos += 16
  510. if len(p)-pos < int(kl) {
  511. return nil, nil, false
  512. }
  513. checks = append(checks, storage.CompareCheck{Key: p[pos : pos+int(kl)], LSN: lsn})
  514. pos += int(kl)
  515. }
  516. pos += int(ml)
  517. ops := make([]storage.BatchOp, 0, no)
  518. for i := uint32(0); i < no; i++ {
  519. if len(p)-pos < 12 {
  520. return nil, nil, false
  521. }
  522. op := p[pos]
  523. kl := getU32(p[pos+4 : pos+8])
  524. vl := getU32(p[pos+8 : pos+12])
  525. pos += 12
  526. if op != batchPut && op != batchDelete {
  527. return nil, nil, false
  528. }
  529. if len(p)-pos < int(kl)+int(vl) {
  530. return nil, nil, false
  531. }
  532. key := p[pos : pos+int(kl)]
  533. pos += int(kl)
  534. value := p[pos : pos+int(vl)]
  535. pos += int(vl)
  536. ops = append(ops, storage.BatchOp{Op: op, Key: key, Value: value})
  537. }
  538. return checks, ops, pos == len(p)
  539. }
  540. func parseScanOpen(p []byte) (bool, uint32, []byte, bool) {
  541. if len(p) < 12 {
  542. return false, 0, nil, false
  543. }
  544. include := p[0] != 0
  545. limit := getU32(p[4:8])
  546. pl := getU32(p[8:12])
  547. if 12+int(pl) != len(p) {
  548. return false, 0, nil, false
  549. }
  550. return include, limit, p[12:], true
  551. }
  552. func parseScanID(p []byte) (uint64, bool) {
  553. if len(p) < 8 {
  554. return 0, false
  555. }
  556. return getU64(p[0:8]), true
  557. }