1use super::*;
3use zencodec::animation::FrameDuration;
4use zencodec::decode::DecodePolicy;
5use zencodec::encode::DynEncoderConfig;
6use zencodec::encode::EncodePolicy;
7use zencodec::metadata_rewrite::{Error as RewriteError, Plan, RewriteRequest};
8use zencodec::{LimitKind, ResourceLimits};
9
10pub fn check_animation_durations<D: DecoderConfig + 'static>(
15 dec: D,
16 bytes: &[u8],
17 expected: &[FrameDuration],
18) -> Conformance {
19 const CHECK: &str = "animation durations";
20 let err = |e: &dyn std::fmt::Display| fail(CHECK, e.to_string());
21 let mut borrowed = dec
22 .clone()
23 .job()
24 .animation_frame_decoder(Cow::Borrowed(bytes), &[])
25 .map_err(|e| err(&e))?;
26 let mut owned = dec
27 .clone()
28 .job()
29 .animation_frame_decoder(Cow::Borrowed(bytes), &[])
30 .map_err(|e| err(&e))?;
31 let mut dynamic = dec
32 .dyn_job()
33 .into_animation_frame_decoder(Cow::Borrowed(bytes), &[])
34 .map_err(|e| err(&e))?;
35 for (index, duration) in expected.iter().enumerate() {
36 let a = borrowed
37 .render_next_frame(None)
38 .map_err(|e| err(&e))?
39 .ok_or_else(|| fail(CHECK, format!("borrowed frame {index} missing")))?;
40 let b = owned
41 .render_next_frame_owned(None)
42 .map_err(|e| err(&e))?
43 .ok_or_else(|| fail(CHECK, format!("owned frame {index} missing")))?;
44 let c = dynamic
45 .render_next_frame_owned(None)
46 .map_err(|e| err(&e))?
47 .ok_or_else(|| fail(CHECK, format!("erased frame {index} missing")))?;
48 for (path, actual, frame_index) in [
49 ("borrowed", a.duration(), a.frame_index()),
50 ("owned", b.duration(), b.frame_index()),
51 ("erased", c.duration(), c.frame_index()),
52 ] {
53 if actual != *duration || frame_index != index as u32 {
54 return Err(fail(
55 CHECK,
56 format!(
57 "{path} frame {index}: duration {actual:?}, index {frame_index}; expected {duration:?}"
58 ),
59 ));
60 }
61 }
62 if grab_ref(a.pixels()) != grab(b.pixels()) || grab(b.pixels()) != grab(c.pixels()) {
63 return Err(fail(
64 CHECK,
65 format!("frame {index}: generic/owned/erased pixels disagree"),
66 ));
67 }
68 }
69 if borrowed
70 .render_next_frame(None)
71 .map_err(|e| err(&e))?
72 .is_some()
73 || owned
74 .render_next_frame_owned(None)
75 .map_err(|e| err(&e))?
76 .is_some()
77 || dynamic
78 .render_next_frame_owned(None)
79 .map_err(|e| err(&e))?
80 .is_some()
81 {
82 return Err(fail(
83 CHECK,
84 "unexpected extra frame (possibly accepted a rejected input)",
85 ));
86 }
87 Ok(())
88}
89
90pub fn check_animation_timing<E, D>(
97 enc: E,
98 dec: D,
99 pixels: PixelSlice<'_>,
100 exact: bool,
101 cases: &[(FrameDuration, Option<FrameDuration>)],
102) -> Conformance
103where
104 E: EncoderConfig + 'static,
105 D: DecoderConfig + 'static,
106 <E::Job as EncodeJob>::Enc: Encoder + Send,
107 <E::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder,
108{
109 const CHECK: &str = "animation timing";
110 let err = |e: &dyn std::fmt::Display| fail(CHECK, e.to_string());
111 let expected: Vec<_> = cases.iter().filter_map(|(_, result)| *result).collect();
112 if expected.len() < 2 {
113 return Err(fail(CHECK, "supply at least two accepted frames"));
114 }
115 let mut generic = enc
116 .clone()
117 .job()
118 .animation_frame_encoder()
119 .map_err(|e| err(&e))?;
120 let mut erased = enc
121 .dyn_job()
122 .into_animation_frame_encoder()
123 .map_err(|e| err(&e))?;
124 for (index, (requested, expected)) in cases.iter().enumerate() {
125 if exact && expected.is_some_and(|v| v != *requested) {
126 return Err(fail(CHECK, "exact test case requests rounding"));
127 }
128 let a = if exact {
129 generic
130 .push_frame_exact(pixels.sub_rows(0, pixels.rows()), *requested, None)
131 .map(|()| *requested)
132 } else {
133 generic.push_frame_timed(pixels.sub_rows(0, pixels.rows()), *requested, None)
134 };
135 let b = if exact {
136 erased
137 .push_frame_exact(pixels.sub_rows(0, pixels.rows()), *requested, None)
138 .map(|()| *requested)
139 } else {
140 erased.push_frame_timed(pixels.sub_rows(0, pixels.rows()), *requested, None)
141 };
142 for (path, result) in [
143 ("generic", a.map_err(|e| e.to_string())),
144 ("erased", b.map_err(|e| e.to_string())),
145 ] {
146 match (expected, result) {
147 (Some(want), Ok(got)) if *want == got => (),
148 (None, Err(_)) => (),
149 (want, got) => {
150 return Err(fail(
151 CHECK,
152 format!("{path} case {index}: expected {want:?}, got {got:?}"),
153 ));
154 }
155 }
156 }
157 }
158 let a = generic.finish(None).map_err(|e| err(&e))?;
159 let b = erased.finish(None).map_err(|e| err(&e))?;
160 check_animation_durations(dec.clone(), a.data(), &expected)?;
161 check_animation_durations(dec.clone(), b.data(), &expected)?;
162 if decode_anim_owned(&dec, a.data()).map_err(|e| fail(CHECK, e))?
163 != decode_anim_owned(&dec, b.data()).map_err(|e| fail(CHECK, e))?
164 {
165 return Err(fail(CHECK, "generic/erased encoded pixels disagree"));
166 }
167 Ok(())
168}
169
170pub fn check_inventory_input_limit<D: DecoderConfig + 'static>(
174 dec: D,
175 valid: &[u8],
176) -> Conformance {
177 const CHECK: &str = "inventory input limit";
178 if valid.is_empty() || !D::capabilities().inventory() {
179 return Err(fail(CHECK, "requires nonempty input and enabled inventory"));
180 }
181 for cap in [valid.len() - 1, valid.len()] {
182 let limits = ResourceLimits::none().with_max_input_bytes(cap as u64);
183 let a = dec.clone().job().with_limits(limits).inventory(valid);
184 let mut job = dec.dyn_job();
185 job.set_limits(limits);
186 let b = job.inventory(valid);
187 if cap == valid.len() {
188 let a = a.map_err(|e| fail(CHECK, format!("exact boundary: {e}")))?;
189 let b = b.map_err(|e| fail(CHECK, format!("erased exact boundary: {e}")))?;
190 a.validate().map_err(|e| fail(CHECK, e.to_string()))?;
191 if a != b {
192 return Err(fail(CHECK, "boundary inventories disagree"));
193 }
194 } else {
195 let wanted = Some(ErrorCategory::Resource(zencodec::ResourceError::Limits(
196 LimitKind::InputSize,
197 )));
198 let a = a
199 .err()
200 .ok_or_else(|| fail(CHECK, "generic accepted oversized input"))?;
201 let b = b
202 .err()
203 .ok_or_else(|| fail(CHECK, "erased accepted oversized input"))?;
204 if a.error_category() != wanted || b.error_category() != wanted {
205 return Err(fail(
206 CHECK,
207 format!("expected InputSize, got {a:?} and {b:?}"),
208 ));
209 }
210 }
211 }
212 Ok(())
213}
214
215#[allow(clippy::too_many_arguments)]
221pub fn check_encode_policy_admission<E, D>(
222 enc: E,
223 dec: D,
224 pixels: PixelSlice<'_>,
225 metadata: Metadata,
226 initial: EncodePolicy,
227 request: EncodePolicy,
228 accepted: bool,
229 expected: &Metadata,
230) -> Conformance
231where
232 E: EncoderConfig + 'static,
233 D: DecoderConfig,
234 <E::Job as EncodeJob>::Enc: Encoder<Error = E::Error> + Send,
235 <E::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder,
236{
237 const CHECK: &str = "checked encode policy";
238 let mut job = enc
239 .clone()
240 .job()
241 .with_metadata_policy(metadata.clone(), MetadataPolicy::PreserveExact)
242 .try_with_policy(initial)
243 .map_err(|e| fail(CHECK, format!("initial: {e}")))?;
244 let mut dynamic = enc.dyn_job();
245 dynamic.set_metadata_policy(metadata.clone(), MetadataPolicy::PreserveExact);
246 dynamic
247 .try_set_policy(initial)
248 .map_err(|e| fail(CHECK, format!("erased initial: {e}")))?;
249 let a = job.try_set_policy(request);
250 let b = dynamic.try_set_policy(request);
251 if a.is_ok() != accepted || b.is_ok() != accepted {
252 return Err(fail(CHECK, format!("admission mismatch: {a:?}, {b:?}")));
253 }
254 if !accepted
255 && (a.err() != Some(UnsupportedOperation::EncodePolicy)
256 || b.err()
257 .and_then(|e| e.downcast_ref::<UnsupportedOperation>().copied())
258 != Some(UnsupportedOperation::EncodePolicy))
259 {
260 return Err(fail(CHECK, "rejection lost typed EncodePolicy error"));
261 }
262 let builder = enc
263 .clone()
264 .job()
265 .with_metadata_policy(metadata.clone(), MetadataPolicy::PreserveExact)
266 .try_with_policy(initial)
267 .map_err(|e| fail(CHECK, e.to_string()))?
268 .try_with_policy(request);
269 if builder.is_ok() != accepted {
270 return Err(fail(CHECK, "builder and mutable admission disagree"));
271 }
272 let baseline = match builder {
273 Ok(job) => job,
274 Err(UnsupportedOperation::EncodePolicy) => enc
275 .job()
276 .with_metadata_policy(metadata, MetadataPolicy::PreserveExact)
277 .try_with_policy(initial)
278 .map_err(|e| fail(CHECK, e.to_string()))?,
279 Err(e) => return Err(fail(CHECK, format!("wrong builder rejection: {e}"))),
280 }
281 .encode(pixels.sub_rows(0, pixels.rows()))
282 .map_err(|e| fail(CHECK, e.to_string()))?;
283 let (baseline_pixels, baseline_metadata) =
284 dec_oneshot(&dec, baseline.data()).map_err(|e| fail(CHECK, e))?;
285 let a = job
286 .encode(pixels.sub_rows(0, pixels.rows()))
287 .map_err(|e| fail(CHECK, e.to_string()))?;
288 let b = dynamic
289 .encode(pixels.sub_rows(0, pixels.rows()))
290 .map_err(|e| fail(CHECK, e.to_string()))?;
291 let (pa, ma) = dec_oneshot(&dec, a.data()).map_err(|e| fail(CHECK, e))?;
292 let (pb, mb) = dec_oneshot(&dec, b.data()).map_err(|e| fail(CHECK, e))?;
293 if pa != pb
294 || pa != baseline_pixels
295 || ma != baseline_metadata
296 || ma != *expected
297 || mb != *expected
298 {
299 return Err(fail(
300 CHECK,
301 format!("output mismatch: generic {ma:?}, erased {mb:?}, expected {expected:?}"),
302 ));
303 }
304 Ok(())
305}
306
307#[allow(clippy::too_many_arguments)]
311pub fn check_decode_policy_admission<D: DecoderConfig + 'static>(
312 dec: D,
313 bytes: &[u8],
314 initial: DecodePolicy,
315 request: DecodePolicy,
316 accepted: bool,
317 expected: &Metadata,
318) -> Conformance {
319 const CHECK: &str = "checked decode policy";
320 let mut job = dec
321 .clone()
322 .job()
323 .try_with_policy(initial)
324 .map_err(|e| fail(CHECK, e.to_string()))?;
325 let mut dynamic = dec.dyn_job();
326 dynamic
327 .try_set_policy(initial)
328 .map_err(|e| fail(CHECK, e.to_string()))?;
329 let a = job.try_set_policy(request);
330 let b = dynamic.try_set_policy(request);
331 if a.is_ok() != accepted || b.is_ok() != accepted {
332 return Err(fail(CHECK, format!("admission mismatch: {a:?}, {b:?}")));
333 }
334 if !accepted
335 && (a.err() != Some(UnsupportedOperation::DecodePolicy)
336 || b.err()
337 .and_then(|e| e.downcast_ref::<UnsupportedOperation>().copied())
338 != Some(UnsupportedOperation::DecodePolicy))
339 {
340 return Err(fail(CHECK, "rejection lost typed DecodePolicy error"));
341 }
342 let builder = dec
343 .clone()
344 .job()
345 .try_with_policy(initial)
346 .map_err(|e| fail(CHECK, e.to_string()))?
347 .try_with_policy(request);
348 if builder.is_ok() != accepted {
349 return Err(fail(CHECK, "builder and mutable admission disagree"));
350 }
351 let baseline = match builder {
352 Ok(job) => job,
353 Err(UnsupportedOperation::DecodePolicy) => dec
354 .clone()
355 .job()
356 .try_with_policy(initial)
357 .map_err(|e| fail(CHECK, e.to_string()))?,
358 Err(e) => return Err(fail(CHECK, format!("wrong builder rejection: {e}"))),
359 }
360 .decoder(Cow::Borrowed(bytes), &[])
361 .map_err(|e| fail(CHECK, e.to_string()))?
362 .decode()
363 .map_err(|e| fail(CHECK, e.to_string()))?;
364 let a = job
365 .decoder(Cow::Borrowed(bytes), &[])
366 .map_err(|e| fail(CHECK, e.to_string()))?
367 .decode()
368 .map_err(|e| fail(CHECK, e.to_string()))?;
369 let b = dynamic
370 .into_decoder(Cow::Borrowed(bytes), &[])
371 .map_err(|e| fail(CHECK, e.to_string()))?
372 .decode()
373 .map_err(|e| fail(CHECK, e.to_string()))?;
374 if grab(a.pixels()) != grab(b.pixels())
375 || a.metadata() != *expected
376 || b.metadata() != *expected
377 {
378 return Err(fail(
379 CHECK,
380 "decoded pixels/metadata disagree with expected policy outcome",
381 ));
382 }
383 if grab(a.pixels()) != grab(baseline.pixels()) || a.metadata() != baseline.metadata() {
384 return Err(fail(
385 CHECK,
386 "builder/mutable output differs or rejection changed output",
387 ));
388 }
389 Ok(())
390}
391
392pub fn check_metadata_rewrite<'a>(
398 request: RewriteRequest<'a>,
399 rewrite: impl Fn(&RewriteRequest<'a>) -> Result<Plan<'a>, RewriteError>,
400 observe: impl Fn(&[u8]) -> Conformance,
401) -> Conformance {
402 const CHECK: &str = "metadata rewrite";
403 let plan = rewrite(&request).map_err(|e| fail(CHECK, e.to_string()))?;
404 let output = plan.to_vec().map_err(|e| fail(CHECK, e.to_string()))?;
405 if output.is_empty() || request.input().is_empty() {
406 return Err(fail(CHECK, "requires nonempty encoded input and output"));
407 }
408 if plan.output_len() != output.len()
409 || plan.chunks().flatten().copied().collect::<Vec<_>>() != output
410 {
411 return Err(fail(CHECK, "chunk/materialized length disagreement"));
412 }
413 for entry in plan.report() {
414 let r = entry.source_range();
415 if r.start > r.end || r.end > request.input().len() {
416 return Err(fail(CHECK, "report outside original input"));
417 }
418 }
419 observe(&output)?;
420 let again = rewrite(&request).map_err(|e| fail(CHECK, e.to_string()))?;
421 if again.to_vec().map_err(|e| fail(CHECK, e.to_string()))? != output
422 || again.report() != plan.report()
423 {
424 return Err(fail(CHECK, "identical request produced different plan"));
425 }
426 let exact = request
427 .clone()
428 .with_byte_limits(request.input().len(), output.len());
429 if rewrite(&exact)
430 .map_err(|e| fail(CHECK, format!("exact budget: {e}")))?
431 .to_vec()
432 .map_err(|e| fail(CHECK, e.to_string()))?
433 != output
434 {
435 return Err(fail(CHECK, "exact budgets changed output"));
436 }
437 for (limited, expected) in [
438 (
439 request
440 .clone()
441 .with_byte_limits(request.input().len() - 1, usize::MAX),
442 LimitKind::InputSize,
443 ),
444 (
445 request
446 .clone()
447 .with_byte_limits(usize::MAX, output.len() - 1),
448 LimitKind::OutputSize,
449 ),
450 (
451 request.clone().with_carrier_limit(0),
452 LimitKind::MetadataCarriers,
453 ),
454 ] {
455 if !matches!(rewrite(&limited),Err(RewriteError::Limit(kind)) if kind==expected) {
456 return Err(fail(CHECK, format!("missing typed {expected:?} limit")));
457 }
458 }
459 struct Cancelled;
460 impl enough::Stop for Cancelled {
461 fn check(&self) -> Result<(), enough::StopReason> {
462 Err(enough::StopReason::Cancelled)
463 }
464 }
465 static PULSE: std::sync::LazyLock<zencodec::how_far::StopOnly<'static>> =
466 std::sync::LazyLock::new(|| zencodec::how_far::StopOnly::new(Cancelled));
467 let cancelled = request.with_pulse(Some(&*PULSE));
468 if !matches!(
469 rewrite(&cancelled),
470 Err(RewriteError::Cancelled) | Err(RewriteError::Stopped(enough::StopReason::Cancelled))
471 ) {
472 return Err(fail(CHECK, "pre-cancellation was not honored"));
473 }
474 Ok(())
475}
476
477pub fn check_animation_timing_unsupported<E, D>(
482 enc: E,
483 dec: D,
484 pixels: PixelSlice<'_>,
485) -> Conformance
486where
487 E: EncoderConfig + 'static,
488 D: DecoderConfig + 'static,
489 <E::Job as EncodeJob>::Enc: Encoder + Send,
490 <E::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder,
491{
492 const CHECK: &str = "unsupported animation timing";
493 if E::capabilities().animation_timing() {
494 return Err(fail(
495 CHECK,
496 "timing-enabled adapter needs explicit timing cases",
497 ));
498 }
499 let mut generic = enc
500 .clone()
501 .job()
502 .animation_frame_encoder()
503 .map_err(|e| fail(CHECK, e.to_string()))?;
504 let mut dynamic = enc
505 .dyn_job()
506 .into_animation_frame_encoder()
507 .map_err(|e| fail(CHECK, e.to_string()))?;
508 for delay in [40, 60] {
509 for duration in [
510 FrameDuration::from_millis(0),
511 FrameDuration::from_millis(10),
512 FrameDuration::new(1, 30000).unwrap(),
513 FrameDuration::new(u64::MAX, 1).unwrap(),
514 ] {
515 for exact in [true, false] {
516 let a = if exact {
517 generic.push_frame_exact(pixels.sub_rows(0, pixels.rows()), duration, None)
518 } else {
519 generic
520 .push_frame_timed(pixels.sub_rows(0, pixels.rows()), duration, None)
521 .map(|_| ())
522 };
523 let b = if exact {
524 dynamic.push_frame_exact(pixels.sub_rows(0, pixels.rows()), duration, None)
525 } else {
526 dynamic
527 .push_frame_timed(pixels.sub_rows(0, pixels.rows()), duration, None)
528 .map(|_| ())
529 };
530 let a = a
531 .err()
532 .ok_or_else(|| fail(CHECK, "generic accepted unsupported timing"))?;
533 let b = b
534 .err()
535 .ok_or_else(|| fail(CHECK, "erased accepted unsupported timing"))?;
536 if a.unsupported_operation() != Some(&UnsupportedOperation::AnimationTiming)
537 || b.unsupported_operation() != Some(&UnsupportedOperation::AnimationTiming)
538 {
539 return Err(fail(CHECK, format!("wrong rejection: {a}, {b}")));
540 }
541 }
542 }
543 generic
544 .push_frame(pixels.sub_rows(0, pixels.rows()), delay, None)
545 .map_err(|e| fail(CHECK, e.to_string()))?;
546 dynamic
547 .push_frame(pixels.sub_rows(0, pixels.rows()), delay, None)
548 .map_err(|e| fail(CHECK, e.to_string()))?;
549 }
550 let a = generic
551 .finish(None)
552 .map_err(|e| fail(CHECK, e.to_string()))?;
553 let b = dynamic
554 .finish(None)
555 .map_err(|e| fail(CHECK, e.to_string()))?;
556 let expected = [
557 FrameDuration::from_millis(40),
558 FrameDuration::from_millis(60),
559 ];
560 check_animation_durations(dec.clone(), a.data(), &expected)?;
561 check_animation_durations(dec, b.data(), &expected)
562}
563
564pub fn check_read_at_contract(
569 source: &mut dyn zencodec::source::DynReadAt,
570 expected: &[u8],
571) -> Conformance {
572 const CHECK: &str = "read_at_contract";
573 let err = |e: &dyn std::fmt::Display| fail(CHECK, e.to_string());
574
575 let total = source.len().map_err(|e| err(&e))?;
576 if total != expected.len() as u64 {
577 return Err(fail(
578 CHECK,
579 format!("len() = {total}, expected {}", expected.len()),
580 ));
581 }
582 if expected.is_empty() {
583 return Ok(());
584 }
585 let mut all = vec![0u8; expected.len()];
586 source.read_exact_at(0, &mut all).map_err(|e| err(&e))?;
587 if all != expected {
588 return Err(fail(CHECK, "full read bytes differ"));
589 }
590 let mid = expected.len() / 2;
591 let take = (expected.len() - mid).min(16);
592 let mut slice = vec![0u8; take];
593 source
594 .read_exact_at(mid as u64, &mut slice)
595 .map_err(|e| err(&e))?;
596 if slice != expected[mid..mid + take] {
597 return Err(fail(CHECK, "middle read bytes differ"));
598 }
599 let mut head = vec![0u8; take];
600 let mut tail = vec![0u8; take];
601 {
602 let mut reqs: Vec<(u64, &mut [u8])> = vec![
603 ((expected.len() - take) as u64, &mut tail[..]),
604 (0, &mut head[..]),
605 ];
606 source.read_many_at(&mut reqs).map_err(|e| err(&e))?;
607 }
608 if head != expected[..take] || tail != expected[expected.len() - take..] {
609 return Err(fail(CHECK, "batched read bytes differ"));
610 }
611 let mut one = [0u8; 1];
612 if source
613 .read_exact_at(expected.len() as u64, &mut one)
614 .is_ok()
615 {
616 return Err(fail(CHECK, "read past end unexpectedly succeeded"));
617 }
618 Ok(())
619}
620
621fn pixels_of(out: &zencodec::decode::DecodeOutput) -> (u32, u32, PixelDescriptor, Vec<u8>) {
622 let ps = out.pixels();
623 let mut bytes = Vec::new();
624 for y in 0..ps.rows() {
625 bytes.extend_from_slice(ps.row(y));
626 }
627 (ps.width(), ps.rows(), ps.descriptor(), bytes)
628}
629
630pub fn check_seekable_capability_honesty<D: DecoderConfig>(dec: D, data: &[u8]) -> Conformance {
635 const CHECK: &str = "seekable_capability_honesty";
636 let declared = D::capabilities().seekable();
637 let mut source = zencodec::source::ReadAtShim(zencodec::source::SliceReadAt::new(data));
638 let mut sink = zencodec_io::CollectSink::new();
639 let result = dec
640 .clone()
641 .job()
642 .push_decoder_from_source(&mut source, &mut sink, &[]);
643 match (declared, result) {
644 (true, Ok(info)) => {
645 let reference = dec
646 .job()
647 .decoder(std::borrow::Cow::Borrowed(data), &[])
648 .and_then(|d| d.decode())
649 .map_err(|e| fail(CHECK, format!("one-shot decode: {e}")))?;
650 let (w, h, desc, bytes) = pixels_of(&reference);
651 if (info.width, info.height) != (w, h) || sink.descriptor() != Some(desc) {
652 return Err(fail(
653 CHECK,
654 format!(
655 "source decode {}x{} {:?} differs from one-shot {w}x{h} {desc:?}",
656 info.width,
657 info.height,
658 sink.descriptor()
659 ),
660 ));
661 }
662 if !sink.finished() {
663 return Err(fail(CHECK, "source decode did not call sink.finish"));
664 }
665 if sink.bytes() != bytes {
666 return Err(fail(CHECK, "source decode pixels differ from one-shot"));
667 }
668 Ok(())
669 }
670 (true, Err(e)) => Err(fail(
671 CHECK,
672 format!("declared seekable but push_decoder_from_source failed: {e}"),
673 )),
674 (false, Ok(_)) => Err(fail(
675 CHECK,
676 "did not declare seekable but push_decoder_from_source succeeded",
677 )),
678 (false, Err(e)) => match e.downcast_ref::<UnsupportedOperation>().copied() {
679 Some(UnsupportedOperation::SeekableDecode) => Ok(()),
680 _ => Err(fail(
681 CHECK,
682 format!("undeclared seekable rejected with wrong error: {e}"),
683 )),
684 },
685 }
686}
687
688pub fn check_incremental_capability_honesty<D: DecoderConfig>(dec: D, data: &[u8]) -> Conformance {
691 const CHECK: &str = "incremental_capability_honesty";
692 let declared = D::capabilities().incremental();
693 match (declared, dec.clone().job().dyn_incremental_decoder(&[])) {
694 (true, Ok(_)) => check_incremental_contract(dec, data),
695 (true, Err(e)) => Err(fail(
696 CHECK,
697 format!("declared incremental but dyn_incremental_decoder failed: {e}"),
698 )),
699 (false, Ok(_)) => Err(fail(
700 CHECK,
701 "did not declare incremental but dyn_incremental_decoder succeeded",
702 )),
703 (false, Err(e)) => match e.downcast_ref::<UnsupportedOperation>().copied() {
704 Some(UnsupportedOperation::IncrementalDecode) => Ok(()),
705 _ => Err(fail(
706 CHECK,
707 format!("undeclared incremental rejected with wrong error: {e}"),
708 )),
709 },
710 }
711}
712
713struct OrderSink {
715 inner: zencodec_io::CollectSink,
716 final_ys: Vec<(u32, u32)>,
717 began: u32,
718}
719
720impl zencodec::decode::DecodeRowSink for OrderSink {
721 fn begin(
722 &mut self,
723 w: u32,
724 h: u32,
725 d: PixelDescriptor,
726 ) -> Result<(), zencodec::decode::SinkError> {
727 self.began += 1;
728 self.inner.begin(w, h, d)
729 }
730 fn provide_next_buffer(
731 &mut self,
732 y: u32,
733 height: u32,
734 width: u32,
735 descriptor: PixelDescriptor,
736 ) -> Result<zenpixels::PixelSliceMut<'_>, zencodec::decode::SinkError> {
737 self.final_ys.push((y, height));
738 self.inner.provide_next_buffer(y, height, width, descriptor)
739 }
740 fn finish(&mut self) -> Result<(), zencodec::decode::SinkError> {
741 self.inner.finish()
742 }
743}
744
745#[derive(Default)]
748struct PassSink {
749 batches: Vec<(zencodec::decode::BatchMeta, u32, Vec<u8>)>,
750 current: Option<(zencodec::decode::BatchMeta, zenpixels::PixelBuffer)>,
751 final_through_pass: u32,
752}
753
754impl PassSink {
755 fn ship(&mut self) {
756 if let Some((meta, buf)) = self.current.take() {
757 let s = buf.as_slice();
758 let mut packed = Vec::new();
759 for r in 0..s.rows() {
760 packed.extend_from_slice(s.row(r));
761 }
762 self.batches.push((meta, s.rows(), packed));
763 }
764 }
765}
766
767impl zencodec::decode::DecodeRowSink for PassSink {
768 fn provide_next_buffer(
769 &mut self,
770 y: u32,
771 height: u32,
772 width: u32,
773 descriptor: PixelDescriptor,
774 ) -> Result<zenpixels::PixelSliceMut<'_>, zencodec::decode::SinkError> {
775 self.ship();
776 let (_, buf) = self.current.insert((
777 zencodec::decode::BatchMeta::final_rows(y),
778 zenpixels::PixelBuffer::new(width, height, descriptor),
779 ));
780 Ok(buf.as_slice_mut())
781 }
782 fn accepts_provisional(&self) -> bool {
783 true
784 }
785 fn provide_pass_buffer(
786 &mut self,
787 meta: zencodec::decode::BatchMeta,
788 height: u32,
789 width: u32,
790 descriptor: PixelDescriptor,
791 ) -> Result<zenpixels::PixelSliceMut<'_>, zencodec::decode::SinkError> {
792 if meta.is_final {
793 self.final_through_pass += 1;
794 }
795 self.ship();
796 let (_, buf) = self
797 .current
798 .insert((meta, zenpixels::PixelBuffer::new(width, height, descriptor)));
799 Ok(buf.as_slice_mut())
800 }
801 fn finish(&mut self) -> Result<(), zencodec::decode::SinkError> {
802 self.ship();
803 Ok(())
804 }
805}
806
807struct TripAfter {
809 left: std::sync::atomic::AtomicU32,
810}
811
812impl enough::Stop for TripAfter {
813 fn check(&self) -> Result<(), enough::StopReason> {
814 use std::sync::atomic::Ordering;
815 let left = self.left.load(Ordering::SeqCst);
817 if left == 0 {
818 return Err(enough::StopReason::Cancelled);
819 }
820 self.left.store(left - 1, Ordering::SeqCst);
821 Ok(())
822 }
823}
824
825pub fn check_incremental_contract<D: DecoderConfig>(dec: D, data: &[u8]) -> Conformance {
847 check_incremental_decoder(
848 || {
849 dec.clone()
850 .job()
851 .dyn_incremental_decoder(&[])
852 .map_err(|e| e.to_string())
853 },
854 |bytes| {
855 dec.clone()
856 .job()
857 .decoder(std::borrow::Cow::Borrowed(bytes), &[])
858 .and_then(|d| d.decode())
859 .map_err(|e| e.to_string())
860 },
861 data,
862 )
863}
864
865pub fn check_incremental_decoder<F, O>(new_decoder: F, one_shot: O, data: &[u8]) -> Conformance
870where
871 F: Fn() -> Result<Box<zencodec::decode::DynIncrementalDecoder>, String>,
872 O: Fn(&[u8]) -> Result<zencodec::decode::DecodeOutput, String>,
873{
874 use zencodec::decode::{Event, IncrementalDecode, Input, StepError};
875 const CHECK: &str = "incremental_contract";
876 let new_dec = || new_decoder().map_err(|e| fail(CHECK, format!("new decoder: {e}")));
877 let reference = one_shot(data).map_err(|e| fail(CHECK, format!("one-shot decode: {e}")))?;
878 let (w, h, desc, bytes) = pixels_of(&reference);
879 let budget = 4 * data.len() as u64 + 4 * h as u64 + 1000;
880
881 for window in [1usize, 7, 64, data.len().max(1)] {
882 let mut inc = new_dec()?;
883 let mut sink = OrderSink {
884 inner: zencodec_io::CollectSink::new(),
885 final_ys: Vec::new(),
886 began: 0,
887 };
888 let (info, stats) = zencodec_io::drive_from_slice(&mut *inc, data, &mut sink, None, window)
889 .map_err(|e| fail(CHECK, format!("window {window}: {e}")))?;
890 if stats.steps > budget {
891 return Err(fail(
892 CHECK,
893 format!(
894 "window {window}: {} steps for {} bytes",
895 stats.steps,
896 data.len()
897 ),
898 ));
899 }
900 if (info.width, info.height) != (w, h) {
901 return Err(fail(
902 CHECK,
903 format!(
904 "window {window}: Done reports {}x{}, one-shot is {w}x{h}",
905 info.width, info.height
906 ),
907 ));
908 }
909 if sink.inner.descriptor() != Some(desc) {
910 return Err(fail(
911 CHECK,
912 format!(
913 "window {window}: sink began with {:?}, one-shot is {desc:?}",
914 sink.inner.descriptor()
915 ),
916 ));
917 }
918 if sink.began != 1 || !sink.inner.finished() {
919 return Err(fail(
920 CHECK,
921 format!(
922 "window {window}: begin called {} times, finish {}",
923 sink.began,
924 sink.inner.finished()
925 ),
926 ));
927 }
928 let mut next = 0u32;
929 for &(y, rows) in &sink.final_ys {
930 if y != next {
931 return Err(fail(
932 CHECK,
933 format!(
934 "window {window}: final rows out of order (got y={y}, expected {next})"
935 ),
936 ));
937 }
938 next = y + rows;
939 }
940 if next != h {
941 return Err(fail(
942 CHECK,
943 format!("window {window}: {next} of {h} rows written"),
944 ));
945 }
946 if sink.inner.bytes() != bytes {
947 return Err(fail(
948 CHECK,
949 format!("window {window}: pixels differ from one-shot"),
950 ));
951 }
952 if inc.info().map(|i| (i.width, i.height)).is_none() {
953 return Err(fail(
954 CHECK,
955 format!("window {window}: info() is None after Done"),
956 ));
957 }
958 let again = inc
960 .step(
961 Input::new(0, data),
962 &mut sink,
963 Some(&zencodec::how_far::NoPulse),
964 )
965 .map_err(|e| fail(CHECK, format!("step after Done: {e}")))?;
966 if !again.event.is_done() || again.consumed != 0 {
967 return Err(fail(CHECK, format!("step after Done returned {again:?}")));
968 }
969 }
970
971 {
973 let mut inc = new_dec()?;
974 let mut sink = zencodec_io::CollectSink::new();
975 let p = inc
976 .step(Input::empty(), &mut sink, Some(&zencodec::how_far::NoPulse))
977 .map_err(|e| fail(CHECK, format!("first step: {e}")))?;
978 if p.event == Event::Need {
979 let wanted: Vec<zencodec::decode::InputRequest> = inc.requested().to_vec();
980 let first = wanted.first().and_then(|r| r.offset()).unwrap_or(0);
981 let bogus = first.saturating_add(1).min(data.len() as u64);
982 let tail = &data[bogus as usize..];
983 let p = inc
984 .step(
985 Input::new(bogus, tail),
986 &mut sink,
987 Some(&zencodec::how_far::NoPulse),
988 )
989 .map_err(|e| fail(CHECK, format!("unrequested input at {bogus} failed: {e}")))?;
990 if p.consumed != 0 || p.event != Event::Need {
991 return Err(fail(
992 CHECK,
993 format!("unrequested input at {bogus} was used: {p:?}"),
994 ));
995 }
996 }
997 }
998
999 {
1001 let mut inc = new_dec()?;
1002 let mut sink = zencodec_io::CollectSink::new();
1003 let stop = TripAfter {
1004 left: std::sync::atomic::AtomicU32::new(2),
1005 };
1006 let mut input = Input::empty().with_total_len(data.len() as u64);
1007 let mut saw_cancel = false;
1008 for step in 0..budget {
1009 match inc.step(
1010 input,
1011 &mut sink,
1012 Some(&zencodec::how_far::StopOnly::borrowed(&stop)),
1013 ) {
1014 Ok(p) => {
1015 if p.event.is_done() {
1016 break;
1017 }
1018 if step >= 2 {
1021 return Err(fail(
1022 CHECK,
1023 format!("step {step} succeeded after the stop tripped: {p:?}"),
1024 ));
1025 }
1026 let next = match inc.requested().first() {
1027 Some(r) if p.event == Event::Need => r.offset().unwrap_or(0),
1028 _ => input.offset + p.consumed as u64,
1029 };
1030 let start = (next as usize).min(data.len());
1031 input =
1032 Input::new(start as u64, &data[start..]).with_total_len(data.len() as u64);
1033 }
1034 Err(StepError::Cancelled(_)) => {
1035 saw_cancel = true;
1036 break;
1037 }
1038 Err(e) => return Err(fail(CHECK, format!("cancelling: unexpected {e}"))),
1039 }
1040 }
1041 if saw_cancel {
1042 match inc.step(input, &mut sink, Some(&zencodec::how_far::NoPulse)) {
1043 Err(StepError::Finished) => {}
1044 other => {
1045 return Err(fail(
1046 CHECK,
1047 format!("step after cancellation returned {other:?}, not Finished"),
1048 ));
1049 }
1050 }
1051 }
1052 }
1053
1054 {
1058 let mut inc = new_dec()?;
1059 let mut sink = PassSink::default();
1060 zencodec_io::drive_from_slice(&mut *inc, data, &mut sink, None, 64)
1061 .map_err(|e| fail(CHECK, format!("provisional sink: {e}")))?;
1062 if sink.final_through_pass != 0 {
1063 return Err(fail(
1064 CHECK,
1065 format!(
1066 "provisional sink: {} final batches came through provide_pass_buffer",
1067 sink.final_through_pass
1068 ),
1069 ));
1070 }
1071 let row = bytes.len() / h.max(1) as usize;
1072 let mut covered = vec![false; h as usize];
1073 let mut out = vec![0u8; bytes.len()];
1074 for (meta, rows, px) in &sink.batches {
1075 if !meta.is_final {
1076 continue;
1077 }
1078 for r in 0..*rows {
1079 let y = (meta.y + r) as usize;
1080 if y >= covered.len() || covered[y] {
1081 return Err(fail(
1082 CHECK,
1083 format!("provisional sink: final row {y} written twice or out of range"),
1084 ));
1085 }
1086 covered[y] = true;
1087 out[y * row..(y + 1) * row]
1088 .copy_from_slice(&px[r as usize * row..(r as usize + 1) * row]);
1089 }
1090 }
1091 if covered.iter().any(|c| !c) || out != bytes {
1092 return Err(fail(
1093 CHECK,
1094 "provisional sink: final batches do not reproduce the one-shot pixels",
1095 ));
1096 }
1097 }
1098
1099 for cut in [data.len() / 4, data.len() / 2] {
1104 if cut >= data.len() {
1105 continue;
1106 }
1107 let part = &data[..cut];
1108 let mut inc = new_dec()?;
1109 let mut sink = zencodec_io::CollectSink::new();
1110 let got = zencodec_io::drive_from_slice(&mut *inc, part, &mut sink, None, 64);
1111 match (got, one_shot(part)) {
1112 (Err(zencodec_io::DriveError::Step(StepError::Codec(_))), Err(_)) => {}
1113 (Ok(_), Ok(want)) => {
1114 let (_, _, want_desc, want_bytes) = pixels_of(&want);
1115 if sink.descriptor() != Some(want_desc) || sink.bytes() != want_bytes {
1116 return Err(fail(
1117 CHECK,
1118 format!(
1119 "truncated to {cut}: pixels differ from one-shot on the same bytes"
1120 ),
1121 ));
1122 }
1123 }
1124 (Ok(_), Err(e)) => {
1125 return Err(fail(
1126 CHECK,
1127 format!(
1128 "truncated to {cut} of {} bytes decoded, but one-shot fails: {e}",
1129 data.len()
1130 ),
1131 ));
1132 }
1133 (Err(e), Ok(_)) => {
1134 return Err(fail(
1135 CHECK,
1136 format!("truncated to {cut}: one-shot decodes, incremental fails: {e}"),
1137 ));
1138 }
1139 (Err(e), Err(_)) => {
1140 return Err(fail(
1141 CHECK,
1142 format!("truncated to {cut}: expected a codec error, got: {e}"),
1143 ));
1144 }
1145 }
1146 }
1147 Ok(())
1148}