| 500 | /// that the footer CRC round-trips cleanly. |
| 501 | #[test] |
| 502 | fn golden_columnar_segment_format() { |
| 503 | // Header golden: magic at [0..4], major at [4], minor at [5]. |
| 504 | let header = SegmentHeader::current(); |
| 505 | let bytes = header.to_bytes(); |
| 506 | assert_eq!(&bytes[0..4], b"NDBS", "magic mismatch"); |
| 507 | assert_eq!(bytes[4], VERSION_MAJOR, "major version mismatch"); |
| 508 | assert_eq!(bytes[5], VERSION_MINOR, "minor version mismatch"); |
| 509 | assert_eq!(bytes[4], 1u8, "expected VERSION_MAJOR == 1"); |
| 510 | assert_eq!(bytes[5], 1u8, "expected VERSION_MINOR == 1"); |
| 511 | |
| 512 | // Footer golden: serialize, then re-parse, asserting CRC consistency. |
| 513 | let footer = SegmentFooter { |
| 514 | schema_hash: 0xAB_CD_EF_01, |
| 515 | column_count: 1, |
| 516 | row_count: 128, |
| 517 | profile_tag: 0, |
| 518 | columns: vec![ColumnMeta { |
| 519 | name: "v".into(), |
| 520 | offset: 0, |
| 521 | length: 64, |
| 522 | codec: nodedb_codec::ResolvedColumnCodec::Lz4, |
| 523 | block_count: 1, |
| 524 | block_stats: vec![BlockStats::non_numeric(0, 128)], |
| 525 | dictionary: None, |
| 526 | }], |
| 527 | }; |
| 528 | let footer_bytes = footer.to_bytes().expect("serialize"); |
| 529 | // Layout: [msgpack_body][footer_len u32 LE][crc u32 LE] |
| 530 | let n = footer_bytes.len(); |
| 531 | let stored_crc = u32::from_le_bytes([ |
| 532 | footer_bytes[n - 4], |
| 533 | footer_bytes[n - 3], |
| 534 | footer_bytes[n - 2], |
| 535 | footer_bytes[n - 1], |
| 536 | ]); |
| 537 | let body_len = u32::from_le_bytes([ |
| 538 | footer_bytes[n - 8], |
| 539 | footer_bytes[n - 7], |
| 540 | footer_bytes[n - 6], |
| 541 | footer_bytes[n - 5], |
| 542 | ]) as usize; |
| 543 | // CRC is computed over the msgpack body only (bytes 0..body_len). |
| 544 | let recomputed = crc32c::crc32c(&footer_bytes[..body_len]); |
| 545 | assert_eq!(stored_crc, recomputed, "footer CRC mismatch"); |
| 546 | |
| 547 | // Round-trip via from_segment_tail. |
| 548 | let mut segment = Vec::new(); |
| 549 | segment.extend_from_slice(&bytes); |
| 550 | segment.extend_from_slice(&[0u8; 64]); |
| 551 | segment.extend_from_slice(&footer_bytes); |
| 552 | let parsed = SegmentFooter::from_segment_tail(&segment).expect("parse"); |
| 553 | assert_eq!(parsed.schema_hash, footer.schema_hash); |
| 554 | assert_eq!(parsed.row_count, 128); |
| 555 | } |
| 556 | } |
| 557 | } |