Skip to main content

nodedb_array/codec/
tag.rs

1// SPDX-License-Identifier: Apache-2.0
2
3// Tag byte identifying the tile codec on the wire. Sits as the very first
4// byte of every tile payload (after BlockFraming unwraps).
5//
6// Any byte not recognized as a valid CodecTag is treated as corruption and
7// must be surfaced as a SegmentCorruption error by the caller.
8
9/// One-byte codec tag at the front of a new-format (v4+) tile payload.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[repr(u8)]
12pub enum CodecTag {
13    /// Raw msgpack fallback: cell_count < 8 or sentinel-only tiles where
14    /// codec overhead exceeds potential savings.
15    Raw = 0,
16    /// Structural codec: coord delta + fastlanes surrogates + gorilla timestamps.
17    Structural = 1,
18}
19
20impl CodecTag {
21    pub fn from_byte(b: u8) -> Option<Self> {
22        match b {
23            0 => Some(CodecTag::Raw),
24            1 => Some(CodecTag::Structural),
25            _ => None,
26        }
27    }
28
29    pub fn as_byte(self) -> u8 {
30        self as u8
31    }
32}
33
34/// Peek at the first byte of a tile payload and return the codec tag.
35///
36/// Returns `Some(tag)` for a recognized new-format tag byte.
37/// Returns `None` for an empty payload or any unrecognized byte (including
38/// former msgpack map-start bytes 0x80–0x8f, 0xde, 0xdf) — callers must
39/// surface a `SegmentCorruption` error.
40pub fn peek_tag(payload: &[u8]) -> Option<CodecTag> {
41    let first = *payload.first()?;
42    CodecTag::from_byte(first)
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn raw_tag_roundtrips() {
51        assert_eq!(CodecTag::Raw.as_byte(), 0);
52        assert_eq!(CodecTag::from_byte(0), Some(CodecTag::Raw));
53    }
54
55    #[test]
56    fn structural_tag_roundtrips() {
57        assert_eq!(CodecTag::Structural.as_byte(), 1);
58        assert_eq!(CodecTag::from_byte(1), Some(CodecTag::Structural));
59    }
60
61    #[test]
62    fn unknown_byte_returns_none() {
63        assert_eq!(CodecTag::from_byte(42), None);
64        assert_eq!(CodecTag::from_byte(255), None);
65    }
66
67    #[test]
68    fn peek_tag_former_msgpack_bytes_return_none() {
69        // Bytes 0x80..=0x8f, 0xde, 0xdf were formerly treated as legacy msgpack
70        // map-start markers. They are now unknown tag bytes and must return None.
71        for b in 0x80u8..=0x8fu8 {
72            let payload = [b, 0x00];
73            assert_eq!(peek_tag(&payload), None, "byte {b:#04x} should be None");
74        }
75        assert_eq!(peek_tag(&[0xde, 0x00]), None);
76        assert_eq!(peek_tag(&[0xdf, 0x00]), None);
77    }
78
79    #[test]
80    fn peek_tag_detects_raw_tag() {
81        assert_eq!(peek_tag(&[0x00, 0x01, 0x02]), Some(CodecTag::Raw));
82    }
83
84    #[test]
85    fn peek_tag_detects_structural_tag() {
86        assert_eq!(peek_tag(&[0x01, 0x00]), Some(CodecTag::Structural));
87    }
88
89    #[test]
90    fn peek_tag_empty_returns_none() {
91        assert_eq!(peek_tag(&[]), None);
92    }
93
94    #[test]
95    fn peek_tag_unknown_byte_returns_none() {
96        // Byte 42 is not a valid tag and not a msgpack map header.
97        assert_eq!(peek_tag(&[42]), None);
98    }
99}