1use crate::dra::op::{Op, PackItem, decode_items};
4use crate::error::{Error, Result};
5use crate::limits::Limits;
6
7pub const DRA_VERSION: u8 = 8;
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 let (total, coverage, _per_op) = self.analyze_inner(object_lens, channel_lens, limits)?;
151 Ok((total, coverage))
152 }
153
154 pub fn analyze_ops(
163 &self,
164 object_lens: &[u64],
165 channel_lens: &[u64],
166 limits: Limits,
167 ) -> Result<Vec<u64>> {
168 let (_total, _coverage, per_op) = self.analyze_inner(object_lens, channel_lens, limits)?;
169 Ok(per_op)
170 }
171
172 fn analyze_inner(
175 &self,
176 object_lens: &[u64],
177 channel_lens: &[u64],
178 limits: Limits,
179 ) -> Result<(u64, CoverageMap, Vec<u64>)> {
180 if self.ops.len() as u64 > limits.max_graph_ops as u64 {
181 return Err(Error::resource_limit("graph instruction limit exceeded"));
182 }
183 let mut spans: Vec<Span> = Vec::new();
184 let mut per_op: Vec<u64> = Vec::with_capacity(self.ops.len());
185 let mut total: u64 = 0;
186 let mut last_len: u64 = 0;
187 let mut have_last = false;
188 let mut marked = [false; MAX_OFFSET_SLOTS];
190
191 for op in &self.ops {
192 let before = total;
193 match op {
194 Op::EmitObject { object_id } => {
195 let len = *object_lens.get(*object_id as usize).ok_or_else(|| {
196 Error::invalid_graph(format!("graph references missing object {object_id}"))
197 })?;
198 total = total
199 .checked_add(len)
200 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
201 if len > 0 {
202 spans.push(Span {
203 start: total - len,
204 len,
205 authority: Authority::Literal,
206 });
207 }
208 last_len = len;
209 have_last = true;
210 }
211 Op::Inline { bytes } => {
212 let len = bytes.len() as u64;
213 total = total
214 .checked_add(len)
215 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
216 if len > 0 {
217 spans.push(Span {
218 start: total - len,
219 len,
220 authority: Authority::Literal,
221 });
222 }
223 last_len = len;
224 have_last = true;
225 }
226 Op::DecodeChannel { channel_id } => {
227 let len = *channel_lens.get(*channel_id as usize).ok_or_else(|| {
228 Error::invalid_graph(format!(
229 "graph references missing entropy channel {channel_id}"
230 ))
231 })?;
232 total = total
233 .checked_add(len)
234 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
235 if len > 0 {
236 spans.push(Span {
237 start: total - len,
238 len,
239 authority: Authority::EntropyChannel,
240 });
241 }
242 last_len = len;
243 have_last = true;
244 }
245 Op::InterleaveChannels {
246 first_payload_channel,
247 payload_channel_count,
248 ..
249 } => {
250 let first = *first_payload_channel as usize;
251 let count = *payload_channel_count as usize;
252 let end = first
253 .checked_add(count)
254 .ok_or_else(|| Error::invalid_graph("interleave channel range overflow"))?;
255 if end > channel_lens.len() {
256 return Err(Error::invalid_graph(format!(
257 "interleave payload channel range {first}..{end} exceeds {} channels",
258 channel_lens.len()
259 )));
260 }
261 let mut len: u64 = 0;
262 for &seg in &channel_lens[first..end] {
263 len = len
264 .checked_add(seg)
265 .ok_or_else(|| Error::resource_limit("interleave length overflow"))?;
266 }
267 total = total
268 .checked_add(len)
269 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
270 if len > 0 {
271 spans.push(Span {
272 start: total - len,
273 len,
274 authority: Authority::Generated,
275 });
276 }
277 last_len = len;
278 have_last = true;
279 }
280 Op::MarkOffset { slot } => {
281 let idx = *slot as usize;
282 if idx >= MAX_OFFSET_SLOTS {
283 return Err(Error::invalid_graph(format!(
284 "MARK_OFFSET slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
285 )));
286 }
287 marked[idx] = true;
290 }
291 Op::EmitOffset { slot, width } => {
292 let idx = *slot as usize;
293 if idx >= MAX_OFFSET_SLOTS {
294 return Err(Error::invalid_graph(format!(
295 "EMIT_OFFSET slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
296 )));
297 }
298 if *width == 0 || *width > 20 {
299 return Err(Error::invalid_graph(format!(
300 "EMIT_OFFSET width {width} is outside 1..=20"
301 )));
302 }
303 if !marked[idx] {
304 return Err(Error::invalid_graph(format!(
305 "EMIT_OFFSET references unmarked slot {slot}"
306 )));
307 }
308 let len = *width as u64;
311 total = total
312 .checked_add(len)
313 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
314 spans.push(Span {
315 start: total - len,
316 len,
317 authority: Authority::Generated,
318 });
319 last_len = len;
320 have_last = true;
321 }
322 Op::RepeatLast { count } => {
323 if !have_last {
324 return Err(Error::invalid_graph(
325 "REPEAT_LAST has no preceding literal instruction",
326 ));
327 }
328 if (*count as u64) > limits.max_repeat_count {
329 return Err(Error::resource_limit(format!(
330 "REPEAT_LAST count {count} exceeds limit {}",
331 limits.max_repeat_count
332 )));
333 }
334 let extra = last_len
335 .checked_mul(*count as u64)
336 .ok_or_else(|| Error::resource_limit("repeat length overflow"))?;
337 total = total
338 .checked_add(extra)
339 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
340 if extra > 0 {
341 spans.push(Span {
342 start: total - extra,
343 len: extra,
344 authority: Authority::Generated,
345 });
346 }
347 have_last = false;
350 last_len = 0;
351 }
352 Op::PackSegments { data_object, items } => {
353 let data_len = *object_lens.get(*data_object as usize).ok_or_else(|| {
354 Error::invalid_graph(format!(
355 "graph references missing object {data_object}"
356 ))
357 })?;
358 let mut literal_total: u64 = 0;
359 let mut produced: u64 = 0;
360 let mut marked = [false; MAX_OFFSET_SLOTS];
362 for item in items {
363 match item {
364 PackItem::Literal { len } => {
365 literal_total =
366 literal_total.checked_add(*len as u64).ok_or_else(|| {
367 Error::resource_limit("packed literal length overflow")
368 })?;
369 produced = produced.checked_add(*len as u64).ok_or_else(|| {
370 Error::resource_limit("output length overflow")
371 })?;
372 }
373 PackItem::Mark { slot } => {
374 let idx = *slot as usize;
375 if idx >= MAX_OFFSET_SLOTS {
376 return Err(Error::invalid_graph(format!(
377 "PACK_SEGMENTS mark slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
378 )));
379 }
380 marked[idx] = true;
381 }
382 PackItem::Emit { slot, width } => {
383 let idx = *slot as usize;
384 if idx >= MAX_OFFSET_SLOTS {
385 return Err(Error::invalid_graph(format!(
386 "PACK_SEGMENTS emit slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
387 )));
388 }
389 if *width == 0 || *width > 20 {
390 return Err(Error::invalid_graph(format!(
391 "PACK_SEGMENTS emit width {width} is outside 1..=20"
392 )));
393 }
394 if !marked[idx] {
395 return Err(Error::invalid_graph(format!(
396 "PACK_SEGMENTS emit references unmarked slot {slot}"
397 )));
398 }
399 produced =
400 produced.checked_add(*width as u64).ok_or_else(|| {
401 Error::resource_limit("output length overflow")
402 })?;
403 }
404 }
405 }
406 if literal_total > data_len {
407 return Err(Error::invalid_graph(format!(
408 "PACK_SEGMENTS literal runs total {literal_total} bytes but data object {data_object} holds {data_len}"
409 )));
410 }
411 total = total
412 .checked_add(produced)
413 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
414 if produced > 0 {
415 spans.push(Span {
416 start: total - produced,
417 len: produced,
418 authority: Authority::Generated,
419 });
420 }
421 last_len = produced;
423 have_last = true;
424 }
425 Op::PackedChannels {
426 data_channel,
427 plan_channel,
428 declared_output_len,
429 } => {
430 if *data_channel as usize >= channel_lens.len() {
431 return Err(Error::invalid_graph(format!(
432 "graph references missing entropy channel {data_channel}"
433 )));
434 }
435 if *plan_channel as usize >= channel_lens.len() {
436 return Err(Error::invalid_graph(format!(
437 "graph references missing entropy channel {plan_channel}"
438 )));
439 }
440 if *declared_output_len > limits.max_output_bytes {
441 return Err(Error::resource_limit(format!(
442 "PACKED_CHANNELS declared output {declared_output_len} exceeds limit {}",
443 limits.max_output_bytes
444 )));
445 }
446 let len = *declared_output_len;
450 total = total
451 .checked_add(len)
452 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
453 if len > 0 {
454 spans.push(Span {
455 start: total - len,
456 len,
457 authority: Authority::Generated,
458 });
459 }
460 last_len = len;
461 have_last = true;
462 }
463 Op::DeflateReplay {
464 replay_codec: _,
465 source_kind,
466 source_id,
467 corrections_object,
468 declared_output_len,
469 } => {
470 let src_len = match *source_kind {
471 crate::dra::op::DEFLATE_SOURCE_OBJECT => {
472 *object_lens.get(*source_id as usize).ok_or_else(|| {
473 Error::invalid_graph(format!(
474 "graph references missing object {source_id}"
475 ))
476 })?
477 }
478 crate::dra::op::DEFLATE_SOURCE_CHANNEL => {
479 *channel_lens.get(*source_id as usize).ok_or_else(|| {
480 Error::invalid_graph(format!(
481 "graph references missing entropy channel {source_id}"
482 ))
483 })?
484 }
485 other => {
486 return Err(Error::invalid_graph(format!(
487 "DEFLATE_REPLAY source kind {other} is invalid"
488 )));
489 }
490 };
491 if *corrections_object as usize >= object_lens.len() {
492 return Err(Error::invalid_graph(format!(
493 "graph references missing object {corrections_object}"
494 )));
495 }
496 if u64::from(*declared_output_len) > limits.max_output_bytes {
499 return Err(Error::resource_limit(format!(
500 "DEFLATE_REPLAY declared output {declared_output_len} exceeds limit {}",
501 limits.max_output_bytes
502 )));
503 }
504 let replay_limit = replay_profile_limit(src_len, limits);
512 if u64::from(*declared_output_len) > replay_limit {
513 return Err(Error::invalid_graph(format!(
514 "DEFLATE_REPLAY declared output {declared_output_len} exceeds the VOLE replay-profile admission limit {replay_limit} for a {src_len}-byte plaintext (a policy bound: RFC 1951 permits unbounded empty non-final blocks; VOLE declines replay outside this profile)"
515 )));
516 }
517 let len = u64::from(*declared_output_len);
518 total = total
519 .checked_add(len)
520 .ok_or_else(|| Error::resource_limit("output length overflow"))?;
521 if len > 0 {
522 spans.push(Span {
523 start: total - len,
524 len,
525 authority: Authority::Generated,
526 });
527 }
528 last_len = len;
529 have_last = true;
530 }
531 }
532 per_op.push(total - before);
533 if total > limits.max_output_bytes {
534 return Err(Error::resource_limit(format!(
535 "predicted output {total} exceeds limit {}",
536 limits.max_output_bytes
537 )));
538 }
539 }
540
541 Ok((total, CoverageMap { spans }, per_op))
542 }
543
544 pub fn analyze_inputs(
546 &self,
547 objects: &[Vec<u8>],
548 channels: &[Vec<u8>],
549 limits: Limits,
550 ) -> Result<(u64, CoverageMap)> {
551 let object_lens: Vec<u64> = objects.iter().map(|o| o.len() as u64).collect();
552 let channel_lens: Vec<u64> = channels.iter().map(|c| c.len() as u64).collect();
553 self.analyze(&object_lens, &channel_lens, limits)
554 }
555
556 pub fn analyze_objects(
559 &self,
560 objects: &[Vec<u8>],
561 limits: Limits,
562 ) -> Result<(u64, CoverageMap)> {
563 let lens: Vec<u64> = objects.iter().map(|o| o.len() as u64).collect();
564 self.analyze(&lens, &[], limits)
565 }
566
567 pub fn eval(
569 &self,
570 objects: &[Vec<u8>],
571 channels: &[Vec<u8>],
572 limits: Limits,
573 ) -> Result<Vec<u8>> {
574 let (predicted, _coverage) = self.analyze_inputs(objects, channels, limits)?;
575 let cap = predicted.min(64 * 1024 * 1024) as usize;
576 let mut out: Vec<u8> = Vec::with_capacity(cap);
577 let mut have_last = false;
578 let mut block_len: usize = 0;
579 let mut slots = [0u64; MAX_OFFSET_SLOTS];
581 let mut marked = [false; MAX_OFFSET_SLOTS];
582
583 for op in &self.ops {
584 match op {
585 Op::EmitObject { object_id } => {
586 let obj = objects.get(*object_id as usize).ok_or_else(|| {
587 Error::invalid_graph(format!("graph references missing object {object_id}"))
588 })?;
589 block_len = obj.len();
590 out.extend_from_slice(obj);
591 have_last = true;
592 }
593 Op::Inline { bytes } => {
594 block_len = bytes.len();
595 out.extend_from_slice(bytes);
596 have_last = true;
597 }
598 Op::DecodeChannel { channel_id } => {
599 let ch = channels.get(*channel_id as usize).ok_or_else(|| {
600 Error::invalid_graph(format!(
601 "graph references missing entropy channel {channel_id}"
602 ))
603 })?;
604 block_len = ch.len();
605 out.extend_from_slice(ch);
606 have_last = true;
607 }
608 Op::InterleaveChannels {
609 kinds_channel,
610 lengths_channel,
611 first_payload_channel,
612 payload_channel_count,
613 } => {
614 let kinds = channels.get(*kinds_channel as usize).ok_or_else(|| {
615 Error::invalid_graph(format!(
616 "graph references missing entropy channel {kinds_channel}"
617 ))
618 })?;
619 let lengths = channels.get(*lengths_channel as usize).ok_or_else(|| {
620 Error::invalid_graph(format!(
621 "graph references missing entropy channel {lengths_channel}"
622 ))
623 })?;
624 let token_count = kinds.len();
625 let required = token_count
626 .checked_mul(4)
627 .ok_or_else(|| Error::invalid_graph("interleave lengths overflow"))?;
628 if lengths.len() != required {
629 return Err(Error::invalid_graph(format!(
630 "interleave length channel has {} bytes but {required} are required",
631 lengths.len()
632 )));
633 }
634 let first = *first_payload_channel as usize;
635 let count = *payload_channel_count as usize;
636 let end = first
637 .checked_add(count)
638 .ok_or_else(|| Error::invalid_graph("interleave channel range overflow"))?;
639 if end > channels.len() {
640 return Err(Error::invalid_graph(format!(
641 "interleave payload channel range {first}..{end} exceeds {} channels",
642 channels.len()
643 )));
644 }
645 let start = out.len();
646 let mut cursors = vec![0usize; count];
647 for (i, chunk) in lengths.as_chunks::<4>().0.iter().enumerate() {
648 let k = kinds[i] as usize;
649 if k >= count {
650 return Err(Error::invalid_graph(format!(
651 "interleave token {i} names kind {k} outside 0..{count}"
652 )));
653 }
654 let l = u32::from_le_bytes(*chunk) as usize;
655 let ch = &channels[first + k];
656 let cursor = cursors[k];
657 let seg_end = cursor.checked_add(l).ok_or_else(|| {
658 Error::invalid_graph("interleave payload cursor overflow")
659 })?;
660 if seg_end > ch.len() {
661 return Err(Error::invalid_graph(format!(
662 "interleave token {i} reads {l} bytes past channel {} ({cursor}..{seg_end} of {})",
663 first + k,
664 ch.len()
665 )));
666 }
667 out.extend_from_slice(&ch[cursor..seg_end]);
668 cursors[k] = seg_end;
669 if out.len() as u64 > limits.max_output_bytes {
670 return Err(Error::resource_limit(
671 "output exceeds materialization limit",
672 ));
673 }
674 }
675 for (k, &cursor) in cursors.iter().enumerate() {
676 let ch_len = channels[first + k].len();
677 if cursor != ch_len {
678 return Err(Error::invalid_graph(format!(
679 "interleave payload channel {} was not fully consumed ({cursor} of {ch_len})",
680 first + k
681 )));
682 }
683 }
684 block_len = out.len() - start;
685 have_last = true;
686 }
687 Op::MarkOffset { slot } => {
688 let idx = *slot as usize;
689 if idx >= MAX_OFFSET_SLOTS {
690 return Err(Error::invalid_graph(format!(
691 "MARK_OFFSET slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
692 )));
693 }
694 slots[idx] = out.len() as u64;
695 marked[idx] = true;
696 }
697 Op::EmitOffset { slot, width } => {
698 let idx = *slot as usize;
699 if idx >= MAX_OFFSET_SLOTS {
700 return Err(Error::invalid_graph(format!(
701 "EMIT_OFFSET slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
702 )));
703 }
704 if *width == 0 || *width > 20 {
705 return Err(Error::invalid_graph(format!(
706 "EMIT_OFFSET width {width} is outside 1..=20"
707 )));
708 }
709 if !marked[idx] {
710 return Err(Error::invalid_graph(format!(
711 "EMIT_OFFSET references unmarked slot {slot}"
712 )));
713 }
714 let digits = slots[idx].to_string();
715 if digits.len() > *width as usize {
716 return Err(Error::invalid_graph(format!(
717 "EMIT_OFFSET slot {slot} value {} needs {} bytes but width is {width}",
718 slots[idx],
719 digits.len()
720 )));
721 }
722 let start = out.len();
723 out.extend(std::iter::repeat_n(b'0', *width as usize - digits.len()));
724 out.extend_from_slice(digits.as_bytes());
725 debug_assert_eq!(out.len() - start, *width as usize);
726 block_len = out.len() - start;
727 have_last = true;
728 }
729 Op::RepeatLast { count } => {
730 if !have_last {
731 return Err(Error::invalid_graph(
732 "REPEAT_LAST has no preceding literal instruction",
733 ));
734 }
735 let start = out.len() - block_len;
740 for _ in 0..*count {
741 out.extend_from_within(start..start + block_len);
742 }
743 have_last = false;
744 block_len = 0;
745 }
746 Op::PackSegments { data_object, items } => {
747 let data = objects.get(*data_object as usize).ok_or_else(|| {
748 Error::invalid_graph(format!(
749 "graph references missing object {data_object}"
750 ))
751 })?;
752 let start = out.len();
753 run_pack_items("PACK_SEGMENTS", items, data, &mut out, limits)?;
754 block_len = out.len() - start;
755 have_last = true;
756 }
757 Op::PackedChannels {
758 data_channel,
759 plan_channel,
760 declared_output_len,
761 } => {
762 let data = channels.get(*data_channel as usize).ok_or_else(|| {
763 Error::invalid_graph(format!(
764 "graph references missing entropy channel {data_channel}"
765 ))
766 })?;
767 let plan = channels.get(*plan_channel as usize).ok_or_else(|| {
768 Error::invalid_graph(format!(
769 "graph references missing entropy channel {plan_channel}"
770 ))
771 })?;
772 let items = decode_items(plan, limits)?;
773 let start = out.len();
774 run_pack_items("PACKED_CHANNELS", &items, data, &mut out, limits)?;
775 let produced = (out.len() - start) as u64;
776 if produced != *declared_output_len {
777 return Err(Error::invalid_graph(format!(
778 "PACKED_CHANNELS produced {produced} bytes but {declared_output_len} were declared"
779 )));
780 }
781 block_len = out.len() - start;
782 have_last = true;
783 }
784 Op::DeflateReplay {
785 replay_codec: _,
786 source_kind,
787 source_id,
788 corrections_object,
789 declared_output_len,
790 } => {
791 let plaintext: &[u8] = match *source_kind {
792 crate::dra::op::DEFLATE_SOURCE_OBJECT => {
793 objects.get(*source_id as usize).ok_or_else(|| {
794 Error::invalid_graph(format!(
795 "graph references missing object {source_id}"
796 ))
797 })?
798 }
799 crate::dra::op::DEFLATE_SOURCE_CHANNEL => {
800 channels.get(*source_id as usize).ok_or_else(|| {
801 Error::invalid_graph(format!(
802 "graph references missing entropy channel {source_id}"
803 ))
804 })?
805 }
806 other => {
807 return Err(Error::invalid_graph(format!(
808 "DEFLATE_REPLAY source kind {other} is invalid"
809 )));
810 }
811 };
812 let corrections =
813 objects.get(*corrections_object as usize).ok_or_else(|| {
814 Error::invalid_graph(format!(
815 "graph references missing object {corrections_object}"
816 ))
817 })?;
818 if plaintext.len() as u64 > u64::from(limits.max_record_len) {
822 return Err(Error::resource_limit(
823 "DEFLATE_REPLAY plaintext exceeds the record limit",
824 ));
825 }
826 if corrections.len() as u64 > u64::from(limits.max_record_len) {
827 return Err(Error::resource_limit(
828 "DEFLATE_REPLAY corrections exceed the record limit",
829 ));
830 }
831 let raw = replay_deflate_bounded(
832 plaintext,
833 corrections,
834 *declared_output_len,
835 limits,
836 )?;
837 if raw.len() as u64 != u64::from(*declared_output_len) {
838 return Err(Error::invalid_graph(format!(
839 "DEFLATE_REPLAY produced {} bytes but {} were declared",
840 raw.len(),
841 declared_output_len
842 )));
843 }
844 block_len = raw.len();
845 out.extend_from_slice(&raw);
846 have_last = true;
847 }
848 }
849 if out.len() as u64 > limits.max_output_bytes {
850 return Err(Error::resource_limit(
851 "output exceeds materialization limit",
852 ));
853 }
854 }
855 Ok(out)
856 }
857}
858
859pub const REPLAY_OUTPUT_RATIO_PERCENT: u64 = 200;
867
868pub const REPLAY_OUTPUT_SLACK: u64 = 1024;
874
875pub(crate) fn replay_profile_limit(plaintext_len: u64, limits: Limits) -> u64 {
888 let profile = plaintext_len
889 .saturating_mul(REPLAY_OUTPUT_RATIO_PERCENT)
890 .saturating_div(100)
891 .saturating_add(REPLAY_OUTPUT_SLACK);
892 limits
893 .max_output_bytes
894 .min(limits.max_replay_bytes)
895 .min(profile)
896}
897
898#[cfg(feature = "deflate-replay")]
908fn replay_deflate_bounded(
909 plaintext: &[u8],
910 corrections: &[u8],
911 declared_output_len: u32,
912 limits: Limits,
913) -> Result<Vec<u8>> {
914 crate::codec::deflate::replay_bounded(plaintext, corrections, declared_output_len, limits)
915}
916
917#[cfg(not(feature = "deflate-replay"))]
919fn replay_deflate_bounded(
920 _plaintext: &[u8],
921 _corrections: &[u8],
922 _declared_output_len: u32,
923 _limits: Limits,
924) -> Result<Vec<u8>> {
925 Err(Error::unsupported_feature(
926 "DEFLATE_REPLAY requires the `deflate-replay` feature",
927 ))
928}
929
930fn run_pack_items(
939 label: &str,
940 items: &[PackItem],
941 data: &[u8],
942 out: &mut Vec<u8>,
943 limits: Limits,
944) -> Result<()> {
945 let mut cursor: usize = 0;
946 let mut slots: [Option<u64>; MAX_OFFSET_SLOTS] = [None; MAX_OFFSET_SLOTS];
947 for item in items {
948 match item {
949 PackItem::Literal { len } => {
950 let len = *len as usize;
951 let end = cursor
952 .checked_add(len)
953 .ok_or_else(|| Error::invalid_graph("packed data cursor overflow"))?;
954 if end > data.len() {
955 return Err(Error::invalid_graph(format!(
956 "{label} literal reads {len} bytes past data ({cursor}..{end} of {})",
957 data.len()
958 )));
959 }
960 out.extend_from_slice(&data[cursor..end]);
961 cursor = end;
962 }
963 PackItem::Mark { slot } => {
964 let idx = *slot as usize;
965 if idx >= MAX_OFFSET_SLOTS {
966 return Err(Error::invalid_graph(format!(
967 "{label} mark slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
968 )));
969 }
970 slots[idx] = Some(out.len() as u64);
971 }
972 PackItem::Emit { slot, width } => {
973 let idx = *slot as usize;
974 if idx >= MAX_OFFSET_SLOTS {
975 return Err(Error::invalid_graph(format!(
976 "{label} emit slot {slot} exceeds {MAX_OFFSET_SLOTS} slots"
977 )));
978 }
979 if *width == 0 || *width > 20 {
980 return Err(Error::invalid_graph(format!(
981 "{label} emit width {width} is outside 1..=20"
982 )));
983 }
984 let value = slots[idx].ok_or_else(|| {
985 Error::invalid_graph(format!("{label} emit references unmarked slot {slot}"))
986 })?;
987 let digits = value.to_string();
988 if digits.len() > *width as usize {
989 return Err(Error::invalid_graph(format!(
990 "{label} slot {slot} value {value} needs {} bytes but width is {width}",
991 digits.len()
992 )));
993 }
994 out.extend(std::iter::repeat_n(b'0', *width as usize - digits.len()));
995 out.extend_from_slice(digits.as_bytes());
996 }
997 }
998 if out.len() as u64 > limits.max_output_bytes {
999 return Err(Error::resource_limit(
1000 "output exceeds materialization limit",
1001 ));
1002 }
1003 }
1004 if cursor != data.len() {
1005 return Err(Error::invalid_graph(format!(
1006 "{label} did not fully consume data ({cursor} of {})",
1007 data.len()
1008 )));
1009 }
1010 Ok(())
1011}
1012
1013#[cfg(test)]
1014mod tests {
1015 use super::*;
1016
1017 fn objs(xs: &[&[u8]]) -> Vec<Vec<u8>> {
1018 xs.iter().map(|x| x.to_vec()).collect()
1019 }
1020
1021 #[test]
1022 fn literal_concat_and_coverage() {
1023 let objects = objs(&[b"hello ", b"world"]);
1024 let p = Program::new(vec![
1025 Op::EmitObject { object_id: 0 },
1026 Op::EmitObject { object_id: 1 },
1027 ]);
1028 let (len, cov) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1029 assert_eq!(len, 11);
1030 cov.validate(11).unwrap();
1031 assert_eq!(
1032 p.eval(&objects, &[], Limits::DEFAULT).unwrap(),
1033 b"hello world"
1034 );
1035 }
1036
1037 #[test]
1038 fn inline_and_repeat() {
1039 let objects = objs(&[]);
1040 let p = Program::new(vec![
1041 Op::Inline {
1042 bytes: b"ab".to_vec(),
1043 },
1044 Op::RepeatLast { count: 2 },
1045 ]);
1046 let (len, cov) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1047 assert_eq!(len, 6);
1048 cov.validate(6).unwrap();
1049 assert_eq!(p.eval(&objects, &[], Limits::DEFAULT).unwrap(), b"ababab");
1050 assert_eq!(
1052 cov.spans[0],
1053 Span {
1054 start: 0,
1055 len: 2,
1056 authority: Authority::Literal
1057 }
1058 );
1059 assert_eq!(
1060 cov.spans[1],
1061 Span {
1062 start: 2,
1063 len: 4,
1064 authority: Authority::Generated
1065 }
1066 );
1067 }
1068
1069 #[test]
1070 fn decode_channel_and_repeat() {
1071 let objects = objs(&[]);
1072 let channels = objs(&[b"abc"]);
1073 let p = Program::new(vec![
1074 Op::DecodeChannel { channel_id: 0 },
1075 Op::RepeatLast { count: 1 },
1076 ]);
1077 let (len, cov) = p
1078 .analyze_inputs(&objects, &channels, Limits::DEFAULT)
1079 .unwrap();
1080 assert_eq!(len, 6);
1081 cov.validate(6).unwrap();
1082 assert_eq!(
1083 cov.spans[0],
1084 Span {
1085 start: 0,
1086 len: 3,
1087 authority: Authority::EntropyChannel,
1088 }
1089 );
1090 assert_eq!(
1091 cov.spans[1],
1092 Span {
1093 start: 3,
1094 len: 3,
1095 authority: Authority::Generated,
1096 }
1097 );
1098 assert_eq!(
1099 p.eval(&objects, &channels, Limits::DEFAULT).unwrap(),
1100 b"abcabc"
1101 );
1102 }
1103
1104 #[test]
1105 fn rejects_missing_channel() {
1106 let p = Program::new(vec![Op::DecodeChannel { channel_id: 5 }]);
1107 let e = p.analyze_inputs(&[], &[], Limits::DEFAULT).unwrap_err();
1108 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1109 }
1110
1111 #[test]
1112 fn rejects_missing_object() {
1113 let objects = objs(&[]);
1114 let p = Program::new(vec![Op::EmitObject { object_id: 3 }]);
1115 let e = p.analyze_objects(&objects, Limits::DEFAULT).unwrap_err();
1116 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1117 }
1118
1119 #[test]
1120 fn rejects_leading_repeat() {
1121 let objects = objs(&[]);
1122 let p = Program::new(vec![Op::RepeatLast { count: 1 }]);
1123 let e = p.analyze_objects(&objects, Limits::DEFAULT).unwrap_err();
1124 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1125 }
1126
1127 #[test]
1128 fn rejects_consecutive_repeats() {
1129 let objects = objs(&[]);
1130 let p = Program::new(vec![
1131 Op::Inline {
1132 bytes: b"x".to_vec(),
1133 },
1134 Op::RepeatLast { count: 1 },
1135 Op::RepeatLast { count: 1 },
1136 ]);
1137 let e = p.analyze_objects(&objects, Limits::DEFAULT).unwrap_err();
1138 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1139 }
1140
1141 #[test]
1142 fn enforces_output_limit() {
1143 let objects = objs(&[b"abcdefgh"]);
1144 let p = Program::new(vec![
1145 Op::EmitObject { object_id: 0 },
1146 Op::RepeatLast { count: 1000 },
1147 ]);
1148 let limits = Limits {
1149 max_output_bytes: 64,
1150 ..Limits::DEFAULT
1151 };
1152 let e = p.analyze_objects(&objects, limits).unwrap_err();
1153 assert_eq!(e.class(), crate::ErrorClass::ResourceLimit);
1154 }
1155
1156 #[test]
1157 fn coverage_gap_is_rejected() {
1158 let cov = CoverageMap {
1159 spans: vec![
1160 Span {
1161 start: 0,
1162 len: 2,
1163 authority: Authority::Literal,
1164 },
1165 Span {
1166 start: 3,
1167 len: 2,
1168 authority: Authority::Literal,
1169 },
1170 ],
1171 };
1172 let e = cov.validate(4).unwrap_err();
1173 assert_eq!(e.class(), crate::ErrorClass::CoverageViolation);
1174 }
1175
1176 #[test]
1177 fn program_roundtrips_via_bytes() {
1178 let p = Program::new(vec![
1179 Op::EmitObject { object_id: 1 },
1180 Op::Inline {
1181 bytes: b"hi".to_vec(),
1182 },
1183 Op::RepeatLast { count: 3 },
1184 ]);
1185 let enc = p.encode().unwrap();
1186 let back = Program::decode(&enc, Limits::DEFAULT).unwrap();
1187 assert_eq!(p, back);
1188 }
1189
1190 fn le_lengths(lens: &[u32]) -> Vec<u8> {
1191 let mut v = Vec::with_capacity(lens.len() * 4);
1192 for &l in lens {
1193 v.extend_from_slice(&l.to_le_bytes());
1194 }
1195 v
1196 }
1197
1198 fn interleave_op() -> Op {
1199 Op::InterleaveChannels {
1200 kinds_channel: 0,
1201 lengths_channel: 1,
1202 first_payload_channel: 2,
1203 payload_channel_count: 2,
1204 }
1205 }
1206
1207 fn interleave_channels() -> Vec<Vec<u8>> {
1210 vec![
1211 vec![0, 1, 0, 1],
1212 le_lengths(&[2, 3, 1, 2]),
1213 b"abc".to_vec(),
1214 b"defgh".to_vec(),
1215 ]
1216 }
1217
1218 #[test]
1219 fn interleave_roundtrip() {
1220 let channels = interleave_channels();
1221 let p = Program::new(vec![interleave_op()]);
1222 let (len, cov) = p.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
1223 assert_eq!(len, 8);
1224 cov.validate(8).unwrap();
1225 assert_eq!(
1226 cov.spans,
1227 vec![Span {
1228 start: 0,
1229 len: 8,
1230 authority: Authority::Generated,
1231 }]
1232 );
1233 assert_eq!(
1234 p.eval(&[], &channels, Limits::DEFAULT).unwrap(),
1235 b"abdefcgh"
1236 );
1237
1238 let enc = p.encode().unwrap();
1240 assert_eq!(Program::decode(&enc, Limits::DEFAULT).unwrap(), p);
1241
1242 let p2 = Program::new(vec![interleave_op(), Op::RepeatLast { count: 1 }]);
1244 let (len2, cov2) = p2.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
1245 assert_eq!(len2, 16);
1246 cov2.validate(16).unwrap();
1247 assert_eq!(
1248 p2.eval(&[], &channels, Limits::DEFAULT).unwrap(),
1249 b"abdefcghabdefcgh"
1250 );
1251 }
1252
1253 #[test]
1254 fn interleave_rejects_bad_channel_index() {
1255 let p = Program::new(vec![Op::InterleaveChannels {
1257 kinds_channel: 0,
1258 lengths_channel: 1,
1259 first_payload_channel: 2,
1260 payload_channel_count: 3,
1261 }]);
1262 let short = vec![vec![0u8], le_lengths(&[0]), vec![0u8]];
1263 let e = p.analyze_inputs(&[], &short, Limits::DEFAULT).unwrap_err();
1264 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1265
1266 let channels = vec![vec![2u8], le_lengths(&[1]), b"x".to_vec(), b"y".to_vec()];
1269 let p = Program::new(vec![interleave_op()]);
1270 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1271 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1272 }
1273
1274 #[test]
1275 fn interleave_rejects_unconsumed_payload() {
1276 let channels = vec![vec![0u8], le_lengths(&[1]), b"ab".to_vec(), b"z".to_vec()];
1278 let p = Program::new(vec![interleave_op()]);
1279 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1280 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1281 }
1282
1283 #[test]
1284 fn interleave_lengths_must_be_4x_tokens() {
1285 let channels = vec![vec![0u8, 0u8], le_lengths(&[1]), b"ab".to_vec()];
1287 let p = Program::new(vec![Op::InterleaveChannels {
1288 kinds_channel: 0,
1289 lengths_channel: 1,
1290 first_payload_channel: 2,
1291 payload_channel_count: 1,
1292 }]);
1293 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1294 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1295 }
1296
1297 #[test]
1298 fn interleave_predicts_from_channel_lens() {
1299 let p = Program::new(vec![interleave_op()]);
1300 let channel_lens = [4u64, 16, 3, 5];
1303 let (len, cov) = p.analyze(&[], &channel_lens, Limits::DEFAULT).unwrap();
1304 assert_eq!(len, 8);
1305 assert_eq!(
1306 cov.spans,
1307 vec![Span {
1308 start: 0,
1309 len: 8,
1310 authority: Authority::Generated,
1311 }]
1312 );
1313 }
1314
1315 #[test]
1316 fn mark_emit_roundtrip() {
1317 let p = Program::new(vec![
1320 Op::Inline {
1321 bytes: b"abc".to_vec(),
1322 },
1323 Op::MarkOffset { slot: 0 },
1324 Op::Inline {
1325 bytes: b"def".to_vec(),
1326 },
1327 Op::EmitOffset { slot: 0, width: 3 },
1328 ]);
1329 let (len, cov) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1330 assert_eq!(len, 9);
1331 cov.validate(9).unwrap();
1332 assert_eq!(p.eval(&[], &[], Limits::DEFAULT).unwrap(), b"abcdef003");
1333
1334 let enc = p.encode().unwrap();
1336 assert_eq!(Program::decode(&enc, Limits::DEFAULT).unwrap(), p);
1337
1338 let p2 = Program::new(vec![
1341 Op::MarkOffset { slot: 0 },
1342 Op::EmitOffset { slot: 0, width: 2 },
1343 Op::RepeatLast { count: 1 },
1344 ]);
1345 let (len2, cov2) = p2.analyze_objects(&[], Limits::DEFAULT).unwrap();
1346 assert_eq!(len2, 4);
1347 cov2.validate(4).unwrap();
1348 assert_eq!(p2.eval(&[], &[], Limits::DEFAULT).unwrap(), b"0000");
1349 }
1350
1351 #[test]
1352 fn emit_zero_pads() {
1353 let p = Program::new(vec![
1355 Op::MarkOffset { slot: 1 },
1356 Op::EmitOffset { slot: 1, width: 3 },
1357 ]);
1358 let (len, cov) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1359 assert_eq!(len, 3);
1360 cov.validate(3).unwrap();
1361 assert_eq!(p.eval(&[], &[], Limits::DEFAULT).unwrap(), b"000");
1362 }
1363
1364 #[test]
1365 fn emit_width_too_small_errors() {
1366 let p = Program::new(vec![
1370 Op::Inline {
1371 bytes: b"0123456789".to_vec(),
1372 },
1373 Op::MarkOffset { slot: 0 },
1374 Op::EmitOffset { slot: 0, width: 1 },
1375 ]);
1376 let (len, _) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1377 assert_eq!(len, 11);
1378 let e = p.eval(&[], &[], Limits::DEFAULT).unwrap_err();
1379 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1380 }
1381
1382 #[test]
1383 fn emit_unmarked_slot_errors() {
1384 let p = Program::new(vec![Op::EmitOffset { slot: 0, width: 4 }]);
1387 let e = p.analyze_objects(&[], Limits::DEFAULT).unwrap_err();
1388 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1389 }
1390
1391 #[test]
1392 fn mark_slot_bound_covers_every_u8_slot() {
1393 let p = Program::new(vec![Op::MarkOffset { slot: 16 }]);
1397 let (len, _) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1398 assert_eq!(len, 0);
1399
1400 let p = Program::new(vec![
1401 Op::MarkOffset { slot: 255 },
1402 Op::EmitOffset {
1403 slot: 255,
1404 width: 1,
1405 },
1406 ]);
1407 let (len, _) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1408 assert_eq!(len, 1);
1409 }
1410
1411 #[test]
1412 fn analyze_predicts_width_bytes() {
1413 let p = Program::new(vec![
1416 Op::MarkOffset { slot: 3 },
1417 Op::EmitOffset { slot: 3, width: 7 },
1418 ]);
1419 let (len, cov) = p.analyze_objects(&[], Limits::DEFAULT).unwrap();
1420 assert_eq!(len, 7);
1421 cov.validate(7).unwrap();
1422 assert_eq!(
1423 cov.spans,
1424 vec![Span {
1425 start: 0,
1426 len: 7,
1427 authority: Authority::Generated,
1428 }]
1429 );
1430 }
1431
1432 #[test]
1433 fn pack_roundtrip() {
1434 let objects = objs(&[b"abcdef"]);
1435 let p = Program::new(vec![Op::PackSegments {
1436 data_object: 0,
1437 items: vec![
1438 PackItem::Literal { len: 3 },
1439 PackItem::Mark { slot: 0 },
1440 PackItem::Literal { len: 3 },
1441 PackItem::Emit { slot: 0, width: 3 },
1442 ],
1443 }]);
1444 let (len, cov) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1445 assert_eq!(len, 9);
1446 cov.validate(9).unwrap();
1447 assert_eq!(
1448 cov.spans,
1449 vec![Span {
1450 start: 0,
1451 len: 9,
1452 authority: Authority::Generated,
1453 }]
1454 );
1455 assert_eq!(
1456 p.eval(&objects, &[], Limits::DEFAULT).unwrap(),
1457 b"abcdef003"
1458 );
1459
1460 let enc = p.encode().unwrap();
1462 assert_eq!(Program::decode(&enc, Limits::DEFAULT).unwrap(), p);
1463 }
1464
1465 #[test]
1466 fn pack_varint_long_len() {
1467 let data: Vec<u8> = (0..300u32).map(|i| i as u8).collect();
1469 let objects = vec![data.clone()];
1470 let p = Program::new(vec![Op::PackSegments {
1471 data_object: 0,
1472 items: vec![PackItem::Literal { len: 300 }],
1473 }]);
1474 let (len, _) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1475 assert_eq!(len, 300);
1476 assert_eq!(p.eval(&objects, &[], Limits::DEFAULT).unwrap(), data);
1477 let enc = p.encode().unwrap();
1478 assert_eq!(Program::decode(&enc, Limits::DEFAULT).unwrap(), p);
1479 }
1480
1481 #[test]
1482 fn pack_rejects_unconsumed_data() {
1483 let objects = objs(&[b"abcdef"]);
1484 let p = Program::new(vec![Op::PackSegments {
1485 data_object: 0,
1486 items: vec![PackItem::Literal { len: 3 }],
1487 }]);
1488 let (len, _) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1490 assert_eq!(len, 3);
1491 let e = p.eval(&objects, &[], Limits::DEFAULT).unwrap_err();
1492 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1493 }
1494
1495 #[test]
1496 fn pack_rejects_emit_before_mark() {
1497 let objects = objs(&[b"abc"]);
1498 let p = Program::new(vec![Op::PackSegments {
1499 data_object: 0,
1500 items: vec![PackItem::Emit { slot: 0, width: 2 }],
1501 }]);
1502 let e = p.analyze_objects(&objects, Limits::DEFAULT).unwrap_err();
1503 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1504 }
1505
1506 #[test]
1507 fn pack_rejects_bad_slot_width() {
1508 let objects = objs(&[b"abc"]);
1509 let too_wide = Program::new(vec![Op::PackSegments {
1510 data_object: 0,
1511 items: vec![
1512 PackItem::Mark { slot: 0 },
1513 PackItem::Emit { slot: 0, width: 21 },
1514 ],
1515 }]);
1516 let e = too_wide
1517 .analyze_objects(&objects, Limits::DEFAULT)
1518 .unwrap_err();
1519 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1520
1521 let zero_width = Program::new(vec![Op::PackSegments {
1522 data_object: 0,
1523 items: vec![
1524 PackItem::Mark { slot: 0 },
1525 PackItem::Emit { slot: 0, width: 0 },
1526 ],
1527 }]);
1528 let e = zero_width
1529 .analyze_objects(&objects, Limits::DEFAULT)
1530 .unwrap_err();
1531 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1532 }
1533
1534 #[test]
1535 fn analyze_predicts_pack_length() {
1536 let objects = objs(&[b"abcdef"]);
1537 let items = vec![
1538 PackItem::Literal { len: 2 },
1539 PackItem::Mark { slot: 1 },
1540 PackItem::Literal { len: 4 },
1541 PackItem::Emit { slot: 1, width: 5 },
1542 ];
1543 let p = Program::new(vec![Op::PackSegments {
1544 data_object: 0,
1545 items: items.clone(),
1546 }]);
1547 let (len, cov) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1549 assert_eq!(len, 11);
1550 assert_eq!(
1551 cov.spans,
1552 vec![Span {
1553 start: 0,
1554 len: 11,
1555 authority: Authority::Generated,
1556 }]
1557 );
1558 let p2 = Program::new(vec![
1560 Op::PackSegments {
1561 data_object: 0,
1562 items,
1563 },
1564 Op::RepeatLast { count: 1 },
1565 ]);
1566 let (len2, cov2) = p2.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1567 assert_eq!(len2, 22);
1568 cov2.validate(22).unwrap();
1569 assert_eq!(
1571 p2.eval(&objects, &[], Limits::DEFAULT).unwrap(),
1572 b"abcdef00002abcdef00002"
1573 );
1574 }
1575
1576 #[test]
1577 fn dra_version_is_eight() {
1578 assert_eq!(DRA_VERSION, 8);
1579 let p = Program::new(vec![Op::Inline {
1580 bytes: b"x".to_vec(),
1581 }]);
1582 let enc = p.encode().unwrap();
1583 assert_eq!(enc[0], DRA_VERSION);
1584 let mut stale = enc.clone();
1586 stale[0] = DRA_VERSION - 1;
1587 let e = Program::decode(&stale, Limits::DEFAULT).unwrap_err();
1588 assert_eq!(e.class(), crate::ErrorClass::UnsupportedVersion);
1589 }
1590
1591 fn packed_channels_program() -> Program {
1594 Program::new(vec![Op::PackedChannels {
1595 data_channel: 0,
1596 plan_channel: 1,
1597 declared_output_len: 9,
1598 }])
1599 }
1600
1601 fn packed_plan() -> Vec<u8> {
1602 crate::dra::op::encode_items(&[
1603 PackItem::Literal { len: 3 },
1604 PackItem::Mark { slot: 0 },
1605 PackItem::Literal { len: 3 },
1606 PackItem::Emit { slot: 0, width: 3 },
1607 ])
1608 .unwrap()
1609 }
1610
1611 #[test]
1612 fn packed_channels_roundtrip() {
1613 let channels = objs(&[b"abcdef", &packed_plan()]);
1614 let p = packed_channels_program();
1615 let (len, cov) = p.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
1616 assert_eq!(len, 9);
1617 cov.validate(9).unwrap();
1618 assert_eq!(
1619 cov.spans,
1620 vec![Span {
1621 start: 0,
1622 len: 9,
1623 authority: Authority::Generated,
1624 }]
1625 );
1626 assert_eq!(
1627 p.eval(&[], &channels, Limits::DEFAULT).unwrap(),
1628 b"abcdef003"
1629 );
1630
1631 assert_eq!(
1633 crate::dra::op::decode_items(&packed_plan(), Limits::DEFAULT).unwrap(),
1634 vec![
1635 PackItem::Literal { len: 3 },
1636 PackItem::Mark { slot: 0 },
1637 PackItem::Literal { len: 3 },
1638 PackItem::Emit { slot: 0, width: 3 },
1639 ]
1640 );
1641 let enc = p.encode().unwrap();
1642 assert_eq!(Program::decode(&enc, Limits::DEFAULT).unwrap(), p);
1643 }
1644
1645 #[test]
1646 fn packed_channels_declared_len_mismatch_errors() {
1647 let channels = objs(&[b"abcdef", &packed_plan()]);
1648 let p = Program::new(vec![Op::PackedChannels {
1650 data_channel: 0,
1651 plan_channel: 1,
1652 declared_output_len: 8,
1653 }]);
1654 let (len, _) = p.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
1655 assert_eq!(len, 8);
1656 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1657 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1658 }
1659
1660 #[test]
1661 fn packed_channels_missing_channel_errors() {
1662 let channels = objs(&[b"abcdef", &packed_plan()]);
1663 let missing_data = Program::new(vec![Op::PackedChannels {
1664 data_channel: 2,
1665 plan_channel: 1,
1666 declared_output_len: 9,
1667 }]);
1668 let e = missing_data
1669 .analyze_inputs(&[], &channels, Limits::DEFAULT)
1670 .unwrap_err();
1671 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1672
1673 let missing_plan = Program::new(vec![Op::PackedChannels {
1674 data_channel: 0,
1675 plan_channel: 2,
1676 declared_output_len: 9,
1677 }]);
1678 let e = missing_plan
1679 .analyze_inputs(&[], &channels, Limits::DEFAULT)
1680 .unwrap_err();
1681 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1682 }
1683
1684 #[test]
1685 fn packed_channels_truncated_plan_errors() {
1686 let plan = packed_plan();
1687 let truncated = &plan[..plan.len() - 1];
1688 let channels = objs(&[b"abcdef", truncated]);
1689 let p = packed_channels_program();
1690 p.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
1692 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1693 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1694 }
1695
1696 #[test]
1697 fn packed_channels_rejects_unconsumed_data() {
1698 let plan = crate::dra::op::encode_items(&[PackItem::Literal { len: 3 }]).unwrap();
1700 let channels = objs(&[b"abcdef", &plan]);
1701 let p = Program::new(vec![Op::PackedChannels {
1702 data_channel: 0,
1703 plan_channel: 1,
1704 declared_output_len: 3,
1705 }]);
1706 let (len, _) = p.analyze_inputs(&[], &channels, Limits::DEFAULT).unwrap();
1707 assert_eq!(len, 3);
1708 let e = p.eval(&[], &channels, Limits::DEFAULT).unwrap_err();
1709 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1710 }
1711
1712 #[cfg(feature = "deflate-replay")]
1713 fn zlib_stream(data: &[u8]) -> Vec<u8> {
1714 use flate2::Compression;
1715 use flate2::write::ZlibEncoder;
1716 use std::io::Write;
1717 let mut e = ZlibEncoder::new(Vec::new(), Compression::new(6));
1718 e.write_all(data).unwrap();
1719 e.finish().unwrap()
1720 }
1721
1722 #[cfg(feature = "deflate-replay")]
1723 #[test]
1724 fn deflate_replay_roundtrip_is_byte_exact() {
1725 let data = b"BT /F1 12 Tf (replay me) Tj ET\n".repeat(300);
1726 let z = zlib_stream(&data);
1727 let plan = crate::codec::deflate::try_replay(&z, Limits::DEFAULT).expect("replay plan");
1728 let objects = objs(&[&plan.plaintext, &plan.corrections]);
1729 let p = Program::new(vec![
1731 Op::Inline {
1732 bytes: plan.header.to_vec(),
1733 },
1734 Op::DeflateReplay {
1735 replay_codec: crate::dra::op::REPLAY_DEFLATE_PREFLATE_0_7_6,
1736 source_kind: crate::dra::op::DEFLATE_SOURCE_OBJECT,
1737 source_id: 0,
1738 corrections_object: 1,
1739 declared_output_len: plan.raw_len,
1740 },
1741 Op::Inline {
1742 bytes: plan.adler.to_vec(),
1743 },
1744 ]);
1745 let (len, cov) = p.analyze_objects(&objects, Limits::DEFAULT).unwrap();
1746 assert_eq!(len, z.len() as u64);
1747 cov.validate(z.len() as u64).unwrap();
1748 assert_eq!(
1749 p.eval(&objects, &[], Limits::DEFAULT).unwrap(),
1750 z,
1751 "DEFLATE_REPLAY must reproduce the original zlib stream byte-for-byte"
1752 );
1753 let enc = p.encode().unwrap();
1755 assert_eq!(Program::decode(&enc, Limits::DEFAULT).unwrap(), p);
1756 }
1757
1758 #[cfg(feature = "deflate-replay")]
1759 #[test]
1760 fn deflate_replay_declared_len_mismatch_errors() {
1761 let data = b"replay mismatch vector".repeat(50);
1762 let z = zlib_stream(&data);
1763 let plan = crate::codec::deflate::try_replay(&z, Limits::DEFAULT).unwrap();
1764 let objects = objs(&[&plan.plaintext, &plan.corrections]);
1765 let p = Program::new(vec![Op::DeflateReplay {
1766 replay_codec: crate::dra::op::REPLAY_DEFLATE_PREFLATE_0_7_6,
1767 source_kind: crate::dra::op::DEFLATE_SOURCE_OBJECT,
1768 source_id: 0,
1769 corrections_object: 1,
1770 declared_output_len: plan.raw_len + 1,
1771 }]);
1772 let e = p.eval(&objects, &[], Limits::DEFAULT).unwrap_err();
1773 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1774 }
1775
1776 #[test]
1777 fn deflate_replay_missing_object_errors() {
1778 let objects = objs(&[b"plain"]);
1779 let p = Program::new(vec![Op::DeflateReplay {
1780 replay_codec: crate::dra::op::REPLAY_DEFLATE_PREFLATE_0_7_6,
1781 source_kind: crate::dra::op::DEFLATE_SOURCE_OBJECT,
1782 source_id: 0,
1783 corrections_object: 9,
1784 declared_output_len: 3,
1785 }]);
1786 let e = p.analyze_objects(&objects, Limits::DEFAULT).unwrap_err();
1787 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1788 }
1789
1790 #[cfg(feature = "deflate-replay")]
1791 #[test]
1792 fn deflate_replay_hostile_corrections_is_typed_error() {
1793 let mut blob = vec![0u8; 64];
1794 for (i, b) in blob.iter_mut().enumerate() {
1795 *b = (i as u8).wrapping_mul(37).wrapping_add(11);
1796 }
1797 let objects = objs(&[b"some plaintext bytes here", &blob]);
1798 let p = Program::new(vec![Op::DeflateReplay {
1799 replay_codec: crate::dra::op::REPLAY_DEFLATE_PREFLATE_0_7_6,
1800 source_kind: crate::dra::op::DEFLATE_SOURCE_OBJECT,
1801 source_id: 0,
1802 corrections_object: 1,
1803 declared_output_len: 8,
1804 }]);
1805 let e = p.eval(&objects, &[], Limits::DEFAULT).unwrap_err();
1806 assert!(
1807 matches!(
1808 e.class(),
1809 crate::ErrorClass::CodecReplay | crate::ErrorClass::InvalidGraph
1810 ),
1811 "unexpected class: {:?}",
1812 e.class()
1813 );
1814 }
1815
1816 #[test]
1817 fn replay_profile_limit_arithmetic() {
1818 let l = Limits::DEFAULT;
1819 assert_eq!(replay_profile_limit(0, l), REPLAY_OUTPUT_SLACK);
1821 assert_eq!(replay_profile_limit(100, l), 200 + REPLAY_OUTPUT_SLACK);
1822 assert_eq!(replay_profile_limit(1000, l), 2000 + REPLAY_OUTPUT_SLACK);
1823 let tiny_replay = Limits {
1825 max_replay_bytes: 500,
1826 ..Limits::DEFAULT
1827 };
1828 assert_eq!(replay_profile_limit(1000, tiny_replay), 500);
1829 let tiny_output = Limits {
1831 max_output_bytes: 300,
1832 ..Limits::DEFAULT
1833 };
1834 assert_eq!(replay_profile_limit(1000, tiny_output), 300);
1835 let both = Limits {
1836 max_output_bytes: 400,
1837 max_replay_bytes: 500,
1838 ..Limits::DEFAULT
1839 };
1840 assert_eq!(replay_profile_limit(1000, both), 400);
1841 }
1842
1843 #[test]
1844 fn replay_declared_len_above_profile_limit_is_rejected() {
1845 let objects = objs(&[b"plaintext!", &[0u8; 4]]);
1849 let limit = replay_profile_limit(10, Limits::DEFAULT);
1850 assert_eq!(limit, 1044);
1851 let p = Program::new(vec![Op::DeflateReplay {
1852 replay_codec: crate::dra::op::REPLAY_DEFLATE_PREFLATE_0_7_6,
1853 source_kind: crate::dra::op::DEFLATE_SOURCE_OBJECT,
1854 source_id: 0,
1855 corrections_object: 1,
1856 declared_output_len: (limit + 1) as u32,
1857 }]);
1858 let e = p.analyze_objects(&objects, Limits::DEFAULT).unwrap_err();
1859 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1860 let e = p.eval(&objects, &[], Limits::DEFAULT).unwrap_err();
1862 assert_eq!(e.class(), crate::ErrorClass::InvalidGraph);
1863 }
1864
1865 #[test]
1866 fn analyze_ops_lengths_match_total_and_spans() {
1867 let p = Program::new(vec![
1870 Op::EmitObject { object_id: 0 }, Op::Inline {
1872 bytes: b"12".to_vec(),
1873 }, Op::RepeatLast { count: 2 }, Op::DecodeChannel { channel_id: 0 }, Op::MarkOffset { slot: 5 }, Op::EmitOffset { slot: 5, width: 4 }, ]);
1879 let (total, cov) = p.analyze(&[6], &[3], Limits::DEFAULT).unwrap();
1880 let per_op = p.analyze_ops(&[6], &[3], Limits::DEFAULT).unwrap();
1881
1882 assert_eq!(per_op.len(), p.ops.len());
1883 assert_eq!(per_op, vec![6, 2, 4, 3, 0, 4]);
1884 assert_eq!(per_op.iter().sum::<u64>(), total);
1885 assert_eq!(total, 19);
1886
1887 let positive: Vec<u64> = per_op.iter().copied().filter(|&l| l > 0).collect();
1890 assert_eq!(positive.len(), cov.spans.len());
1891 let mut start = 0u64;
1892 for (len, span) in positive.iter().zip(cov.spans.iter()) {
1893 assert_eq!(span.start, start);
1894 assert_eq!(span.len, *len);
1895 start += *len;
1896 }
1897 assert_eq!(start, total);
1898 }
1899}