Skip to main content

auths_transparency/
tile.rs

1use crate::error::TransparencyError;
2
3/// Tile height (number of hash levels per tile). C2SP default = 8 → 256 hashes.
4pub const TILE_HEIGHT: u32 = 8;
5
6/// Number of leaf hashes per tile (2^TILE_HEIGHT).
7pub const TILE_WIDTH: u64 = 1 << TILE_HEIGHT;
8
9/// Encode a tile path per C2SP tlog-tiles spec.
10///
11/// Segments are zero-padded to 3 digits. Non-final segments get an `x` prefix.
12/// E.g., index 1234067 → `x001/x234/067`.
13///
14/// Args:
15/// * `level` — Tile level (0 for data tiles, 1+ for hash tiles).
16/// * `index` — Tile index at the given level.
17/// * `width` — Partial tile width (0 means full tile, i.e., 256).
18///
19/// Usage:
20/// ```ignore
21/// let path = tile_path(0, 1234067, 0)?;
22/// assert_eq!(path, "tile/0/x001/x234/067");
23/// ```
24pub fn tile_path(level: u32, index: u64, width: u64) -> Result<String, TransparencyError> {
25    let index_path = encode_index(index)?;
26    let mut path = format!("tile/{level}/{index_path}");
27    if width > 0 && width < TILE_WIDTH {
28        path.push_str(&format!(".p/{width}"));
29    }
30    Ok(path)
31}
32
33/// Encode a tile index into C2SP path segments.
34///
35/// Zero-padded 3-digit segments, non-final segments prefixed with `x`.
36fn encode_index(index: u64) -> Result<String, TransparencyError> {
37    if index == 0 {
38        return Ok("000".into());
39    }
40
41    let mut segments = Vec::new();
42    let mut remaining = index;
43    while remaining > 0 {
44        #[allow(clippy::cast_possible_truncation)]
45        let segment = (remaining % 1000) as u16;
46        segments.push(segment);
47        remaining /= 1000;
48    }
49    segments.reverse();
50
51    let mut parts = Vec::with_capacity(segments.len());
52    for (i, &seg) in segments.iter().enumerate() {
53        if i < segments.len() - 1 {
54            parts.push(format!("x{seg:03}"));
55        } else {
56            parts.push(format!("{seg:03}"));
57        }
58    }
59    Ok(parts.join("/"))
60}
61
62/// Compute which tile contains a given leaf index.
63///
64/// Args:
65/// * `leaf_index` — Zero-based leaf index.
66///
67/// Usage:
68/// ```ignore
69/// let (tile_index, offset) = leaf_tile(42);
70/// ```
71pub fn leaf_tile(leaf_index: u64) -> (u64, u64) {
72    (leaf_index / TILE_WIDTH, leaf_index % TILE_WIDTH)
73}
74
75/// Compute the number of full tiles and the partial tile width for a tree of `size` leaves.
76///
77/// Args:
78/// * `size` — Total number of leaves.
79///
80/// Usage:
81/// ```ignore
82/// let (full_tiles, partial_width) = tile_count(300);
83/// assert_eq!(full_tiles, 1);
84/// assert_eq!(partial_width, 44);
85/// ```
86pub fn tile_count(size: u64) -> (u64, u64) {
87    (size / TILE_WIDTH, size % TILE_WIDTH)
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn encode_index_zero() {
96        assert_eq!(encode_index(0).unwrap(), "000");
97    }
98
99    #[test]
100    fn encode_index_small() {
101        assert_eq!(encode_index(5).unwrap(), "005");
102        assert_eq!(encode_index(42).unwrap(), "042");
103        assert_eq!(encode_index(999).unwrap(), "999");
104    }
105
106    #[test]
107    fn encode_index_multi_segment() {
108        assert_eq!(encode_index(1000).unwrap(), "x001/000");
109        assert_eq!(encode_index(1234).unwrap(), "x001/234");
110        assert_eq!(encode_index(1234067).unwrap(), "x001/x234/067");
111    }
112
113    #[test]
114    fn tile_path_data_tile() {
115        assert_eq!(tile_path(0, 0, 0).unwrap(), "tile/0/000");
116        assert_eq!(tile_path(0, 5, 0).unwrap(), "tile/0/005");
117    }
118
119    #[test]
120    fn tile_path_partial() {
121        assert_eq!(tile_path(0, 0, 42).unwrap(), "tile/0/000.p/42");
122    }
123
124    #[test]
125    fn tile_path_hash_tile() {
126        assert_eq!(tile_path(1, 3, 0).unwrap(), "tile/1/003");
127    }
128
129    #[test]
130    fn tile_path_large_index() {
131        assert_eq!(tile_path(0, 1234067, 0).unwrap(), "tile/0/x001/x234/067");
132    }
133
134    #[test]
135    fn leaf_tile_computation() {
136        assert_eq!(leaf_tile(0), (0, 0));
137        assert_eq!(leaf_tile(255), (0, 255));
138        assert_eq!(leaf_tile(256), (1, 0));
139        assert_eq!(leaf_tile(300), (1, 44));
140    }
141
142    #[test]
143    fn tile_count_computation() {
144        assert_eq!(tile_count(0), (0, 0));
145        assert_eq!(tile_count(256), (1, 0));
146        assert_eq!(tile_count(300), (1, 44));
147        assert_eq!(tile_count(512), (2, 0));
148    }
149}