2
0

testkv.go 14 KB

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