1use crate::dra::op::{Op, PackItem, decode_items};
4use crate::error::{Error, Result};
5use crate::limits::Limits;
6
7pub const DRA_VERSION: u8 = 6;
9
10pub const MAX_OFFSET_SLOTS: usize = 256;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Authority {
21 Literal,
23 Generated,
25 EntropyChannel,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct Span {
32 pub start: u64,
34 pub len: u64,
36 pub authority: Authority,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Default)]
45pub struct CoverageMap {
46 pub spans: Vec<Span>,
48}
49
50impl CoverageMap {
51 pub fn total_len(&self) -> u64 {
53 self.spans.last().map(|s| s.start + s.len).unwrap_or(0)
54 }
55
56 pub fn validate(&self, declared_len: u64) -> Result<()> {
58 let mut expected = 0u64;
59 for s in &self.spans {
60 if s.start != expected {
61 return Err(Error::coverage_violation(format!(
62 "coverage gap/overlap: expected span at {expected}, found {}",
63 s.start
64 )));
65 }
66 expected = expected
67 .checked_add(s.len)
68 .ok_or_else(|| Error::coverage_violation("coverage length overflow"))?;
69 }
70 if expected != declared_len {
71 return Err(Error::coverage_violation(format!(
72 "coverage covers {expected} bytes but {declared_len} were declared"
73 )));
74 }
75 Ok(())
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Default)]
81pub struct Program {
82 pub ops: Vec<Op>,
84}
85
86impl Program {
87 pub fn new(ops: Vec<Op>) -> Self {
89 Program { ops }
90 }
91
92 pub fn encode(&self) -> Result<Vec<u8>> {
94 let count = u32::try_from(self.ops.len())
95 .map_err(|_| Error::resource_limit("too many DRA instructions"))?;
96 let mut out = Vec::with_capacity(5 + self.ops.len() * 5);
97 out.push(DRA_VERSION);
98 out.extend_from_slice(&count.to_le_bytes());
99 for op in &self.ops {
100 op.encode(&mut out)?;
101 }
102 Ok(out)
103 }
104
105 pub fn decode(data: &[u8], limits: Limits) -> Result<Program> {
107 if data.is_empty() {
108 return Err(Error::invalid_graph("empty graph record"));
109 }
110 if data[0] != DRA_VERSION {
111 return Err(Error::unsupported_version(format!(
112 "DRA version {} is not supported",
113 data[0]
114 )));
115 }
116 if data.len() < 5 {
117 return Err(Error::invalid_graph("truncated graph header"));
118 }
119 let count = u32::from_le_bytes([data[1], data[2], data[3], data[4]]);
120 if count > limits.max_graph_ops {
121 return Err(Error::resource_limit(format!(
122 "graph has {count} instructions, limit {}",
123 limits.max_graph_ops
124 )));
125 }
126 let mut pos = 5usize;
127 let mut ops = Vec::with_capacity(count as usize);
128 for _ in 0..count {
129 ops.push(Op::decode(data, &mut pos, limits)?);
130 }
131 if pos != data.len() {
132 return Err(Error::invalid_graph(format!(
133 "graph record has {} trailing bytes",
134 data.len() - pos
135 )));
136 }
137 Ok(Program { ops })
138 }
139
140 pub fn analyze(
145 &self,
146 object_lens: &[u64],
147 channel_lens: &[u64],
148 limits: Limits,
149 ) -> Result<(u64, CoverageMap)> {
150 if self.ops.len() as u64 > limits.max_graph_ops as u64 {
151 return Err(Error::resource_limit("graph instruction limit exceeded"));
152 }
153 let mut spans: Vec<Span> = Vec::new();
154 let mut total: u64 = 0;
155 let mut last_len: u64 = 0;
156 let mut have_last = false;
157 let mut marked = [false; MAX_OFFSET_SLOTS];
159
160 for op in &self.ops {
161 match op {
162 Op::EmitObject { object_id } => {
163 let len = *object_lens.get(*object_id as usize).ok_or_else(|| {
164 Error::invalid_graph(format!("graph references missing object {object_id}"))
165 })?;
166 total = total
167 .checked_add(len)
168 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
169 if len > 0 {
170 spans.push(Span {
171 start: total - len,
172 len,
173 authority: Authority::Literal,
174 });
175 }
176 last_len = len;
177 have_last = true;
178 }
179 Op::Inline { bytes } => {
180 let len = bytes.len() as u64;
181 total = total
182 .checked_add(len)
183 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
184 if len > 0 {
185 spans.push(Span {
186 start: total - len,
187 len,
188 authority: Authority::Literal,
189 });
190 }
191 last_len = len;
192 have_last = true;
193 }
194 Op::DecodeChannel { channel_id } => {
195 let len = *channel_lens.get(*channel_id as usize).ok_or_else(|| {
196 Error::invalid_graph(format!(
197 "graph references missing entropy channel {channel_id}"
198 ))
199 })?;
200 total = total
201 .checked_add(len)
202 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
203 if len > 0 {
204 spans.push(Span {
205 start: total - len,
206 len,
207 authority: Authority::EntropyChannel,
208 });
209 }
210 last_len = len;
211 have_last = true;
212 }
213 Op::InterleaveChannels {
214 first_payload_channel,
215 payload_channel_count,
216 ..
217 } => {
218 let first = *first_payload_channel as usize;
219 let count = *payload_channel_count as usize;
220 let end = first
221 .checked_add(count)
222 .ok_or_else(|| Error::invalid_graph("interleave channel range overflow"))?;
223 if end > channel_lens.len() {
224 return Err(Error::invalid_graph(format!(
225 "interleave payload channel range {first}..{end} exceeds {} channels",
226 channel_lens.len()
227 )));
228 }
229 let mut len: u64 = 0;
230 for &seg in &channel_lens[first..end] {
231 len = len
232 .checked_add(seg)
233 .ok_or_else(|| Error::resource_limit("interleave length overflow"))?;
234 }
235 total = total
236 .checked_add(len)
237 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
238 if len > 0 {
239 spans.push(Span {
240 start: total - len,
241 len,
242 authority: Authority::Generated,
243 });
244 }
245 last_len = len;
246 have_last = true;
247 }
248 Op::MarkOffset { slot } => {
249 let idx = *slot as usize;
250 if idx >= MAX_OFFSET_SLOTS {
251 return Err(Error::invalid_graph(format!(
252 "MARK_OFFSET slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
253 )));
254 }
255 marked[idx] = true;
258 }
259 Op::EmitOffset { slot, width } => {
260 let idx = *slot as usize;
261 if idx >= MAX_OFFSET_SLOTS {
262 return Err(Error::invalid_graph(format!(
263 "EMIT_OFFSET slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
264 )));
265 }
266 if *width == 0 || *width > 20 {
267 return Err(Error::invalid_graph(format!(
268 "EMIT_OFFSET width {width} is outside 1..=20"
269 )));
270 }
271 if !marked[idx] {
272 return Err(Error::invalid_graph(format!(
273 "EMIT_OFFSET references unmarked slot {slot}"
274 )));
275 }
276 let len = *width as u64;
279 total = total
280 .checked_add(len)
281 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
282 spans.push(Span {
283 start: total - len,
284 len,
285 authority: Authority::Generated,
286 });
287 last_len = len;
288 have_last = true;
289 }
290 Op::RepeatLast { count } => {
291 if !have_last {
292 return Err(Error::invalid_graph(
293 "REPEAT_LAST has no preceding literal instruction",
294 ));
295 }
296 if (*count as u64) > limits.max_repeat_count {
297 return Err(Error::resource_limit(format!(
298 "REPEAT_LAST count {count} exceeds limit {}",
299 limits.max_repeat_count
300 )));
301 }
302 let extra = last_len
303 .checked_mul(*count as u64)
304 .ok_or_else(|| Error::resource_limit("repeat length overflow"))?;
305 total = total
306 .checked_add(extra)
307 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
308 if extra > 0 {
309 spans.push(Span {
310 start: total - extra,
311 len: extra,
312 authority: Authority::Generated,
313 });
314 }
315 have_last = false;
318 last_len = 0;
319 }
320 Op::PackSegments { data_object, items } => {
321 let data_len = *object_lens.get(*data_object as usize).ok_or_else(|| {
322 Error::invalid_graph(format!(
323 "graph references missing object {data_object}"
324 ))
325 })?;
326 let mut literal_total: u64 = 0;
327 let mut produced: u64 = 0;
328 let mut marked = [false; MAX_OFFSET_SLOTS];
330 for item in items {
331 match item {
332 PackItem::Literal { len } => {
333 literal_total =
334 literal_total.checked_add(*len as u64).ok_or_else(|| {
335 Error::resource_limit("packed literal length overflow")
336 })?;
337 produced = produced.checked_add(*len as u64).ok_or_else(|| {
338 Error::resource_limit("output length overflow")
339 })?;
340 }
341 PackItem::Mark { slot } => {
342 let idx = *slot as usize;
343 if idx >= MAX_OFFSET_SLOTS {
344 return Err(Error::invalid_graph(format!(
345 "PACK_SEGMENTS mark slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
346 )));
347 }
348 marked[idx] = true;
349 }
350 PackItem::Emit { slot, width } => {
351 let idx = *slot as usize;
352 if idx >= MAX_OFFSET_SLOTS {
353 return Err(Error::invalid_graph(format!(
354 "PACK_SEGMENTS emit slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
355 )));
356 }
357 if *width == 0 || *width > 20 {
358 return Err(Error::invalid_graph(format!(
359 "PACK_SEGMENTS emit width {width} is outside 1..=20"
360 )));
361 }
362 if !marked[idx] {
363 return Err(Error::invalid_graph(format!(
364 "PACK_SEGMENTS emit references unmarked slot {slot}"
365 )));
366 }
367 produced =
368 produced.checked_add(*width as u64).ok_or_else(|| {
369 Error::resource_limit("output length overflow")
370 })?;
371 }
372 }
373 }
374 if literal_total > data_len {
375 return Err(Error::invalid_graph(format!(
376 "PACK_SEGMENTS literal runs total {literal_total} bytes but data object {data_object} holds {data_len}"
377 )));
378 }
379 total = total
380 .checked_add(produced)
381 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
382 if produced > 0 {
383 spans.push(Span {
384 start: total - produced,
385 len: produced,
386 authority: Authority::Generated,
387 });
388 }
389 last_len = produced;
391 have_last = true;
392 }
393 Op::PackedChannels {
394 data_channel,
395 plan_channel,
396 declared_output_len,
397 } => {
398 if *data_channel as usize >= channel_lens.len() {
399 return Err(Error::invalid_graph(format!(
400 "graph references missing entropy channel {data_channel}"
401 )));
402 }
403 if *plan_channel as usize >= channel_lens.len() {
404 return Err(Error::invalid_graph(format!(
405 "graph references missing entropy channel {plan_channel}"
406 )));
407 }
408 if *declared_output_len > limits.max_output_bytes {
409 return Err(Error::resource_limit(format!(
410 "PACKED_CHANNELS declared output {declared_output_len} exceeds limit {}",
411 limits.max_output_bytes
412 )));
413 }
414 let len = *declared_output_len;
418 total = total
419 .checked_add(len)
420 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
421 if len > 0 {
422 spans.push(Span {
423 start: total - len,
424 len,
425 authority: Authority::Generated,
426 });
427 }
428 last_len = len;
429 have_last = true;
430 }
431 }
432 if total > limits.max_output_bytes {
433 return Err(Error::resource_limit(format!(
434 "predicted output {total} exceeds limit {}",
435 limits.max_output_bytes
436 )));
437 }
438 }
439
440 Ok((total, CoverageMap { spans }))
441 }
442
443 pub fn analyze_inputs(
445 &self,
446 objects: &[Vec<u8>],
447 channels: &[Vec<u8>],
448 limits: Limits,
449 ) -> Result<(u64, CoverageMap)> {
450 let object_lens: Vec<u64> = objects.iter().map(|o| o.len() as u64).collect();
451 let channel_lens: Vec<u64> = channels.iter().map(|c| c.len() as u64).collect();
452 self.analyze(&object_lens, &channel_lens, limits)
453 }
454
455 pub fn analyze_objects(
458 &self,
459 objects: &[Vec<u8>],
460 limits: Limits,
461 ) -> Result<(u64, CoverageMap)> {
462 let lens: Vec<u64> = objects.iter().map(|o| o.len() as u64).collect();
463 self.analyze(&lens, &[], limits)
464 }
465
466 pub fn eval(
468 &self,
469 objects: &[Vec<u8>],
470 channels: &[Vec<u8>],
471 limits: Limits,
472 ) -> Result<Vec<u8>> {
473 let (predicted, _coverage) = self.analyze_inputs(objects, channels, limits)?;
474 let cap = predicted.min(64 * 1024 * 1024) as usize;
475 let mut out: Vec<u8> = Vec::with_capacity(cap);
476 let mut have_last = false;
477 let mut block_len: usize = 0;
478 let mut slots = [0u64; MAX_OFFSET_SLOTS];
480 let mut marked = [false; MAX_OFFSET_SLOTS];
481
482 for op in &self.ops {
483 match op {
484 Op::EmitObject { object_id } => {
485 let obj = objects.get(*object_id as usize).ok_or_else(|| {
486 Error::invalid_graph(format!("graph references missing object {object_id}"))
487 })?;
488 block_len = obj.len();
489 out.extend_from_slice(obj);
490 have_last = true;
491 }
492 Op::Inline { bytes } => {
493 block_len = bytes.len();
494 out.extend_from_slice(bytes);
495 have_last = true;
496 }
497 Op::DecodeChannel { channel_id } => {
498 let ch = channels.get(*channel_id as usize).ok_or_else(|| {
499 Error::invalid_graph(format!(
500 "graph references missing entropy channel {channel_id}"
501 ))
502 })?;
503 block_len = ch.len();
504 out.extend_from_slice(ch);
505 have_last = true;
506 }
507 Op::InterleaveChannels {
508 kinds_channel,
509 lengths_channel,
510 first_payload_channel,
511 payload_channel_count,
512 } => {
513 let kinds = channels.get(*kinds_channel as usize).ok_or_else(|| {
514 Error::invalid_graph(format!(
515 "graph references missing entropy channel {kinds_channel}"
516 ))
517 })?;
518 let lengths = channels.get(*lengths_channel as usize).ok_or_else(|| {
519 Error::invalid_graph(format!(
520 "graph references missing entropy channel {lengths_channel}"
521 ))
522 })?;
523 let token_count = kinds.len();
524 let required = token_count
525 .checked_mul(4)
526 .ok_or_else(|| Error::invalid_graph("interleave lengths overflow"))?;
527 if lengths.len() != required {
528 return Err(Error::invalid_graph(format!(
529 "interleave length channel has {} bytes but {required} are required",
530 lengths.len()
531 )));
532 }
533 let first = *first_payload_channel as usize;
534 let count = *payload_channel_count as usize;
535 let end = first
536 .checked_add(count)
537 .ok_or_else(|| Error::invalid_graph("interleave channel range overflow"))?;
538 if end > channels.len() {
539 return Err(Error::invalid_graph(format!(
540 "interleave payload channel range {first}..{end} exceeds {} channels",
541 channels.len()
542 )));
543 }
544 let start = out.len();
545 let mut cursors = vec![0usize; count];
546 for (i, chunk) in lengths.as_chunks::<4>().0.iter().enumerate() {
547 let k = kinds[i] as usize;
548 if k >= count {
549 return Err(Error::invalid_graph(format!(
550 "interleave token {i} names kind {k} outside 0..{count}"
551 )));
552 }
553 let l = u32::from_le_bytes(*chunk) as usize;
554 let ch = &channels[first + k];
555 let cursor = cursors[k];
556 let seg_end = cursor.checked_add(l).ok_or_else(|| {
557 Error::invalid_graph("interleave payload cursor overflow")
558 })?;
559 if seg_end > ch.len() {
560 return Err(Error::invalid_graph(format!(
561 "interleave token {i} reads {l} bytes past channel {} ({cursor}..{seg_end} of {})",
562 first + k,
563 ch.len()
564 )));
565 }
566 out.extend_from_slice(&ch[cursor..seg_end]);
567 cursors[k] = seg_end;
568 if out.len() as u64 > limits.max_output_bytes {
569 return Err(Error::resource_limit(
570 "output exceeds materialization limit",
571 ));
572 }
573 }
574 for (k, &cursor) in cursors.iter().enumerate() {
575 let ch_len = channels[first + k].len();
576 if cursor != ch_len {
577 return Err(Error::invalid_graph(format!(
578 "interleave payload channel {} was not fully consumed ({cursor} of {ch_len})",
579 first + k
580 )));
581 }
582 }
583 block_len = out.len() - start;
584 have_last = true;
585 }
586 Op::MarkOffset { slot } => {
587 let idx = *slot as usize;
588 if idx >= MAX_OFFSET_SLOTS {
589 return Err(Error::invalid_graph(format!(
590 "MARK_OFFSET slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
591 )));
592 }
593 slots[idx] = out.len() as u64;
594 marked[idx] = true;
595 }
596 Op::EmitOffset { slot, width } => {
597 let idx = *slot as usize;
598 if idx >= MAX_OFFSET_SLOTS {
599 return Err(Error::invalid_graph(format!(
600 "EMIT_OFFSET slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
601 )));
602 }
603 if *width == 0 || *width > 20 {
604 return Err(Error::invalid_graph(format!(
605 "EMIT_OFFSET width {width} is outside 1..=20"
606 )));
607 }
608 if !marked[idx] {
609 return Err(Error::invalid_graph(format!(
610 "EMIT_OFFSET references unmarked slot {slot}"
611 )));
612 }
613 let digits = slots[idx].to_string();
614 if digits.len() > *width as usize {
615 return Err(Error::invalid_graph(format!(
616 "EMIT_OFFSET slot {slot} value {} needs {} bytes but width is {width}",
617 slots[idx],
618 digits.len()
619 )));
620 }
621 let start = out.len();
622 out.extend(std::iter::repeat_n(b'0', *width as usize - digits.len()));
623 out.extend_from_slice(digits.as_bytes());
624 debug_assert_eq!(out.len() - start, *width as usize);
625 block_len = out.len() - start;
626 have_last = true;
627 }
628 Op::RepeatLast { count } => {
629 if !have_last {
630 return Err(Error::invalid_graph(
631 "REPEAT_LAST has no preceding literal instruction",
632 ));
633 }
634 let start = out.len() - block_len;
639 for _ in 0..*count {
640 out.extend_from_within(start..start + block_len);
641 }
642 have_last = false;
643 block_len = 0;
644 }
645 Op::PackSegments { data_object, items } => {
646 let data = objects.get(*data_object as usize).ok_or_else(|| {
647 Error::invalid_graph(format!(
648 "graph references missing object {data_object}"
649 ))
650 })?;
651 let start = out.len();
652 run_pack_items("PACK_SEGMENTS", items, data, &mut out, limits)?;
653 block_len = out.len() - start;
654 have_last = true;
655 }
656 Op::PackedChannels {
657 data_channel,
658 plan_channel,
659 declared_output_len,
660 } => {
661 let data = channels.get(*data_channel as usize).ok_or_else(|| {
662 Error::invalid_graph(format!(
663 "graph references missing entropy channel {data_channel}"
664 ))
665 })?;
666 let plan = channels.get(*plan_channel as usize).ok_or_else(|| {
667 Error::invalid_graph(format!(
668 "graph references missing entropy channel {plan_channel}"
669 ))
670 })?;
671 let items = decode_items(plan, limits)?;
672 let start = out.len();
673 run_pack_items("PACKED_CHANNELS", &items, data, &mut out, limits)?;
674 let produced = (out.len() - start) as u64;
675 if produced != *declared_output_len {
676 return Err(Error::invalid_graph(format!(
677 "PACKED_CHANNELS produced {produced} bytes but {declared_output_len} were declared"
678 )));
679 }
680 block_len = out.len() - start;
681 have_last = true;
682 }
683 }
684 if out.len() as u64 > limits.max_output_bytes {
685 return Err(Error::resource_limit(
686 "output exceeds materialization limit",
687 ));
688 }
689 }
690 Ok(out)
691 }
692}
693
694fn run_pack_items(
703 label: &str,
704 items: &[PackItem],
705 data: &[u8],
706 out: &mut Vec<u8>,
707 limits: Limits,
708) -> Result<()> {
709 let mut cursor: usize = 0;
710 let mut slots: [Option<u64>; MAX_OFFSET_SLOTS] = [None; MAX_OFFSET_SLOTS];
711 for item in items {
712 match item {
713 PackItem::Literal { len } => {
714 let len = *len as usize;
715 let end = cursor
716 .checked_add(len)
717 .ok_or_else(|| Error::invalid_graph("packed data cursor overflow"))?;
718 if end > data.len() {
719 return Err(Error::invalid_graph(format!(
720 "{label} literal reads {len} bytes past data ({cursor}..{end} of {})",
721 data.len()
722 )));
723 }
724 out.extend_from_slice(&data[cursor..end]);
725 cursor = end;
726 }
727 PackItem::Mark { slot } => {
728 let idx = *slot as usize;
729 if idx >= MAX_OFFSET_SLOTS {
730 return Err(Error::invalid_graph(format!(
731 "{label} mark slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
732 )));
733 }
734 slots[idx] = Some(out.len() as u64);
735 }
736 PackItem::Emit { slot, width } => {
737 let idx = *slot as usize;
738 if idx >= MAX_OFFSET_SLOTS {
739 return Err(Error::invalid_graph(format!(
740 "{label} emit slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
741 )));
742 }
743 if *width == 0 || *width > 20 {
744 return Err(Error::invalid_graph(format!(
745 "{label} emit width {width} is outside 1..=20"
746 )));
747 }
748 let value = slots[idx].ok_or_else(|| {
749 Error::invalid_graph(format!("{label} emit references unmarked slot {slot}"))
750 })?;
751 let digits = value.to_string();
752 if digits.len() > *width as usize {
753 return Err(Error::invalid_graph(format!(
754 "{label} slot {slot} value {value} needs {} bytes but width is {width}",
755 digits.len()
756 )));
757 }
758 out.extend(std::iter::repeat_n(b'0', *width as usize - digits.len()));
759 out.extend_from_slice(digits.as_bytes());
760 }
761 }
762 if out.len() as u64 > limits.max_output_bytes {
763 return Err(Error::resource_limit(
764 "output exceeds materialization limit",
765 ));
766 }
767 }
768 if cursor != data.len() {
769 return Err(Error::invalid_graph(format!(
770 "{label} did not fully consume data ({cursor} of {})",
771 data.len()
772 )));
773 }
774 Ok(())
775}
776
777#[cfg(test)]
778mod tests {
779 use super::*;
780
781 fn objs(xs: &[&[u8]]) -> Vec<Vec<u8>> {
782 xs.iter().map(|x| x.to_vec()).collect()
783 }
784
785 #[test]
786 fn literal_concat_and_coverage() {
787 let objects = objs(&[b"hello ", b"world"]);
788 let p = Program::new(vec![
789 Op::EmitObject { object_id: 0 },
790 Op::EmitObject { object_id: 1 },
791 ]);
792 let (len, cov) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
793 assert_eq!(len, 11);
794 cov.validate(11).unwrap();
795 assert_eq!(
796 p.eval(&objects, &[], Limits::DEFAULT).unwrap(),
797 b"hello world"
798 );
799 }
800
801 #[test]
802 fn inline_and_repeat() {
803 let objects = objs(&[]);
804 let p = Program::new(vec![
805 Op::Inline {
806 bytes: b"ab".to_vec(),
807 },
808 Op::RepeatLast { count: 2 },
809 ]);
810 let (len, cov) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
811 assert_eq!(len, 6);
812 cov.validate(6).unwrap();
813 assert_eq!(p.eval(&objects, &[], Limits::DEFAULT).unwrap(), b"ababab");
814 assert_eq!(
816 cov.spans[0],
817 Span {
818 start: 0,
819 len: 2,
820 authority: Authority::Literal
821 }
822 );
823 assert_eq!(
824 cov.spans[1],
825 Span {
826 start: 2,
827 len: 4,
828 authority: Authority::Generated
829 }
830 );
831 }
832
833 #[test]
834 fn decode_channel_and_repeat() {
835 let objects = objs(&[]);
836 let channels = objs(&[b"abc"]);
837 let p = Program::new(vec![
838 Op::DecodeChannel { channel_id: 0 },
839 Op::RepeatLast { count: 1 },
840 ]);
841 let (len, cov) = p
842 .analyze_inputs(&objects, &channels, Limits::DEFAULT)
843 .unwrap();
844 assert_eq!(len, 6);
845 cov.validate(6).unwrap();
846 assert_eq!(
847 cov.spans[0],
848 Span {
849 start: 0,
850 len: 3,
851 authority: Authority::EntropyChannel,
852 }
853 );
854 assert_eq!(
855 cov.spans[1],
856 Span {
857 start: 3,
858 len: 3,
859 authority: Authority::Generated,
860 }
861 );
862 assert_eq!(
863 p.eval(&objects, &channels, Limits::DEFAULT).unwrap(),
864 b"abcabc"
865 );
866 }
867
868 #[test]
869 fn rejects_missing_channel() {
870 let p = Program::new(vec![Op::DecodeChannel { channel_id: 5 }]);
871 let e = p.analyze_inputs(&[], &[], Limits::DEFAULT).unwrap_err();
872 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
873 }
874
875 #[test]
876 fn rejects_missing_object() {
877 let objects = objs(&[]);
878 let p = Program::new(vec![Op::EmitObject { object_id: 3 }]);
879 let e = p.analyze_objects(&objects, Limits::DEFAULT).unwrap_err();
880 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
881 }
882
883 #[test]
884 fn rejects_leading_repeat() {
885 let objects = objs(&[]);
886 let p = Program::new(vec![Op::RepeatLast { count: 1 }]);
887 let e = p.analyze_objects(&objects, Limits::DEFAULT).unwrap_err();
888 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
889 }
890
891 #[test]
892 fn rejects_consecutive_repeats() {
893 let objects = objs(&[]);
894 let p = Program::new(vec![
895 Op::Inline {
896 bytes: b"x".to_vec(),
897 },
898 Op::RepeatLast { count: 1 },
899 Op::RepeatLast { count: 1 },
900 ]);
901 let e = p.analyze_objects(&objects, Limits::DEFAULT).unwrap_err();
902 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
903 }
904
905 #[test]
906 fn enforces_output_limit() {
907 let objects = objs(&[b"abcdefgh"]);
908 let p = Program::new(vec![
909 Op::EmitObject { object_id: 0 },
910 Op::RepeatLast { count: 1000 },
911 ]);
912 let limits = Limits {
913 max_output_bytes: 64,
914 ..Limits::DEFAULT
915 };
916 let e = p.analyze_objects(&objects, limits).unwrap_err();
917 assert_eq!(e.class(), crate::ErrorClass::ResourceLimit);
918 }
919
920 #[test]
921 fn coverage_gap_is_rejected() {
922 let cov = CoverageMap {
923 spans: vec![
924 Span {
925 start: 0,
926 len: 2,
927 authority: Authority::Literal,
928 },
929 Span {
930 start: 3,
931 len: 2,
932 authority: Authority::Literal,
933 },
934 ],
935 };
936 let e = cov.validate(4).unwrap_err();
937 assert_eq!(e.class(), crate::ErrorClass::CoverageViolation);
938 }
939
940 #[test]
941 fn program_roundtrips_via_bytes() {
942 let p = Program::new(vec![
943 Op::EmitObject { object_id: 1 },
944 Op::Inline {
945 bytes: b"hi".to_vec(),
946 },
947 Op::RepeatLast { count: 3 },
948 ]);
949 let enc = p.encode().unwrap();
950 let back = Program::decode(&enc, Limits::DEFAULT).unwrap();
951 assert_eq!(p, back);
952 }
953
954 fn le_lengths(lens: &[u32]) -> Vec<u8> {
955 let mut v = Vec::with_capacity(lens.len() * 4);
956 for &l in lens {
957 v.extend_from_slice(&l.to_le_bytes());
958 }
959 v
960 }
961
962 fn interleave_op() -> Op {
963 Op::InterleaveChannels {
964 kinds_channel: 0,
965 lengths_channel: 1,
966 first_payload_channel: 2,
967 payload_channel_count: 2,
968 }
969 }
970
971 fn interleave_channels() -> Vec<Vec<u8>> {
974 vec![
975 vec![0, 1, 0, 1],
976 le_lengths(&[2, 3, 1, 2]),
977 b"abc".to_vec(),
978 b"defgh".to_vec(),
979 ]
980 }
981
982 #[test]
983 fn interleave_roundtrip() {
984 let channels = interleave_channels();
985 let p = Program::new(vec![interleave_op()]);
986 let (len, cov) = p.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
987 assert_eq!(len, 8);
988 cov.validate(8).unwrap();
989 assert_eq!(
990 cov.spans,
991 vec![Span {
992 start: 0,
993 len: 8,
994 authority: Authority::Generated,
995 }]
996 );
997 assert_eq!(
998 p.eval(&[], &channels, Limits::DEFAULT).unwrap(),
999 b"abdefcgh"
1000 );
1001
1002 let enc = p.encode().unwrap();
1004 assert_eq!(Program::decode(&enc, Limits::DEFAULT).unwrap(), p);
1005
1006 let p2 = Program::new(vec![interleave_op(), Op::RepeatLast { count: 1 }]);
1008 let (len2, cov2) = p2.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
1009 assert_eq!(len2, 16);
1010 cov2.validate(16).unwrap();
1011 assert_eq!(
1012 p2.eval(&[], &channels, Limits::DEFAULT).unwrap(),
1013 b"abdefcghabdefcgh"
1014 );
1015 }
1016
1017 #[test]
1018 fn interleave_rejects_bad_channel_index() {
1019 let p = Program::new(vec![Op::InterleaveChannels {
1021 kinds_channel: 0,
1022 lengths_channel: 1,
1023 first_payload_channel: 2,
1024 payload_channel_count: 3,
1025 }]);
1026 let short = vec![vec![0u8], le_lengths(&[0]), vec![0u8]];
1027 let e = p.analyze_inputs(&[], &short, Limits::DEFAULT).unwrap_err();
1028 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1029
1030 let channels = vec![vec![2u8], le_lengths(&[1]), b"x".to_vec(), b"y".to_vec()];
1033 let p = Program::new(vec![interleave_op()]);
1034 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1035 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1036 }
1037
1038 #[test]
1039 fn interleave_rejects_unconsumed_payload() {
1040 let channels = vec![vec![0u8], le_lengths(&[1]), b"ab".to_vec(), b"z".to_vec()];
1042 let p = Program::new(vec![interleave_op()]);
1043 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1044 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1045 }
1046
1047 #[test]
1048 fn interleave_lengths_must_be_4x_tokens() {
1049 let channels = vec![vec![0u8, 0u8], le_lengths(&[1]), b"ab".to_vec()];
1051 let p = Program::new(vec![Op::InterleaveChannels {
1052 kinds_channel: 0,
1053 lengths_channel: 1,
1054 first_payload_channel: 2,
1055 payload_channel_count: 1,
1056 }]);
1057 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1058 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1059 }
1060
1061 #[test]
1062 fn interleave_predicts_from_channel_lens() {
1063 let p = Program::new(vec![interleave_op()]);
1064 let channel_lens = [4u64, 16, 3, 5];
1067 let (len, cov) = p.analyze(&[], &channel_lens, Limits::DEFAULT).unwrap();
1068 assert_eq!(len, 8);
1069 assert_eq!(
1070 cov.spans,
1071 vec![Span {
1072 start: 0,
1073 len: 8,
1074 authority: Authority::Generated,
1075 }]
1076 );
1077 }
1078
1079 #[test]
1080 fn mark_emit_roundtrip() {
1081 let p = Program::new(vec![
1084 Op::Inline {
1085 bytes: b"abc".to_vec(),
1086 },
1087 Op::MarkOffset { slot: 0 },
1088 Op::Inline {
1089 bytes: b"def".to_vec(),
1090 },
1091 Op::EmitOffset { slot: 0, width: 3 },
1092 ]);
1093 let (len, cov) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1094 assert_eq!(len, 9);
1095 cov.validate(9).unwrap();
1096 assert_eq!(p.eval(&[], &[], Limits::DEFAULT).unwrap(), b"abcdef003");
1097
1098 let enc = p.encode().unwrap();
1100 assert_eq!(Program::decode(&enc, Limits::DEFAULT).unwrap(), p);
1101
1102 let p2 = Program::new(vec![
1105 Op::MarkOffset { slot: 0 },
1106 Op::EmitOffset { slot: 0, width: 2 },
1107 Op::RepeatLast { count: 1 },
1108 ]);
1109 let (len2, cov2) = p2.analyze_objects(&[], Limits::DEFAULT).unwrap();
1110 assert_eq!(len2, 4);
1111 cov2.validate(4).unwrap();
1112 assert_eq!(p2.eval(&[], &[], Limits::DEFAULT).unwrap(), b"0000");
1113 }
1114
1115 #[test]
1116 fn emit_zero_pads() {
1117 let p = Program::new(vec![
1119 Op::MarkOffset { slot: 1 },
1120 Op::EmitOffset { slot: 1, width: 3 },
1121 ]);
1122 let (len, cov) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1123 assert_eq!(len, 3);
1124 cov.validate(3).unwrap();
1125 assert_eq!(p.eval(&[], &[], Limits::DEFAULT).unwrap(), b"000");
1126 }
1127
1128 #[test]
1129 fn emit_width_too_small_errors() {
1130 let p = Program::new(vec![
1134 Op::Inline {
1135 bytes: b"0123456789".to_vec(),
1136 },
1137 Op::MarkOffset { slot: 0 },
1138 Op::EmitOffset { slot: 0, width: 1 },
1139 ]);
1140 let (len, _) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1141 assert_eq!(len, 11);
1142 let e = p.eval(&[], &[], Limits::DEFAULT).unwrap_err();
1143 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1144 }
1145
1146 #[test]
1147 fn emit_unmarked_slot_errors() {
1148 let p = Program::new(vec![Op::EmitOffset { slot: 0, width: 4 }]);
1151 let e = p.analyze_objects(&[], Limits::DEFAULT).unwrap_err();
1152 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1153 }
1154
1155 #[test]
1156 fn mark_slot_bound_covers_every_u8_slot() {
1157 let p = Program::new(vec![Op::MarkOffset { slot: 16 }]);
1161 let (len, _) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1162 assert_eq!(len, 0);
1163
1164 let p = Program::new(vec![
1165 Op::MarkOffset { slot: 255 },
1166 Op::EmitOffset {
1167 slot: 255,
1168 width: 1,
1169 },
1170 ]);
1171 let (len, _) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1172 assert_eq!(len, 1);
1173 }
1174
1175 #[test]
1176 fn analyze_predicts_width_bytes() {
1177 let p = Program::new(vec![
1180 Op::MarkOffset { slot: 3 },
1181 Op::EmitOffset { slot: 3, width: 7 },
1182 ]);
1183 let (len, cov) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1184 assert_eq!(len, 7);
1185 cov.validate(7).unwrap();
1186 assert_eq!(
1187 cov.spans,
1188 vec![Span {
1189 start: 0,
1190 len: 7,
1191 authority: Authority::Generated,
1192 }]
1193 );
1194 }
1195
1196 #[test]
1197 fn pack_roundtrip() {
1198 let objects = objs(&[b"abcdef"]);
1199 let p = Program::new(vec![Op::PackSegments {
1200 data_object: 0,
1201 items: vec![
1202 PackItem::Literal { len: 3 },
1203 PackItem::Mark { slot: 0 },
1204 PackItem::Literal { len: 3 },
1205 PackItem::Emit { slot: 0, width: 3 },
1206 ],
1207 }]);
1208 let (len, cov) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1209 assert_eq!(len, 9);
1210 cov.validate(9).unwrap();
1211 assert_eq!(
1212 cov.spans,
1213 vec![Span {
1214 start: 0,
1215 len: 9,
1216 authority: Authority::Generated,
1217 }]
1218 );
1219 assert_eq!(
1220 p.eval(&objects, &[], Limits::DEFAULT).unwrap(),
1221 b"abcdef003"
1222 );
1223
1224 let enc = p.encode().unwrap();
1226 assert_eq!(Program::decode(&enc, Limits::DEFAULT).unwrap(), p);
1227 }
1228
1229 #[test]
1230 fn pack_varint_long_len() {
1231 let data: Vec<u8> = (0..300u32).map(|i| i as u8).collect();
1233 let objects = vec![data.clone()];
1234 let p = Program::new(vec![Op::PackSegments {
1235 data_object: 0,
1236 items: vec![PackItem::Literal { len: 300 }],
1237 }]);
1238 let (len, _) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1239 assert_eq!(len, 300);
1240 assert_eq!(p.eval(&objects, &[], Limits::DEFAULT).unwrap(), data);
1241 let enc = p.encode().unwrap();
1242 assert_eq!(Program::decode(&enc, Limits::DEFAULT).unwrap(), p);
1243 }
1244
1245 #[test]
1246 fn pack_rejects_unconsumed_data() {
1247 let objects = objs(&[b"abcdef"]);
1248 let p = Program::new(vec![Op::PackSegments {
1249 data_object: 0,
1250 items: vec![PackItem::Literal { len: 3 }],
1251 }]);
1252 let (len, _) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1254 assert_eq!(len, 3);
1255 let e = p.eval(&objects, &[], Limits::DEFAULT).unwrap_err();
1256 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1257 }
1258
1259 #[test]
1260 fn pack_rejects_emit_before_mark() {
1261 let objects = objs(&[b"abc"]);
1262 let p = Program::new(vec![Op::PackSegments {
1263 data_object: 0,
1264 items: vec![PackItem::Emit { slot: 0, width: 2 }],
1265 }]);
1266 let e = p.analyze_objects(&objects, Limits::DEFAULT).unwrap_err();
1267 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1268 }
1269
1270 #[test]
1271 fn pack_rejects_bad_slot_width() {
1272 let objects = objs(&[b"abc"]);
1273 let too_wide = Program::new(vec![Op::PackSegments {
1274 data_object: 0,
1275 items: vec![
1276 PackItem::Mark { slot: 0 },
1277 PackItem::Emit { slot: 0, width: 21 },
1278 ],
1279 }]);
1280 let e = too_wide
1281 .analyze_objects(&objects, Limits::DEFAULT)
1282 .unwrap_err();
1283 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1284
1285 let zero_width = Program::new(vec![Op::PackSegments {
1286 data_object: 0,
1287 items: vec![
1288 PackItem::Mark { slot: 0 },
1289 PackItem::Emit { slot: 0, width: 0 },
1290 ],
1291 }]);
1292 let e = zero_width
1293 .analyze_objects(&objects, Limits::DEFAULT)
1294 .unwrap_err();
1295 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1296 }
1297
1298 #[test]
1299 fn analyze_predicts_pack_length() {
1300 let objects = objs(&[b"abcdef"]);
1301 let items = vec![
1302 PackItem::Literal { len: 2 },
1303 PackItem::Mark { slot: 1 },
1304 PackItem::Literal { len: 4 },
1305 PackItem::Emit { slot: 1, width: 5 },
1306 ];
1307 let p = Program::new(vec![Op::PackSegments {
1308 data_object: 0,
1309 items: items.clone(),
1310 }]);
1311 let (len, cov) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1313 assert_eq!(len, 11);
1314 assert_eq!(
1315 cov.spans,
1316 vec![Span {
1317 start: 0,
1318 len: 11,
1319 authority: Authority::Generated,
1320 }]
1321 );
1322 let p2 = Program::new(vec![
1324 Op::PackSegments {
1325 data_object: 0,
1326 items,
1327 },
1328 Op::RepeatLast { count: 1 },
1329 ]);
1330 let (len2, cov2) = p2.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1331 assert_eq!(len2, 22);
1332 cov2.validate(22).unwrap();
1333 assert_eq!(
1335 p2.eval(&objects, &[], Limits::DEFAULT).unwrap(),
1336 b"abcdef00002abcdef00002"
1337 );
1338 }
1339
1340 #[test]
1341 fn dra_version_is_six() {
1342 assert_eq!(DRA_VERSION, 6);
1343 let p = Program::new(vec![Op::Inline {
1344 bytes: b"x".to_vec(),
1345 }]);
1346 let enc = p.encode().unwrap();
1347 assert_eq!(enc[0], DRA_VERSION);
1348 let mut stale = enc.clone();
1350 stale[0] = DRA_VERSION - 1;
1351 let e = Program::decode(&stale, Limits::DEFAULT).unwrap_err();
1352 assert_eq!(e.class(), crate::ErrorClass::UnsupportedVersion);
1353 }
1354
1355 fn packed_channels_program() -> Program {
1358 Program::new(vec![Op::PackedChannels {
1359 data_channel: 0,
1360 plan_channel: 1,
1361 declared_output_len: 9,
1362 }])
1363 }
1364
1365 fn packed_plan() -> Vec<u8> {
1366 crate::dra::op::encode_items(&[
1367 PackItem::Literal { len: 3 },
1368 PackItem::Mark { slot: 0 },
1369 PackItem::Literal { len: 3 },
1370 PackItem::Emit { slot: 0, width: 3 },
1371 ])
1372 .unwrap()
1373 }
1374
1375 #[test]
1376 fn packed_channels_roundtrip() {
1377 let channels = objs(&[b"abcdef", &packed_plan()]);
1378 let p = packed_channels_program();
1379 let (len, cov) = p.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
1380 assert_eq!(len, 9);
1381 cov.validate(9).unwrap();
1382 assert_eq!(
1383 cov.spans,
1384 vec![Span {
1385 start: 0,
1386 len: 9,
1387 authority: Authority::Generated,
1388 }]
1389 );
1390 assert_eq!(
1391 p.eval(&[], &channels, Limits::DEFAULT).unwrap(),
1392 b"abcdef003"
1393 );
1394
1395 assert_eq!(
1397 crate::dra::op::decode_items(&packed_plan(), Limits::DEFAULT).unwrap(),
1398 vec![
1399 PackItem::Literal { len: 3 },
1400 PackItem::Mark { slot: 0 },
1401 PackItem::Literal { len: 3 },
1402 PackItem::Emit { slot: 0, width: 3 },
1403 ]
1404 );
1405 let enc = p.encode().unwrap();
1406 assert_eq!(Program::decode(&enc, Limits::DEFAULT).unwrap(), p);
1407 }
1408
1409 #[test]
1410 fn packed_channels_declared_len_mismatch_errors() {
1411 let channels = objs(&[b"abcdef", &packed_plan()]);
1412 let p = Program::new(vec![Op::PackedChannels {
1414 data_channel: 0,
1415 plan_channel: 1,
1416 declared_output_len: 8,
1417 }]);
1418 let (len, _) = p.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
1419 assert_eq!(len, 8);
1420 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1421 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1422 }
1423
1424 #[test]
1425 fn packed_channels_missing_channel_errors() {
1426 let channels = objs(&[b"abcdef", &packed_plan()]);
1427 let missing_data = Program::new(vec![Op::PackedChannels {
1428 data_channel: 2,
1429 plan_channel: 1,
1430 declared_output_len: 9,
1431 }]);
1432 let e = missing_data
1433 .analyze_inputs(&[], &channels, Limits::DEFAULT)
1434 .unwrap_err();
1435 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1436
1437 let missing_plan = Program::new(vec![Op::PackedChannels {
1438 data_channel: 0,
1439 plan_channel: 2,
1440 declared_output_len: 9,
1441 }]);
1442 let e = missing_plan
1443 .analyze_inputs(&[], &channels, Limits::DEFAULT)
1444 .unwrap_err();
1445 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1446 }
1447
1448 #[test]
1449 fn packed_channels_truncated_plan_errors() {
1450 let plan = packed_plan();
1451 let truncated = &plan[..plan.len() - 1];
1452 let channels = objs(&[b"abcdef", truncated]);
1453 let p = packed_channels_program();
1454 p.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
1456 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1457 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1458 }
1459
1460 #[test]
1461 fn packed_channels_rejects_unconsumed_data() {
1462 let plan = crate::dra::op::encode_items(&[PackItem::Literal { len: 3 }]).unwrap();
1464 let channels = objs(&[b"abcdef", &plan]);
1465 let p = Program::new(vec![Op::PackedChannels {
1466 data_channel: 0,
1467 plan_channel: 1,
1468 declared_output_len: 3,
1469 }]);
1470 let (len, _) = p.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
1471 assert_eq!(len, 3);
1472 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1473 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1474 }
1475}