1use crate::varint::{read_uvarint, write_uvarint};
10
11pub type Triple = (u32, u32, u32);
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct ZoneMap {
17 pub min_a: u32,
18 pub max_a: u32,
19 pub min_b: u32,
20 pub max_b: u32,
21 pub min_c: u32,
22 pub max_c: u32,
23 pub count: u32,
24}
25
26impl ZoneMap {
27 pub fn may_contain(&self, a: Option<u32>, b: Option<u32>, c: Option<u32>) -> bool {
30 let in_range = |v: Option<u32>, lo: u32, hi: u32| v.is_none_or(|x| lo <= x && x <= hi);
31 in_range(a, self.min_a, self.max_a)
32 && in_range(b, self.min_b, self.max_b)
33 && in_range(c, self.min_c, self.max_c)
34 }
35}
36
37#[derive(Debug, thiserror::Error)]
38pub enum TripleError {
39 #[error("malformed triple block: {0}")]
40 Malformed(&'static str),
41}
42
43#[derive(Default)]
45pub struct TripleBlockBuilder {
46 triples: Vec<Triple>,
47}
48
49impl TripleBlockBuilder {
50 pub fn new() -> Self {
51 Self::default()
52 }
53
54 pub fn push(&mut self, t: Triple) {
55 self.triples.push(t);
56 }
57
58 pub fn len(&self) -> usize {
59 self.triples.len()
60 }
61
62 pub fn is_empty(&self) -> bool {
63 self.triples.is_empty()
64 }
65
66 pub fn build(mut self) -> Vec<u8> {
68 self.triples.sort_unstable();
69 self.triples.dedup();
70 let t = &self.triples;
71
72 let mut out = Vec::new();
73 if t.is_empty() {
74 for _ in 0..7 {
76 write_uvarint(&mut out, 0);
77 }
78 write_uvarint(&mut out, 0); return out;
80 }
81
82 let (mut min_a, mut max_a) = (u32::MAX, 0u32);
84 let (mut min_b, mut max_b) = (u32::MAX, 0u32);
85 let (mut min_c, mut max_c) = (u32::MAX, 0u32);
86 for &(a, b, c) in t {
87 min_a = min_a.min(a);
88 max_a = max_a.max(a);
89 min_b = min_b.min(b);
90 max_b = max_b.max(b);
91 min_c = min_c.min(c);
92 max_c = max_c.max(c);
93 }
94 for v in [min_a, max_a, min_b, max_b, min_c, max_c, t.len() as u32] {
95 write_uvarint(&mut out, v as u64);
96 }
97
98 type BGroup = (u32, Vec<u32>);
102 type AGroup = (u32, Vec<BGroup>);
103 let mut i = 0;
104 let mut a_groups: Vec<AGroup> = Vec::new();
105 while i < t.len() {
106 let a = t[i].0;
107 let mut b_groups: Vec<(u32, Vec<u32>)> = Vec::new();
108 while i < t.len() && t[i].0 == a {
109 let b = t[i].1;
110 let mut cs = Vec::new();
111 while i < t.len() && t[i].0 == a && t[i].1 == b {
112 cs.push(t[i].2);
113 i += 1;
114 }
115 b_groups.push((b, cs));
116 }
117 a_groups.push((a, b_groups));
118 }
119
120 write_uvarint(&mut out, a_groups.len() as u64);
121 let mut prev_a = 0u32;
122 for (a, b_groups) in &a_groups {
123 write_uvarint(&mut out, (a - prev_a) as u64);
124 prev_a = *a;
125 write_uvarint(&mut out, b_groups.len() as u64);
126 let mut prev_b = 0u32;
127 for (b, cs) in b_groups {
128 write_uvarint(&mut out, (b - prev_b) as u64);
129 prev_b = *b;
130 write_uvarint(&mut out, cs.len() as u64);
131 let mut prev_c = 0u32;
132 for c in cs {
133 write_uvarint(&mut out, (c - prev_c) as u64);
134 prev_c = *c;
135 }
136 }
137 }
138 out
139 }
140}
141
142pub struct TripleBlock<'a> {
144 bytes: &'a [u8],
145 zone: ZoneMap,
146 body_start: usize,
147}
148
149impl<'a> TripleBlock<'a> {
150 pub fn parse(bytes: &'a [u8]) -> Result<Self, TripleError> {
151 let mut pos = 0;
152 let take = |pos: &mut usize| -> Result<u32, TripleError> {
153 rd(bytes, pos).ok_or(TripleError::Malformed("truncated or out-of-range zone map"))
154 };
155 let zone = ZoneMap {
156 min_a: take(&mut pos)?,
157 max_a: take(&mut pos)?,
158 min_b: take(&mut pos)?,
159 max_b: take(&mut pos)?,
160 min_c: take(&mut pos)?,
161 max_c: take(&mut pos)?,
162 count: take(&mut pos)?,
163 };
164 Ok(Self {
165 bytes,
166 zone,
167 body_start: pos,
168 })
169 }
170
171 pub fn zone(&self) -> &ZoneMap {
172 &self.zone
173 }
174
175 pub fn triples(&self) -> Vec<Triple> {
180 self.try_triples().unwrap_or_default()
181 }
182
183 fn try_triples(&self) -> Option<Vec<Triple>> {
184 let mut out = Vec::with_capacity((self.zone.count as usize).min(self.bytes.len()));
187 let mut pos = self.body_start;
188 let g = |pos: &mut usize| rd(self.bytes, pos);
189 let num_a = g(&mut pos)?;
190 let mut a = 0u32;
191 for _ in 0..num_a {
192 a = a.checked_add(g(&mut pos)?)?;
195 let num_b = g(&mut pos)?;
196 let mut b = 0u32;
197 for _ in 0..num_b {
198 b = b.checked_add(g(&mut pos)?)?;
199 let num_c = g(&mut pos)?;
200 let mut c = 0u32;
201 for _ in 0..num_c {
202 c = c.checked_add(g(&mut pos)?)?;
203 out.push((a, b, c));
204 }
205 }
206 }
207 Some(out)
208 }
209
210 pub fn group_directory(&self) -> GroupDirectory {
215 let bytes = self.bytes;
216 let mut entries = Vec::new();
217 let mut p = self.body_start;
218 let mut walk = || -> Option<()> {
219 let num_a = rd(bytes, &mut p)?;
220 entries.reserve((num_a as usize).min(bytes.len()));
223 let mut a = 0u32;
224 for i in 0..num_a {
225 a = a.checked_add(rd(bytes, &mut p)?)?;
226 let num_b = rd(bytes, &mut p)?;
227 entries.push(DirEntry {
228 a,
229 pos: p,
230 num_b,
231 a_rem_after: num_a - 1 - i,
232 });
233 for _ in 0..num_b {
234 rd(bytes, &mut p)?; let nc = rd(bytes, &mut p)?;
236 for _ in 0..nc {
237 rd(bytes, &mut p)?;
238 }
239 }
240 }
241 Some(())
242 };
243 let _ = walk();
244 GroupDirectory { entries }
245 }
246
247 pub fn scan_from(
252 &self,
253 dir: &GroupDirectory,
254 pa: u32,
255 pb: Option<u32>,
256 pc: Option<u32>,
257 ) -> BlockCursor<'a> {
258 let mut cursor = BlockCursor {
259 bytes: self.bytes,
260 pos: self.body_start,
261 a: 0,
262 b: 0,
263 c: 0,
264 a_rem: 0,
265 b_rem: 0,
266 c_rem: 0,
267 started: true, pa: Some(pa),
269 pb,
270 pc,
271 };
272 if let Ok(i) = dir.entries.binary_search_by_key(&pa, |e| e.a) {
273 let e = &dir.entries[i];
274 cursor.pos = e.pos;
277 cursor.a = e.a;
278 cursor.a_rem = e.a_rem_after;
279 cursor.b_rem = e.num_b;
280 }
281 cursor
282 }
283
284 pub fn scan_resume(
297 &self,
298 dir: &GroupDirectory,
299 from_a: u32,
300 pb: Option<u32>,
301 pc: Option<u32>,
302 ) -> BlockCursor<'a> {
303 let mut cursor = BlockCursor {
304 bytes: self.bytes,
305 pos: self.body_start,
306 a: 0,
307 b: 0,
308 c: 0,
309 a_rem: 0,
310 b_rem: 0,
311 c_rem: 0,
312 started: true, pa: None,
314 pb,
315 pc,
316 };
317 let i = dir.entries.partition_point(|e| e.a < from_a);
321 if let Some(e) = dir.entries.get(i) {
322 cursor.pos = e.pos;
325 cursor.a = e.a;
326 cursor.a_rem = e.a_rem_after;
327 cursor.b_rem = e.num_b;
328 }
329 cursor
330 }
331
332 pub fn scan(&self, pa: Option<u32>, pb: Option<u32>, pc: Option<u32>) -> BlockCursor<'a> {
350 BlockCursor {
351 bytes: self.bytes,
352 pos: self.body_start,
353 a: 0,
354 b: 0,
355 c: 0,
356 a_rem: 0,
357 b_rem: 0,
358 c_rem: 0,
359 started: false,
360 pa,
361 pb,
362 pc,
363 }
364 }
365}
366
367#[inline]
373fn rd(bytes: &[u8], pos: &mut usize) -> Option<u32> {
374 let (v, n) = read_uvarint(bytes.get(*pos..)?)?;
375 let v = u32::try_from(v).ok()?;
376 *pos += n;
377 Some(v)
378}
379
380pub struct GroupDirectory {
385 entries: Vec<DirEntry>,
386}
387
388impl GroupDirectory {
389 pub fn len(&self) -> usize {
391 self.entries.len()
392 }
393
394 pub fn is_empty(&self) -> bool {
395 self.entries.is_empty()
396 }
397
398 pub fn heap_bytes(&self) -> u64 {
400 (self.entries.capacity() * std::mem::size_of::<DirEntry>()) as u64
401 }
402
403 pub fn first_at_or_after(&self, a: u32) -> Option<u32> {
405 let i = self.entries.partition_point(|e| e.a < a);
406 self.entries.get(i).map(|e| e.a)
407 }
408
409 #[cfg(test)]
411 pub(crate) fn empty_for_tests() -> Self {
412 GroupDirectory {
413 entries: Vec::new(),
414 }
415 }
416}
417
418struct DirEntry {
421 a: u32,
422 pos: usize,
423 num_b: u32,
424 a_rem_after: u32,
425}
426
427pub struct BlockCursor<'a> {
431 bytes: &'a [u8],
432 pos: usize,
433 a: u32,
435 b: u32,
436 c: u32,
437 a_rem: u32,
439 b_rem: u32,
440 c_rem: u32,
441 started: bool,
442 pa: Option<u32>,
443 pb: Option<u32>,
444 pc: Option<u32>,
445}
446
447impl Iterator for BlockCursor<'_> {
448 type Item = Triple;
449
450 fn next(&mut self) -> Option<Triple> {
451 let bytes = self.bytes;
452 if !self.started {
453 self.a_rem = rd(bytes, &mut self.pos)?; self.started = true;
455 }
456 loop {
457 while self.c_rem > 0 {
460 self.c_rem -= 1;
461 self.c = self.c.checked_add(rd(bytes, &mut self.pos)?)?;
462 if self.pc.is_none_or(|z| z == self.c) {
463 return Some((self.a, self.b, self.c));
464 }
465 }
466 while self.b_rem > 0 {
468 self.b_rem -= 1;
469 self.b = self.b.checked_add(rd(bytes, &mut self.pos)?)?;
470 let num_c = rd(bytes, &mut self.pos)?;
471 if self.pb.is_some_and(|y| y != self.b) {
472 for _ in 0..num_c {
473 rd(bytes, &mut self.pos)?; }
475 continue;
476 }
477 self.c = 0; self.c_rem = num_c;
479 break;
480 }
481 if self.c_rem > 0 {
482 continue; }
484 if self.a_rem == 0 {
486 return None;
487 }
488 self.a_rem -= 1;
489 self.a = self.a.checked_add(rd(bytes, &mut self.pos)?)?;
490 let num_b = rd(bytes, &mut self.pos)?;
491 self.b = 0; if let Some(x) = self.pa {
493 if self.a > x {
494 return None; }
496 if self.a < x {
497 for _ in 0..num_b {
499 rd(bytes, &mut self.pos)?; let nc = rd(bytes, &mut self.pos)?;
501 for _ in 0..nc {
502 rd(bytes, &mut self.pos)?;
503 }
504 }
505 continue;
506 }
507 }
508 self.b_rem = num_b;
509 }
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 fn sample() -> Vec<Triple> {
518 vec![
520 (5, 2, 9),
521 (1, 1, 1),
522 (1, 1, 4),
523 (1, 3, 2),
524 (5, 2, 7),
525 (1, 1, 1), (2, 9, 9),
527 ]
528 }
529
530 #[test]
531 fn round_trip_sorted_dedup() {
532 let mut b = TripleBlockBuilder::new();
533 for t in sample() {
534 b.push(t);
535 }
536 let bytes = b.build();
537 let blk = TripleBlock::parse(&bytes).unwrap();
538
539 let mut expected = sample();
540 expected.sort_unstable();
541 expected.dedup();
542 assert_eq!(blk.zone().count as usize, expected.len());
543 assert_eq!(blk.triples(), expected);
544 }
545
546 #[test]
547 fn zone_map_bounds_and_skipping() {
548 let mut b = TripleBlockBuilder::new();
549 for t in sample() {
550 b.push(t);
551 }
552 let bytes = b.build();
553 let blk = TripleBlock::parse(&bytes).unwrap();
554 let z = blk.zone();
555 assert_eq!((z.min_a, z.max_a), (1, 5));
556 assert_eq!((z.min_b, z.max_b), (1, 9));
557 assert_eq!((z.min_c, z.max_c), (1, 9));
558 assert!(z.may_contain(Some(3), None, None));
560 assert!(!z.may_contain(Some(99), None, None));
561 assert!(z.may_contain(None, None, None)); }
563
564 #[test]
565 fn empty_block() {
566 let blk_bytes = TripleBlockBuilder::new().build();
567 let blk = TripleBlock::parse(&blk_bytes).unwrap();
568 assert_eq!(blk.zone().count, 0);
569 assert!(blk.triples().is_empty());
570 assert!(blk.scan(None, None, None).next().is_none());
571 assert!(blk.scan(Some(1), None, None).next().is_none());
572 }
573
574 #[test]
577 fn scan_matches_full_decode_every_shape() {
578 let mut b = TripleBlockBuilder::new();
579 for t in sample() {
580 b.push(t);
581 }
582 let bytes = b.build();
583 let blk = TripleBlock::parse(&bytes).unwrap();
584 let all = blk.triples(); let opt = |v: u32| [None, Some(v)];
587 for pa in opt(1).into_iter().chain([Some(5), Some(99)]) {
589 for pb in opt(1).into_iter().chain([Some(2), Some(99)]) {
590 for pc in opt(1).into_iter().chain([Some(9), Some(99)]) {
591 let want: Vec<Triple> = all
592 .iter()
593 .copied()
594 .filter(|&(a, bb, c)| {
595 pa.is_none_or(|x| x == a)
596 && pb.is_none_or(|x| x == bb)
597 && pc.is_none_or(|x| x == c)
598 })
599 .collect();
600 let got: Vec<Triple> = blk.scan(pa, pb, pc).collect();
602 assert_eq!(got, want, "scan({pa:?},{pb:?},{pc:?})");
603 }
604 }
605 }
606 }
607
608 #[test]
611 fn scan_range_stops_on_leading_bound() {
612 let mut b = TripleBlockBuilder::new();
613 for t in [(1, 1, 1), (1, 2, 2), (3, 1, 1), (5, 1, 1)] {
614 b.push(t);
615 }
616 let bytes = b.build();
617 let blk = TripleBlock::parse(&bytes).unwrap();
618 let got: Vec<Triple> = blk.scan(Some(1), None, None).collect();
619 assert_eq!(got, vec![(1, 1, 1), (1, 2, 2)]);
620 assert!(blk.scan(Some(2), None, None).next().is_none());
622 assert!(blk.scan(Some(99), None, None).next().is_none());
623 }
624
625 #[test]
628 fn scan_never_panics_on_bad_bytes() {
629 let mut b = TripleBlockBuilder::new();
630 for t in sample() {
631 b.push(t);
632 }
633 let bytes = b.build();
634 for len in 0..bytes.len() {
635 if let Ok(blk) = TripleBlock::parse(&bytes[..len]) {
636 for pat in [(None, None, None), (Some(1u32), Some(1u32), Some(1u32))] {
637 let _ = blk.scan(pat.0, pat.1, pat.2).count();
638 }
639 }
640 }
641 for i in 0..bytes.len() {
642 for v in [0x00u8, 0xff, 0x80, 0x7f] {
643 let mut bad = bytes.clone();
644 bad[i] = v;
645 if let Ok(blk) = TripleBlock::parse(&bad) {
646 let _ = blk.scan(None, None, None).count();
647 let _ = blk.scan(Some(1), None, None).count();
648 }
649 }
650 }
651 }
652}