From 1050fa206137d23739a5222fa11fa8d47dfce442 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 05:36:48 -0400 Subject: [PATCH 01/55] wai: render_hls_with and the playlist receipt's rendering (W3-4) hls_render holds the segments and their HLS text, with no dependency, so the HTTP binding renders WAI playlists alone: render_hls_with adds a media and discontinuity sequence, a fixed target duration, partial segments with their server control, discontinuities, program date-times, #WAI-LAYER lines (N only) and a preload hint; under HlsRender::legacy with no extras it gives render_hls's bytes. check_render refuses inputs that would not read back as themselves (playlist_malformed). ManifestReceipt gains render and extras: verify and verify_render recompute the playlist with render_hls_with, and the signature also binds a render digest (BLAKE3 over the JCS of render and extras) only when a rendering is present, so every receipt sealed before keeps its signed bytes (a pinned pre-retrofit receipt still verifies) and a rendering has one form. seal_with, to_json/from_json (strict) and receipt_file_name (mr/.json) are new. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/hls_render.rs | 346 ++++++++++++++ wai/wai-rs/src/lib.rs | 6 + wai/wai-rs/src/wai_video_manifest.rs | 646 ++++++++++++++++++++++++--- 3 files changed, 935 insertions(+), 63 deletions(-) create mode 100644 wai/wai-rs/src/hls_render.rs diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs new file mode 100644 index 000000000..3baa4daf5 --- /dev/null +++ b/wai/wai-rs/src/hls_render.rs @@ -0,0 +1,346 @@ +//! HLS media playlist text (RFC 8216, and the second edition's partial segments): a +//! pure function of the ordered segments and the rendering parameters, so anyone +//! holding a playlist's bytes recomputes them, and a receipt over the bytes +//! (`wai.video.manifest`, http-delivery §8.6) binds every line. +//! +//! [`render_hls`] is the original rendering of `wai.video.manifest`: version 6, media +//! sequence 0, a derived target duration, a discontinuity before each ad and +//! `EXT-X-ENDLIST`. [`render_hls_with`] adds what an on-demand, live or low-latency WAI +//! playlist needs (http-delivery §10.4, §11): a media sequence and discontinuity +//! sequence, a fixed target duration, partial segments and their server control, +//! discontinuities and program date-times, `#WAI-LAYER` lines and a preload hint. With +//! [`HlsRender::legacy`] and no extras it gives [`render_hls`]'s bytes exactly. +//! +//! Pure text, no dependency: `http_binding` renders WAI playlists with it, and +//! `wai_video_manifest` seals them. + +/// Whether a segment carries programme content or a stitched ad. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SegmentKind { + Content, + Ad, +} + +impl SegmentKind { + /// The byte a manifest receipt's segment leaf carries. + pub(crate) fn tag(self) -> u8 { + match self { + SegmentKind::Content => 1, + SegmentKind::Ad => 2, + } + } + + /// `content` or `ad`. + pub fn label(self) -> &'static str { + match self { + SegmentKind::Content => "content", + SegmentKind::Ad => "ad", + } + } +} + +/// One entry in the ordered manifest composition. +/// +/// `ad_id.is_some()` iff `kind == Ad`; a manifest receipt's `verify` rejects any other +/// combination, so the monetisation audit (the ad-bearing segments) is a single source +/// of truth rather than a second list that could disagree. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Segment { + pub index: u64, + pub kind: SegmentKind, + /// The segment URI as it appears in the manifest (content or ad). + pub uri: String, + pub duration_ms: u64, + /// Present exactly for ad segments — the ad decision this break resolved to. + pub ad_id: Option, +} + +/// The playlist-wide rendering parameters (the header lines and the end). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct HlsRender { + /// `EXT-X-VERSION`. + pub version: u8, + /// `EXT-X-MEDIA-SEQUENCE`. + pub media_sequence: u64, + /// `EXT-X-DISCONTINUITY-SEQUENCE`, written only when it is not 0. + pub discontinuity_sequence: u64, + /// `EXT-X-TARGETDURATION` in seconds; `None` derives it as the longest segment's + /// duration rounded up. A live playlist fixes it: it must not change between + /// revisions. + pub target_duration_s: Option, + /// `EXT-X-PART-INF:PART-TARGET`, in milliseconds. + pub part_target_ms: Option, + /// `EXT-X-SERVER-CONTROL:PART-HOLD-BACK`, in milliseconds: required with a part + /// target, and at least twice it. + pub part_hold_back_ms: Option, + /// `EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES`. + pub can_block_reload: bool, + /// `EXT-X-ENDLIST`. + pub end_list: bool, +} + +impl HlsRender { + /// [`render_hls`]'s parameters: version 6, media sequence 0, a derived target + /// duration, no parts, and the end of the list. + pub fn legacy() -> Self { + HlsRender { + version: 6, + media_sequence: 0, + discontinuity_sequence: 0, + target_duration_s: None, + part_target_ms: None, + part_hold_back_ms: None, + can_block_reload: false, + end_list: true, + } + } +} + +/// A `#WAI-LAYER` line (http-delivery §10.4): the layer object a client may fetch to +/// depth `n` over the segment or part it follows. `n` is a locator only; the +/// envelope's `refines.layer` decides. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LayerLine { + pub n: u8, + pub uri: String, +} + +/// An `EXT-X-PART` line and the layer lines that follow it. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PartLine { + pub duration_ms: u64, + pub independent: bool, + pub uri: String, + pub layers: Vec, +} + +/// What a rendering adds to one segment: a discontinuity, a program date-time, the +/// partial segments that precede its URI, the layer lines after its `EXTINF`, and a +/// preload hint (only on the playlist's last entry). +/// +/// [`render_hls_with`] takes no extras, one per segment, or one per segment and one +/// more: the **open** segment at a live edge, whose parts are listed and whose +/// `EXTINF` and URI are not yet (it carries no layer lines of its own). +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct SegmentExtra { + pub discontinuity: bool, + pub program_date_time: Option, + pub parts: Vec, + pub layers: Vec, + pub preload_hint: Option, +} + +/// Why a rendering's inputs are refused (`playlist_malformed`, http-delivery §13): the +/// text would not read back as the lines it was given. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RenderError(pub String); + +impl RenderError { + /// `playlist_malformed`. + pub fn code(&self) -> &'static str { + "playlist_malformed" + } +} + +impl std::fmt::Display for RenderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "playlist_malformed: {}", self.0) + } +} + +impl std::error::Error for RenderError {} + +/// Milliseconds as a decimal number of seconds with three fractional digits, the same +/// on every platform. +pub fn seconds(ms: u64) -> String { + format!("{}.{:03}", ms / 1000, ms % 1000) +} + +/// Deterministic HLS rendering: the manifest is a pure function of the ordered +/// segments, so `hash(render(segments))` is a stable identity a verifier can +/// recompute. Ad segments carry an `EXT-X-DISCONTINUITY` — the real SSAI splice +/// marker — so the stitched breaks are visible in the bytes, not only the +/// receipt. Target duration is derived (ceil of the longest segment) rather than +/// supplied, so nothing outside `segments` can move the output. +pub fn render_hls(segments: &[Segment]) -> String { + render_hls_with(segments, &HlsRender::legacy(), &[]) +} + +fn layer_lines(out: &mut String, layers: &[LayerLine]) { + for l in layers { + out.push_str(&format!("#WAI-LAYER:N={},URI=\"{}\"\n", l.n, l.uri)); + } +} + +fn part_lines(out: &mut String, parts: &[PartLine]) { + for p in parts { + out.push_str(&format!("#EXT-X-PART:DURATION={}", seconds(p.duration_ms))); + if p.independent { + out.push_str(",INDEPENDENT=YES"); + } + out.push_str(&format!(",URI=\"{}\"\n", p.uri)); + layer_lines(out, &p.layers); + } +} + +/// The playlist `segments` render to under `r`, with `extras` (none, one per segment, +/// or one per segment and the open segment's). Each segment is written as: its +/// discontinuity (an ad's, or its extra's; one line), its program date-time, its +/// parts (each `EXT-X-PART` followed by its layer lines), its `EXTINF`, its layer +/// lines, and its URI. The open segment follows with its discontinuity, program +/// date-time and parts; then the preload hint and `EXT-X-ENDLIST`. The header is +/// `EXTM3U`, `EXT-X-VERSION`, `EXT-X-TARGETDURATION`, `EXT-X-PART-INF`, +/// `EXT-X-SERVER-CONTROL`, `EXT-X-MEDIA-SEQUENCE` and, when not 0, +/// `EXT-X-DISCONTINUITY-SEQUENCE`. Durations have three fractional digits. +/// +/// Rendering never fails; [`check_render`] says whether the inputs read back as +/// themselves, and a WAI playlist is published or sealed only when they do. +pub fn render_hls_with(segments: &[Segment], r: &HlsRender, extras: &[SegmentExtra]) -> String { + let target = r.target_duration_s.unwrap_or_else(|| segments.iter().map(|s| s.duration_ms.div_ceil(1000)).max().unwrap_or(0)); + let mut out = String::new(); + out.push_str(&format!("#EXTM3U\n#EXT-X-VERSION:{}\n", r.version)); + out.push_str(&format!("#EXT-X-TARGETDURATION:{target}\n")); + if let Some(pt) = r.part_target_ms { + out.push_str(&format!("#EXT-X-PART-INF:PART-TARGET={}\n", seconds(pt))); + } + let mut control: Vec = Vec::new(); + if r.can_block_reload { + control.push("CAN-BLOCK-RELOAD=YES".into()); + } + if let Some(h) = r.part_hold_back_ms { + control.push(format!("PART-HOLD-BACK={}", seconds(h))); + } + if !control.is_empty() { + out.push_str(&format!("#EXT-X-SERVER-CONTROL:{}\n", control.join(","))); + } + out.push_str(&format!("#EXT-X-MEDIA-SEQUENCE:{}\n", r.media_sequence)); + if r.discontinuity_sequence != 0 { + out.push_str(&format!("#EXT-X-DISCONTINUITY-SEQUENCE:{}\n", r.discontinuity_sequence)); + } + let none = SegmentExtra::default(); + for (i, s) in segments.iter().enumerate() { + let e = extras.get(i).unwrap_or(&none); + if s.kind == SegmentKind::Ad || e.discontinuity { + out.push_str("#EXT-X-DISCONTINUITY\n"); + } + if let Some(pdt) = &e.program_date_time { + out.push_str(&format!("#EXT-X-PROGRAM-DATE-TIME:{pdt}\n")); + } + part_lines(&mut out, &e.parts); + // Fixed 3-dp seconds so the byte output is stable across platforms. + out.push_str(&format!("#EXTINF:{},\n", seconds(s.duration_ms))); + layer_lines(&mut out, &e.layers); + out.push_str(&s.uri); + out.push('\n'); + } + if let Some(open) = extras.get(segments.len()) { + if open.discontinuity { + out.push_str("#EXT-X-DISCONTINUITY\n"); + } + if let Some(pdt) = &open.program_date_time { + out.push_str(&format!("#EXT-X-PROGRAM-DATE-TIME:{pdt}\n")); + } + part_lines(&mut out, &open.parts); + } + if let Some(hint) = extras.last().and_then(|e| e.preload_hint.as_ref()) { + out.push_str(&format!("#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"{hint}\"\n")); + } + if r.end_list { + out.push_str("#EXT-X-ENDLIST\n"); + } + out +} + +fn uri_ok(u: &str, quoted: bool) -> bool { + !u.is_empty() && !u.starts_with('#') && !u.bytes().any(|b| b == b'\r' || b == b'\n' || (quoted && b == b'"')) +} + +fn check_layers(layers: &[LayerLine], at: &str) -> Result<(), RenderError> { + let mut last = 0u8; + for l in layers { + if l.n == 0 || l.n <= last { + return Err(RenderError(format!("{at}: layer numbers are 1 to 255 and strictly increasing"))); + } + if !uri_ok(&l.uri, true) { + return Err(RenderError(format!("{at}: a layer URI is empty or holds a quote, CR or LF"))); + } + last = l.n; + } + Ok(()) +} + +/// Whether `segments`, `r` and `extras` render to a playlist that reads back as them +/// (`playlist_malformed` otherwise): +/// +/// - `extras` is empty, one per segment, or one more (the open segment, which carries +/// no layer lines, needs a part before a discontinuity or a date-time, and is not +/// in a playlist that ends); +/// - a preload hint is on the last extra only, and not in a playlist that ends; +/// - parts need a part target, and a part target needs a part hold-back of at least +/// twice it (the HLS second edition requires both); +/// - every URI is non-empty, does not begin with `#`, and holds no CR or LF, and a +/// quoted one (a part's, a layer's, a hint's) no `"`; +/// - a program date-time is ASCII letters, digits and `:.+-` only; +/// - layer numbers are 1 to 255 and strictly increasing after each segment or part; +/// - the version is at least 1. +pub fn check_render(segments: &[Segment], r: &HlsRender, extras: &[SegmentExtra]) -> Result<(), RenderError> { + let n = segments.len(); + if !(extras.is_empty() || extras.len() == n || extras.len() == n + 1) { + return Err(RenderError(format!("{} extras for {n} segments", extras.len()))); + } + if r.version == 0 { + return Err(RenderError("version 0".into())); + } + if let Some(pt) = r.part_target_ms { + match r.part_hold_back_ms { + Some(h) if pt > 0 && h >= pt.saturating_mul(2) => {} + _ => return Err(RenderError("a part target needs a part hold-back of at least twice it".into())), + } + } + for (i, s) in segments.iter().enumerate() { + if !uri_ok(&s.uri, false) { + return Err(RenderError(format!("segment {i}: its URI is empty, begins with # or holds CR or LF"))); + } + } + for (i, e) in extras.iter().enumerate() { + let at = if i == n { "the open segment".to_string() } else { format!("segment {i}") }; + if let Some(p) = &e.program_date_time { + if p.is_empty() || !p.bytes().all(|b| b.is_ascii_alphanumeric() || b":.+-".contains(&b)) { + return Err(RenderError(format!("{at}: a program date-time outside letters, digits and :.+-"))); + } + } + if !e.parts.is_empty() && r.part_target_ms.is_none() { + return Err(RenderError(format!("{at}: parts with no part target"))); + } + for p in &e.parts { + if !uri_ok(&p.uri, true) { + return Err(RenderError(format!("{at}: a part URI is empty or holds a quote, CR or LF"))); + } + check_layers(&p.layers, &at)?; + } + check_layers(&e.layers, &at)?; + if let Some(h) = &e.preload_hint { + if i + 1 != extras.len() || r.end_list { + return Err(RenderError(format!("{at}: a preload hint is on the last entry of a playlist that does not end"))); + } + if !uri_ok(h, true) { + return Err(RenderError(format!("{at}: the hint URI is empty or holds a quote, CR or LF"))); + } + } + if i == n { + if r.end_list { + return Err(RenderError("an open segment in a playlist that ends".into())); + } + if !e.layers.is_empty() { + return Err(RenderError("the open segment carries layer lines of its own".into())); + } + if e.parts.is_empty() && (e.discontinuity || e.program_date_time.is_some()) { + return Err(RenderError("the open segment's discontinuity or date-time comes before a part".into())); + } + if e.parts.is_empty() && e.preload_hint.is_none() { + return Err(RenderError("an open segment with no part and no hint".into())); + } + } + } + Ok(()) +} diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 2fae3496b..88bfcb953 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -463,6 +463,12 @@ pub mod ternary_receipt; #[cfg(feature = "quantum_ops")] pub use wai_quantum::quantum_ops; +// HLS media playlist text: a pure function of the segments and the rendering +// parameters (`render_hls`, and `render_hls_with` for on-demand, live and low-latency +// WAI playlists). No dependency, so the HTTP binding renders playlists with it alone +// and wai.video.manifest seals what it renders. +pub mod hls_render; + // Attested per-session manifest (wai.video.manifest): the incumbent OTT 1:1 // manifest, made deterministic (a pure function of the session *class*, so it // dedups across viewers), energy-accounted, and sealed in a signed receipt that diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs index e151110c9..273ce8f09 100644 --- a/wai/wai-rs/src/wai_video_manifest.rs +++ b/wai/wai-rs/src/wai_video_manifest.rs @@ -43,6 +43,8 @@ const DOMAIN_RECEIPT: &[u8] = b"wai:video-manifest-receipt\x01"; const DOMAIN_RECEIPT_ID: &[u8] = b"wai:video-manifest-receipt-id\x01"; const DOMAIN_CLASS: &[u8] = b"wai:video-manifest-class\x01"; const DOMAIN_SEG: &[u8] = b"wai:video-manifest-seg\x01"; +/// The domain of a playlist receipt's render digest (http-delivery §8.6). +const DOMAIN_RENDER: &[u8] = b"wai:video-manifest-render\x01"; /// How an energy figure was acquired. The one definition lives in /// [`crate::energy_class`] (no pixel-decode dependency, so this module stays @@ -52,37 +54,13 @@ const DOMAIN_SEG: &[u8] = b"wai:video-manifest-seg\x01"; pub use crate::energy_class::EnergyProvenance; use crate::energy_class::{figure_and_class_cohere, write_optional}; -/// Whether a segment carries programme content or a stitched ad. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum SegmentKind { - Content, - Ad, -} - -impl SegmentKind { - fn tag(self) -> u8 { - match self { - SegmentKind::Content => 1, - SegmentKind::Ad => 2, - } - } -} - -/// One entry in the ordered manifest composition. -/// -/// `ad_id.is_some()` iff `kind == Ad`; [`ManifestReceipt::verify`] rejects any -/// other combination, so the monetisation audit (the ad-bearing segments) is a -/// single source of truth rather than a second list that could disagree. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Segment { - pub index: u64, - pub kind: SegmentKind, - /// The segment URI as it appears in the manifest (content or ad). - pub uri: String, - pub duration_ms: u64, - /// Present exactly for ad segments — the ad decision this break resolved to. - pub ad_id: Option, -} +// The segments and their rendering live in `crate::hls_render`, which needs no +// dependency, so the HTTP binding renders WAI playlists with it alone; they are +// re-exported here under their original paths. +pub use crate::hls_render::{ + check_render, render_hls, render_hls_with, HlsRender, LayerLine, PartLine, RenderError, Segment, SegmentExtra, + SegmentKind, +}; impl Segment { fn leaf(&self) -> [u8; 32] { @@ -152,35 +130,6 @@ impl SessionClass { } } -/// Deterministic HLS rendering: the manifest is a pure function of the ordered -/// segments, so `hash(render(segments))` is a stable identity a verifier can -/// recompute. Ad segments carry an `EXT-X-DISCONTINUITY` — the real SSAI splice -/// marker — so the stitched breaks are visible in the bytes, not only the -/// receipt. Target duration is derived (ceil of the longest segment) rather than -/// supplied, so nothing outside `segments` can move the output. -pub fn render_hls(segments: &[Segment]) -> String { - let target = segments - .iter() - .map(|s| s.duration_ms.div_ceil(1000)) - .max() - .unwrap_or(0); - let mut out = String::new(); - out.push_str("#EXTM3U\n#EXT-X-VERSION:6\n"); - out.push_str(&format!("#EXT-X-TARGETDURATION:{target}\n")); - out.push_str("#EXT-X-MEDIA-SEQUENCE:0\n"); - for s in segments { - if s.kind == SegmentKind::Ad { - out.push_str("#EXT-X-DISCONTINUITY\n"); - } - // Fixed 3-dp seconds so the byte output is stable across platforms. - out.push_str(&format!("#EXTINF:{}.{:03},\n", s.duration_ms / 1000, s.duration_ms % 1000)); - out.push_str(&s.uri); - out.push('\n'); - } - out.push_str("#EXT-X-ENDLIST\n"); - out -} - /// A signed, energy-accounted per-session manifest. #[derive(Clone, Debug, PartialEq)] pub struct ManifestReceipt { @@ -215,9 +164,70 @@ pub struct ManifestReceipt { pub signer_pubkey: [u8; 32], pub signer_id: String, pub sig: [u8; 64], + /// The rendering parameters of the playlist this receipt signs, when it is a WAI + /// playlist (http-delivery §8.6). `None`: the playlist is [`render_hls`]'s, and the + /// receipt's signed bytes are exactly those it had before rendering parameters + /// existed. + pub render: Option, + /// The per-segment lines the rendering adds (http-delivery §8.6): none, one per + /// segment, or one more for the open segment at a live edge. Empty when `render` + /// is `None`. + pub extras: Vec, +} + +/// The JSON of a rendering's parameters and extras, as [`ManifestReceipt::render_digest`] +/// canonicalises it and the receipt file carries it. +pub fn render_json(r: &HlsRender) -> serde_json::Value { + serde_json::json!({ + "version": r.version, + "media_sequence": r.media_sequence, + "discontinuity_sequence": r.discontinuity_sequence, + "target_duration_s": r.target_duration_s, + "part_target_ms": r.part_target_ms, + "part_hold_back_ms": r.part_hold_back_ms, + "can_block_reload": r.can_block_reload, + "end_list": r.end_list, + }) +} + +fn layers_json(ls: &[LayerLine]) -> serde_json::Value { + ls.iter().map(|l| serde_json::json!({ "n": l.n, "uri": l.uri })).collect() +} + +/// The JSON of one extra (http-delivery §8.6). +pub fn extra_json(e: &SegmentExtra) -> serde_json::Value { + serde_json::json!({ + "discontinuity": e.discontinuity, + "program_date_time": e.program_date_time, + "parts": e.parts.iter().map(|p| serde_json::json!({ + "duration_ms": p.duration_ms, "independent": p.independent, "uri": p.uri, "layers": layers_json(&p.layers), + })).collect::>(), + "layers": layers_json(&e.layers), + "preload_hint": e.preload_hint, + }) } impl ManifestReceipt { + /// The playlist bytes this receipt signs: [`render_hls_with`] over the segments + /// with `render` (or [`HlsRender::legacy`]) and `extras`. + pub fn rendered(&self) -> String { + render_hls_with(&self.segments, &self.render.unwrap_or_else(HlsRender::legacy), &self.extras) + } + + /// BLAKE3 over the render domain and the canonical JSON (RFC 8785) of + /// `{"render": …, "extras": […]}`: what the signature binds of a rendering besides + /// its bytes, so a receipt has exactly one form. `None` without `render`, or when + /// a number is past the canonical subset. + pub fn render_digest(&self) -> Option<[u8; 32]> { + let r = self.render.as_ref()?; + let v = serde_json::json!({ "render": render_json(r), "extras": self.extras.iter().map(extra_json).collect::>() }); + let c = crate::jcs::jcs(&v).ok()?; + let mut h = blake3::Hasher::new(); + h.update(DOMAIN_RENDER); + h.update(c.as_bytes()); + Some(*h.finalize().as_bytes()) + } + fn signing_payload(&self) -> Vec { let mut o = DOMAIN_RECEIPT.to_vec(); o.extend_from_slice(&self.manifest_hash); @@ -254,6 +264,19 @@ impl ManifestReceipt { let id = self.signer_id.as_bytes(); o.extend_from_slice(&(id.len() as u32).to_be_bytes()); o.extend_from_slice(id); + // A WAI playlist's rendering (http-delivery §8.6): nothing without one, so the + // payload of every receipt sealed before rendering parameters existed is + // unchanged; `1` and the render digest with one (`2`, never signed, when the + // rendering has no canonical form). + if self.render.is_some() { + match self.render_digest() { + Some(d) => { + o.push(1); + o.extend_from_slice(&d); + } + None => o.push(2), + } + } o } @@ -273,7 +296,50 @@ impl ManifestReceipt { grant: GrantRef, parent_receipt_hash: Option<[u8; 32]>, ) -> ManifestReceipt { - let manifest_hash = *blake3::hash(render_hls(&segments).as_bytes()).as_bytes(); + Self::seal_inner(signer, signer_id, class, segments, viewers_served, joules_micro, energy_provenance, grant, parent_receipt_hash, None, Vec::new()) + } + + /// [`Self::seal`] for a WAI playlist rendered by [`render_hls_with`] under `render` + /// with `extras` (http-delivery §8.6). Refused (`playlist_malformed`) when the + /// inputs do not read back as themselves ([`check_render`]) or have no canonical + /// form. + #[allow(clippy::too_many_arguments)] + pub fn seal_with( + signer: &SigningKey, + signer_id: impl Into, + class: SessionClass, + segments: Vec, + viewers_served: u64, + joules_micro: u64, + energy_provenance: Option, + grant: GrantRef, + parent_receipt_hash: Option<[u8; 32]>, + render: HlsRender, + extras: Vec, + ) -> Result { + check_render(&segments, &render, &extras)?; + let r = Self::seal_inner(signer, signer_id, class, segments, viewers_served, joules_micro, energy_provenance, grant, parent_receipt_hash, Some(render), extras); + if r.render_digest().is_none() { + return Err(RenderError("a number past the canonical subset (2^53 - 1)".into())); + } + Ok(r) + } + + #[allow(clippy::too_many_arguments)] + fn seal_inner( + signer: &SigningKey, + signer_id: impl Into, + class: SessionClass, + segments: Vec, + viewers_served: u64, + joules_micro: u64, + energy_provenance: Option, + grant: GrantRef, + parent_receipt_hash: Option<[u8; 32]>, + render: Option, + extras: Vec, + ) -> ManifestReceipt { + let manifest_hash = *blake3::hash(render_hls_with(&segments, &render.unwrap_or_else(HlsRender::legacy), &extras).as_bytes()).as_bytes(); let session_class_hash = class.class_hash(); let leaves: Vec<[u8; 32]> = segments.iter().map(Segment::leaf).collect(); let mut r = ManifestReceipt { @@ -291,6 +357,8 @@ impl ManifestReceipt { signer_pubkey: signer.verifying_key().to_bytes(), signer_id: signer_id.into(), sig: [0u8; 64], + render, + extras, }; r.sig = signer.sign(&r.signing_payload()).to_bytes(); r @@ -310,7 +378,18 @@ impl ManifestReceipt { if self.class.class_hash() != self.session_class_hash { return false; } - if *blake3::hash(render_hls(&self.segments).as_bytes()).as_bytes() != self.manifest_hash { + // A rendering is a WAI playlist's (http-delivery §8.6): it reads back as itself + // and has a canonical form; extras without one are not a rendering. + match &self.render { + None if !self.extras.is_empty() => return false, + None => {} + Some(r) => { + if check_render(&self.segments, r, &self.extras).is_err() || self.render_digest().is_none() { + return false; + } + } + } + if *blake3::hash(self.rendered().as_bytes()).as_bytes() != self.manifest_hash { return false; } if self.work_compose_ops != self.segments.len() as u64 { @@ -348,7 +427,7 @@ impl ManifestReceipt { if *blake3::hash(rendered_manifest).as_bytes() != self.manifest_hash { return false; } - if render_hls(&self.segments).as_bytes() != rendered_manifest { + if self.rendered().as_bytes() != rendered_manifest { return false; } self.verify() @@ -385,6 +464,244 @@ impl ManifestReceipt { h.update(&self.sig); *h.finalize().as_bytes() } + + /// The receipt's file name beside the playlist it signs (http-delivery §8.6): + /// `.json`, served under `mr/`. + pub fn receipt_file_name(&self) -> String { + format!("{}.json", hex(&self.manifest_hash)) + } + + /// The receipt file (http-delivery §8.6): one JSON object, its members in a fixed + /// order, `render` and `extras` only when the receipt has a rendering. + pub fn to_json(&self) -> String { + use serde_json::{json, Map, Value}; + let mut o = Map::new(); + o.insert("kind".into(), json!(RECEIPT_KIND)); + o.insert("manifest_hash".into(), json!(hex(&self.manifest_hash))); + o.insert("session_class_hash".into(), json!(hex(&self.session_class_hash))); + o.insert("class".into(), json!({ + "content_id": self.class.content_id, "entitlement": self.class.entitlement, "geo": self.class.geo, + "blackouts": self.class.blackouts, "ad_pod_id": self.class.ad_pod_id, "window": self.class.window, + })); + o.insert("segments".into(), Value::Array(self.segments.iter().map(|s| json!({ + "index": s.index, "kind": s.kind.label(), "uri": s.uri, "duration_ms": s.duration_ms, "ad_id": s.ad_id, + })).collect())); + o.insert("merkle_root".into(), json!(hex(&self.merkle_root))); + o.insert("work_compose_ops".into(), json!(self.work_compose_ops)); + o.insert("viewers_served".into(), json!(self.viewers_served)); + o.insert("joules_micro".into(), json!(self.joules_micro)); + if let Some(p) = &self.energy_provenance { + for (k, v) in crate::energy_class::json_fields(p) { + o.insert(k.into(), v); + } + } + o.insert("grant".into(), json!({ + "grant_hash": hex(&self.grant.grant_hash), "capability": self.grant.capability, + "joule_ceiling_micro": self.grant.joule_ceiling_micro, "funds_ceiling": self.grant.funds_ceiling, + })); + o.insert("parent_receipt_hash".into(), json!(self.parent_receipt_hash.map(|h| hex(&h)))); + o.insert("signer_pubkey".into(), json!(hex(&self.signer_pubkey))); + o.insert("signer_id".into(), json!(self.signer_id)); + o.insert("sig".into(), json!(hex(&self.sig))); + if let Some(r) = &self.render { + o.insert("render".into(), render_json(r)); + o.insert("extras".into(), Value::Array(self.extras.iter().map(extra_json).collect())); + } + let mut out = Value::Object(o).to_string(); + out.push('\n'); + out + } + + /// Read a receipt file ([`Self::to_json`]). Strict: every member present with its + /// type, no member this form does not name, hashes as lowercase hexadecimal, and + /// `render` and `extras` together or not at all. It does not verify: call + /// [`Self::verify`] or [`Self::verify_render`]. + pub fn from_json(text: &str) -> Result { + use serde_json::Value; + let v: Value = serde_json::from_str(text).map_err(|e| e.to_string())?; + let o = v.as_object().ok_or("a receipt is a JSON object")?; + const KNOWN: &[&str] = &[ + "kind", "manifest_hash", "session_class_hash", "class", "segments", "merkle_root", "work_compose_ops", + "viewers_served", "joules_micro", "grant", "parent_receipt_hash", "signer_pubkey", "signer_id", "sig", + "render", "extras", crate::energy_class::JSON_CLASS, crate::energy_class::JSON_UNCERTAINTY, + crate::energy_class::JSON_FILTERING, crate::energy_class::JSON_CALIBRATION_REF, + ]; + if let Some(k) = o.keys().find(|k| !KNOWN.contains(&k.as_str())) { + return Err(format!("an unknown member {k:?}")); + } + if o.get("kind").and_then(Value::as_str) != Some(RECEIPT_KIND) { + return Err(format!("kind is not {RECEIPT_KIND:?}")); + } + let class = o.get("class").and_then(Value::as_object).ok_or("class")?; + if class.len() != 6 { + return Err("class has other members than its six".into()); + } + let class = SessionClass { + content_id: j_str(class, "content_id")?, + entitlement: j_str(class, "entitlement")?, + geo: j_str(class, "geo")?, + blackouts: class.get("blackouts").and_then(Value::as_array).ok_or("class.blackouts")? + .iter().map(|b| b.as_str().map(String::from).ok_or("class.blackouts")).collect::>()?, + ad_pod_id: j_str(class, "ad_pod_id")?, + window: j_u64(class, "window")?, + }; + let mut segments = Vec::new(); + for s in o.get("segments").and_then(Value::as_array).ok_or("segments")? { + let s = s.as_object().ok_or("a segment is an object")?; + if s.len() != 5 { + return Err("a segment has other members than its five".into()); + } + let kind = match s.get("kind").and_then(Value::as_str) { + Some("content") => SegmentKind::Content, + Some("ad") => SegmentKind::Ad, + _ => return Err("a segment's kind is content or ad".into()), + }; + let ad_id = match s.get("ad_id") { + Some(Value::Null) => None, + Some(Value::String(a)) => Some(a.clone()), + _ => return Err("a segment's ad_id is a string or null".into()), + }; + segments.push(Segment { index: j_u64(s, "index")?, kind, uri: j_str(s, "uri")?, duration_ms: j_u64(s, "duration_ms")?, ad_id }); + } + let grant = o.get("grant").and_then(Value::as_object).ok_or("grant")?; + if grant.len() != 4 { + return Err("grant has other members than its four".into()); + } + let grant = GrantRef { + grant_hash: j_hex32(grant, "grant_hash")?, + capability: j_str(grant, "capability")?, + joule_ceiling_micro: j_u64(grant, "joule_ceiling_micro")?, + funds_ceiling: match grant.get("funds_ceiling") { + Some(Value::Null) => None, + Some(v) => Some(v.as_u64().ok_or("grant.funds_ceiling")?), + None => return Err("grant.funds_ceiling".into()), + }, + }; + let parent_receipt_hash = match o.get("parent_receipt_hash") { + Some(Value::Null) => None, + Some(_) => Some(j_hex32(o, "parent_receipt_hash")?), + None => return Err("parent_receipt_hash".into()), + }; + let sig_hex = j_str(o, "sig")?; + let sig: [u8; 64] = unhex(&sig_hex).and_then(|b| b.try_into().ok()).ok_or("sig is 128 lowercase hexadecimal digits")?; + let energy_provenance = crate::energy_class::label_from_json(o).map_err(|e| format!("energy class: {e}"))?; + let (render, extras) = match (o.get("render"), o.get("extras")) { + (None, None) => (None, Vec::new()), + (Some(r), Some(Value::Array(es))) => (Some(render_from_json(r)?), es.iter().map(extra_from_json).collect::>()?), + _ => return Err("render and extras come together, extras an array".into()), + }; + Ok(ManifestReceipt { + manifest_hash: j_hex32(o, "manifest_hash")?, + session_class_hash: j_hex32(o, "session_class_hash")?, + class, + segments, + merkle_root: j_hex32(o, "merkle_root")?, + work_compose_ops: j_u64(o, "work_compose_ops")?, + viewers_served: j_u64(o, "viewers_served")?, + joules_micro: j_u64(o, "joules_micro")?, + energy_provenance, + grant, + parent_receipt_hash, + signer_pubkey: j_hex32(o, "signer_pubkey")?, + signer_id: j_str(o, "signer_id")?, + sig, + render, + extras, + }) + } +} + +/// The `kind` of a receipt file (http-delivery §8.6): the capability it is a record of. +pub const RECEIPT_KIND: &str = "wai.video.manifest"; + +fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +fn unhex(s: &str) -> Option> { + if s.len() % 2 != 0 || !s.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)) { + return None; + } + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok()).collect() +} + +type JObj = serde_json::Map; + +fn j_str(o: &JObj, k: &str) -> Result { + o.get(k).and_then(serde_json::Value::as_str).map(String::from).ok_or(format!("{k} is a string")) +} + +fn j_u64(o: &JObj, k: &str) -> Result { + o.get(k).and_then(serde_json::Value::as_u64).ok_or(format!("{k} is an unsigned integer")) +} + +fn j_bool(o: &JObj, k: &str) -> Result { + o.get(k).and_then(serde_json::Value::as_bool).ok_or(format!("{k} is true or false")) +} + +fn j_opt_u64(o: &JObj, k: &str) -> Result, String> { + match o.get(k) { + Some(serde_json::Value::Null) => Ok(None), + Some(v) => v.as_u64().map(Some).ok_or(format!("{k} is an unsigned integer or null")), + None => Err(format!("{k} is missing")), + } +} + +fn j_opt_str(o: &JObj, k: &str) -> Result, String> { + match o.get(k) { + Some(serde_json::Value::Null) => Ok(None), + Some(serde_json::Value::String(s)) => Ok(Some(s.clone())), + _ => Err(format!("{k} is a string or null")), + } +} + +fn j_hex32(o: &JObj, k: &str) -> Result<[u8; 32], String> { + unhex(&j_str(o, k)?).and_then(|b| b.try_into().ok()).ok_or(format!("{k} is 64 lowercase hexadecimal digits")) +} + +fn j_members(v: &serde_json::Value, n: usize, what: &str) -> Result { + match v.as_object() { + Some(o) if o.len() == n => Ok(o.clone()), + _ => Err(format!("{what} is an object of exactly {n} members")), + } +} + +/// The rendering parameters a receipt file carries ([`render_json`]). +pub fn render_from_json(v: &serde_json::Value) -> Result { + let o = j_members(v, 8, "render")?; + Ok(HlsRender { + version: u8::try_from(j_u64(&o, "version")?).map_err(|_| "render.version is at most 255")?, + media_sequence: j_u64(&o, "media_sequence")?, + discontinuity_sequence: j_u64(&o, "discontinuity_sequence")?, + target_duration_s: j_opt_u64(&o, "target_duration_s")?, + part_target_ms: j_opt_u64(&o, "part_target_ms")?, + part_hold_back_ms: j_opt_u64(&o, "part_hold_back_ms")?, + can_block_reload: j_bool(&o, "can_block_reload")?, + end_list: j_bool(&o, "end_list")?, + }) +} + +fn layers_from_json(v: Option<&serde_json::Value>) -> Result, String> { + v.and_then(serde_json::Value::as_array).ok_or("layers is an array")?.iter().map(|l| { + let o = j_members(l, 2, "a layer")?; + Ok(LayerLine { n: u8::try_from(j_u64(&o, "n")?).map_err(|_| "a layer's n is at most 255")?, uri: j_str(&o, "uri")? }) + }).collect() +} + +/// One extra a receipt file carries ([`extra_json`]). +pub fn extra_from_json(v: &serde_json::Value) -> Result { + let o = j_members(v, 5, "an extra")?; + let parts = o.get("parts").and_then(serde_json::Value::as_array).ok_or("parts is an array")?.iter().map(|p| { + let p = j_members(p, 4, "a part")?; + Ok(PartLine { duration_ms: j_u64(&p, "duration_ms")?, independent: j_bool(&p, "independent")?, uri: j_str(&p, "uri")?, layers: layers_from_json(p.get("layers"))? }) + }).collect::>()?; + Ok(SegmentExtra { + discontinuity: j_bool(&o, "discontinuity")?, + program_date_time: j_opt_str(&o, "program_date_time")?, + parts, + layers: layers_from_json(o.get("layers"))?, + preload_hint: j_opt_str(&o, "preload_hint")?, + }) } #[cfg(test)] @@ -586,4 +903,207 @@ mod tests { .collect(); assert_eq!(got, want); } + + /// The original rendering is unchanged by the retrofit: `render_hls` gives the bytes + /// it gave before rendering parameters existed (captured from the tree before them), + /// and `render_hls_with` under the legacy parameters with no extras gives the same. + #[test] + fn render_hls_legacy_byte_identical() { + let before = "#EXTM3U\n#EXT-X-VERSION:6\n#EXT-X-TARGETDURATION:6\n#EXT-X-MEDIA-SEQUENCE:0\n#EXTINF:6.000,\nseg0.ts\n#EXT-X-DISCONTINUITY\n#EXTINF:4.000,\nad/xyz/0.ts\n#EXTINF:6.000,\nseg1.ts\n#EXT-X-ENDLIST\n"; + assert_eq!(render_hls(&segments()), before); + assert_eq!(render_hls_with(&segments(), &HlsRender::legacy(), &[]), before); + assert_eq!(render_hls(&[]), "#EXTM3U\n#EXT-X-VERSION:6\n#EXT-X-TARGETDURATION:0\n#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-ENDLIST\n"); + } + + fn unhex_n(s: &str) -> [u8; N] { + unhex(s).unwrap().try_into().unwrap() + } + + /// A receipt sealed by the tree before rendering parameters existed: its fields and + /// signature, captured then. It still verifies, against the bytes it signed, and has + /// the receipt hash it had. + #[test] + fn old_receipt_verifies() { + let r = ManifestReceipt { + manifest_hash: unhex_n("5da852a2dce832a13bee19f57d82ad29fc2c75e0c5d26c84435c1820b69972ca"), + session_class_hash: unhex_n("82baa70a290bef84777cbe22eadf2d74d93070c0bb5d01331bcab7d9d09fde6c"), + class: class(), + segments: segments(), + merkle_root: unhex_n("4ded33ff67ad7bdd0f6b3d61336909adefeb3e682c2bf371429705bf4c703a7e"), + work_compose_ops: 3, + viewers_served: 12_000, + joules_micro: 800, + energy_provenance: Some(EnergyProvenance::ModelBased), + grant: GrantRef { grant_hash: [9u8; 32], capability: "video.manifest".into(), joule_ceiling_micro: 5_000_000, funds_ceiling: Some(250) }, + parent_receipt_hash: Some([3u8; 32]), + signer_pubkey: unhex_n("ea4a6c63e29c520abef5507b132ec5f9954776aebebe7b92421eea691446d22c"), + signer_id: "did:key:origin".into(), + sig: unhex_n("1706dc6f3593069eff2d2241f0d46b6be3609b875ebe491076c39eb351215a7354f05dbd864025a707f5a21e860c592c6b43c2dfd1d86aaf256ff2c56059090b"), + render: None, + extras: Vec::new(), + }; + let signed = "#EXTM3U\n#EXT-X-VERSION:6\n#EXT-X-TARGETDURATION:6\n#EXT-X-MEDIA-SEQUENCE:0\n#EXTINF:6.000,\nseg0.ts\n#EXT-X-DISCONTINUITY\n#EXTINF:4.000,\nad/xyz/0.ts\n#EXTINF:6.000,\nseg1.ts\n#EXT-X-ENDLIST\n"; + assert!(r.verify()); + assert!(r.verify_render(signed.as_bytes())); + assert_eq!(hex(&r.receipt_hash()), "f94942be7cbad0aaae98cfb38571ed56219ea3c755ee3fb9872184cc9afaa0bb"); + // Its file round-trips and still verifies; the file carries no rendering. + let j = r.to_json(); + assert!(!j.contains("\"render\"")); + let back = ManifestReceipt::from_json(&j).unwrap(); + assert_eq!(back, r); + assert!(back.verify_render(signed.as_bytes())); + // Extras without a rendering are not a receipt this form has. + let mut t = r.clone(); + t.extras = vec![SegmentExtra::default(); 3]; + assert!(!t.verify()); + } + + fn public_class(window: u64) -> SessionClass { + SessionClass { content_id: "live/720".into(), entitlement: "public".into(), geo: String::new(), blackouts: Vec::new(), ad_pod_id: String::new(), window } + } + + fn part(seq: u64, i: u64) -> PartLine { + PartLine { duration_ms: 320, independent: true, uri: format!("live/720/{seq}.{i}.wai#cid={}&n=400", hex(&[i as u8; 32])), layers: Vec::new() } + } + + fn live_render(media_sequence: u64) -> HlsRender { + HlsRender { + version: 9, media_sequence, discontinuity_sequence: 0, target_duration_s: Some(4), part_target_ms: Some(320), + part_hold_back_ms: Some(960), can_block_reload: true, end_list: false, + } + } + + /// Three revisions of a low-latency playlist, each sealed with the previous one as + /// its parent: each verifies against its own bytes, names its parent, and refuses + /// another revision's bytes. A revision whose part lines are reordered after sealing + /// fails, and so does one whose bytes were rendered from reordered parts. + #[test] + fn live_chain() { + let key = key(11); + let seg = |i: u64| Segment { index: i, kind: SegmentKind::Content, uri: format!("objects/{}.wai#n=3000&h=1009", hex(&[0x40 + i as u8; 32])), duration_ms: 3840, ad_id: None }; + // r0: segment 1200 open with 5 parts; r1: open with 12; r2: 1200 complete, 1201 open with 3. + let open = |seq: u64, k: u64, hint: u64| SegmentExtra { + program_date_time: (seq == 1200).then(|| "2026-10-01T12:00:00.000Z".to_string()), + parts: (0..k).map(|i| part(seq, i)).collect(), + preload_hint: Some(format!("live/720/{seq}.{hint}.wai")), + ..Default::default() + }; + let revisions: Vec<(Vec, Vec)> = vec![ + (vec![], vec![open(1200, 5, 5)]), + (vec![], vec![open(1200, 12, 12)]), + (vec![seg(0)], vec![ + SegmentExtra { program_date_time: Some("2026-10-01T12:00:00.000Z".into()), parts: (0..12).map(|i| part(1200, i)).collect(), ..Default::default() }, + open(1201, 3, 3), + ]), + ]; + let mut sealed: Vec = Vec::new(); + for (segs, extras) in &revisions { + let parent = sealed.last().map(ManifestReceipt::receipt_hash); + let r = ManifestReceipt::seal_with(&key, "did:example:live", public_class(1200), segs.clone(), 1, 0, None, + GrantRef::unbounded("video.manifest"), parent, live_render(1200), extras.clone()).unwrap(); + sealed.push(r); + } + let bytes: Vec = sealed.iter().map(ManifestReceipt::rendered).collect(); + assert!(bytes[0].contains("#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"live/720/1200.5.wai\"\n")); + assert!(bytes[2].contains("#EXT-X-PART-INF:PART-TARGET=0.320\n#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960\n")); + for (i, r) in sealed.iter().enumerate() { + assert!(r.verify_render(bytes[i].as_bytes()), "revision {i}"); + for (j, b) in bytes.iter().enumerate() { + if j != i { + assert!(!r.verify_render(b.as_bytes()), "revision {i} against {j}'s bytes"); + } + } + let back = ManifestReceipt::from_json(&r.to_json()).unwrap(); + assert_eq!(&back, r); + assert!(back.verify_render(bytes[i].as_bytes())); + } + assert_eq!(sealed[0].parent_receipt_hash, None); + assert_eq!(sealed[1].parent_receipt_hash, Some(sealed[0].receipt_hash())); + assert_eq!(sealed[2].parent_receipt_hash, Some(sealed[1].receipt_hash())); + // Parts reordered after sealing: the receipt no longer verifies. + let mut t = sealed[2].clone(); + t.extras[0].parts.swap(3, 4); + assert!(!t.verify()); + assert!(!t.verify_render(bytes[2].as_bytes())); + // The bytes of a reordering: not the bytes the receipt signed. + let mut parts = revisions[2].1.clone(); + parts[0].parts.swap(3, 4); + let reordered = render_hls_with(&revisions[2].0, &live_render(1200), &parts); + assert!(!sealed[2].verify_render(reordered.as_bytes())); + } + + /// The rendering is inside the signature twice over: through `manifest_hash`, and + /// through the render digest, so a rendering that renders the same bytes another way + /// (no extras against empty ones; no parameters against the legacy ones) is another + /// receipt that does not verify. + #[test] + fn render_and_extras_are_signed() { + let k = key(12); + let r = ManifestReceipt::seal_with(&k, "o", public_class(0), segments(), 1, 0, None, GrantRef::unbounded("video.manifest"), None, + HlsRender { version: 7, ..HlsRender::legacy() }, Vec::new()).unwrap(); + assert!(r.verify()); + let mut t = r.clone(); + t.render = Some(HlsRender { end_list: false, ..t.render.unwrap() }); + assert!(!t.verify()); + let mut t = r.clone(); + t.extras = vec![SegmentExtra::default(); 3]; + assert_eq!(t.rendered(), r.rendered(), "the same bytes"); + assert!(!t.verify(), "another form of the same bytes"); + // The legacy parameters as an explicit rendering: the same bytes as no rendering, + // and a different receipt. + let a = ManifestReceipt::seal_with(&k, "o", public_class(0), segments(), 1, 0, None, GrantRef::unbounded("video.manifest"), None, HlsRender::legacy(), Vec::new()).unwrap(); + let b = ManifestReceipt::seal(&k, "o", public_class(0), segments(), 1, 0, None, GrantRef::unbounded("video.manifest"), None); + assert_eq!(a.manifest_hash, b.manifest_hash); + assert_ne!(a.receipt_hash(), b.receipt_hash()); + let mut c = a.clone(); + c.render = None; + assert!(!c.verify()); + assert!(a.verify() && b.verify()); + } + + /// A rendering that would not read back as its inputs is refused when sealing and + /// when verifying: a URI that breaks a line, a part with no part target, a hint + /// before the end, extras of the wrong count. + #[test] + fn malformed_renderings_are_refused() { + let k = key(13); + let seal = |segs: Vec, r: HlsRender, e: Vec| ManifestReceipt::seal_with(&k, "o", public_class(0), segs, 1, 0, None, GrantRef::unbounded("video.manifest"), None, r, e); + let mut bad = segments(); + bad[0].uri = "seg0.ts\n#EXT-X-ENDLIST".into(); + assert_eq!(seal(bad, HlsRender::legacy(), vec![]).unwrap_err().code(), "playlist_malformed"); + let parts = SegmentExtra { parts: vec![part(0, 0)], ..Default::default() }; + assert!(seal(segments(), HlsRender::legacy(), vec![parts.clone(), Default::default(), Default::default()]).is_err()); + let hint_early = SegmentExtra { preload_hint: Some("x.wai".into()), ..Default::default() }; + assert!(seal(segments(), HlsRender { end_list: false, ..HlsRender::legacy() }, vec![hint_early, Default::default(), Default::default()]).is_err()); + assert!(seal(segments(), HlsRender::legacy(), vec![Default::default(); 2]).is_err()); + let quoted = SegmentExtra { layers: vec![LayerLine { n: 1, uri: "a\".wai".into() }], ..Default::default() }; + assert!(seal(segments(), HlsRender::legacy(), vec![quoted, Default::default(), Default::default()]).is_err()); + let order = SegmentExtra { layers: vec![LayerLine { n: 2, uri: "a.wai".into() }, LayerLine { n: 1, uri: "b.wai".into() }], ..Default::default() }; + assert!(seal(segments(), HlsRender::legacy(), vec![order, Default::default(), Default::default()]).is_err()); + let past = HlsRender { media_sequence: 1 << 53, ..HlsRender::legacy() }; + assert!(seal(segments(), past, vec![]).is_err(), "no canonical form"); + let hold = HlsRender { part_target_ms: Some(320), part_hold_back_ms: Some(639), ..live_render(0) }; + assert!(seal(vec![], hold, vec![]).is_err()); + } + + /// A receipt file is read strictly: an unknown member, a missing one, an upper-case + /// hash, `render` without `extras`, are each refused. + #[test] + fn receipt_file_is_strict() { + let r = ManifestReceipt::seal_with(&key(14), "o", public_class(0), segments(), 1, 0, None, GrantRef::unbounded("video.manifest"), None, + HlsRender { version: 7, ..HlsRender::legacy() }, vec![SegmentExtra::default(); 3]).unwrap(); + let j: serde_json::Value = serde_json::from_str(&r.to_json()).unwrap(); + assert_eq!(r.receipt_file_name(), format!("{}.json", hex(&r.manifest_hash))); + let edit = |f: &dyn Fn(&mut serde_json::Map)| { + let mut o = j.as_object().unwrap().clone(); + f(&mut o); + ManifestReceipt::from_json(&serde_json::Value::Object(o).to_string()) + }; + assert!(edit(&|_| {}).is_ok()); + assert!(edit(&|o| { o.insert("note".into(), serde_json::json!(1)); }).is_err()); + assert!(edit(&|o| { o.remove("merkle_root"); }).is_err()); + assert!(edit(&|o| { o.insert("sig".into(), serde_json::json!(hex(&r.sig).to_uppercase())); }).is_err()); + assert!(edit(&|o| { o.remove("extras"); }).is_err()); + assert!(edit(&|o| { o["segments"][0]["kind"] = serde_json::json!("Content"); }).is_err()); + } } -- 2.54.0 (Apple Git-157) From 79f30c5bb170baef701264590e8fe1216aa926c5 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 05:42:18 -0400 Subject: [PATCH 02/55] =?UTF-8?q?wai:=20http=5Fbinding::hls=20=E2=80=94=20?= =?UTF-8?q?WAI=20playlists=20beside=20classical=20HLS=20(W3-4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parse_media_playlist reads a classical or WAI media playlist (parts, open segment, preload hint, #WAI-LAYER lines attached to their EXTINF or EXT-X-PART line, in increasing N) with durations read exactly and rounded to the millisecond, half up. check_adjacent and timelines_adjacent hold a WAI playlist to its classical one (adjacency_misaligned). wai_media_playlist renders the WAI media playlist of a classical timeline and the objects covering it (on demand, Live-S, Live-P with parts under alias and cid, layers, the open segment and its hint) through render_hls_with, and reads it back; render_inputs and is_rendered_form give a rendered playlist's inputs back for sealing. wai_multivariant writes wai.m3u8 with the union, the floor and integer bandwidths computed from envelope_len over duration (no NAME/GROUP-ID, three-decimal frame rate). session_data_line and classical_unchanged (classical_changed) keep the classical master to at most the one added line. package_map relates the classical package's segment map by sequence number. LayerTag is now the renderer's LayerLine. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/http_binding/hls.rs | 712 ++++++++++++++++++ wai/wai-rs/src/http_binding/mod.rs | 5 + wai/wai-rs/src/http_binding/playlist.rs | 9 +- .../src/http_binding/tests_playlists.rs | 250 ++++++ 4 files changed, 970 insertions(+), 6 deletions(-) create mode 100644 wai/wai-rs/src/http_binding/hls.rs create mode 100644 wai/wai-rs/src/http_binding/tests_playlists.rs diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs new file mode 100644 index 000000000..ac1a8a813 --- /dev/null +++ b/wai/wai-rs/src/http_binding/hls.rs @@ -0,0 +1,712 @@ +//! WAI playlists beside a classical HLS presentation (http-delivery §11, Mode N): +//! +//! - [`parse_media_playlist`] reads a media playlist, classical or WAI, into its +//! segments, parts, layer lines and timeline; [`check_adjacent`] holds a WAI media +//! playlist to the classical one it sits beside (§11.2, `adjacency_misaligned`); +//! - [`wai_media_playlist`] renders the WAI media playlist of a classical timeline and +//! the objects that cover it, on demand, live (Live-S) or low-latency (Live-P), with +//! `crate::hls_render::render_hls_with`, so the bytes are what a playlist receipt +//! (§8.6) signs; +//! - [`wai_multivariant`] writes `wai.m3u8` (each variant's `CODECS` union, its +//! `X-WAI-FLOOR` floor, and bandwidths computed from the objects, [`bandwidths`]); +//! - [`session_data_line`] is the one line a publisher MAY add to the classical +//! multivariant playlist, and [`classical_unchanged`] holds the published classical +//! playlist to its original with at most that line added (`classical_changed`). +//! +//! Durations are compared and written in milliseconds: a decimal number of seconds is +//! read exactly, rounded to the nearest millisecond with a half rounded up, and never +//! through a float. + +use super::codecs::{self, Element}; +use super::playlist::{parse_layer_tag, LayerTag, LAYER_TAG}; +use super::{hex, RangeRefusal}; +use crate::hls_render::{check_render, render_hls_with, seconds, HlsRender, PartLine, Segment, SegmentExtra, SegmentKind}; + +/// The `DATA-ID` of the session-data line that points a WAI client from the classical +/// multivariant playlist to `wai.m3u8` (§11.1). +pub const SESSION_DATA_ID: &str = "science.transaction.wai.multivariant"; + +/// Why a playlist is refused (http-delivery §13). +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PlaylistError { + /// A playlist that does not read as a media playlist this binding renders or + /// compares, or inputs that would not render as themselves. + Malformed(String), + /// §11.2: the WAI playlist is not adjacent to the classical one. + Misaligned(String), + /// §11.1: the published classical playlist differs from its original by more than + /// the session-data line. + ClassicalChanged(String), + /// §10.4: a `#WAI-LAYER` line that does not have its form. + Layer(RangeRefusal), +} + +impl PlaylistError { + /// `playlist_malformed`, `adjacency_misaligned`, `classical_changed` or + /// `layer_tag_malformed`. + pub fn code(&self) -> &'static str { + match self { + PlaylistError::Malformed(_) => "playlist_malformed", + PlaylistError::Misaligned(_) => "adjacency_misaligned", + PlaylistError::ClassicalChanged(_) => "classical_changed", + PlaylistError::Layer(e) => e.code(), + } + } +} + +impl std::fmt::Display for PlaylistError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PlaylistError::Malformed(s) | PlaylistError::Misaligned(s) | PlaylistError::ClassicalChanged(s) => write!(f, "{}: {s}", self.code()), + PlaylistError::Layer(e) => e.fmt(f), + } + } +} + +impl std::error::Error for PlaylistError {} + +fn malformed(s: impl Into) -> PlaylistError { + PlaylistError::Malformed(s.into()) +} + +fn misaligned(s: impl Into) -> PlaylistError { + PlaylistError::Misaligned(s.into()) +} + +/// A decimal number of seconds (`decimal-floating-point` or `decimal-integer`, RFC 8216 +/// §4.2: digits, optionally a point and digits; no sign, no exponent) in milliseconds, +/// rounded to the nearest with a half rounded up. `None` for anything else, or past +/// `u64`. +pub fn decimal_ms(s: &str) -> Option { + let (int, frac) = match s.split_once('.') { + Some((i, f)) => (i, Some(f)), + None => (s, None), + }; + if int.is_empty() || !int.bytes().all(|b| b.is_ascii_digit()) { + return None; + } + let f = frac.unwrap_or(""); + if frac.is_some() && (f.is_empty() || !f.bytes().all(|b| b.is_ascii_digit())) { + return None; + } + let digit = |i: usize| f.as_bytes().get(i).map_or(0, |b| u64::from(b - b'0')); + let whole: u64 = int.parse().ok()?; + let ms = whole.checked_mul(1000)?.checked_add(digit(0) * 100 + digit(1) * 10 + digit(2))?; + if digit(3) >= 5 { ms.checked_add(1) } else { Some(ms) } +} + +fn decimal_int(s: &str) -> Option { + if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) { + return None; + } + s.parse().ok() +} + +/// An attribute's value: a quoted string (without its quotes) or the text of any other +/// kind of value. +#[derive(Clone, Debug, PartialEq, Eq)] +enum AttrValue { + Quoted(String), + Plain(String), +} + +/// An attribute list (RFC 8216 §4.2): `NAME=VALUE` pairs separated by commas, names of +/// `A`–`Z`, `0`–`9` and `-`, each named once; a quoted value holds no `"`, CR or LF, and +/// an unquoted one no `"` or white space and is not empty. +fn attributes(s: &str, tag: &str) -> Result, PlaylistError> { + let bad = |why: &str| malformed(format!("{tag}: {why}")); + let mut out: Vec<(String, AttrValue)> = Vec::new(); + let mut rest = s; + loop { + let (name, after) = rest.split_once('=').ok_or_else(|| bad("an attribute without ="))?; + if name.is_empty() || !name.bytes().all(|b| b.is_ascii_uppercase() || b.is_ascii_digit() || b == b'-') { + return Err(bad("an attribute name")); + } + if out.iter().any(|(n, _)| n == name) { + return Err(bad("an attribute named twice")); + } + let (value, after) = if let Some(q) = after.strip_prefix('"') { + let close = q.find('"').ok_or_else(|| bad("an unterminated quoted string"))?; + let v = &q[..close]; + if v.contains(['\r', '\n']) { + return Err(bad("CR or LF in a quoted string")); + } + (AttrValue::Quoted(v.to_string()), &q[close + 1..]) + } else { + let end = after.find(',').unwrap_or(after.len()); + let v = &after[..end]; + if v.is_empty() || v.bytes().any(|b| b == b'"' || b.is_ascii_whitespace()) { + return Err(bad("an unquoted value")); + } + (AttrValue::Plain(v.to_string()), &after[end..]) + }; + out.push((name.to_string(), value)); + if after.is_empty() { + return Ok(out); + } + rest = after.strip_prefix(',').ok_or_else(|| bad("text after a value"))?; + } +} + +fn attr<'a>(a: &'a [(String, AttrValue)], name: &str) -> Option<&'a AttrValue> { + a.iter().find(|(n, _)| n == name).map(|(_, v)| v) +} + +fn attr_ms(a: &[(String, AttrValue)], name: &str, tag: &str) -> Result, PlaylistError> { + match attr(a, name) { + None => Ok(None), + Some(AttrValue::Plain(v)) => decimal_ms(v).map(Some).ok_or_else(|| malformed(format!("{tag}: {name} is not a decimal number"))), + Some(AttrValue::Quoted(_)) => Err(malformed(format!("{tag}: {name} is quoted"))), + } +} + +fn attr_quoted(a: &[(String, AttrValue)], name: &str, tag: &str) -> Result, PlaylistError> { + match attr(a, name) { + None => Ok(None), + Some(AttrValue::Quoted(v)) => Ok(Some(v.clone())), + Some(AttrValue::Plain(_)) => Err(malformed(format!("{tag}: {name} is not a quoted string"))), + } +} + +fn attr_yes(a: &[(String, AttrValue)], name: &str, tag: &str) -> Result { + match attr(a, name) { + None => Ok(false), + Some(AttrValue::Plain(v)) if v == "YES" => Ok(true), + Some(AttrValue::Plain(v)) if v == "NO" => Ok(false), + _ => Err(malformed(format!("{tag}: {name} is neither YES nor NO"))), + } +} + +/// One segment of a media playlist: its `EXTINF` duration and URI, and in `extra` the +/// lines before and after its `EXTINF` (discontinuity, date-time, parts, layer lines). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ListedSegment { + pub duration_ms: u64, + pub uri: String, + pub extra: SegmentExtra, +} + +/// A media playlist as [`parse_media_playlist`] reads it. +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct MediaPlaylist { + pub version: Option, + pub target_duration_s: Option, + pub media_sequence: u64, + pub discontinuity_sequence: u64, + pub part_target_ms: Option, + pub part_hold_back_ms: Option, + pub can_block_reload: bool, + pub segments: Vec, + /// The open segment at a live edge: parts listed after the last segment's URI, + /// with the discontinuity and date-time before them. + pub open: Option, + /// The `EXT-X-PRELOAD-HINT` of `TYPE=PART`. + pub preload_hint: Option, + pub end_list: bool, +} + +/// Where a `#WAI-LAYER` line attaches: the `EXTINF` line just before it (its segment) +/// or the `EXT-X-PART` line just before it (that part), with only other layer lines +/// and blank lines between. +#[derive(Clone, Copy, PartialEq, Eq)] +enum Anchor { + None, + Extinf, + Part, +} + +fn push_layer(layers: &mut Vec, l: LayerTag) -> Result<(), PlaylistError> { + if layers.last().is_some_and(|p| p.n >= l.n) { + return Err(malformed("layer lines are not in increasing N")); + } + layers.push(l); + Ok(()) +} + +/// Read a media playlist (RFC 8216 §4, with the second edition's partial segments), +/// classical or WAI, as far as §11 needs it: +/// +/// - the first line is `#EXTM3U`; lines end in LF or CR LF; blank lines are skipped; +/// - `EXT-X-VERSION`, `EXT-X-TARGETDURATION`, `EXT-X-MEDIA-SEQUENCE` and +/// `EXT-X-DISCONTINUITY-SEQUENCE` (the last two before any segment), `EXT-X-PART-INF`, +/// `EXT-X-SERVER-CONTROL`, `EXT-X-DISCONTINUITY`, `EXT-X-PROGRAM-DATE-TIME`, +/// `EXT-X-PART`, `EXTINF`, `EXT-X-PRELOAD-HINT` (`TYPE=PART`, once) and +/// `EXT-X-ENDLIST` are read; a delta update (`EXT-X-SKIP`) and a multivariant +/// playlist's variant tags are refused; any other tag and any comment is skipped; +/// - a `#WAI-LAYER` line (§10.4) attaches to the `EXTINF` or `EXT-X-PART` line just +/// before it, with only other layer lines between, in increasing `N`; +/// - a URI line ends the segment its `EXTINF` began; an `EXTINF` with no URI after it +/// is refused, and so is a URI with no `EXTINF`; parts after the last URI are the +/// open segment's. +pub fn parse_media_playlist(text: &str) -> Result { + let mut p = MediaPlaylist::default(); + let mut lines = text.split('\n').map(|l| l.strip_suffix('\r').unwrap_or(l)).filter(|l| !l.is_empty()); + if lines.next() != Some("#EXTM3U") { + return Err(malformed("the first line is not #EXTM3U")); + } + let mut pending = SegmentExtra::default(); + let mut extinf: Option = None; + let mut anchor = Anchor::None; + let mut started = false; + let int = |v: &str, tag: &str| decimal_int(v).ok_or_else(|| malformed(format!("{tag} is not a decimal integer"))); + for line in lines { + if line.starts_with(LAYER_TAG) { + let l = parse_layer_tag(line).map_err(PlaylistError::Layer)?; + match anchor { + Anchor::Extinf => push_layer(&mut pending.layers, l)?, + Anchor::Part => push_layer(&mut pending.parts.last_mut().expect("a part").layers, l)?, + Anchor::None => return Err(malformed("a #WAI-LAYER line not after an EXTINF or EXT-X-PART line")), + } + continue; + } + anchor = Anchor::None; + if !line.starts_with('#') { + let Some(d) = extinf.take() else { return Err(malformed(format!("the URI {line:?} has no EXTINF"))) }; + p.segments.push(ListedSegment { duration_ms: d, uri: line.to_string(), extra: std::mem::take(&mut pending) }); + started = true; + continue; + } + if !line.starts_with("#EXT") { + continue; + } + let (tag, value) = line.split_once(':').unwrap_or((line, "")); + match tag { + "#EXTM3U" => return Err(malformed("#EXTM3U twice")), + "#EXT-X-VERSION" => p.version = Some(u8::try_from(int(value, tag)?).map_err(|_| malformed("EXT-X-VERSION past 255"))?), + "#EXT-X-TARGETDURATION" => p.target_duration_s = Some(int(value, tag)?), + "#EXT-X-MEDIA-SEQUENCE" | "#EXT-X-DISCONTINUITY-SEQUENCE" => { + if started || extinf.is_some() || !pending.parts.is_empty() { + return Err(malformed(format!("{tag} after a segment"))); + } + let v = int(value, tag)?; + if tag == "#EXT-X-MEDIA-SEQUENCE" { p.media_sequence = v } else { p.discontinuity_sequence = v } + } + "#EXT-X-PART-INF" => { + let a = attributes(value, tag)?; + p.part_target_ms = Some(attr_ms(&a, "PART-TARGET", tag)?.ok_or_else(|| malformed("EXT-X-PART-INF without PART-TARGET"))?); + } + "#EXT-X-SERVER-CONTROL" => { + let a = attributes(value, tag)?; + p.can_block_reload = attr_yes(&a, "CAN-BLOCK-RELOAD", tag)?; + p.part_hold_back_ms = attr_ms(&a, "PART-HOLD-BACK", tag)?; + } + "#EXT-X-DISCONTINUITY" => pending.discontinuity = true, + "#EXT-X-PROGRAM-DATE-TIME" => pending.program_date_time = Some(value.to_string()), + "#EXT-X-PART" => { + let a = attributes(value, tag)?; + let duration_ms = attr_ms(&a, "DURATION", tag)?.ok_or_else(|| malformed("EXT-X-PART without DURATION"))?; + let uri = attr_quoted(&a, "URI", tag)?.ok_or_else(|| malformed("EXT-X-PART without URI"))?; + let independent = attr_yes(&a, "INDEPENDENT", tag)?; + pending.parts.push(PartLine { duration_ms, independent, uri, layers: Vec::new() }); + anchor = Anchor::Part; + } + "#EXTINF" => { + if extinf.is_some() { + return Err(malformed("EXTINF twice before a URI")); + } + let d = value.split_once(',').map_or(value, |(d, _)| d); + extinf = Some(decimal_ms(d).ok_or_else(|| malformed("EXTINF is not a decimal number"))?); + anchor = Anchor::Extinf; + } + "#EXT-X-PRELOAD-HINT" => { + let a = attributes(value, tag)?; + match attr(&a, "TYPE") { + Some(AttrValue::Plain(t)) if t == "PART" => { + if p.preload_hint.is_some() { + return Err(malformed("two preload hints of TYPE=PART")); + } + p.preload_hint = Some(attr_quoted(&a, "URI", tag)?.ok_or_else(|| malformed("EXT-X-PRELOAD-HINT without URI"))?); + } + Some(AttrValue::Plain(_)) => {} + _ => return Err(malformed("EXT-X-PRELOAD-HINT without TYPE")), + } + } + "#EXT-X-ENDLIST" => p.end_list = true, + "#EXT-X-SKIP" => return Err(malformed("a delta update (EXT-X-SKIP) is not compared")), + "#EXT-X-STREAM-INF" | "#EXT-X-I-FRAME-STREAM-INF" | "#EXT-X-MEDIA" => { + return Err(malformed(format!("{tag}: a multivariant playlist, not a media playlist"))) + } + _ => {} + } + } + if extinf.is_some() { + return Err(malformed("an EXTINF with no URI after it")); + } + if pending != SegmentExtra::default() { + p.open = Some(pending); + } + Ok(p) +} + +impl MediaPlaylist { + /// The inputs `crate::hls_render::render_hls_with` takes to render this playlist: + /// its segments (content, no ad decision), its parameters and its extras (one per + /// segment, and the open segment's, which carries the preload hint when there is + /// one). For a playlist this binding rendered, rendering them gives its bytes again. + pub fn render_inputs(&self) -> (Vec, HlsRender, Vec) { + let segments: Vec = self.segments.iter().enumerate() + .map(|(i, s)| Segment { index: i as u64, kind: SegmentKind::Content, uri: s.uri.clone(), duration_ms: s.duration_ms, ad_id: None }) + .collect(); + let r = HlsRender { + version: self.version.unwrap_or(1), + media_sequence: self.media_sequence, + discontinuity_sequence: self.discontinuity_sequence, + target_duration_s: self.target_duration_s, + part_target_ms: self.part_target_ms, + part_hold_back_ms: self.part_hold_back_ms, + can_block_reload: self.can_block_reload, + end_list: self.end_list, + }; + let mut extras: Vec = self.segments.iter().map(|s| s.extra.clone()).collect(); + if let Some(o) = &self.open { + extras.push(o.clone()); + } + if let Some(h) = &self.preload_hint { + if extras.is_empty() { + extras.push(SegmentExtra::default()); + } + extras.last_mut().expect("an extra").preload_hint = Some(h.clone()); + } + if extras.iter().all(|e| *e == SegmentExtra::default()) { + extras.clear(); + } + (segments, r, extras) + } + + /// Whether this playlist is in the form this binding renders: rendering + /// [`Self::render_inputs`] gives `text` again, byte for byte. + pub fn is_rendered_form(&self, text: &str) -> bool { + let (s, r, e) = self.render_inputs(); + check_render(&s, &r, &e).is_ok() && render_hls_with(&s, &r, &e) == text + } +} + +fn parts_ms(parts: &[PartLine]) -> Vec { + parts.iter().map(|p| p.duration_ms).collect() +} + +/// §11.2 over two parsed playlists: the same media sequence and discontinuity sequence, +/// the same number of segments, and segment for segment the same duration to the +/// millisecond, discontinuity and program date-time; where both carry parts, the same +/// part target, and the same part durations in each segment both list parts for, the +/// open segment included when both have one. Layer lines are not compared. +pub fn timelines_adjacent(wai: &MediaPlaylist, classical: &MediaPlaylist) -> Result<(), PlaylistError> { + if wai.media_sequence != classical.media_sequence { + return Err(misaligned(format!("EXT-X-MEDIA-SEQUENCE {} beside {}", wai.media_sequence, classical.media_sequence))); + } + if wai.discontinuity_sequence != classical.discontinuity_sequence { + return Err(misaligned(format!("EXT-X-DISCONTINUITY-SEQUENCE {} beside {}", wai.discontinuity_sequence, classical.discontinuity_sequence))); + } + if wai.segments.len() != classical.segments.len() { + return Err(misaligned(format!("{} segments beside {}", wai.segments.len(), classical.segments.len()))); + } + let both_parts = wai.part_target_ms.is_some() && classical.part_target_ms.is_some(); + if both_parts && wai.part_target_ms != classical.part_target_ms { + return Err(misaligned("another PART-TARGET")); + } + for (i, (w, c)) in wai.segments.iter().zip(&classical.segments).enumerate() { + let seq = wai.media_sequence.saturating_add(i as u64); + if w.duration_ms != c.duration_ms { + return Err(misaligned(format!("segment {seq}: {} ms beside {} ms", w.duration_ms, c.duration_ms))); + } + if w.extra.discontinuity != c.extra.discontinuity { + return Err(misaligned(format!("segment {seq}: a discontinuity on one side only"))); + } + if w.extra.program_date_time != c.extra.program_date_time { + return Err(misaligned(format!("segment {seq}: another program date-time"))); + } + if both_parts && !w.extra.parts.is_empty() && !c.extra.parts.is_empty() && parts_ms(&w.extra.parts) != parts_ms(&c.extra.parts) { + return Err(misaligned(format!("segment {seq}: other part durations"))); + } + } + if let (Some(w), Some(c)) = (&wai.open, &classical.open) { + let seq = wai.media_sequence.saturating_add(wai.segments.len() as u64); + if w.discontinuity != c.discontinuity || w.program_date_time != c.program_date_time { + return Err(misaligned(format!("segment {seq} (open): another discontinuity or program date-time"))); + } + if both_parts && parts_ms(&w.parts) != parts_ms(&c.parts) { + return Err(misaligned(format!("segment {seq} (open): other part durations"))); + } + } + Ok(()) +} + +/// §11.2: whether the WAI media playlist `wai` is adjacent to the classical media +/// playlist `classical` ([`timelines_adjacent`]). A playlist that does not parse is +/// `playlist_malformed` (or `layer_tag_malformed`). +pub fn check_adjacent(wai: &str, classical: &str) -> Result<(), PlaylistError> { + let w = parse_media_playlist(wai).map_err(|e| prefix_err(e, "the WAI playlist"))?; + let c = parse_media_playlist(classical).map_err(|e| prefix_err(e, "the classical playlist"))?; + timelines_adjacent(&w, &c) +} + +fn prefix_err(e: PlaylistError, what: &str) -> PlaylistError { + match e { + PlaylistError::Malformed(s) => PlaylistError::Malformed(format!("{what}: {s}")), + other => other, + } +} + +/// A layer object listed after a segment or part (§10.4): its depth `n`, its cid and its +/// `envelope_len`, written `#n=` (a layer is fetched whole). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WaiLayer { + pub n: u8, + pub cid: [u8; 32], + pub len: u64, +} + +/// A part's object (§11.4), served under `alias` and listed with its cid: +/// `#cid=&n=`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WaiPart { + pub alias: String, + pub cid: [u8; 32], + pub n: u64, + pub duration_ms: u64, + pub independent: bool, + pub layers: Vec, +} + +/// A segment's object, listed as `.wai#n=[&h=]` (§11.3), with its +/// layers and, in Live-P, its parts. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WaiSegment { + pub cid: [u8; 32], + pub n: u64, + pub h: Option, + pub duration_ms: u64, + pub layers: Vec, + pub parts: Vec, +} + +/// The open segment at a live edge: its parts so far and the alias of the part the +/// origin will hold a request for (the preload hint). +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct WaiOpen { + pub parts: Vec, + pub preload_hint: Option, +} + +fn layer_lines(layers: &[WaiLayer], prefix: &str) -> Vec { + layers.iter().map(|l| LayerTag { n: l.n, uri: format!("{prefix}{}.wai#n={}", hex(&l.cid), l.len) }).collect() +} + +fn part_lines(parts: &[WaiPart], prefix: &str) -> Vec { + parts.iter().map(|p| PartLine { + duration_ms: p.duration_ms, + independent: p.independent, + uri: format!("{}#cid={}&n={}", p.alias, hex(&p.cid), p.n), + layers: layer_lines(&p.layers, prefix), + }).collect() +} + +/// The WAI media playlist beside the classical media playlist `classical` (§11.1 to +/// §11.4): segment for segment the classical timeline (media and discontinuity +/// sequences, durations, discontinuities, program date-times, part target), each +/// segment listing its object, its layers and its parts, and the open segment's parts +/// and preload hint at a live edge. The target duration, part target, part hold-back +/// (three part targets when the classical states none), blocking reload and version +/// are the classical playlist's (version 6 when it states none). `uri_prefix` is put +/// before every `.wai`. Returns the renderer's inputs (what a playlist receipt +/// seals) and the text. +/// +/// Refused when the objects do not cover the classical timeline: another number of +/// segments, another duration, parts beside a classical playlist without a part target, +/// other part durations where both list parts, or an open segment the classical playlist +/// does not have (`adjacency_misaligned`); or when the result would not read back as its +/// inputs (`playlist_malformed`). +pub fn wai_media_playlist( + classical: &MediaPlaylist, + segs: &[WaiSegment], + open: Option<&WaiOpen>, + uri_prefix: &str, + end_list: bool, +) -> Result<(Vec, HlsRender, Vec, String), PlaylistError> { + if segs.len() != classical.segments.len() { + return Err(misaligned(format!("{} objects for {} segments", segs.len(), classical.segments.len()))); + } + let has_parts = segs.iter().any(|s| !s.parts.is_empty()) || open.is_some_and(|o| !o.parts.is_empty()); + if has_parts && classical.part_target_ms.is_none() { + return Err(misaligned("parts beside a classical playlist with no part target")); + } + let mut segments = Vec::with_capacity(segs.len()); + let mut extras = Vec::with_capacity(segs.len() + 1); + for (i, (w, c)) in segs.iter().zip(&classical.segments).enumerate() { + if w.duration_ms != c.duration_ms { + return Err(misaligned(format!("segment {i}: an object of {} ms for {} ms", w.duration_ms, c.duration_ms))); + } + if !w.parts.is_empty() && !c.extra.parts.is_empty() && w.parts.iter().map(|p| p.duration_ms).collect::>() != parts_ms(&c.extra.parts) { + return Err(misaligned(format!("segment {i}: other part durations"))); + } + let h = w.h.map(|h| format!("&h={h}")).unwrap_or_default(); + segments.push(Segment { index: i as u64, kind: SegmentKind::Content, uri: format!("{uri_prefix}{}.wai#n={}{h}", hex(&w.cid), w.n), duration_ms: w.duration_ms, ad_id: None }); + extras.push(SegmentExtra { + discontinuity: c.extra.discontinuity, + program_date_time: c.extra.program_date_time.clone(), + parts: part_lines(&w.parts, uri_prefix), + layers: layer_lines(&w.layers, uri_prefix), + preload_hint: None, + }); + } + if let Some(o) = open { + let c = classical.open.as_ref().ok_or_else(|| misaligned("an open segment the classical playlist does not have"))?; + if !o.parts.is_empty() && !c.parts.is_empty() && o.parts.iter().map(|p| p.duration_ms).collect::>() != parts_ms(&c.parts) { + return Err(misaligned("the open segment: other part durations")); + } + extras.push(SegmentExtra { + discontinuity: c.discontinuity, + program_date_time: c.program_date_time.clone(), + parts: part_lines(&o.parts, uri_prefix), + layers: Vec::new(), + preload_hint: o.preload_hint.clone(), + }); + } + if extras.iter().all(|e| *e == SegmentExtra::default()) { + extras.clear(); + } + let r = HlsRender { + version: classical.version.unwrap_or(6), + media_sequence: classical.media_sequence, + discontinuity_sequence: classical.discontinuity_sequence, + target_duration_s: classical.target_duration_s, + part_target_ms: classical.part_target_ms, + part_hold_back_ms: classical.part_hold_back_ms.or(classical.part_target_ms.map(|t| t.saturating_mul(3))), + can_block_reload: classical.can_block_reload, + end_list, + }; + check_render(&segments, &r, &extras).map_err(|e| malformed(e.0))?; + let text = render_hls_with(&segments, &r, &extras); + // What was rendered reads back, and is adjacent to what it was rendered beside. + let back = parse_media_playlist(&text)?; + timelines_adjacent(&back, classical)?; + Ok((segments, r, extras, text)) +} + +/// The peak and average bit rates of a sequence of objects (§11.1), in bits per second, +/// from each object's `envelope_len` `n` and duration `d` in milliseconds: the peak is +/// the largest `⌈8000·n / d⌉` of any one object, which is never below RFC 8216's peak +/// over any run of segments; the average is `⌈8000·Σn / Σd⌉`. `None` for no objects or +/// a duration of 0. Layer objects are not included: these are the rates of the objects +/// a client must fetch. +pub fn bandwidths(objects: &[(u64, u64)]) -> Option<(u64, u64)> { + if objects.is_empty() || objects.iter().any(|&(_, d)| d == 0) { + return None; + } + let rate = |n: u128, d: u128| u64::try_from((8000 * n).div_ceil(d)).unwrap_or(u64::MAX); + let peak = objects.iter().map(|&(n, d)| rate(u128::from(n), u128::from(d))).max()?; + let (sn, sd) = objects.iter().fold((0u128, 0u128), |(a, b), &(n, d)| (a + u128::from(n), b + u128::from(d))); + Some((peak, rate(sn, sd))) +} + +/// One variant of `wai.m3u8` (§11.1). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VariantLine { + pub uri: String, + pub union: Element, + pub floor: Element, + pub peak_bps: u64, + pub avg_bps: u64, + pub resolution: Option<(u32, u32)>, + pub frame_rate_milli: Option, +} + +/// `wai.m3u8` (§11.1): `#EXTM3U`, then per variant an `EXT-X-STREAM-INF` with +/// `BANDWIDTH` and `AVERAGE-BANDWIDTH` (computed by [`bandwidths`], never stated by +/// hand), `CODECS` (the union), `X-WAI-FLOOR` (the floor), and `RESOLUTION` and +/// `FRAME-RATE` (three fractional digits) when given, followed by its URI. Nothing in +/// it needs a version. Refused (`playlist_malformed`) for no variant, a URI that is +/// empty, begins with `#` or holds CR or LF, a floor whose container is not the +/// union's, or a frame rate of 0. +pub fn wai_multivariant(variants: &[VariantLine]) -> Result { + if variants.is_empty() { + return Err(malformed("no variant")); + } + let mut out = String::from("#EXTM3U\n"); + for v in variants { + if v.uri.is_empty() || v.uri.starts_with('#') || v.uri.contains(['\r', '\n']) { + return Err(malformed("a variant URI is empty, begins with # or holds CR or LF")); + } + if v.floor.prefix != v.union.prefix { + return Err(malformed("the floor is written with the union's prefix")); + } + out.push_str(&format!( + "#EXT-X-STREAM-INF:BANDWIDTH={},AVERAGE-BANDWIDTH={},CODECS=\"{}\",X-WAI-FLOOR=\"{}\"", + v.peak_bps, v.avg_bps, codecs::render(&v.union), codecs::render(&v.floor) + )); + if let Some((w, h)) = v.resolution { + out.push_str(&format!(",RESOLUTION={w}x{h}")); + } + if let Some(f) = v.frame_rate_milli { + if f == 0 { + return Err(malformed("a frame rate of 0")); + } + out.push_str(&format!(",FRAME-RATE={}", seconds(u64::from(f)))); + } + out.push('\n'); + out.push_str(&v.uri); + out.push('\n'); + } + Ok(out) +} + +/// The line a publisher MAY add to the classical multivariant playlist (§11.1): +/// `#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE=""`. +/// Refused for a URI that is empty or holds a quote, CR or LF. +pub fn session_data_line(wai_uri: &str) -> Result { + if wai_uri.is_empty() || wai_uri.contains(['"', '\r', '\n']) { + return Err(malformed("the wai.m3u8 URI is empty or holds a quote, CR or LF")); + } + Ok(format!("#EXT-X-SESSION-DATA:DATA-ID=\"{SESSION_DATA_ID}\",VALUE=\"{wai_uri}\"")) +} + +/// Whether `line` is a session-data line of [`session_data_line`]'s form. +fn is_session_data_line(line: &str) -> bool { + line.strip_prefix(&format!("#EXT-X-SESSION-DATA:DATA-ID=\"{SESSION_DATA_ID}\",VALUE=\"")) + .and_then(|r| r.strip_suffix('"')) + .is_some_and(|u| session_data_line(u).is_ok()) +} + +/// §11.1: the published classical playlist is its original, byte for byte, or the +/// original with one line added after its first: a session-data line of +/// [`session_data_line`]'s form (its line end the line's own, LF or CR LF). Anything +/// else is `classical_changed`. +pub fn classical_unchanged(original: &str, published: &str) -> Result<(), PlaylistError> { + if original == published { + return Ok(()); + } + let lines: Vec<&str> = published.split_inclusive('\n').collect(); + for (i, l) in lines.iter().enumerate().skip(1) { + let bare = l.strip_suffix('\n').map(|x| x.strip_suffix('\r').unwrap_or(x)).unwrap_or(l); + if !l.ends_with('\n') || !is_session_data_line(bare) { + continue; + } + let without: String = lines.iter().enumerate().filter(|(j, _)| *j != i).map(|(_, x)| *x).collect(); + if without == original { + return Ok(()); + } + } + Err(PlaylistError::ClassicalChanged("the published playlist is not the original with at most the session-data line added".into())) +} + +/// The classical package's segment map beside a WAI media playlist (§11.6): for each +/// segment, its media sequence number, the package's content hash of its media and the +/// cid of the WAI object that carries it, matched by position. They are hashes of +/// different bytes, and neither replaces the other. Refused when the package's entries +/// are not numbered from 0 in order, or their durations are not the segments'. +#[cfg(feature = "video_package")] +pub fn package_map( + entries: &[crate::wai_video_package::SegmentEntry], + segs: &[WaiSegment], + media_sequence: u64, +) -> Result, PlaylistError> { + if entries.len() != segs.len() { + return Err(misaligned(format!("{} package entries for {} segments", entries.len(), segs.len()))); + } + entries.iter().zip(segs).enumerate().map(|(i, (e, s))| { + if e.index != i as u64 || e.duration_ms != s.duration_ms { + return Err(misaligned(format!("package entry {i}: index {} and {} ms beside {} ms", e.index, e.duration_ms, s.duration_ms))); + } + Ok((media_sequence.saturating_add(i as u64), e.content_hash, s.cid)) + }).collect() +} diff --git a/wai/wai-rs/src/http_binding/mod.rs b/wai/wai-rs/src/http_binding/mod.rs index 209d06399..de165018b 100644 --- a/wai/wai-rs/src/http_binding/mod.rs +++ b/wai/wai-rs/src/http_binding/mod.rs @@ -10,6 +10,8 @@ //! whole-object fallback; //! - [`codecs`]: the `codecs` grammar, the union and the floor (§4); //! - [`playlist`]: the `#WAI-LAYER` tag (§10.4); +//! - [`hls`]: WAI media and multivariant playlists beside a classical HLS +//! presentation, their adjacency and the session-data line (§11); //! - [`origin`]: an origin's responses (§5.3) and byte-range answers. //! //! Nothing here performs I/O: the `wai http` commands and the reference origin do. @@ -17,6 +19,7 @@ pub mod codecs; pub mod head; pub mod head_proof; +pub mod hls; pub mod origin; pub mod plan; pub mod playlist; @@ -24,6 +27,8 @@ pub mod session; #[cfg(test)] mod tests; +#[cfg(test)] +mod tests_playlists; /// The proposed media type of an object (§3). It is not registered. pub const MEDIA_TYPE: &str = "application/wai"; diff --git a/wai/wai-rs/src/http_binding/playlist.rs b/wai/wai-rs/src/http_binding/playlist.rs index 4ffa9defb..6c060f29d 100644 --- a/wai/wai-rs/src/http_binding/playlist.rs +++ b/wai/wai-rs/src/http_binding/playlist.rs @@ -7,12 +7,9 @@ use super::RangeRefusal; pub const LAYER_TAG: &str = "#WAI-LAYER:"; /// A `#WAI-LAYER` line: the layer a client may fetch to depth `n`, and its URI. `n` is a -/// locator only; the envelope's `refines.layer` decides (§10.4). -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct LayerTag { - pub n: u8, - pub uri: String, -} +/// locator only; the envelope's `refines.layer` decides (§10.4). The one type +/// [`crate::hls_render`] renders the line from. +pub type LayerTag = crate::hls_render::LayerLine; /// Read a `#WAI-LAYER` line (§10.4). Its attributes are an HLS attribute list (RFC 8216 /// §4.2): names of `A`–`Z`, `0`–`9` and `-`; values a decimal integer, a quoted string diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs new file mode 100644 index 000000000..17c4318aa --- /dev/null +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -0,0 +1,250 @@ +//! Unit tests of the playlist side of the HTTP binding (http-delivery §10.4, §11): the +//! media playlist reader, adjacency, the WAI media and multivariant playlists, the +//! session-data line and the classical playlist left unchanged. + +use super::codecs::parse_element; +use super::hls::*; +use crate::hls_render::{render_hls_with, HlsRender, LayerLine, PartLine, SegmentExtra}; + +/// A classical low-latency playlist: segment 1200 complete with four parts, then +/// segment 1201 open with two parts and a hint. +const CLASSICAL_LL: &str = "#EXTM3U\n#EXT-X-VERSION:9\n#EXT-X-TARGETDURATION:2\n#EXT-X-PART-INF:PART-TARGET=0.5\n#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.5\n#EXT-X-MEDIA-SEQUENCE:1200\n#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z\n#EXT-X-PART:DURATION=0.50000,INDEPENDENT=YES,URI=\"c/1200.0.m4s\"\n#EXT-X-PART:DURATION=0.5,URI=\"c/1200.1.m4s\"\n#EXT-X-PART:DURATION=0.5,INDEPENDENT=YES,URI=\"c/1200.2.m4s\"\n#EXT-X-PART:DURATION=0.5,URI=\"c/1200.3.m4s\"\n#EXTINF:2.00002,\nc/1200.m4s\n#EXT-X-PART:DURATION=0.5,INDEPENDENT=YES,URI=\"c/1201.0.m4s\"\n#EXT-X-PART:DURATION=0.5,URI=\"c/1201.1.m4s\"\n#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"c/1201.2.m4s\"\n"; + +/// A classical on-demand playlist with a discontinuity. +const CLASSICAL_VOD: &str = "#EXTM3U\r\n#EXT-X-VERSION:7\r\n#EXT-X-TARGETDURATION:1\r\n#EXT-X-MEDIA-SEQUENCE:0\r\n#EXT-X-MAP:URI=\"init.mp4\"\r\n#EXTINF:0.640,\r\nseg0.m4s\r\n#EXTINF:0.64,title\r\nseg1.m4s\r\n#EXT-X-DISCONTINUITY\r\n#EXTINF:0.6400,\r\nseg2.m4s\r\n#EXT-X-ENDLIST\r\n"; + +fn cid(b: u8) -> [u8; 32] { + [b; 32] +} + +fn seg(b: u8, ms: u64) -> WaiSegment { + WaiSegment { cid: cid(b), n: 2000 + u64::from(b), h: Some(1009), duration_ms: ms, layers: Vec::new(), parts: Vec::new() } +} + +fn wpart(seq: u64, i: u64, ms: u64) -> WaiPart { + WaiPart { alias: format!("live/{seq}.{i}.wai"), cid: cid(0x80 + i as u8), n: 400 + i, duration_ms: ms, independent: i % 2 == 0, layers: Vec::new() } +} + +#[test] +fn decimal_seconds_to_milliseconds() { + for (s, ms) in [("4", Some(4000)), ("4.00008", Some(4000)), ("3.9995", Some(4000)), ("3.99949", Some(3999)), ("0.333", Some(333)), + ("0.3335", Some(334)), ("0.5", Some(500)), ("12.000", Some(12_000)), ("18446744073709551.615", Some(u64::MAX)), + ("18446744073709551.6155", None), ("18446744073709552", None)] { + assert_eq!(decimal_ms(s), ms, "{s}"); + } + for s in ["", "-1", "+1", "1e3", " 4", "4.", ".5", "4,0", "4", "0x10"] { + assert_eq!(decimal_ms(s), None, "{s:?}"); + } +} + +/// The reader takes a classical playlist, CR LF and all, into its timeline: rounded +/// durations, the discontinuity, the parts, the open segment and the hint. +#[test] +fn media_playlist_reader() { + let v = parse_media_playlist(CLASSICAL_VOD).unwrap(); + assert_eq!(v.segments.iter().map(|s| s.duration_ms).collect::>(), [640, 640, 640]); + assert_eq!(v.segments.iter().map(|s| s.extra.discontinuity).collect::>(), [false, false, true]); + assert!(v.end_list && v.open.is_none() && v.version == Some(7)); + let l = parse_media_playlist(CLASSICAL_LL).unwrap(); + assert_eq!((l.media_sequence, l.part_target_ms, l.part_hold_back_ms, l.can_block_reload), (1200, Some(500), Some(1500), true)); + assert_eq!(l.segments.len(), 1); + assert_eq!(l.segments[0].duration_ms, 2000); + assert_eq!(l.segments[0].extra.parts.len(), 4); + assert_eq!(l.segments[0].extra.program_date_time.as_deref(), Some("2026-10-01T12:00:00.000Z")); + assert_eq!(l.open.as_ref().unwrap().parts.len(), 2); + assert_eq!(l.preload_hint.as_deref(), Some("c/1201.2.m4s")); + for (bad, code) in [ + ("#EXT-X-VERSION:3\n#EXTINF:1,\na\n", "playlist_malformed"), + ("#EXTM3U\n#EXTINF:1,\n", "playlist_malformed"), + ("#EXTM3U\na.wai\n", "playlist_malformed"), + ("#EXTM3U\n#EXTINF:1,\n#EXTINF:1,\na\n", "playlist_malformed"), + ("#EXTM3U\n#EXT-X-SKIP:SKIPPED-SEGMENTS=3\n", "playlist_malformed"), + ("#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1\na.m3u8\n", "playlist_malformed"), + ("#EXTM3U\n#EXTINF:1,\na\n#EXT-X-MEDIA-SEQUENCE:3\n", "playlist_malformed"), + ("#EXTM3U\n#EXTINF:1,\na\n#WAI-LAYER:N=1,URI=\"l.wai\"\n", "playlist_malformed"), + ("#EXTM3U\n#EXTINF:1,\n#WAI-LAYER:N=2,URI=\"l.wai\"\n#WAI-LAYER:N=1,URI=\"m.wai\"\na\n", "playlist_malformed"), + ("#EXTM3U\n#EXTINF:1,\n#WAI-LAYER:N=0,URI=\"l.wai\"\na\n", "layer_tag_malformed"), + ("#EXTM3U\n#EXT-X-PART:DURATION=0.5\n", "playlist_malformed"), + ("#EXTM3U\n#EXT-X-PART:DURATION=0.5,URI=\"a\",URI=\"b\"\n", "playlist_malformed"), + ("#EXTM3U\n#EXT-X-PART-INF:PART-TARGET=\"0.5\"\n", "playlist_malformed"), + ("#EXTM3U\n#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=MAYBE\n", "playlist_malformed"), + ("#EXTM3U\n#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"a\"\n#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"b\"\n", "playlist_malformed"), + ("#EXTM3U\n#EXTINF:-1,\na\n", "playlist_malformed"), + ] { + assert_eq!(parse_media_playlist(bad).map(|_| ()).unwrap_err().code(), code, "{bad:?}"); + } + // A layer line after the EXTINF (before the URI) and after a part, with blank lines. + let ok = parse_media_playlist("#EXTM3U\n#EXT-X-PART-INF:PART-TARGET=1\n#EXT-X-PART:DURATION=1,URI=\"p\"\n\n#WAI-LAYER:N=1,URI=\"pl.wai\"\n#EXTINF:1,\n#WAI-LAYER:N=1,URI=\"l1.wai\"\n#WAI-LAYER:N=3,URI=\"l3.wai\"\na\n").unwrap(); + assert_eq!(ok.segments[0].extra.parts[0].layers, vec![LayerLine { n: 1, uri: "pl.wai".into() }]); + assert_eq!(ok.segments[0].extra.layers.iter().map(|l| l.n).collect::>(), [1, 3]); +} + +/// §11.2: aligned pairs pass, segment for segment and part for part; a shifted EXTINF, +/// a missing discontinuity, another media sequence, another program date-time, another +/// part duration or part target, another segment count each fail; layer lines and part +/// lists one side omits do not. +#[test] +fn adjacency() { + let classical = parse_media_playlist(CLASSICAL_VOD).unwrap(); + let segs = vec![seg(1, 640), seg(2, 640), seg(3, 640)]; + let (_, _, _, wai) = wai_media_playlist(&classical, &segs, None, "../objects/", true).unwrap(); + check_adjacent(&wai, CLASSICAL_VOD).unwrap(); + assert!(wai.contains("#EXT-X-DISCONTINUITY\n#EXTINF:0.640,\n../objects/0303")); + let broken = |from: &str, to: &str| { + assert!(wai.contains(from), "{from}"); + check_adjacent(&wai.replacen(from, to, 1), CLASSICAL_VOD).unwrap_err().code() + }; + assert_eq!(broken("#EXTINF:0.640,\n../objects/0101", "#EXTINF:0.641,\n../objects/0101"), "adjacency_misaligned"); + assert_eq!(broken("#EXT-X-DISCONTINUITY\n", ""), "adjacency_misaligned"); + assert_eq!(broken("#EXT-X-MEDIA-SEQUENCE:0\n", "#EXT-X-MEDIA-SEQUENCE:1\n"), "adjacency_misaligned"); + assert_eq!(broken("#EXT-X-MEDIA-SEQUENCE:0\n", "#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-DISCONTINUITY-SEQUENCE:2\n"), "adjacency_misaligned"); + assert_eq!(broken("#EXT-X-ENDLIST\n", "#EXTINF:0.640,\nmore.wai\n#EXT-X-ENDLIST\n"), "adjacency_misaligned"); + // Layer lines do not affect alignment. + let layered = wai.replacen("#EXTINF:0.640,\n../objects/0101", "#EXTINF:0.640,\n#WAI-LAYER:N=1,URI=\"l.wai#n=9\"\n../objects/0101", 1); + check_adjacent(&layered, CLASSICAL_VOD).unwrap(); + + // Low latency: a segment with parts and the open segment's parts. + let ll = parse_media_playlist(CLASSICAL_LL).unwrap(); + let mut s0 = seg(1, 2000); + s0.parts = (0..4).map(|i| wpart(1200, i, 500)).collect(); + s0.parts[1].layers = vec![WaiLayer { n: 1, cid: cid(0xee), len: 700 }]; + let open = WaiOpen { parts: (0..2).map(|i| wpart(1201, i, 500)).collect(), preload_hint: Some("live/1201.2.wai".into()) }; + let (_, r, extras, text) = wai_media_playlist(&ll, &[s0.clone()], Some(&open), "objects/", false).unwrap(); + check_adjacent(&text, CLASSICAL_LL).unwrap(); + assert_eq!((r.part_target_ms, r.part_hold_back_ms, r.target_duration_s), (Some(500), Some(1500), Some(2))); + assert_eq!(extras.len(), 2); + assert!(text.contains(&format!("#EXT-X-PART:DURATION=0.500,URI=\"live/1200.1.wai#cid={}&n=401\"\n#WAI-LAYER:N=1,URI=\"objects/{}.wai#n=700\"\n", super::hex(&cid(0x81)), super::hex(&cid(0xee))))); + assert!(text.ends_with("#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"live/1201.2.wai\"\n")); + assert!(text.contains("#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z\n#EXT-X-PART:DURATION=0.500,INDEPENDENT=YES,URI=\"live/1200.0.wai#cid=")); + let broken = |from: &str, to: &str| { + assert!(text.contains(from), "{from}"); + check_adjacent(&text.replacen(from, to, 1), CLASSICAL_LL).unwrap_err().code() + }; + assert_eq!(broken("#EXT-X-PART:DURATION=0.500,URI=\"live/1200.1", "#EXT-X-PART:DURATION=0.499,URI=\"live/1200.1"), "adjacency_misaligned"); + assert_eq!(broken("#EXT-X-PART:DURATION=0.500,URI=\"live/1201.1", "#EXT-X-PART:DURATION=0.501,URI=\"live/1201.1"), "adjacency_misaligned"); + assert_eq!(broken("PART-TARGET=0.500", "PART-TARGET=0.600"), "adjacency_misaligned"); + assert_eq!(broken("#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z", "#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.001Z"), "adjacency_misaligned"); + // A WAI segment at segment granularity (no parts) beside the classical parts. + let (_, _, _, live_s) = wai_media_playlist(&ll, &[seg(1, 2000)], None, "objects/", false).unwrap(); + check_adjacent(&live_s, CLASSICAL_LL).unwrap(); + // Objects that do not cover the timeline are refused before anything is written. + let mut short = s0.clone(); + short.parts[3].duration_ms = 400; + assert_eq!(wai_media_playlist(&ll, &[short], None, "objects/", false).unwrap_err().code(), "adjacency_misaligned"); + assert_eq!(wai_media_playlist(&ll, &[seg(1, 1999)], None, "objects/", false).unwrap_err().code(), "adjacency_misaligned"); + assert_eq!(wai_media_playlist(&ll, &[], None, "objects/", false).unwrap_err().code(), "adjacency_misaligned"); + assert_eq!(wai_media_playlist(&classical, &[s0.clone(), seg(2, 640), seg(3, 640)], None, "o/", true).unwrap_err().code(), "adjacency_misaligned"); + assert_eq!(wai_media_playlist(&classical, &segs, Some(&open), "o/", false).unwrap_err().code(), "adjacency_misaligned"); + let quoted = WaiOpen { parts: Vec::new(), preload_hint: Some("a\"b".into()) }; + assert_eq!(wai_media_playlist(&ll, &[seg(1, 2000)], Some("ed), "o/", false).unwrap_err().code(), "playlist_malformed"); +} + +/// A WAI playlist this binding rendered reads back as the inputs it was rendered from, +/// so a receipt can be sealed over a playlist file; a hand-edited one is not in the +/// rendered form. +#[test] +fn rendered_playlists_read_back() { + let ll = parse_media_playlist(CLASSICAL_LL).unwrap(); + let mut s0 = seg(1, 2000); + s0.parts = (0..4).map(|i| wpart(1200, i, 500)).collect(); + s0.layers = vec![WaiLayer { n: 1, cid: cid(0xe1), len: 70 }, WaiLayer { n: 2, cid: cid(0xe2), len: 90 }]; + let open = WaiOpen { parts: vec![wpart(1201, 0, 500), wpart(1201, 1, 500)], preload_hint: Some("live/1201.2.wai".into()) }; + let (segments, r, extras, text) = wai_media_playlist(&ll, &[s0], Some(&open), "objects/", false).unwrap(); + let back = parse_media_playlist(&text).unwrap(); + let (s2, r2, e2) = back.render_inputs(); + assert_eq!((s2, r2, e2.clone()), (segments, r, extras)); + assert!(back.is_rendered_form(&text)); + assert!(!parse_media_playlist(&text.replace("\n", "\r\n")).unwrap().is_rendered_form(&text.replace("\n", "\r\n"))); + // On demand with no extras at all. + let vod = parse_media_playlist(CLASSICAL_VOD).unwrap(); + let (_, _, ex, t) = wai_media_playlist(&vod, &[seg(1, 640), seg(2, 640), seg(3, 640)], None, "", true).unwrap(); + assert_eq!(ex.len(), 3, "the discontinuity is an extra"); + assert!(parse_media_playlist(&t).unwrap().is_rendered_form(&t)); + // Just a hint, before any part of a new segment. + let r = HlsRender { version: 9, part_target_ms: Some(500), part_hold_back_ms: Some(1500), end_list: false, ..HlsRender::legacy() }; + let e = vec![SegmentExtra { preload_hint: Some("h.wai".into()), ..Default::default() }]; + let t = render_hls_with(&[], &r, &e); + assert!(parse_media_playlist(&t).unwrap().is_rendered_form(&t)); + let e = vec![SegmentExtra { parts: vec![PartLine { duration_ms: 500, independent: false, uri: "p".into(), layers: vec![] }], ..Default::default() }]; + let t = render_hls_with(&[], &r, &e); + assert!(parse_media_playlist(&t).unwrap().is_rendered_form(&t)); +} + +/// The multivariant writer: no `NAME` or `GROUP-ID` on `EXT-X-STREAM-INF`, bandwidths +/// computed from `envelope_len` and durations in integers, three-decimal frame rate, the +/// union and the floor; media playlists with three-decimal `EXTINF`. +#[test] +fn hls_port_corrections() { + // ⌈8000·n/d⌉: 21034 B over 640 ms is 262,925 b/s; the mean over the three is + // ⌈8000·57419/1920⌉. + let objs = [(21_034u64, 640u64), (18_004, 640), (18_381, 640)]; + let (peak, avg) = bandwidths(&objs).unwrap(); + assert_eq!((peak, avg), (262_925, 239_246)); + assert_eq!(bandwidths(&[(1, 3)]), Some((2667, 2667))); + assert_eq!(bandwidths(&[]), None); + assert_eq!(bandwidths(&[(10, 0)]), None); + assert_eq!(bandwidths(&[(u64::MAX, 1)]), Some((u64::MAX, u64::MAX))); + let v = VariantLine { + uri: "wai-180.m3u8".into(), + union: parse_element("wai2.video.av1|video.int_hyper").unwrap(), + floor: parse_element("wai2.video.av1").unwrap(), + peak_bps: peak, avg_bps: avg, resolution: Some((320, 180)), frame_rate_milli: Some(25_000), + }; + let m = wai_multivariant(&[v.clone()]).unwrap(); + assert_eq!(m, "#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=262925,AVERAGE-BANDWIDTH=239246,CODECS=\"wai2.video.av1|video.int_hyper\",X-WAI-FLOOR=\"wai2.video.av1\",RESOLUTION=320x180,FRAME-RATE=25.000\nwai-180.m3u8\n"); + assert!(!m.contains("NAME=") && !m.contains("GROUP-ID=")); + let mut bad = v.clone(); + bad.floor = parse_element("wai1.video.av1").unwrap(); + assert_eq!(wai_multivariant(&[bad]).unwrap_err().code(), "playlist_malformed"); + let mut bad = v.clone(); + bad.uri = "a\nb".into(); + assert!(wai_multivariant(&[bad]).is_err()); + assert!(wai_multivariant(&[]).is_err()); + let mut fr = v; + fr.frame_rate_milli = Some(29_970); + assert!(wai_multivariant(&[fr]).unwrap().contains("FRAME-RATE=29.970\n")); + // Media playlists: EXTINF with three decimals, from integer milliseconds. + let classical = parse_media_playlist("#EXTM3U\n#EXT-X-TARGETDURATION:5\n#EXTINF:4.00008,\na.m4s\n#EXT-X-ENDLIST\n").unwrap(); + let (_, _, _, t) = wai_media_playlist(&classical, &[seg(1, 4000)], None, "", true).unwrap(); + assert!(t.contains("#EXTINF:4.000,\n")); +} + +/// §11.1: the published classical playlist is the original, or the original with the +/// one session-data line added; anything else is `classical_changed`. +#[test] +fn classical_left_unchanged() { + let master = "#EXTM3U\n#EXT-X-VERSION:6\n#EXT-X-STREAM-INF:BANDWIDTH=800000,CODECS=\"avc1.42e01e\"\nv0.m3u8\n"; + let line = session_data_line("wai.m3u8").unwrap(); + assert_eq!(line, "#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\""); + classical_unchanged(master, master).unwrap(); + for at in [1usize, 2, 3, 4] { + let mut lines: Vec = master.lines().map(|l| format!("{l}\n")).collect(); + lines.insert(at, format!("{line}\n")); + classical_unchanged(master, &lines.concat()).unwrap(); + } + let crlf = master.replace('\n', "\r\n"); + classical_unchanged(&crlf, &crlf.replacen("\r\n", &format!("\r\n{line}\r\n"), 1)).unwrap(); + let changed = |published: String| classical_unchanged(master, &published).unwrap_err().code(); + assert_eq!(changed(format!("{line}\n{master}")), "classical_changed"); + assert_eq!(changed(master.replacen("\n", &format!("\n{line}\n{line}\n"), 1)), "classical_changed"); + assert_eq!(changed(master.replace("800000", "800001")), "classical_changed"); + assert_eq!(changed(master.replacen("\n", "\n#EXT-X-SESSION-DATA:DATA-ID=\"other\",VALUE=\"wai.m3u8\"\n", 1)), "classical_changed"); + assert_eq!(changed(master.replacen("\n", &format!("\n{line} \n"), 1)), "classical_changed"); + assert_eq!(changed(format!("{master}{line}")), "classical_changed"); + for bad in ["", "a\"b", "a\nb", "a\rb"] { + assert!(session_data_line(bad).is_err(), "{bad:?}"); + } +} + +#[cfg(feature = "video_package")] +#[test] +fn package_map_by_sequence_number() { + use crate::wai_video_package::SegmentEntry; + let e = |i: u64, ms: u64| SegmentEntry { index: i, content_hash: [i as u8 + 0x10; 32], duration_ms: ms, byte_len: 100 }; + let segs = [seg(1, 640), seg(2, 640)]; + let m = package_map(&[e(0, 640), e(1, 640)], &segs, 1200).unwrap(); + assert_eq!(m, vec![(1200, [0x10; 32], cid(1)), (1201, [0x11; 32], cid(2))]); + assert_eq!(package_map(&[e(0, 640), e(2, 640)], &segs, 0).unwrap_err().code(), "adjacency_misaligned"); + assert_eq!(package_map(&[e(0, 640), e(1, 600)], &segs, 0).unwrap_err().code(), "adjacency_misaligned"); + assert!(package_map(&[e(0, 640)], &segs, 0).is_err()); +} -- 2.54.0 (Apple Git-157) From 21400a27b10cde22f2031c245a358825aa228ee6 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 05:47:27 -0400 Subject: [PATCH 03/55] =?UTF-8?q?wai:=20http=5Fbinding::abr=20=E2=80=94=20?= =?UTF-8?q?reference=20adaptation=20in=20integers=20(W3-5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four policies over options of increasing size, re-implemented in integer arithmetic: Throughput (the highest option under the last samples' throughput less a margin), BufferBased (reservoir and cushion), BolaLike (utility over buffer; the log is ilog2_q16, a 16-interval table interpolation in Q16; unlike the published algorithm it never waits) and Mpc (every sequence over a short horizon, scored by utility less stalls and changes). clamp_to_budget applies a joule budget only through figures a receipt labels with their class (labelled_figure); a choice without one reports the budget not applied. order_entries puts the choice first within each capability of a WAI2 and keeps every entry; depth chooses a layer depth over a base. The Selector's order now takes the verified head with its manifest, so a size-aware policy (selector_order) gives the same order, and the same pick, on the range path and the whole path. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/bin/wai_cli/http_vectors.rs | 6 +- wai/wai-rs/src/http_binding/abr.rs | 413 +++++++++++++++++++++ wai/wai-rs/src/http_binding/mod.rs | 5 + wai/wai-rs/src/http_binding/session.rs | 22 +- wai/wai-rs/src/http_binding/tests.rs | 2 +- wai/wai-rs/src/http_binding/tests_abr.rs | 277 ++++++++++++++ 6 files changed, 711 insertions(+), 14 deletions(-) create mode 100644 wai/wai-rs/src/http_binding/abr.rs create mode 100644 wai/wai-rs/src/http_binding/tests_abr.rs diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors.rs index 338a90589..fd6a85097 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors.rs @@ -393,7 +393,7 @@ fn selector(c: &Corpus, input: &Value) -> Result { }); if let Some(order) = input["order"].as_array() { let order: Vec = order.iter().filter_map(|v| v.as_u64().map(|x| x as usize)).collect(); - s.order = Some(Box::new(move |_| order.clone())); + s.order = Some(Box::new(move |_, _| order.clone())); } if let Some(d) = input["declared"].as_str() { s.declared = Some(codecs::parse_element(d).map_err(|e| Stop::Refused(json!({ "error": e.code() })))?); @@ -417,11 +417,11 @@ fn head(b: &[u8]) -> Result { fn select(c: &Corpus, input: &Value) -> Result { let env = c.object(input)?; - whole_head(&env)?; + let h = whole_head(&env)?; let mut sel = selector(c, input)?; let w = WaiMulti::from_bytes(&env).map_err(|e| refusal_of(&RangeRefusal::HeadMalformed(e.to_string())))?; let order: Vec = match &sel.order { - Some(f) => f(&w.manifest), + Some(f) => f(&h, &w.manifest), None => (0..w.manifest.renditions.len()).collect(), }; let advertises = &sel.advertises; diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs new file mode 100644 index 000000000..5f08d6980 --- /dev/null +++ b/wai/wai-rs/src/http_binding/abr.rs @@ -0,0 +1,413 @@ +//! Adaptation (http-delivery Appendix F, informative): reference policies that choose +//! among options of increasing size from measured delivery — throughput, a buffer +//! level, a utility-over-buffer rule and a short look-ahead — in integer arithmetic +//! only, so every platform chooses the same, and corpus vectors pin each choice. +//! +//! They act in two places, both deployer policy and never a change to what a sink may +//! decode: +//! +//! - [`order_entries`] reorders the selectable entries of one `WAI2` by size within +//! each capability, putting the policy's choice first (SPEC §7.1 step 2 lets a +//! deployer order entries); selection then runs unchanged on the head and on the +//! whole envelope, so the pick is the same on every path (§7.2); +//! - [`depth`] chooses how many layer objects (§10.4) a client fetches over a base. +//! +//! An option's energy enters only as a figure a receipt labels with its acquisition +//! class ([`labelled_figure`]); an unlabelled figure is never used, and a budget that +//! has no labelled figure to act on is reported as not applied ([`Budget::NotApplied`]). +//! Nothing here reads a clock: samples carry the caller's measurements. + +use super::head::{Head, Version}; +use crate::container::ManifestV2; + +/// One measured request: `bytes` of body received in `ms` milliseconds, finished at +/// `at_ms` on the caller's clock. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Sample { + pub at_ms: u64, + pub bytes: u64, + pub ms: u64, +} + +/// What a policy chooses from. +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct AbrState { + /// Media buffered ahead of the play point, in milliseconds. + pub buffer_ms: u32, + /// Measured requests, oldest first. + pub samples: Vec, + /// The option chosen last, if any. + pub last: Option, + /// Each option's bytes, in increasing order: a component's `len` from the head, or + /// the bytes through a layer depth. + pub option_bytes: Vec, + /// Each option's energy in microjoules, only from a receipt figure labelled with its + /// acquisition class ([`labelled_figure`]); `None` otherwise. + pub option_joules_micro: Vec>, + /// A budget in microjoules for one option, if the client holds one. + pub joule_budget_micro: Option, + /// The media duration one option covers, in milliseconds. + pub segment_ms: u32, +} + +/// A policy: a pure function from the state to an option index. +pub trait Policy { + /// The policy's identifier, as the vectors name it. + fn id(&self) -> &'static str; + /// The option chosen, before any budget: an index into `option_bytes` (0 when + /// there are none). + fn choose(&self, s: &AbrState) -> usize; +} + +/// The highest option whose rate fits the measured throughput, less a safety margin. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Throughput { + pub last_n: u8, + pub safety_permille: u16, +} + +/// The lowest option while the buffer is at or below the reservoir, the highest once it +/// reaches the reservoir and the cushion, and in between the highest option under a rate +/// rising linearly from the lowest option's to the highest's. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BufferBased { + pub reservoir_ms: u32, + pub cushion_ms: u32, +} + +/// A utility-over-buffer rule in the manner of BOLA: each option's utility is the log +/// of its size over the smallest's, and the option with the best ratio of +/// `V·(utility + 1) − buffer` to its size is chosen, `V` set by `v_milli` and +/// `max_buffer_ms`. It is not the published algorithm: where that one waits (no option +/// scores above zero), this one chooses the option closest to scoring, so it always +/// returns an option. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BolaLike { + pub v_milli: u32, + pub max_buffer_ms: u32, +} + +/// A look-ahead: every sequence of `horizon` choices (fewer when the options are many) +/// is played against the measured throughput less a margin, scored by utility, less +/// stalls and less changes of utility, and the first choice of the best sequence is +/// taken. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Mpc { + pub horizon: u8, + pub safety_permille: u16, +} + +/// What a joule budget did to a choice. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Budget { + /// No budget is held. + NotSet, + /// The chosen option has no labelled figure: the budget is not applied. + NotApplied, + /// The chosen option's labelled figure is within the budget. + Within, + /// The choice was lowered to the highest option below it with a labelled figure + /// within the budget. + Lowered, + /// No option at or below the choice has a labelled figure within the budget: the + /// lowest option. + Floor, +} + +impl Budget { + /// `not_set`, `not_applied`, `within`, `lowered` or `floor`. + pub fn label(self) -> &'static str { + match self { + Budget::NotSet => "not_set", + Budget::NotApplied => "not_applied", + Budget::Within => "within", + Budget::Lowered => "lowered", + Budget::Floor => "floor", + } + } +} + +/// `round(65536 · log2(1 + i/16))` for `i` in `0..=16`. +pub const LOG2_TABLE: [u64; 17] = [0, 5732, 11136, 16248, 21098, 25711, 30109, 34312, 38336, 42196, 45904, 49472, 52911, 56229, 59434, 62534, 65536]; + +/// `log2(a / b)` in Q16 (×65536), 0 when `a ≤ b` (`b` taken as at least 1): the ratio +/// `x = ⌊65536·a/b⌋`, its exponent `e = ⌊log2 x⌋ − 16`, the mantissa `m = x >> e` in +/// `[65536, 131072)`, and `e·65536` plus [`LOG2_TABLE`] interpolated linearly on the +/// mantissa's top four fractional bits, its next twelve the weight, rounded down. +pub fn ilog2_q16(a: u64, b: u64) -> u64 { + let b = b.max(1); + if a <= b { + return 0; + } + let x = (u128::from(a) << 16) / u128::from(b); + let e = (127 - x.leading_zeros()) as u64 - 16; + let f = ((x >> e) - 65536) as u64; + let (i, r) = ((f >> 12) as usize, f & 4095); + e * 65536 + LOG2_TABLE[i] + (((LOG2_TABLE[i + 1] - LOG2_TABLE[i]) * r) >> 12) +} + +/// The throughput the last `n` samples give, in bits per second: `⌊8000·Σbytes / Σms⌋`, +/// each sample's `ms` taken as at least 1; 0 with no sample. `n` 0 is taken as 1. +pub fn throughput_bps(samples: &[Sample], n: u8) -> u64 { + let k = usize::from(n.max(1)).min(samples.len()); + let tail = &samples[samples.len() - k..]; + let (b, t) = tail.iter().fold((0u128, 0u128), |(b, t), s| (b + u128::from(s.bytes), t + u128::from(s.ms.max(1)))); + if t == 0 { + return 0; + } + u64::try_from(b * 8000 / t).unwrap_or(u64::MAX) +} + +/// An option's rate in bits per second: `⌈8000·bytes / segment_ms⌉`, `segment_ms` taken +/// as at least 1. +pub fn option_bps(bytes: u64, segment_ms: u32) -> u64 { + u64::try_from((u128::from(bytes) * 8000).div_ceil(u128::from(segment_ms.max(1)))).unwrap_or(u64::MAX) +} + +/// The highest index whose rate is at most `budget_bps`; 0 when none is. +fn highest_under(s: &AbrState, budget_bps: u64) -> usize { + s.option_bytes.iter().enumerate().rev().find(|(_, b)| option_bps(**b, s.segment_ms) <= budget_bps).map_or(0, |(k, _)| k) +} + +fn permille(x: u64, p: u16) -> u64 { + u64::try_from(u128::from(x) * u128::from(p) / 1000).unwrap_or(u64::MAX) +} + +impl Policy for Throughput { + fn id(&self) -> &'static str { + "throughput" + } + fn choose(&self, s: &AbrState) -> usize { + if s.option_bytes.is_empty() { + return 0; + } + highest_under(s, permille(throughput_bps(&s.samples, self.last_n), self.safety_permille)) + } +} + +impl Policy for BufferBased { + fn id(&self) -> &'static str { + "buffer_based" + } + fn choose(&self, s: &AbrState) -> usize { + let k = s.option_bytes.len(); + if k == 0 { + return 0; + } + let (b, r, c) = (u64::from(s.buffer_ms), u64::from(self.reservoir_ms), u64::from(self.cushion_ms)); + if b <= r { + return 0; + } + if c == 0 || b >= r + c { + return k - 1; + } + let lo = option_bps(s.option_bytes[0], s.segment_ms); + let hi = option_bps(s.option_bytes[k - 1], s.segment_ms); + let target = u128::from(lo) + u128::from(hi.saturating_sub(lo)) * u128::from(b - r) / u128::from(c); + highest_under(s, u64::try_from(target).unwrap_or(u64::MAX)) + } +} + +/// Each option's utility, `ilog2_q16(bytes, the first option's bytes)`. +fn utilities(s: &AbrState) -> Vec { + let s0 = s.option_bytes.first().copied().unwrap_or(1).max(1); + s.option_bytes.iter().map(|&b| ilog2_q16(b.max(1), s0)).collect() +} + +impl Policy for BolaLike { + fn id(&self) -> &'static str { + "bola_like" + } + fn choose(&self, s: &AbrState) -> usize { + let k = s.option_bytes.len(); + if k == 0 { + return 0; + } + let seg = i128::from(s.segment_ms.max(1)); + let q = i128::from(s.buffer_ms) * 65536 / seg; + let qmax = i128::from(self.max_buffer_ms) * 65536 / seg; + let u = utilities(s); + let u_top = i128::from(*u.iter().max().unwrap_or(&0)); + let (vn, vd) = ((qmax - 65536).max(0), u_top + 65536); + let score: Vec = u.iter().map(|&uk| vn * i128::from(self.v_milli) * (i128::from(uk) + 65536) / (vd * 1000) - q).collect(); + let size = |i: usize| i128::from(s.option_bytes[i].max(1)); + let mut best: Option = None; + if score.iter().any(|&n| n > 0) { + for i in (0..k).filter(|&i| score[i] > 0) { + // score[i]/size(i) > score[b]/size(b), by cross-multiplication. + if best.is_none_or(|b| score[i] * size(b) > score[b] * size(i)) { + best = Some(i); + } + } + } else { + for i in 0..k { + if best.is_none_or(|b| score[i] > score[b]) { + best = Some(i); + } + } + } + best.unwrap_or(0) + } +} + +impl Policy for Mpc { + fn id(&self) -> &'static str { + "mpc" + } + fn choose(&self, s: &AbrState) -> usize { + let k = s.option_bytes.len(); + let pred = permille(throughput_bps(&s.samples, 5), self.safety_permille); + if k == 0 || pred == 0 { + return 0; + } + let mut h = u32::from(self.horizon.clamp(1, 5)); + while h > 1 && (k as u128).pow(h) > 65536 { + h -= 1; + } + let u = utilities(s); + let u_top = i128::from(*u.iter().max().unwrap_or(&0)); + let seg = u64::from(s.segment_ms); + let mut best: Option<(i128, usize)> = None; + let total = (k as u64).pow(h); + let mut seq = vec![0usize; h as usize]; + for code in 0..total { + // The sequence `code` names, most significant choice first. + let mut c = code; + for t in (0..h as usize).rev() { + seq[t] = (c % k as u64) as usize; + c /= k as u64; + } + let (mut b, mut rebuf, mut qoe) = (u64::from(s.buffer_ms), 0u128, 0i128); + let mut prev: Option = s.last.filter(|&l| l < k).map(|l| i128::from(u[l])); + for &o in &seq { + let dl = u64::try_from((u128::from(s.option_bytes[o]) * 8000).div_ceil(u128::from(pred))).unwrap_or(u64::MAX); + if dl > b { + rebuf += u128::from(dl - b); + b = 0; + } else { + b -= dl; + } + b = b.saturating_add(seg); + let uo = i128::from(u[o]); + qoe += 1000 * uo; + if let Some(p) = prev { + qoe -= 1000 * (uo - p).abs(); + } + prev = Some(uo); + } + qoe -= i128::try_from(rebuf).unwrap_or(i128::MAX / 2).saturating_mul(u_top + 65536); + if best.is_none_or(|(q, _)| qoe > q) { + best = Some((qoe, seq[0])); + } + } + best.map_or(0, |(_, o)| o) + } +} + +/// The figure a receipt states, usable for a budget only when it carries its +/// acquisition class and the two cohere: an unlabelled figure, an unmetered one, or a +/// class beside a zero figure gives `None`. +pub fn labelled_figure(joules_micro: u64, class: Option<&crate::energy_class::EnergyProvenance>) -> Option { + (class.is_some() && joules_micro > 0 && crate::energy_class::figure_and_class_cohere(joules_micro, class)).then_some(joules_micro) +} + +/// Apply the state's joule budget to `choice`: unchanged when no budget is held, or the +/// choice has no labelled figure (the budget is not applied); unchanged when its figure +/// is within the budget; otherwise the highest option below it whose labelled figure is +/// within the budget, or the lowest option when none is. +pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) { + let Some(b) = s.joule_budget_micro else { return (choice, Budget::NotSet) }; + let fig = |k: usize| s.option_joules_micro.get(k).copied().flatten(); + match fig(choice) { + None => (choice, Budget::NotApplied), + Some(j) if j <= b => (choice, Budget::Within), + Some(_) => match (0..choice).rev().find(|&k| fig(k).is_some_and(|j| j <= b)) { + Some(k) => (k, Budget::Lowered), + None => (0, Budget::Floor), + }, + } +} + +/// The policy's choice under the state's budget. +pub fn decide(p: &dyn Policy, s: &AbrState) -> (usize, Budget) { + clamp_to_budget(s, p.choose(s)) +} + +/// How many layers to fetch over a base (§10.4, the counterpart of a MoQ subscriber's +/// layer cap): `layers_n[0]` is the base's `envelope_len` and `layers_n[k]` layer `k`'s; +/// option `k` is fetching through layer `k`, its bytes the sum through it and its energy +/// the sum of the state's per-object labelled figures through it (`None` when any is +/// unlabelled). Returns the depth (0: the base alone) and what the budget did. +pub fn depth(layers_n: &[u64], p: &dyn Policy, s: &AbrState) -> (usize, Budget) { + let mut st = s.clone(); + st.option_bytes = layers_n.iter().scan(0u64, |acc, &n| { + *acc = acc.saturating_add(n); + Some(*acc) + }).collect(); + st.option_joules_micro = (0..layers_n.len()).map(|k| { + (0..=k).try_fold(0u64, |acc, j| s.option_joules_micro.get(j).copied().flatten().map(|f| acc.saturating_add(f))) + }).collect(); + if layers_n.is_empty() { + return (0, clamp_to_budget(&st, 0).1); + } + decide(p, &st) +} + +/// SPEC §7.1 step 2's order for one `WAI2`, as a deployer policy: the selectable +/// entries whose capability `eligible` accepts, grouped by capability in the order each +/// first appears; within a group, the entries by component length (then table index) +/// are the options, `option_bytes` their lengths and `option_joules_micro` the state's +/// figure of each entry by its table index; the group is ordered as the policy's choice +/// under the budget, then the options below it from the nearest down, then those above +/// it from the nearest up. Every other entry follows in table order. A `WAI1` gives +/// `[0]`. +pub fn order_entries(head: &Head, eligible: &dyn Fn(&str) -> bool, p: &dyn Policy, s: &AbrState) -> Vec { + if head.version != Version::Wai2 { + return vec![0]; + } + match head.manifest_v2() { + Ok(m) => order_manifest(head, &m, eligible, p, s), + Err(_) => (0..head.components.len()).collect(), + } +} + +/// [`order_entries`] with the manifest already read from `head`. +pub fn order_manifest(head: &Head, m: &ManifestV2, eligible: &dyn Fn(&str) -> bool, p: &dyn Policy, s: &AbrState) -> Vec { + let e = &m.renditions; + let len = |i: usize| head.components.get(i).map_or(0, |c| c.len); + let mut groups: Vec<(String, Vec)> = Vec::new(); + for (i, r) in e.iter().enumerate() { + if !r.is_selectable() || !eligible(&r.capability) { + continue; + } + match groups.iter_mut().find(|(c, _)| *c == r.capability) { + Some((_, g)) => g.push(i), + None => groups.push((r.capability.clone(), vec![i])), + } + } + let mut order = Vec::with_capacity(e.len()); + for (_, mut g) in groups { + g.sort_by_key(|&i| (len(i), i)); + let mut st = s.clone(); + st.option_bytes = g.iter().map(|&i| len(i)).collect(); + st.option_joules_micro = g.iter().map(|&i| s.option_joules_micro.get(i).copied().flatten()).collect(); + let (c, _) = decide(p, &st); + let c = c.min(g.len() - 1); + order.push(g[c]); + order.extend(g[..c].iter().rev()); + order.extend(&g[c + 1..]); + } + let rest: Vec = (0..e.len()).filter(|i| !order.contains(i)).collect(); + order.extend(rest); + order +} + +/// A [`super::session::Selector`] order from a policy and a state: [`order_manifest`] +/// over the head the session verified, on both paths. +pub fn selector_order( + p: Box, + s: AbrState, + eligible: impl Fn(&str) -> bool + 'static, +) -> Box Vec> { + Box::new(move |h: &Head, m: &ManifestV2| order_manifest(h, m, &eligible, p.as_ref(), &s)) +} diff --git a/wai/wai-rs/src/http_binding/mod.rs b/wai/wai-rs/src/http_binding/mod.rs index de165018b..273804927 100644 --- a/wai/wai-rs/src/http_binding/mod.rs +++ b/wai/wai-rs/src/http_binding/mod.rs @@ -10,12 +10,15 @@ //! whole-object fallback; //! - [`codecs`]: the `codecs` grammar, the union and the floor (§4); //! - [`playlist`]: the `#WAI-LAYER` tag (§10.4); +//! - [`abr`]: reference adaptation policies, in integers: an entry order for one +//! `WAI2` and a layer depth (Appendix F); //! - [`hls`]: WAI media and multivariant playlists beside a classical HLS //! presentation, their adjacency and the session-data line (§11); //! - [`origin`]: an origin's responses (§5.3) and byte-range answers. //! //! Nothing here performs I/O: the `wai http` commands and the reference origin do. +pub mod abr; pub mod codecs; pub mod head; pub mod head_proof; @@ -28,6 +31,8 @@ pub mod session; #[cfg(test)] mod tests; #[cfg(test)] +mod tests_abr; +#[cfg(test)] mod tests_playlists; /// The proposed media type of an object (§3). It is not registered. diff --git a/wai/wai-rs/src/http_binding/session.rs b/wai/wai-rs/src/http_binding/session.rs index ec31518e0..10758f5be 100644 --- a/wai/wai-rs/src/http_binding/session.rs +++ b/wai/wai-rs/src/http_binding/session.rs @@ -125,9 +125,11 @@ pub struct Selector { pub verify_pin: Box Result<(), PinRefusal>>, /// Whether the sink applies a companion or refinement entry. pub applies: Box bool>, - /// The order in which entries are considered (SPEC §7.1 step 2); `None` is table - /// order. - pub order: Option Vec>>, + /// The order in which entries are considered (SPEC §7.1 step 2), from the verified + /// head and its manifest (a deployer policy, such as `abr::selector_order`); `None` + /// is table order. The same head is given on the range path and the whole path, so + /// the order, and the pick, are the same on both (§7.2). + pub order: Option Vec>>, /// The variant's union (§4.2), when the client holds one. pub declared: Option, /// For a layer, the object its playlist lists it under: the base a declared layer is @@ -149,17 +151,17 @@ impl Selector { } } - fn order_of(&self, m: &ManifestV2) -> Vec { + fn order_of(&self, h: &Head, m: &ManifestV2) -> Vec { match &self.order { - Some(f) => f(m), + Some(f) => f(h, m), None => (0..m.renditions.len()).collect(), } } - /// SPEC §7.1 over `m`, with the digest test `digest_ok`. - fn select(&mut self, m: &ManifestV2, digest_ok: impl Fn(usize, &RenditionMeta) -> bool) + /// SPEC §7.1 over `m`, the manifest of `h`, with the digest test `digest_ok`. + fn select(&mut self, h: &Head, m: &ManifestV2, digest_ok: impl Fn(usize, &RenditionMeta) -> bool) -> Result<(usize, Vec<(usize, Skip)>), Vec<(usize, Skip)>> { - let order = self.order_of(m); + let order = self.order_of(h, m); let advertises = &self.advertises; let verify = &mut self.verify_pin; let eligible = |_: usize, e: &RenditionMeta| if advertises(&e.capability) { Ok(()) } else { Err(Skip::NotEligible) }; @@ -654,7 +656,7 @@ impl FetchSession { } Version::Wai2 => { let m = head.manifest_v2()?; - let (index, skipped) = self.sel.select(&m, |_, _| true).map_err(RangeRefusal::Selection)?; + let (index, skipped) = self.sel.select(&head, &m, |_, _| true).map_err(RangeRefusal::Selection)?; let mut wanted = vec![(index, Class::Base)]; wanted.extend(self.sel.applied(&m, index)); let present: Vec = m.renditions.iter().map(|e| e.sha256.is_some()).collect(); @@ -842,7 +844,7 @@ impl FetchSession { let w = WaiMulti::from_bytes(&envelope).map_err(|e| RangeRefusal::HeadMalformed(e.to_string()))?; let payloads = w.renditions.clone(); let digest_ok = |i: usize, e: &RenditionMeta| payloads.get(i).is_some_and(|p| e.payload_digest_matches(p)); - let (index, skipped) = self.sel.select(&w.manifest, digest_ok).map_err(RangeRefusal::Selection)?; + let (index, skipped) = self.sel.select(&head, &w.manifest, digest_ok).map_err(RangeRefusal::Selection)?; let (mut companions, mut refinements, mut dropped) = (Vec::new(), Vec::new(), Vec::new()); for (i, class) in self.sel.applied(&w.manifest, index) { // A refinement that states no digest is dropped on this path as on the diff --git a/wai/wai-rs/src/http_binding/tests.rs b/wai/wai-rs/src/http_binding/tests.rs index a48747d94..38b78a4a7 100644 --- a/wai/wai-rs/src/http_binding/tests.rs +++ b/wai/wai-rs/src/http_binding/tests.rs @@ -336,7 +336,7 @@ fn sink(adv: &'static [&'static str], applies: &'static [&'static str], order: O let mut s = Selector::new(move |c| adv.contains(&c)); s.applies = Box::new(move |e: &RenditionMeta| applies.contains(&e.capability.as_str())); if let Some(o) = order { - s.order = Some(Box::new(move |_| o.clone())); + s.order = Some(Box::new(move |_, _| o.clone())); } s } diff --git a/wai/wai-rs/src/http_binding/tests_abr.rs b/wai/wai-rs/src/http_binding/tests_abr.rs new file mode 100644 index 000000000..64fad70db --- /dev/null +++ b/wai/wai-rs/src/http_binding/tests_abr.rs @@ -0,0 +1,277 @@ +//! Unit tests of the reference adaptation policies (http-delivery Appendix F): the +//! integer log, each policy at points worked by hand, the budget rule, layer depth, and +//! an entry order that keeps the pick the same on the range path and the whole path. + +use super::abr::*; +use super::head::{parse_head, Head, HeadParse}; +use super::head_proof::HeadProof; +use super::origin; +use super::plan::FetchPolicy; +use super::session::{drive, FetchSession, ResponseMeta, Selector, VerifiedBy, Want}; +use super::{content_hash, Fragment, RangeRefusal}; +use crate::container::{Rendition, RenditionMeta, Skip, WaiMulti}; +use crate::energy_class::EnergyProvenance; + +fn pattern(len: usize, a: usize, b: usize) -> Vec { + (0..len).map(|i| ((a * i + b) % 256) as u8).collect() +} + +fn head_of(env: &[u8]) -> Head { + match parse_head(env, env.len() as u64).unwrap() { + HeadParse::Head(h) => h, + HeadParse::Need(n) => panic!("needs {n}"), + } +} + +fn state(buffer_ms: u32, bps: u64, options: &[u64]) -> AbrState { + AbrState { + buffer_ms, + // One second at `bps`. + samples: if bps == 0 { Vec::new() } else { vec![Sample { at_ms: 1000, bytes: bps / 8, ms: 1000 }] }, + last: None, + option_bytes: options.to_vec(), + option_joules_micro: vec![None; options.len()], + joule_budget_micro: None, + segment_ms: 1000, + } +} + +const OPTIONS: [u64; 3] = [10_000, 100_000, 1_000_000]; + +#[test] +fn ilog2_q16_points() { + for (a, b, want) in [(1u64, 1u64, 0u64), (1, 2, 0), (2, 1, 65_536), (4, 1, 131_072), (3, 2, 38_336), (3, 1, 103_872), (17, 16, 5_732), + (1000, 1, 653_108), (u64::MAX, 1, 63 * 65_536 + 65_535), (5, 0, 152_170)] { + assert_eq!(ilog2_q16(a, b), want, "log2({a}/{b})"); + } + // Within 48/65536 of log2 over a sweep (linear interpolation over sixteenths of an + // octave), never above it, and never decreasing in a. + let mut prev = 0; + for a in 1..5000u64 { + let got = ilog2_q16(a, 1); + assert!(got >= prev); + prev = got; + let exact = (a as f64).log2() * 65536.0; + let gap = exact - (got as f64); + assert!(gap > -1.0 && gap < 48.0, "a={a}: {got} against {exact}"); + } +} + +/// The throughput policy at points worked by hand: options of 80 kb/s, 800 kb/s and +/// 8 Mb/s, a 900 ‰ margin. +#[test] +fn throughput_points() { + let p = Throughput { last_n: 3, safety_permille: 900 }; + assert_eq!(throughput_bps(&state(0, 10_000_000, &OPTIONS).samples, 3), 10_000_000); + assert_eq!(p.choose(&state(0, 10_000_000, &OPTIONS)), 2, "9 Mb/s fits 8"); + assert_eq!(p.choose(&state(0, 8_000_000, &OPTIONS)), 1, "7.2 Mb/s does not fit 8"); + assert_eq!(p.choose(&state(0, 1_000_000, &OPTIONS)), 1); + assert_eq!(p.choose(&state(0, 80_000, &OPTIONS)), 0, "below the lowest: the lowest"); + assert_eq!(p.choose(&state(0, 0, &OPTIONS)), 0, "no sample"); + assert_eq!(p.choose(&state(0, 10_000_000, &[])), 0); + // Only the last n samples count; a sample of 0 ms counts as 1 ms. + let mut s = state(0, 0, &OPTIONS); + s.samples = vec![Sample { at_ms: 0, bytes: 10, ms: 1000 }, Sample { at_ms: 1, bytes: 1_250_000, ms: 1000 }]; + assert_eq!(Throughput { last_n: 1, safety_permille: 1000 }.choose(&s), 2); + assert_eq!(Throughput { last_n: 2, safety_permille: 1000 }.choose(&s), 1); + assert_eq!(throughput_bps(&[Sample { at_ms: 0, bytes: 1, ms: 0 }], 0), 8000); +} + +#[test] +fn buffer_based_points() { + let p = BufferBased { reservoir_ms: 5000, cushion_ms: 10_000 }; + assert_eq!(p.choose(&state(5000, 0, &OPTIONS)), 0); + assert_eq!(p.choose(&state(15_000, 0, &OPTIONS)), 2); + // 80 kb/s + (8 Mb/s − 80 kb/s)·5/10 = 4.04 Mb/s: the 800 kb/s option. + assert_eq!(p.choose(&state(10_000, 0, &OPTIONS)), 1); + // 80 kb/s + 7.92 Mb/s · 100/10000 = 159.2 kb/s: only the lowest fits. + assert_eq!(p.choose(&state(5_100, 0, &OPTIONS)), 0); + assert_eq!(BufferBased { reservoir_ms: 5000, cushion_ms: 0 }.choose(&state(5001, 0, &OPTIONS)), 2); +} + +/// Utility over buffer: the choice never falls as the buffer grows, an empty buffer +/// takes the lowest option and a buffer past every threshold the highest. +#[test] +fn bola_like_shape() { + let p = BolaLike { v_milli: 1000, max_buffer_ms: 20_000 }; + let opts = [100_000u64, 200_000, 400_000, 800_000]; + let mut prev = 0; + for b in (0..=30_000).step_by(250) { + let c = p.choose(&state(b, 0, &opts)); + assert!(c >= prev, "buffer {b}: {c} after {prev}"); + prev = c; + } + assert_eq!(p.choose(&state(0, 0, &opts)), 0); + assert_eq!(p.choose(&state(30_000, 0, &opts)), 3); + // A maximum buffer of one segment or less leaves V at 0: the lowest option until the + // buffer is past it, the scores then all equal. + assert_eq!(BolaLike { v_milli: 1000, max_buffer_ms: 1000 }.choose(&state(0, 0, &opts)), 0); + assert_eq!(BolaLike { v_milli: 1000, max_buffer_ms: 500 }.choose(&state(9000, 0, &opts)), 0); +} + +#[test] +fn mpc_shape() { + let p = Mpc { horizon: 3, safety_permille: 1000 }; + assert_eq!(p.choose(&state(0, 0, &OPTIONS)), 0, "no prediction"); + assert_eq!(p.choose(&state(10_000, 100_000_000, &OPTIONS)), 2); + assert_eq!(p.choose(&state(0, 100_000, &OPTIONS)), 0); + let mut prev = 0; + for bps in (100_000..20_000_000).step_by(250_000) { + let c = p.choose(&state(4000, bps, &OPTIONS)); + assert!(c >= prev, "{bps}: {c} after {prev}"); + prev = c; + } + assert_eq!(prev, 2); + // Many options shorten the horizon; the choice is still made. + let many: Vec = (1..=40).map(|k| k * 10_000).collect(); + assert!(Mpc { horizon: 5, safety_permille: 900 }.choose(&state(8000, 3_000_000, &many)) < 40); +} + +/// An unlabelled figure is never used for the budget: a choice with no labelled figure +/// is left as it is and reported as not applied, whatever the options below it state. +#[test] +fn abr_budget_ignores_unlabelled() { + let mut s = state(0, 100_000_000, &OPTIONS); + s.joule_budget_micro = Some(100); + let p = Throughput { last_n: 1, safety_permille: 900 }; + assert_eq!(decide(&p, &s), (2, Budget::NotApplied)); + s.option_joules_micro = vec![Some(10), Some(50), None]; + assert_eq!(decide(&p, &s), (2, Budget::NotApplied)); + s.option_joules_micro = vec![Some(10), Some(50), Some(500)]; + assert_eq!(decide(&p, &s), (1, Budget::Lowered)); + s.option_joules_micro = vec![Some(10), None, Some(500)]; + assert_eq!(decide(&p, &s), (0, Budget::Lowered), "the unlabelled option is passed over"); + s.option_joules_micro = vec![Some(200), None, Some(500)]; + assert_eq!(decide(&p, &s), (0, Budget::Floor)); + s.option_joules_micro = vec![Some(10), Some(50), Some(100)]; + assert_eq!(decide(&p, &s), (2, Budget::Within)); + s.joule_budget_micro = None; + assert_eq!(decide(&p, &s), (2, Budget::NotSet)); + // Only a figure with its class is a labelled figure. + assert_eq!(labelled_figure(500, None), None); + assert_eq!(labelled_figure(500, Some(&EnergyProvenance::ModelBased)), Some(500)); + assert_eq!(labelled_figure(0, Some(&EnergyProvenance::ModelBased)), None); + assert_eq!(labelled_figure(0, None), None); +} + +/// Layer depth over a base and two layers: the bytes through each depth, the budget on +/// the sum of labelled figures, and a budget that cannot be applied. +#[test] +fn depth_over_layers() { + let layers = [1825u64, 12_711, 18_367]; + let p = Throughput { last_n: 1, safety_permille: 1000 }; + // Through depth 2: 32,903 B a second, 263,224 b/s. + assert_eq!(depth(&layers, &p, &state(0, 263_224, &[])), (2, Budget::NotSet)); + assert_eq!(depth(&layers, &p, &state(0, 263_223, &[])), (1, Budget::NotSet)); + assert_eq!(depth(&layers, &p, &state(0, 14_600, &[])), (0, Budget::NotSet)); + let mut s = state(0, 10_000_000, &[]); + s.joule_budget_micro = Some(30); + s.option_joules_micro = vec![Some(5), Some(10), Some(20)]; + assert_eq!(depth(&layers, &p, &s), (1, Budget::Lowered), "5 + 10 + 20 is past 30"); + s.option_joules_micro = vec![Some(5), None, Some(20)]; + assert_eq!(depth(&layers, &p, &s), (2, Budget::NotApplied), "an unlabelled layer leaves the sum unknown"); + assert_eq!(depth(&[], &p, &s).0, 0); +} + +fn appendix_d() -> Vec { + WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![ + Rendition::media("wai.video.av1", "av1", pattern(3000, 7, 3)).with_id("low").with_payload_digest(), + Rendition::media("wai.video.av1", "av1", pattern(50_000, 13, 5)).with_id("high").with_payload_digest(), + Rendition::companion("wai.meta.hdr10plus", pattern(200, 31, 1), "wai.video.av1").with_id("hdr") + .with_payload_digest().with_companion_of_id("high"), + ]).to_bytes().unwrap() +} + +/// Three capabilities, two sizes of one, in table order av1-large, vp9, av1-small. +fn mixed() -> Vec { + let mut wrong = Rendition::media("wai.video.av1", "av1", pattern(9000, 3, 1)).with_id("big").with_payload_digest(); + wrong.meta.sha256 = Some(crate::pin::sha256_hex(b"not these bytes")); + WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![ + wrong, + Rendition::media("wai.video.vp9", "vp9", pattern(4000, 5, 1)).with_id("v").with_payload_digest(), + Rendition::media("wai.video.av1", "av1", pattern(2000, 7, 2)).with_id("small").with_payload_digest(), + ]).to_bytes().unwrap() +} + +/// Appendix D under a throughput policy: low throughput keeps `low` first, high puts +/// `high` first; the companion stays after the options; an ineligible capability is not +/// an option. +#[test] +fn order_entries_appendix_d() { + let h = head_of(&appendix_d()); + let all = |_: &str| true; + let p = Throughput { last_n: 1, safety_permille: 900 }; + // `high` is 50,000 B a second: 400 kb/s. + assert_eq!(order_entries(&h, &all, &p, &state(0, 100_000, &[])), vec![0, 1, 2]); + assert_eq!(order_entries(&h, &all, &p, &state(0, 10_000_000, &[])), vec![1, 0, 2]); + let only_vp9 = |c: &str| c == "wai.video.vp9"; + assert_eq!(order_entries(&h, &only_vp9, &p, &state(0, 10_000_000, &[])), vec![0, 1, 2]); + let m = head_of(&mixed()); + // av1 options by size: small (2), big (0); vp9 alone. + assert_eq!(order_entries(&m, &all, &p, &state(0, 10_000_000, &[])), vec![0, 2, 1]); + assert_eq!(order_entries(&m, &all, &p, &state(0, 10, &[])), vec![2, 0, 1]); +} + +fn fetch_with(env: &[u8], adv: &'static [&'static str], p: Box, s: AbrState, frag: Fragment) -> Result { + let proof = HeadProof::build(env).unwrap().to_bytes(); + let mut sel = Selector::new(move |c| adv.contains(&c)); + sel.order = Some(selector_order(p, s, move |c| adv.contains(&c))); + let mut sess = FetchSession::new(content_hash(env), frag, FetchPolicy::default(), sel); + drive(&mut sess, |w| { + let range = match w { + Want::HeadProof => return Some((ResponseMeta { status: 200, ..Default::default() }, proof.clone())), + Want::Whole(_) => None, + Want::Range(r, _) => Some(r.header()), + Want::Rest(r, _) => Some(format!("bytes={}-", r.first)), + }; + let a = origin::serve(env.len() as u64, range.as_deref()); + Some((ResponseMeta { status: a.status, content_range: a.content_range, content_encoding: None }, env[a.body.start as usize..a.body.end as usize].to_vec())) + }) +} + +fn whole_pick(env: &[u8], adv: &[&str], order: &[usize]) -> Result> { + let w = WaiMulti::from_bytes(env).unwrap(); + let eligible = |_: usize, e: &RenditionMeta| if adv.contains(&e.capability.as_str()) { Ok(()) } else { Err(Skip::NotEligible) }; + let digest_ok = |i: usize, e: &RenditionMeta| e.payload_digest_matches(&w.renditions[i]); + crate::container::select_entries(&w.manifest, order, eligible, digest_ok, + |c: &str, _: &crate::container::Prior| Err::<(), _>(crate::container::PinRefusal::NotHeld { capability: c.into() })) + .map(|(i, _, _)| i) +} + +/// The order a policy gives is the order on both paths: for every policy, state and +/// sink, the range fetch picks what whole-envelope selection picks in that order. Under +/// high throughput Appendix D's `high` is picked, which (with its companion) is past +/// the threshold, so the object is fetched whole. +#[test] +fn same_pick_under_policies() { + const ADV: [&[&str]; 4] = [&["wai.video.av1"], &["wai.video.vp9"], &["wai.video.av1", "wai.video.vp9", "wai.meta.hdr10plus"], &[]]; + let policies = || -> Vec> { + vec![ + Box::new(Throughput { last_n: 2, safety_permille: 900 }), + Box::new(BufferBased { reservoir_ms: 2000, cushion_ms: 8000 }), + Box::new(BolaLike { v_milli: 900, max_buffer_ms: 12_000 }), + Box::new(Mpc { horizon: 3, safety_permille: 800 }), + ] + }; + for env in [appendix_d(), mixed()] { + let h = head_of(&env); + for adv in ADV { + for (buffer, bps) in [(0u32, 50_000u64), (6000, 900_000), (20_000, 50_000_000)] { + for (k, p) in policies().into_iter().enumerate() { + let s = state(buffer, bps, &[]); + let order = order_entries(&h, &|c| adv.contains(&c), p.as_ref(), &s); + let want = whole_pick(&env, adv, &order); + let got = fetch_with(&env, adv, policies().remove(k), s, Fragment { h: Some(1009), n: Some(env.len() as u64), ..Default::default() }); + match (&want, &got) { + (Ok(i), Ok(f)) => assert_eq!(*i, f.index, "{adv:?} {buffer} {bps} {}", p.id()), + (Err(_), Err(RangeRefusal::Selection(_))) => {} + _ => panic!("paths differ: whole {want:?}, range {got:?}"), + } + } + } + } + } + let f = fetch_with(&appendix_d(), &["wai.video.av1"], Box::new(Throughput { last_n: 1, safety_permille: 900 }), state(0, 10_000_000, &[]), + Fragment { h: Some(1009), n: Some(53_794), ..Default::default() }).unwrap(); + assert_eq!((f.index, f.verified_by), (1, VerifiedBy::WholeObject)); +} -- 2.54.0 (Apple Git-157) From 85bb26425a24ec41c709ba278deee233dc9d4b40 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 05:50:39 -0400 Subject: [PATCH 04/55] wai: wai.mpd, playlist receipts and listed cids (W3-4) dash::wai_mpd writes a static MPD with one WAI AdaptationSet (application/wai, the union as @codecs, the delivery EssentialProperty and the floor SupplementalProperty) whose SegmentTimeline is the classical timeline, given or read from the classical HLS playlist (DashTimeline::from_hls). playlist_receipt (with video_manifest) seals a WAI playlist file in its rendered form under a public class (window = media sequence), checks a playlist against mr/.json in a fixed order (playlist_receipt_malformed, receipt_name_mismatch, playlist_receipt_mismatch, playlist_receipt_invalid) and holds a live revision chain to one signer and its parents (playlist_receipt_unchained). hls::listed_cid gives the cid a playlist lists for an alias or an object name. The segment kind's leaf tag moves into the receipt. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/hls_render.rs | 8 -- wai/wai-rs/src/http_binding/dash.rs | 113 ++++++++++++++++ wai/wai-rs/src/http_binding/hls.rs | 29 ++++ wai/wai-rs/src/http_binding/mod.rs | 6 + .../src/http_binding/playlist_receipt.rs | 126 ++++++++++++++++++ .../src/http_binding/tests_playlists.rs | 112 ++++++++++++++++ wai/wai-rs/src/wai_video_manifest.rs | 5 +- 7 files changed, 390 insertions(+), 9 deletions(-) create mode 100644 wai/wai-rs/src/http_binding/dash.rs create mode 100644 wai/wai-rs/src/http_binding/playlist_receipt.rs diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs index 3baa4daf5..dcea26bfc 100644 --- a/wai/wai-rs/src/hls_render.rs +++ b/wai/wai-rs/src/hls_render.rs @@ -22,14 +22,6 @@ pub enum SegmentKind { } impl SegmentKind { - /// The byte a manifest receipt's segment leaf carries. - pub(crate) fn tag(self) -> u8 { - match self { - SegmentKind::Content => 1, - SegmentKind::Ad => 2, - } - } - /// `content` or `ad`. pub fn label(self) -> &'static str { match self { diff --git a/wai/wai-rs/src/http_binding/dash.rs b/wai/wai-rs/src/http_binding/dash.rs new file mode 100644 index 000000000..685d25ba2 --- /dev/null +++ b/wai/wai-rs/src/http_binding/dash.rs @@ -0,0 +1,113 @@ +//! `wai.mpd` (http-delivery §11.1): a separate static MPD (ISO/IEC 23009-1) with one +//! `AdaptationSet` per WAI stream, `mimeType="application/wai"`, `@codecs` the union, an +//! `EssentialProperty` a client that does not implement the extension ignores the set +//! by, and a `SupplementalProperty` carrying the floor. Its timeline is the classical +//! one (§11.2: the same `Period@id` and `@start`, and a `SegmentTimeline` with the same +//! timescale, `t` and `d`), given by the caller, or read from the classical HLS media +//! playlist it shares a timeline with ([`DashTimeline::from_hls`]). +//! +//! A DASH playlist is not authenticated in this revision: the playlist receipt (§8.6) +//! signs HLS renderings only. + +use super::codecs::{self, Element}; +use super::hls::{MediaPlaylist, PlaylistError}; + +/// The `EssentialProperty` scheme a client that does not implement the extension +/// ignores a WAI `AdaptationSet` by. +pub const SCHEME_DELIVERY: &str = "tag:transaction.science,2026:wai/http-delivery"; +/// The `SupplementalProperty` scheme whose `value` is the floor. +pub const SCHEME_FLOOR: &str = "tag:transaction.science,2026:wai/floor"; + +/// The classical timeline a WAI `AdaptationSet` is aligned with: one period's `@id` +/// and `@start`, and its segments' `t` and `d` at `timescale`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DashTimeline { + pub period_id: String, + pub period_start_ms: u64, + pub timescale: u64, + pub segments: Vec<(u64, u64)>, +} + +impl DashTimeline { + /// The timeline of a classical HLS media playlist at timescale 1000: `t` from 0, + /// each `d` a segment's duration in milliseconds. + pub fn from_hls(p: &MediaPlaylist, period_id: &str, period_start_ms: u64) -> DashTimeline { + let mut t = 0u64; + let segments = p.segments.iter().map(|s| { + let at = t; + t = t.saturating_add(s.duration_ms); + (at, s.duration_ms) + }).collect(); + DashTimeline { period_id: period_id.to_string(), period_start_ms, timescale: 1000, segments } + } +} + +/// One `Representation` of the WAI `AdaptationSet`: its `@id`, its `@bandwidth` (the +/// peak of `hls::bandwidths`) and each segment's object URI with its fragment. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WaiRepresentation { + pub id: String, + pub bandwidth: u64, + pub media: Vec, +} + +/// An XML attribute value, escaped. +fn esc(s: &str) -> String { + s.replace('&', "&").replace('<', "<").replace('>', ">").replace('"', """) +} + +/// `PTS` with three fractional digits. +fn duration(ms: u64) -> String { + format!("PT{}S", crate::hls_render::seconds(ms)) +} + +/// `wai.mpd`: a static MPD with one period and one WAI `AdaptationSet` (`@id` +/// `set_id`), its representations each listing one `SegmentURL` per segment of the +/// timeline under a `SegmentList` whose `SegmentTimeline` has one `S` per segment with +/// its `t` and `d`. `mediaPresentationDuration` is the timeline's length and +/// `minBufferTime` its longest segment. Attribute order is fixed and lines end in LF. +/// Refused (`playlist_malformed`) for no segment or representation, a timescale of 0, +/// a representation with another number of segments, an `@id` that is empty or holds +/// white space, or a floor whose container is not the union's. +pub fn wai_mpd(t: &DashTimeline, set_id: u32, union: &Element, floor: &Element, reps: &[WaiRepresentation]) -> Result { + let bad = |s: &str| PlaylistError::Malformed(s.to_string()); + if t.segments.is_empty() || reps.is_empty() || t.timescale == 0 { + return Err(bad("an MPD needs a segment, a representation and a timescale")); + } + if floor.prefix != union.prefix { + return Err(bad("the floor is written with the union's prefix")); + } + let id_ok = |s: &str| !s.is_empty() && !s.chars().any(char::is_whitespace); + if !id_ok(&t.period_id) || reps.iter().any(|r| !id_ok(&r.id) || r.media.len() != t.segments.len()) { + return Err(bad("an @id that is empty or holds white space, or a representation of another length")); + } + let to_ms = |ticks: u64| u64::try_from(u128::from(ticks) * 1000 / u128::from(t.timescale)).unwrap_or(u64::MAX); + let total: u64 = t.segments.iter().map(|&(_, d)| to_ms(d)).fold(0, u64::saturating_add); + let longest = t.segments.iter().map(|&(_, d)| to_ms(d)).max().unwrap_or(0); + let mut out = String::from("\n"); + out.push_str(&format!( + "\n", + duration(total), duration(longest) + )); + out.push_str(&format!(" \n", esc(&t.period_id), duration(t.period_start_ms))); + out.push_str(&format!( + " \n", + esc(&codecs::render(union)) + )); + out.push_str(&format!(" \n")); + out.push_str(&format!(" \n", esc(&codecs::render(floor)))); + for r in reps { + out.push_str(&format!(" \n", esc(&r.id), r.bandwidth)); + out.push_str(&format!(" \n \n", t.timescale)); + for &(st, d) in &t.segments { + out.push_str(&format!(" \n")); + } + out.push_str(" \n"); + for m in &r.media { + out.push_str(&format!(" \n", esc(m))); + } + out.push_str(" \n \n"); + } + out.push_str(" \n \n\n"); + Ok(out) +} diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs index ac1a8a813..8bf302781 100644 --- a/wai/wai-rs/src/http_binding/hls.rs +++ b/wai/wai-rs/src/http_binding/hls.rs @@ -710,3 +710,32 @@ pub fn package_map( Ok((media_sequence.saturating_add(i as u64), e.content_hash, s.cid)) }).collect() } + +/// The content hash a playlist lists for the object at `uri` (without its fragment), +/// as §5.2 and §11.4 let a client learn it: the `cid` fragment of a part, segment or +/// layer line naming `uri`, or the `.wai` name a segment or layer URI ends in. +/// The first line naming `uri` decides. `None` when no line names it, or the one that +/// does lists no cid. +pub fn listed_cid(p: &MediaPlaylist, uri: &str) -> Option<[u8; 32]> { + fn parts<'a>(e: &'a SegmentExtra, lines: &mut Vec<&'a str>) { + for part in &e.parts { + lines.push(&part.uri); + lines.extend(part.layers.iter().map(|l| l.uri.as_str())); + } + } + let mut lines: Vec<&str> = Vec::new(); + for s in &p.segments { + parts(&s.extra, &mut lines); + lines.extend(s.extra.layers.iter().map(|l| l.uri.as_str())); + lines.push(&s.uri); + } + if let Some(o) = &p.open { + parts(o, &mut lines); + } + let line = lines.into_iter().find(|l| super::parse_fragment(l).0 == uri)?; + let (base, frag) = super::parse_fragment(line); + frag.cid.or_else(|| { + let name = base.rsplit('/').next()?; + super::unhex32(name.strip_suffix(".wai")?) + }) +} diff --git a/wai/wai-rs/src/http_binding/mod.rs b/wai/wai-rs/src/http_binding/mod.rs index 273804927..ca8f9b25e 100644 --- a/wai/wai-rs/src/http_binding/mod.rs +++ b/wai/wai-rs/src/http_binding/mod.rs @@ -14,18 +14,24 @@ //! `WAI2` and a layer depth (Appendix F); //! - [`hls`]: WAI media and multivariant playlists beside a classical HLS //! presentation, their adjacency and the session-data line (§11); +//! - [`dash`]: `wai.mpd` on the classical timeline (§11.1); +//! - `playlist_receipt` (with `video_manifest`): playlist receipts, sealed and checked, +//! and a live revision chain (§8.6, §11.6); //! - [`origin`]: an origin's responses (§5.3) and byte-range answers. //! //! Nothing here performs I/O: the `wai http` commands and the reference origin do. pub mod abr; pub mod codecs; +pub mod dash; pub mod head; pub mod head_proof; pub mod hls; pub mod origin; pub mod plan; pub mod playlist; +#[cfg(feature = "video_manifest")] +pub mod playlist_receipt; pub mod session; #[cfg(test)] diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs new file mode 100644 index 000000000..a42d4379b --- /dev/null +++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs @@ -0,0 +1,126 @@ +//! Playlist receipts (http-delivery §8.6, §11.6): a WAI playlist sealed by a +//! `wai.video.manifest` receipt whose `manifest_hash` is the BLAKE3 of the playlist's +//! bytes, served at `mr/.json`, and each live revision chained to the +//! one before it. +//! +//! A public WAI playlist is sealed under [`public_class`]: entitlement `public`, no +//! geography, blackout or ad pod, and the playlist's media sequence as the window. The +//! receipt carries the playlist's rendering (`render`, `extras`), so a verifier renders +//! the playlist from the receipt and compares bytes. + +use ed25519_dalek::SigningKey; + +use super::hls::{parse_media_playlist, PlaylistError}; +use crate::quantum_ops::GrantRef; +use crate::wai_video_manifest::{ManifestReceipt, SessionClass}; + +/// Why a playlist and its receipt are refused (http-delivery §13). +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ReceiptError { + /// The receipt file does not read as one (`playlist_receipt_malformed`). + Malformed(String), + /// The file's name is not `.json` (`receipt_name_mismatch`, §8.1). + NameMismatch, + /// The playlist's bytes are not the ones the receipt renders and hashes + /// (`playlist_receipt_mismatch`). + Mismatch, + /// The receipt does not verify: its signature, or a value it recomputes + /// (`playlist_receipt_invalid`). + Invalid, + /// A live revision whose parent is not the receipt before it + /// (`playlist_receipt_unchained`). + Unchained, +} + +impl ReceiptError { + pub fn code(&self) -> &'static str { + match self { + ReceiptError::Malformed(_) => "playlist_receipt_malformed", + ReceiptError::NameMismatch => "receipt_name_mismatch", + ReceiptError::Mismatch => "playlist_receipt_mismatch", + ReceiptError::Invalid => "playlist_receipt_invalid", + ReceiptError::Unchained => "playlist_receipt_unchained", + } + } +} + +impl std::fmt::Display for ReceiptError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ReceiptError::Malformed(s) => write!(f, "{}: {s}", self.code()), + other => f.write_str(other.code()), + } + } +} + +impl std::error::Error for ReceiptError {} + +/// The session class of a public WAI playlist: `content_id` as given, entitlement +/// `public`, no geography, blackout or ad pod, and the window `media_sequence`. +pub fn public_class(content_id: &str, media_sequence: u64) -> SessionClass { + SessionClass { + content_id: content_id.to_string(), + entitlement: "public".into(), + geo: String::new(), + blackouts: Vec::new(), + ad_pod_id: String::new(), + window: media_sequence, + } +} + +/// Seal a WAI playlist (its text, in the form this binding renders) under +/// [`public_class`], unmetered, for one generation, with `parent`'s receipt hash as +/// its parent. Refused when the text does not parse or is not in the rendered form +/// (`playlist_malformed`). +pub fn seal_playlist( + text: &str, + signer: &SigningKey, + signer_id: &str, + content_id: &str, + grant: GrantRef, + parent: Option<&ManifestReceipt>, +) -> Result { + let p = parse_media_playlist(text)?; + if !p.is_rendered_form(text) { + return Err(PlaylistError::Malformed("the playlist is not in the form this binding renders, so its rendering cannot be sealed".into())); + } + let (segments, render, extras) = p.render_inputs(); + let r = ManifestReceipt::seal_with(signer, signer_id, public_class(content_id, render.media_sequence), segments, 1, 0, None, grant, + parent.map(ManifestReceipt::receipt_hash), render, extras) + .map_err(|e| PlaylistError::Malformed(e.0))?; + debug_assert!(r.verify_render(text.as_bytes())); + Ok(r) +} + +/// §8.6, §11.6: check `playlist` against the receipt file `receipt` named `name` (its +/// file name, when the client fetched it as `mr/`), in order: the file reads as a +/// receipt (`playlist_receipt_malformed`); its name is `.json` +/// (`receipt_name_mismatch`); the playlist's BLAKE3 is `manifest_hash` and the receipt's +/// rendering gives the playlist's bytes (`playlist_receipt_mismatch`); the receipt +/// verifies (`playlist_receipt_invalid`). +pub fn check_playlist(playlist: &[u8], receipt: &[u8], name: Option<&str>) -> Result { + let text = std::str::from_utf8(receipt).map_err(|_| ReceiptError::Malformed("not UTF-8".into()))?; + let r = ManifestReceipt::from_json(text).map_err(ReceiptError::Malformed)?; + if name.is_some_and(|n| n != r.receipt_file_name()) { + return Err(ReceiptError::NameMismatch); + } + if *blake3::hash(playlist).as_bytes() != r.manifest_hash || r.rendered().as_bytes() != playlist { + return Err(ReceiptError::Mismatch); + } + if !r.verify() { + return Err(ReceiptError::Invalid); + } + Ok(r) +} + +/// A live playlist's receipts, oldest first (§8.6): each after the first names the one +/// before it as its parent, and all have one signer. The first failing revision's index +/// and `playlist_receipt_unchained`. +pub fn check_chain(revisions: &[ManifestReceipt]) -> Result<(), (usize, ReceiptError)> { + for (k, w) in revisions.windows(2).enumerate() { + if w[1].parent_receipt_hash != Some(w[0].receipt_hash()) || w[1].signer_pubkey != w[0].signer_pubkey { + return Err((k + 1, ReceiptError::Unchained)); + } + } + Ok(()) +} diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index 17c4318aa..5fefb41c0 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -248,3 +248,115 @@ fn package_map_by_sequence_number() { assert_eq!(package_map(&[e(0, 640), e(1, 600)], &segs, 0).unwrap_err().code(), "adjacency_misaligned"); assert!(package_map(&[e(0, 640)], &segs, 0).is_err()); } + +/// `wai.mpd`: one WAI AdaptationSet with the union, the essential and supplemental +/// properties, the classical timeline's `t` and `d`, and each object's URI with its +/// fragment escaped. +#[test] +fn wai_mpd_shape() { + use super::dash::*; + let classical = parse_media_playlist(CLASSICAL_VOD).unwrap(); + let t = DashTimeline::from_hls(&classical, "p0", 0); + assert_eq!(t.segments, vec![(0, 640), (640, 640), (1280, 640)]); + let union = parse_element("wai2.video.av1|video.int_hyper").unwrap(); + let floor = parse_element("wai2.video.av1").unwrap(); + let rep = WaiRepresentation { id: "wai-180".into(), bandwidth: 262_925, media: (1..=3).map(|b| format!("../objects/{}.wai#n=9&h=1009", super::hex(&cid(b)))).collect() }; + let m = wai_mpd(&t, 1, &union, &floor, &[rep.clone()]).unwrap(); + assert!(m.starts_with("\n\n \n")); + assert!(m.contains("\n \n \n")); + assert!(m.contains("\n")); + assert!(m.contains("#n=9&h=1009\"/>")); + assert!(!m.contains("#n=9&h")); + let mut short = rep.clone(); + short.media.pop(); + assert_eq!(wai_mpd(&t, 1, &union, &floor, &[short]).unwrap_err().code(), "playlist_malformed"); + assert!(wai_mpd(&t, 1, &union, &parse_element("wai1.video.av1").unwrap(), &[rep.clone()]).is_err()); + assert!(wai_mpd(&DashTimeline { timescale: 0, ..t.clone() }, 1, &union, &floor, &[rep.clone()]).is_err()); + let mut spaced = rep; + spaced.id = "a b".into(); + assert!(wai_mpd(&t, 1, &union, &floor, &[spaced]).is_err()); +} + +/// §5.2, §11.4: the cid a playlist lists for a part's alias (its `cid` fragment) and +/// for a segment or layer (the `.wai` name); nothing for a URI no line names. +#[test] +fn listed_cid_alias_and_name() { + let ll = parse_media_playlist(CLASSICAL_LL).unwrap(); + let mut s0 = seg(1, 2000); + s0.parts = (0..4).map(|i| wpart(1200, i, 500)).collect(); + s0.layers = vec![WaiLayer { n: 1, cid: cid(0xe1), len: 70 }]; + let open = WaiOpen { parts: (0..2).map(|i| wpart(1201, i, 500)).collect(), preload_hint: Some("live/1201.2.wai".into()) }; + let (_, _, _, text) = wai_media_playlist(&ll, &[s0], Some(&open), "objects/", false).unwrap(); + let p = parse_media_playlist(&text).unwrap(); + assert_eq!(listed_cid(&p, "live/1200.2.wai"), Some(cid(0x82))); + assert_eq!(listed_cid(&p, "live/1201.1.wai"), Some(cid(0x81))); + assert_eq!(listed_cid(&p, &format!("objects/{}.wai", super::hex(&cid(1)))), Some(cid(1))); + assert_eq!(listed_cid(&p, &format!("objects/{}.wai", super::hex(&cid(0xe1)))), Some(cid(0xe1))); + assert_eq!(listed_cid(&p, "live/1201.2.wai"), None, "a hint lists no cid"); + assert_eq!(listed_cid(&p, "live/9.9.wai"), None); +} + +#[cfg(feature = "video_manifest")] +mod receipts { + use super::super::hls::*; + use super::super::playlist_receipt::*; + use super::{cid, seg, wpart, CLASSICAL_LL, CLASSICAL_VOD}; + use crate::quantum_ops::GrantRef; + use crate::wai_video_manifest::ManifestReceipt; + use ed25519_dalek::SigningKey; + + fn key() -> SigningKey { + SigningKey::from_bytes(&[21u8; 32]) + } + + /// A WAI playlist sealed from its file and checked against its receipt file: the + /// name, the bytes, the signature, the file's form, each refused by its code. + #[test] + fn seal_and_check_playlist() { + let vod = parse_media_playlist(CLASSICAL_VOD).unwrap(); + let (_, _, _, text) = wai_media_playlist(&vod, &[seg(1, 640), seg(2, 640), seg(3, 640)], None, "../objects/", true).unwrap(); + let r = seal_playlist(&text, &key(), "did:example:test", "players/v0", GrantRef::unbounded("video.manifest"), None).unwrap(); + let file = r.to_json(); + let name = r.receipt_file_name(); + assert_eq!(name, format!("{}.json", blake3::hash(text.as_bytes()).to_hex())); + assert_eq!(check_playlist(text.as_bytes(), file.as_bytes(), Some(&name)).unwrap(), r); + assert_eq!(r.class, public_class("players/v0", 0)); + let code = |pl: &[u8], f: &[u8], n: Option<&str>| check_playlist(pl, f, n).unwrap_err().code(); + assert_eq!(code(text.as_bytes(), file.as_bytes(), Some("00.json")), "receipt_name_mismatch"); + let mut flipped = text.clone().into_bytes(); + flipped[40] ^= 1; + assert_eq!(code(&flipped, file.as_bytes(), Some(&name)), "playlist_receipt_mismatch"); + let sig = crate::http_binding::hex(&r.sig); + let mut bad_sig = sig.clone(); + bad_sig.replace_range(0..2, if &sig[0..2] == "00" { "01" } else { "00" }); + assert_eq!(code(text.as_bytes(), file.replace(&sig, &bad_sig).as_bytes(), Some(&name)), "playlist_receipt_invalid"); + assert_eq!(code(text.as_bytes(), b"{", Some(&name)), "playlist_receipt_malformed"); + assert_eq!(code(text.as_bytes(), file.replace("\"public\"", "\"svod\"").as_bytes(), Some(&name)), "playlist_receipt_invalid"); + // A playlist not in the rendered form is not sealed. + assert_eq!(seal_playlist(&text.replace('\n', "\r\n"), &key(), "o", "c", GrantRef::unbounded("video.manifest"), None).unwrap_err().code(), "playlist_malformed"); + } + + /// Live revisions sealed with their parents form a chain; one sealed against + /// another parent, or by another signer, breaks it at that revision. + #[test] + fn live_revision_chain() { + let ll = parse_media_playlist(CLASSICAL_LL).unwrap(); + let mut s0 = seg(1, 2000); + s0.parts = (0..4).map(|i| wpart(1200, i, 500)).collect(); + let revs: Vec = [1usize, 2].iter().map(|&k| { + let open = WaiOpen { parts: (0..2).map(|i| wpart(1201, i, 500)).collect(), preload_hint: Some(format!("live/1201.{}.wai", k + 1)) }; + wai_media_playlist(&ll, &[s0.clone()], Some(&open), "objects/", false).unwrap().3 + }).collect(); + let g = || GrantRef::unbounded("video.manifest"); + let a = seal_playlist(&revs[0], &key(), "k", "live", g(), None).unwrap(); + let b = seal_playlist(&revs[1], &key(), "k", "live", g(), Some(&a)).unwrap(); + check_chain(&[a.clone(), b.clone()]).unwrap(); + assert_eq!(a.class.window, 1200); + let orphan = seal_playlist(&revs[1], &key(), "k", "live", g(), None).unwrap(); + assert_eq!(check_chain(&[a.clone(), orphan]).unwrap_err(), (1, ReceiptError::Unchained)); + let other = seal_playlist(&revs[1], &SigningKey::from_bytes(&[22u8; 32]), "k", "live", g(), Some(&a)).unwrap(); + assert_eq!(check_chain(&[a.clone(), other]).unwrap_err().0, 1); + let _: Vec = vec![a, b]; + let _ = cid(0); + } +} diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs index 273ce8f09..fd25c6287 100644 --- a/wai/wai-rs/src/wai_video_manifest.rs +++ b/wai/wai-rs/src/wai_video_manifest.rs @@ -67,7 +67,10 @@ impl Segment { let mut h = blake3::Hasher::new(); h.update(DOMAIN_SEG); h.update(&self.index.to_be_bytes()); - h.update(&[self.kind.tag()]); + h.update(&[match self.kind { + SegmentKind::Content => 1, + SegmentKind::Ad => 2, + }]); h.update(&self.duration_ms.to_be_bytes()); h.update(&(self.uri.len() as u32).to_be_bytes()); h.update(self.uri.as_bytes()); -- 2.54.0 (Apple Git-157) From e199867ec30bf4222bd72db4acb95ed37ad9508e Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 05:54:16 -0400 Subject: [PATCH 05/55] wai: wai http hls, mpd, seal-playlist and check-playlist (W3-4) The wai CLI writes a variant's WAI media playlist aligned with its classical one and wai.m3u8 (union, floor, computed bandwidths) from an objects list, and wai.mpd on the same timeline; seal-playlist writes mr/.json for a playlist in its rendered form (with --parent for a live revision); check-playlist reports signed, or the reason it is not, and checks the parent and adjacency. Without video_manifest the two receipt commands say so. The origin serves .m3u8 and .mpd to be revalidated on every use. hls::{WaiSegment, WaiPart, WaiLayer}::of build entries from envelopes, a layer's N from its refines.layer. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/bin/wai.rs | 3 + wai/wai-rs/src/bin/wai_cli/http.rs | 17 +- wai/wai-rs/src/bin/wai_cli/http_playlists.rs | 274 +++++++++++++++++++ wai/wai-rs/src/http_binding/hls.rs | 39 +++ wai/wai-rs/src/http_binding/origin.rs | 11 + wai/wai-rs/tests/cli_http.rs | 94 +++++++ 6 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 wai/wai-rs/src/bin/wai_cli/http_playlists.rs diff --git a/wai/wai-rs/src/bin/wai.rs b/wai/wai-rs/src/bin/wai.rs index ffdb0b173..7baec4ca8 100644 --- a/wai/wai-rs/src/bin/wai.rs +++ b/wai/wai-rs/src/bin/wai.rs @@ -28,6 +28,9 @@ mod http; #[cfg(feature = "http")] #[path = "wai_cli/http_vectors.rs"] mod http_vectors; +#[cfg(feature = "http")] +#[path = "wai_cli/http_playlists.rs"] +mod http_playlists; fn die(msg: impl std::fmt::Display) -> ExitCode { eprintln!("error: {msg}"); diff --git a/wai/wai-rs/src/bin/wai_cli/http.rs b/wai/wai-rs/src/bin/wai_cli/http.rs index 0fd7fee3e..aa98a410c 100644 --- a/wai/wai-rs/src/bin/wai_cli/http.rs +++ b/wai/wai-rs/src/bin/wai_cli/http.rs @@ -25,6 +25,14 @@ //! answers 400 to a //! path segment that is empty, `.` or `..`, or holds `\`, `:`, NUL or a control //! byte +//! wai http hls --objects --out [--master wai.m3u8] +//! wai http mpd --objects --out [--period-id ID] [--set-id N] +//! wai http seal-playlist --key [--signer-id ID] [--content-id ID] +//! [--parent ] [--out ] +//! wai http check-playlist --receipt [--parent ] +//! [--classical ] +//! WAI playlists beside a classical presentation, and their receipts (§8.6, §11; +//! wai_cli/http_playlists.rs) //! wai http vectors gen|verify|diff … //! the corpus tool (as `wai_http_vectors`) //! wai http bench --url http://HOST:PORT/PATH[#frag] --runs N --advertise CAP… --out F @@ -71,7 +79,7 @@ fn positional(args: &[String], flags: &[&str]) -> Vec { } fn usage() -> String { - "usage: wai http publish … --out \n wai http codecs …\n wai http origin --root [--port P] [--delay-range FIRST-LAST:MS]\n wai http vectors gen|verify|diff …\n wai http bench --url URL --runs N --advertise CAP [--advertise CAP]… --out F".into() + "usage: wai http publish … --out \n wai http codecs …\n wai http origin --root [--port P] [--delay-range FIRST-LAST:MS]\n wai http hls --objects --out [--master NAME]\n wai http mpd --objects --out [--period-id ID] [--set-id N]\n wai http seal-playlist --key [--signer-id ID] [--content-id ID] [--parent ] [--out ]\n wai http check-playlist --receipt [--parent ] [--classical ]\n wai http vectors gen|verify|diff …\n wai http bench --url URL --runs N --advertise CAP [--advertise CAP]… --out F".into() } pub fn main(args: &[String]) -> Result { @@ -81,6 +89,10 @@ pub fn main(args: &[String]) -> Result { Some("origin") => origin_cmd(&args[1..]).map(|()| true), Some("vectors") => crate::http_vectors::main(&args[1..]), Some("bench") => bench(&args[1..]).map(|()| true), + Some("hls") => crate::http_playlists::hls(&args[1..]).map(|()| true), + Some("mpd") => crate::http_playlists::mpd(&args[1..]).map(|()| true), + Some("seal-playlist") => crate::http_playlists::seal(&args[1..]).map(|()| true), + Some("check-playlist") => crate::http_playlists::check(&args[1..]), _ => Err(usage()), } } @@ -281,6 +293,9 @@ fn resource(cfg: &OriginConfig, path: &str) -> Option<(Vec, Vec<(&'static st ("ETag", format!("\"{}\"", hex(&content_hash(&bytes)))), ("Access-Control-Allow-Origin", "*".to_string()), ] + } else if name.ends_with(".m3u8") || name.ends_with(".mpd") { + // A playlist changes at a live edge: revalidated on every use (§11). + origin::playlist_headers(if name.ends_with(".mpd") { "application/dash+xml" } else { "application/vnd.apple.mpegurl" }) } else if name.ends_with(".json") { origin::immutable_headers("application/json") } else { diff --git a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs new file mode 100644 index 000000000..29e80eb5c --- /dev/null +++ b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs @@ -0,0 +1,274 @@ +//! `wai http hls|mpd|seal-playlist|check-playlist` — WAI playlists beside a classical +//! HLS presentation (`extensions/http-delivery.md` §8.6, §11): +//! +//! wai http hls --objects --out [--master wai.m3u8] +//! write each variant's WAI media playlist, aligned with its classical media +//! playlist, and the WAI multivariant playlist; print the union and the floor +//! wai http mpd --objects --out [--period-id p0] [--set-id 1] +//! write wai.mpd on the classical timeline, one Representation per variant +//! wai http seal-playlist --key +//! [--signer-id ID] [--content-id ID] [--parent ] +//! [--out ] +//! seal a WAI playlist in its rendered form; write /mr/.json +//! (dir: the playlist's own by default) and print its path and receipt hash +//! wai http check-playlist --receipt +//! [--parent ] [--classical ] +//! check the playlist against its receipt (and its parent, and adjacency to the +//! classical playlist); print what held, or the reason it did not +//! +//! The objects list, its paths relative to its own directory: +//! +//! { "uri_prefix": "objects/", "end_list": true, +//! "variants": [ { "uri": "wai-720.m3u8", "classical": "classical-720.m3u8", +//! "resolution": [1280, 720], "frame_rate_milli": 25000, +//! "segments": [ { "object": "a.wai", "layers": ["l1.wai"], +//! "parts": [ { "object": "p.wai", "alias": "live/720/1200.0.wai", +//! "independent": true, "layers": [] } ] } ], +//! "open": { "parts": [ … ], "preload_hint": "live/720/1201.2.wai" } } ] } +//! +//! A segment's and a part's duration is the classical playlist's (a part's own +//! `duration_ms` where the classical segment lists no parts); `end_list` defaults to +//! the classical playlist's. + +use std::path::{Path, PathBuf}; + +use serde_json::Value; + +use wai::http_binding::codecs::{self, SeqObject}; +use wai::http_binding::dash::{wai_mpd, DashTimeline, WaiRepresentation}; +use wai::http_binding::hls::{ + bandwidths, parse_media_playlist, wai_media_playlist, wai_multivariant, MediaPlaylist, VariantLine, WaiLayer, WaiOpen, + WaiPart, WaiSegment, +}; +use wai::http_binding::hex; + +fn opt<'a>(args: &'a [String], flag: &str) -> Option<&'a str> { + args.iter().position(|a| a == flag).and_then(|i| args.get(i + 1).map(String::as_str)) +} + +fn read(p: &Path) -> Result, String> { + std::fs::read(p).map_err(|e| format!("{}: {e}", p.display())) +} + +fn write(p: &Path, b: &[u8]) -> Result<(), String> { + if let Some(d) = p.parent() { + std::fs::create_dir_all(d).map_err(|e| format!("{}: {e}", d.display()))?; + } + std::fs::write(p, b).map_err(|e| format!("{}: {e}", p.display())) +} + +/// One variant of an objects list, read: its classical playlist and the objects that +/// cover it, with every envelope's bytes (for the union and the floor). +struct Variant { + uri: String, + classical: MediaPlaylist, + resolution: Option<(u32, u32)>, + frame_rate_milli: Option, + segments: Vec, + open: Option, + envelopes: Vec>, +} + +struct List { + uri_prefix: String, + end_list: Option, + variants: Vec, +} + +fn strs(v: &Value) -> Vec { + v.as_array().into_iter().flatten().filter_map(|x| x.as_str().map(String::from)).collect() +} + +fn layers(dir: &Path, v: &Value, envelopes: &mut Vec>) -> Result, String> { + strs(v).iter().map(|p| { + let env = read(&dir.join(p))?; + let l = WaiLayer::of(&env).map_err(|e| format!("{p}: {e}"))?; + envelopes.push(env); + Ok(l) + }).collect() +} + +fn parts(dir: &Path, v: &Value, classical_ms: &[u64], envelopes: &mut Vec>) -> Result, String> { + v.as_array().into_iter().flatten().enumerate().map(|(j, p)| { + let path = p["object"].as_str().ok_or("a part names its object")?; + let alias = p["alias"].as_str().ok_or("a part names its alias")?; + let ms = classical_ms.get(j).copied().or(p["duration_ms"].as_u64()) + .ok_or(format!("{path}: no classical part {j} and no duration_ms"))?; + let env = read(&dir.join(path))?; + let mut part = WaiPart::of(&env, alias, ms, p["independent"].as_bool() == Some(true)).map_err(|e| format!("{path}: {e}"))?; + envelopes.push(env); + part.layers = layers(dir, &p["layers"], envelopes)?; + Ok(part) + }).collect() +} + +fn load(path: &Path) -> Result { + let dir = path.parent().unwrap_or(Path::new(".")); + let v: Value = serde_json::from_slice(&read(path)?).map_err(|e| format!("{}: {e}", path.display()))?; + let mut variants = Vec::new(); + for x in v["variants"].as_array().ok_or("the list has variants")? { + let cpath = x["classical"].as_str().ok_or("a variant names its classical playlist")?; + let ctext = String::from_utf8(read(&dir.join(cpath))?).map_err(|_| format!("{cpath}: not UTF-8"))?; + let classical = parse_media_playlist(&ctext).map_err(|e| format!("{cpath}: {e}"))?; + let mut envelopes = Vec::new(); + let mut segments = Vec::new(); + for (i, s) in x["segments"].as_array().ok_or("a variant has segments")?.iter().enumerate() { + let c = classical.segments.get(i).ok_or(format!("{cpath}: {} segments, and the list has more", classical.segments.len()))?; + let p = s["object"].as_str().ok_or("a segment names its object")?; + let env = read(&dir.join(p))?; + let mut seg = WaiSegment::of(&env, c.duration_ms).map_err(|e| format!("{p}: {e}"))?; + envelopes.push(env); + seg.layers = layers(dir, &s["layers"], &mut envelopes)?; + let cms: Vec = c.extra.parts.iter().map(|q| q.duration_ms).collect(); + seg.parts = parts(dir, &s["parts"], &cms, &mut envelopes)?; + segments.push(seg); + } + let open = match &x["open"] { + Value::Null => None, + o => { + let cms: Vec = classical.open.as_ref().map(|c| c.parts.iter().map(|q| q.duration_ms).collect()).unwrap_or_default(); + Some(WaiOpen { parts: parts(dir, &o["parts"], &cms, &mut envelopes)?, preload_hint: o["preload_hint"].as_str().map(String::from) }) + } + }; + let resolution = x["resolution"].as_array().and_then(|r| Some((u32::try_from(r.first()?.as_u64()?).ok()?, u32::try_from(r.get(1)?.as_u64()?).ok()?))); + variants.push(Variant { + uri: x["uri"].as_str().ok_or("a variant names its URI")?.to_string(), + classical, + resolution, + frame_rate_milli: x["frame_rate_milli"].as_u64().and_then(|f| u32::try_from(f).ok()), + segments, + open, + envelopes, + }); + } + Ok(List { uri_prefix: v["uri_prefix"].as_str().unwrap_or("objects/").to_string(), end_list: v["end_list"].as_bool(), variants }) +} + +/// The union and floor of a variant's objects (§4.2): its segments, layers and parts. +fn union_floor(envs: &[Vec]) -> Result<(codecs::Element, codecs::Element), String> { + let objs: Vec = envs.iter().map(|e| SeqObject::from_envelope(e).map_err(|e| e.to_string())).collect::>()?; + Ok((codecs::union_of(&objs).map_err(|e| e.to_string())?, codecs::floor_of(&objs).map_err(|e| e.to_string())?)) +} + +/// The peak and average of a variant: from its segment objects, or its parts when it +/// has no segment yet. +fn rates(v: &Variant) -> Result<(u64, u64), String> { + let mut objs: Vec<(u64, u64)> = v.segments.iter().map(|s| (s.n, s.duration_ms)).collect(); + if objs.is_empty() { + objs = v.open.iter().flat_map(|o| o.parts.iter().map(|p| (p.n, p.duration_ms))).collect(); + } + bandwidths(&objs).ok_or(format!("{}: no object with a duration to compute a bandwidth from", v.uri)) +} + +pub fn hls(args: &[String]) -> Result<(), String> { + let list = load(Path::new(opt(args, "--objects").ok_or("hls needs --objects ")?))?; + let out = PathBuf::from(opt(args, "--out").ok_or("hls needs --out ")?); + let mut lines = Vec::new(); + for v in &list.variants { + let end = list.end_list.unwrap_or(v.classical.end_list); + let (_, _, _, text) = wai_media_playlist(&v.classical, &v.segments, v.open.as_ref(), &list.uri_prefix, end).map_err(|e| format!("{}: {e}", v.uri))?; + write(&out.join(&v.uri), text.as_bytes())?; + let (union, floor) = union_floor(&v.envelopes)?; + let (peak, avg) = rates(v)?; + println!("{}\tunion {}\tfloor {}\tbandwidth {peak}\taverage {avg}", out.join(&v.uri).display(), codecs::render(&union), codecs::render(&floor)); + lines.push(VariantLine { uri: v.uri.clone(), union, floor, peak_bps: peak, avg_bps: avg, resolution: v.resolution, frame_rate_milli: v.frame_rate_milli }); + } + let master = out.join(opt(args, "--master").unwrap_or("wai.m3u8")); + write(&master, wai_multivariant(&lines).map_err(|e| e.to_string())?.as_bytes())?; + println!("{}", master.display()); + Ok(()) +} + +pub fn mpd(args: &[String]) -> Result<(), String> { + let list = load(Path::new(opt(args, "--objects").ok_or("mpd needs --objects ")?))?; + let out = PathBuf::from(opt(args, "--out").ok_or("mpd needs --out ")?); + let period = opt(args, "--period-id").unwrap_or("p0"); + let set_id: u32 = opt(args, "--set-id").unwrap_or("1").parse().map_err(|_| "--set-id is an unsigned integer")?; + let first = list.variants.first().ok_or("the list has no variant")?; + let timeline = DashTimeline::from_hls(&first.classical, period, 0); + let mut reps = Vec::new(); + let mut all = Vec::new(); + for v in &list.variants { + if DashTimeline::from_hls(&v.classical, period, 0) != timeline { + return Err(format!("{}: its classical timeline is not the first variant's; one AdaptationSet shares one timeline", v.uri)); + } + let (peak, _) = rates(v)?; + let id = v.uri.trim_end_matches(".m3u8").rsplit('/').next().unwrap_or(&v.uri).to_string(); + let media = v.segments.iter().map(|s| format!("{}{}.wai#n={}{}", list.uri_prefix, hex(&s.cid), s.n, s.h.map(|h| format!("&h={h}")).unwrap_or_default())).collect(); + reps.push(WaiRepresentation { id, bandwidth: peak, media }); + all.extend(v.envelopes.iter().cloned()); + } + let (union, floor) = union_floor(&all)?; + write(&out, wai_mpd(&timeline, set_id, &union, &floor, &reps).map_err(|e| e.to_string())?.as_bytes())?; + println!("{}", out.display()); + Ok(()) +} + +/// A 32-byte seed: 64 hexadecimal digits, given or in a file. +#[cfg(feature = "video_manifest")] +fn seed(s: &str) -> Result<[u8; 32], String> { + let text = if wai::http_binding::unhex32(s.trim()).is_some() { s.to_string() } else { String::from_utf8_lossy(&read(Path::new(s))?).to_string() }; + wai::http_binding::unhex32(text.trim()).ok_or_else(|| "--key is 64 lowercase hexadecimal digits, or a file holding them".to_string()) +} + +#[cfg(feature = "video_manifest")] +pub fn seal(args: &[String]) -> Result<(), String> { + use wai::http_binding::playlist_receipt::seal_playlist; + use wai::wai_video_manifest::ManifestReceipt; + let path = args.first().filter(|a| !a.starts_with("--")).ok_or("seal-playlist needs the playlist")?; + let text = String::from_utf8(read(Path::new(path))?).map_err(|_| format!("{path}: not UTF-8"))?; + let key = ed25519_dalek::SigningKey::from_bytes(&seed(opt(args, "--key").ok_or("seal-playlist needs --key")?)?); + let parent = opt(args, "--parent").map(|p| ManifestReceipt::from_json(&String::from_utf8_lossy(&read(Path::new(p))?)).map_err(|e| format!("{p}: {e}"))).transpose()?; + let stem = Path::new(path).file_name().and_then(|n| n.to_str()).unwrap_or("playlist"); + let r = seal_playlist(&text, &key, opt(args, "--signer-id").unwrap_or("unconfirmed"), opt(args, "--content-id").unwrap_or(stem), + wai::quantum_ops::GrantRef::unbounded("video.manifest"), parent.as_ref()).map_err(|e| format!("{path}: {e}"))?; + let dir = opt(args, "--out").map(PathBuf::from).unwrap_or_else(|| Path::new(path).parent().unwrap_or(Path::new(".")).to_path_buf()); + let file = dir.join("mr").join(r.receipt_file_name()); + write(&file, r.to_json().as_bytes())?; + println!("{}\treceipt {}", file.display(), hex(&r.receipt_hash())); + Ok(()) +} + +#[cfg(not(feature = "video_manifest"))] +pub fn seal(_: &[String]) -> Result<(), String> { + Err("built without video_manifest: sealing a playlist needs a build with the features http and video_manifest".into()) +} + +#[cfg(feature = "video_manifest")] +pub fn check(args: &[String]) -> Result { + use wai::http_binding::hls::check_adjacent; + use wai::http_binding::playlist_receipt::{check_chain, check_playlist}; + use wai::wai_video_manifest::ManifestReceipt; + let path = args.first().filter(|a| !a.starts_with("--")).ok_or("check-playlist needs the playlist")?; + let text = read(Path::new(path))?; + let rpath = opt(args, "--receipt").ok_or("check-playlist needs --receipt")?; + let name = Path::new(rpath).file_name().and_then(|n| n.to_str()); + let held = match check_playlist(&text, &read(Path::new(rpath))?, name) { + Ok(r) => r, + Err(e) => { + println!("refused\t{}", e.code()); + return Ok(false); + } + }; + if let Some(p) = opt(args, "--parent") { + let parent = ManifestReceipt::from_json(&String::from_utf8_lossy(&read(Path::new(p))?)).map_err(|e| format!("{p}: {e}"))?; + if let Err((_, e)) = check_chain(&[parent, held.clone()]) { + println!("refused\t{}", e.code()); + return Ok(false); + } + } + if let Some(c) = opt(args, "--classical") { + let ctext = String::from_utf8(read(Path::new(c))?).map_err(|_| format!("{c}: not UTF-8"))?; + if let Err(e) = check_adjacent(&String::from_utf8_lossy(&text), &ctext) { + println!("refused\t{}", e.code()); + return Ok(false); + } + } + println!("signed\treceipt {}\tsigner {}\t(signer unconfirmed until anchored)", hex(&held.receipt_hash()), hex(&held.signer_pubkey)); + Ok(true) +} + +#[cfg(not(feature = "video_manifest"))] +pub fn check(_: &[String]) -> Result { + Err("built without video_manifest: checking a playlist receipt needs a build with the features http and video_manifest".into()) +} diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs index 8bf302781..c4d9766bb 100644 --- a/wai/wai-rs/src/http_binding/hls.rs +++ b/wai/wai-rs/src/http_binding/hls.rs @@ -739,3 +739,42 @@ pub fn listed_cid(p: &MediaPlaylist, uri: &str) -> Option<[u8; 32]> { super::unhex32(name.strip_suffix(".wai")?) }) } + +fn whole_head(env: &[u8]) -> Result { + match super::head::parse_head(env, env.len() as u64)? { + super::head::HeadParse::Head(h) => Ok(h), + super::head::HeadParse::Need(n) => Err(RangeRefusal::HeadMalformed(format!("the head needs {n} bytes"))), + } +} + +impl WaiLayer { + /// The layer an envelope is (§10.4): its cid, its length and, as `n`, its + /// `refines.layer`. An envelope that is not a layer object is `not_a_layer`. + pub fn of(env: &[u8]) -> Result { + let h = whole_head(env)?; + if !h.is_layer() { + return Err(RangeRefusal::NotALayer); + } + let m = h.manifest_v1()?; + let r = m.refines.as_ref() + .and_then(|v| crate::container::Refines::from_value(v, crate::container::RefForm::ContentHash).ok()) + .ok_or(RangeRefusal::NotALayer)?; + Ok(WaiLayer { n: r.layer, cid: super::content_hash(env), len: env.len() as u64 }) + } +} + +impl WaiSegment { + /// A segment's object (§11.3): its cid, `n` and, as `h`, its `covered_len`. + pub fn of(env: &[u8], duration_ms: u64) -> Result { + let h = whole_head(env)?; + Ok(WaiSegment { cid: super::content_hash(env), n: env.len() as u64, h: Some(h.covered_len()), duration_ms, layers: Vec::new(), parts: Vec::new() }) + } +} + +impl WaiPart { + /// A part's object (§11.4), served under `alias`. + pub fn of(env: &[u8], alias: &str, duration_ms: u64, independent: bool) -> Result { + whole_head(env)?; + Ok(WaiPart { alias: alias.to_string(), cid: super::content_hash(env), n: env.len() as u64, duration_ms, independent, layers: Vec::new() }) + } +} diff --git a/wai/wai-rs/src/http_binding/origin.rs b/wai/wai-rs/src/http_binding/origin.rs index a5c895dc1..111108acf 100644 --- a/wai/wai-rs/src/http_binding/origin.rs +++ b/wai/wai-rs/src/http_binding/origin.rs @@ -37,6 +37,17 @@ pub fn immutable_headers(content_type: &str) -> Vec<(&'static str, String)> { ] } +/// The headers for a playlist (§11): revalidated on every use, since a live playlist +/// changes from revision to revision; its receipt (`mr/`) is immutable. +pub fn playlist_headers(content_type: &str) -> Vec<(&'static str, String)> { + vec![ + ("Content-Type", content_type.to_string()), + ("Cache-Control", "no-cache, no-transform".to_string()), + ("Access-Control-Allow-Origin", "*".to_string()), + ("Timing-Allow-Origin", "*".to_string()), + ] +} + /// The headers for an object listed only at a live edge, which an origin MAY serve /// with a smaller `max-age` (§5.3): [`object_headers`] with that `Cache-Control`. pub fn live_object_headers(cid: &[u8; 32], codecs: Option<&str>, max_age_s: u64) -> Vec<(&'static str, String)> { diff --git a/wai/wai-rs/tests/cli_http.rs b/wai/wai-rs/tests/cli_http.rs index ababcbebf..78823b515 100644 --- a/wai/wai-rs/tests/cli_http.rs +++ b/wai/wai-rs/tests/cli_http.rs @@ -418,3 +418,97 @@ fn bench_times_the_base_apart_from_its_refinement() { assert!(r["ms_to_complete"]["median"].as_f64().unwrap() < 1000.0); drop(origin); } + +/// A classical playlist for the O9 segments (three of 640 ms), and an objects list over +/// them, written in a scratch directory with the corpus objects beside them. +fn o9_presentation(name: &str) -> PathBuf { + let d = scratch(name); + std::fs::write(d.join("classical.m3u8"), "#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-TARGETDURATION:1\n#EXT-X-MEDIA-SEQUENCE:0\n#EXTINF:0.640,\nc0.m4s\n#EXTINF:0.640,\nc1.m4s\n#EXTINF:0.640,\nc2.m4s\n#EXT-X-ENDLIST\n").unwrap(); + let objs: Vec = ["O9a", "O9b", "O9c"].iter().map(|id| object(id).to_str().unwrap().to_string()).collect(); + let list = serde_json::json!({ + "uri_prefix": "objects/", + "variants": [{ "uri": "wai-180.m3u8", "classical": "classical.m3u8", "resolution": [320, 180], "frame_rate_milli": 25000, + "segments": objs.iter().map(|o| serde_json::json!({ "object": o })).collect::>() }], + }); + std::fs::write(d.join("list.json"), serde_json::to_vec_pretty(&list).unwrap()).unwrap(); + d +} + +/// `wai http hls` writes the WAI media playlist aligned with the classical one, and +/// `wai.m3u8` with the union, the floor and computed bandwidths; `wai http mpd` writes +/// `wai.mpd` on the same timeline. +#[test] +fn hls_and_mpd_write_aligned_playlists() { + let d = o9_presentation("hls"); + let run = wai(&["http", "hls", "--objects", d.join("list.json").to_str().unwrap(), "--out", d.to_str().unwrap()]); + assert!(run.status.success(), "{}", String::from_utf8_lossy(&run.stderr)); + let media = std::fs::read_to_string(d.join("wai-180.m3u8")).unwrap(); + let classical = std::fs::read_to_string(d.join("classical.m3u8")).unwrap(); + wai::http_binding::hls::check_adjacent(&media, &classical).unwrap(); + let cid = hex(&content_hash(&std::fs::read(object("O9b")).unwrap())); + assert!(media.contains(&format!("#EXTINF:0.640,\nobjects/{cid}.wai#n=18004&h=1009\n")), "{media}"); + let master = std::fs::read_to_string(d.join("wai.m3u8")).unwrap(); + assert_eq!(master, "#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=262925,AVERAGE-BANDWIDTH=239246,CODECS=\"wai2.video.av1|video.int_hyper\",X-WAI-FLOOR=\"wai2.video.av1\",RESOLUTION=320x180,FRAME-RATE=25.000\nwai-180.m3u8\n"); + let run = wai(&["http", "mpd", "--objects", d.join("list.json").to_str().unwrap(), "--out", d.join("wai.mpd").to_str().unwrap()]); + assert!(run.status.success(), "{}", String::from_utf8_lossy(&run.stderr)); + let mpd = std::fs::read_to_string(d.join("wai.mpd")).unwrap(); + assert!(mpd.contains("") && mpd.contains(""), "{mpd}"); + // An object list that does not cover the classical timeline is refused: three + // objects for two classical segments. + std::fs::write(d.join("classical.m3u8"), classical.replacen("#EXTINF:0.640,\nc2.m4s\n", "", 1)).unwrap(); + let run = wai(&["http", "hls", "--objects", d.join("list.json").to_str().unwrap(), "--out", d.to_str().unwrap()]); + assert!(!run.status.success()); +} + +/// `wai http seal-playlist` writes `mr/.json`; `check-playlist` reports +/// it signed, refuses a changed playlist, and holds a revision to its parent. +#[cfg(feature = "video_manifest")] +#[test] +fn seal_and_check_playlist() { + let d = o9_presentation("seal"); + let run = wai(&["http", "hls", "--objects", d.join("list.json").to_str().unwrap(), "--out", d.to_str().unwrap()]); + assert!(run.status.success(), "{}", String::from_utf8_lossy(&run.stderr)); + let pl = d.join("wai-180.m3u8"); + let key = "07".repeat(32); + let run = wai(&["http", "seal-playlist", pl.to_str().unwrap(), "--key", &key, "--signer-id", "did:example:cli", "--content-id", "o9"]); + assert!(run.status.success(), "{}", String::from_utf8_lossy(&run.stderr)); + let bytes = std::fs::read(&pl).unwrap(); + let name = format!("{}.json", blake3::hash(&bytes).to_hex()); + let receipt = d.join("mr").join(&name); + assert!(String::from_utf8_lossy(&run.stdout).starts_with(receipt.to_str().unwrap())); + let ok = wai(&["http", "check-playlist", pl.to_str().unwrap(), "--receipt", receipt.to_str().unwrap(), "--classical", d.join("classical.m3u8").to_str().unwrap()]); + assert!(ok.status.success(), "{}", String::from_utf8_lossy(&ok.stdout)); + assert!(String::from_utf8_lossy(&ok.stdout).starts_with("signed\t")); + let mut changed = bytes.clone(); + let at = changed.len() - 20; + changed[at] ^= 1; + std::fs::write(d.join("changed.m3u8"), &changed).unwrap(); + let bad = wai(&["http", "check-playlist", d.join("changed.m3u8").to_str().unwrap(), "--receipt", receipt.to_str().unwrap()]); + assert_eq!(bad.status.code(), Some(1)); + assert_eq!(String::from_utf8_lossy(&bad.stdout), "refused\tplaylist_receipt_mismatch\n"); + // A second revision sealed with the first as its parent checks against it; the + // first does not chain to itself. + let run = wai(&["http", "seal-playlist", pl.to_str().unwrap(), "--key", &key, "--parent", receipt.to_str().unwrap(), "--out", d.join("r2").to_str().unwrap()]); + assert!(run.status.success(), "{}", String::from_utf8_lossy(&run.stderr)); + let r2 = d.join("r2/mr").join(&name); + let ok = wai(&["http", "check-playlist", pl.to_str().unwrap(), "--receipt", r2.to_str().unwrap(), "--parent", receipt.to_str().unwrap()]); + assert!(ok.status.success(), "{}", String::from_utf8_lossy(&ok.stdout)); + let bad = wai(&["http", "check-playlist", pl.to_str().unwrap(), "--receipt", receipt.to_str().unwrap(), "--parent", receipt.to_str().unwrap()]); + assert_eq!(String::from_utf8_lossy(&bad.stdout), "refused\tplaylist_receipt_unchained\n"); +} + +/// The origin serves a playlist to be revalidated on every use, and its receipt +/// immutable. +#[test] +fn origin_serves_playlists_revalidated() { + let d = o9_presentation("origin-playlists"); + let run = wai(&["http", "hls", "--objects", d.join("list.json").to_str().unwrap(), "--out", d.to_str().unwrap()]); + assert!(run.status.success()); + std::fs::write(d.join("wai.mpd"), "\n").unwrap(); + let o = Origin::start(&d, &[]); + let (h, body) = get(o.port, "/wai-180.m3u8", &[]); + assert!(h.contains("Content-Type: application/vnd.apple.mpegurl\r\n") && h.contains("Cache-Control: no-cache, no-transform\r\n"), "{h}"); + assert_eq!(body, std::fs::read(d.join("wai-180.m3u8")).unwrap()); + let (h, _) = get(o.port, "/wai.mpd", &[]); + assert!(h.contains("Content-Type: application/dash+xml\r\n") && h.contains("Cache-Control: no-cache, no-transform\r\n"), "{h}"); +} -- 2.54.0 (Apple Git-157) From 3909f98a0ee719acfd08c322402bbf9a7741872e Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 06:05:11 -0400 Subject: [PATCH 06/55] =?UTF-8?q?wai:=20the=20playlist=20corpus=20?= =?UTF-8?q?=E2=80=94=20layered,=20low-latency=20and=20player=20objects,=20?= =?UTF-8?q?playlists,=20receipts=20and=20adaptation=20vectors=20(W3-4,=20W?= =?UTF-8?q?3-5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Objects (O10 is the HTTP link's layer set, so these take the next numbers): O11a, O11b and O11bL1, a staged-delivery pair with a layer over the second (the design's O10, layered segments); O12s0/O12s1 and 24 parts O12p_, a low-latency set of two 3.84 s segments of twelve 0.32 s parts, each part its own object decoded alone (the design's O11); O13a-c, the player fixture's WAI segments. Their payloads are int_motion clips encoded once by this tree's encoder and frozen in src/ with their SHA-256; the player fixture's classical presentation (H.264 fragmented MP4, playable, no tool name left in it) is frozen in players/classical/. Generated: the WAI media playlists beside their classical ones (O9, O10 with two layer lines, O11, four live revisions, the player fixture), the multivariant playlists, two MPDs, the published classical master with its one session-data line, and a playlist receipt for every media playlist under a published test key, the live revisions chained. Cases (new ops media_playlist, multivariant, mpd, playlist_parse, adjacency, unchanged, playlist_receipt, live, abr, abr_order, depth; fetch and same_pick under a policy's order) hold rendering, reading, adjacency, the unchanged master, receipt checks and chains, parts verified against the cids later revisions list, and 40 seeded states per adaptation policy with the budget's every outcome. The tool needs video_manifest for the receipts. Co-Authored-By: Claude Opus 5.5 --- wai/http-conformance/cases.json | 388 ++++++++- .../cases/live-r2-orphan.json | 1 + ...9b1279a07e3a16b7189c4df9ed150bc369ac5.json | 1 + ...63c7518c7e6c1d89d81328a419f8d406c75d9.json | 1 + ...331427f731719277601cca4359985b7103a4d.json | 1 + ...6632b7516d833b969b6549f6f8e5f7b3c8b11.json | 1 + wai/http-conformance/live/wai-r0.m3u8 | 13 + wai/http-conformance/live/wai-r1.m3u8 | 25 + wai/http-conformance/live/wai-r2.m3u8 | 36 + wai/http-conformance/live/wai-r3.m3u8 | 12 + wai/http-conformance/live/wai.m3u8 | 3 + wai/http-conformance/objects.json | 256 ++++++ ...aac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai | Bin 0 -> 3018 bytes ...71e7b3ba39e8f8354eb3ac3fabd3392d4.wai.etag | 1 + ...2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai.hp | Bin 0 -> 117 bytes ...ea3d73622b5c230325096eed3585aa5f813008.wai | Bin 0 -> 2994 bytes ...3622b5c230325096eed3585aa5f813008.wai.etag | 1 + ...d73622b5c230325096eed3585aa5f813008.wai.hp | Bin 0 -> 117 bytes ...a15273791059a2626b78a2d1cd4d99ffbe6f59.wai | Bin 0 -> 21758 bytes ...3791059a2626b78a2d1cd4d99ffbe6f59.wai.etag | 1 + ...273791059a2626b78a2d1cd4d99ffbe6f59.wai.hp | Bin 0 -> 213 bytes ...902fac152de57ec5420330b9ba5afd1fe0873a.wai | Bin 0 -> 2994 bytes ...c152de57ec5420330b9ba5afd1fe0873a.wai.etag | 1 + ...fac152de57ec5420330b9ba5afd1fe0873a.wai.hp | Bin 0 -> 117 bytes ...64bd86dc1310c7681a732e9d0886373411915f.wai | Bin 0 -> 3222 bytes ...6dc1310c7681a732e9d0886373411915f.wai.etag | 1 + ...d86dc1310c7681a732e9d0886373411915f.wai.hp | Bin 0 -> 117 bytes ...3768d249982ed842d3e8659d0e39f69e92c815.wai | Bin 0 -> 3026 bytes ...249982ed842d3e8659d0e39f69e92c815.wai.etag | 1 + ...8d249982ed842d3e8659d0e39f69e92c815.wai.hp | Bin 0 -> 117 bytes ...0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai | Bin 0 -> 2962 bytes ...e0846fe09c2d9fb09ed88b54cabc3a778.wai.etag | 1 + ...74e0846fe09c2d9fb09ed88b54cabc3a778.wai.hp | Bin 0 -> 117 bytes ...cd430302f395280aa79f9657b3cb16f23ba5b0.wai | Bin 0 -> 3146 bytes ...302f395280aa79f9657b3cb16f23ba5b0.wai.etag | 1 + ...30302f395280aa79f9657b3cb16f23ba5b0.wai.hp | Bin 0 -> 117 bytes ...04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai | Bin 0 -> 3170 bytes ...e0b8ebb7ce3a5b9aca275ec5015d54312.wai.etag | 1 + ...aee0b8ebb7ce3a5b9aca275ec5015d54312.wai.hp | Bin 0 -> 117 bytes ...fb71f07acbb176d171508cf269d06f0cf69676.wai | Bin 0 -> 2962 bytes ...07acbb176d171508cf269d06f0cf69676.wai.etag | 1 + ...1f07acbb176d171508cf269d06f0cf69676.wai.hp | Bin 0 -> 117 bytes ...2d6f52c60829e89d47cc544dfb0891cac41304.wai | Bin 0 -> 3222 bytes ...2c60829e89d47cc544dfb0891cac41304.wai.etag | 1 + ...f52c60829e89d47cc544dfb0891cac41304.wai.hp | Bin 0 -> 117 bytes ...254b7bb0af2cf0691e4e759b89cc320f5f8659.wai | Bin 0 -> 3014 bytes ...bb0af2cf0691e4e759b89cc320f5f8659.wai.etag | 1 + ...b7bb0af2cf0691e4e759b89cc320f5f8659.wai.hp | Bin 0 -> 117 bytes ...78a391b7d449b0010a4ddd1894efb60026bb17.wai | Bin 0 -> 19118 bytes ...1b7d449b0010a4ddd1894efb60026bb17.wai.etag | 1 + ...391b7d449b0010a4ddd1894efb60026bb17.wai.hp | Bin 0 -> 213 bytes ...22840e65839fdbf47d2a0c03d68695860c0c70.wai | Bin 0 -> 3238 bytes ...e65839fdbf47d2a0c03d68695860c0c70.wai.etag | 1 + ...40e65839fdbf47d2a0c03d68695860c0c70.wai.hp | Bin 0 -> 117 bytes ...0f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai | Bin 0 -> 3026 bytes ...03b9c1492fa302d1dcbdb26f1527d9ce6.wai.etag | 1 + ...3303b9c1492fa302d1dcbdb26f1527d9ce6.wai.hp | Bin 0 -> 117 bytes ...ce60a286cbd20e478619944fbb385736c6e543.wai | Bin 0 -> 21338 bytes ...286cbd20e478619944fbb385736c6e543.wai.etag | 1 + ...0a286cbd20e478619944fbb385736c6e543.wai.hp | Bin 0 -> 213 bytes ...af732893632ab0713cca8139969d3529e8ec8e.wai | Bin 0 -> 3190 bytes ...893632ab0713cca8139969d3529e8ec8e.wai.etag | 1 + ...32893632ab0713cca8139969d3529e8ec8e.wai.hp | Bin 0 -> 117 bytes ...e6626e5c61ef2b332b12c6fdba4d7c79871504.wai | Bin 0 -> 535 bytes ...e5c61ef2b332b12c6fdba4d7c79871504.wai.etag | 1 + ...26e5c61ef2b332b12c6fdba4d7c79871504.wai.hp | Bin 0 -> 53 bytes ...12cafed3410eeb84ad41db82acdc8f9a10adf7.wai | Bin 0 -> 551 bytes ...ed3410eeb84ad41db82acdc8f9a10adf7.wai.etag | 1 + ...afed3410eeb84ad41db82acdc8f9a10adf7.wai.hp | Bin 0 -> 53 bytes ...551c5af730a176180151756f8071fbb7d5f716.wai | Bin 0 -> 16606 bytes ...af730a176180151756f8071fbb7d5f716.wai.etag | 1 + ...c5af730a176180151756f8071fbb7d5f716.wai.hp | Bin 0 -> 213 bytes ...9fdd41da51c85d970a6a54d850c55c255d5486.wai | Bin 0 -> 523 bytes ...1da51c85d970a6a54d850c55c255d5486.wai.etag | 1 + ...d41da51c85d970a6a54d850c55c255d5486.wai.hp | Bin 0 -> 53 bytes ...53a3def273636f4bd7ca182b635326d9f74c7b.wai | Bin 0 -> 3122 bytes ...ef273636f4bd7ca182b635326d9f74c7b.wai.etag | 1 + ...3def273636f4bd7ca182b635326d9f74c7b.wai.hp | Bin 0 -> 117 bytes ...fedd51eddb754dc3a95ed6fcd9386b2a6372cf.wai | Bin 0 -> 3190 bytes ...1eddb754dc3a95ed6fcd9386b2a6372cf.wai.etag | 1 + ...d51eddb754dc3a95ed6fcd9386b2a6372cf.wai.hp | Bin 0 -> 117 bytes ...4f1c7edf311119358fdb59870ad9d51c369c3d.wai | Bin 0 -> 3042 bytes ...edf311119358fdb59870ad9d51c369c3d.wai.etag | 1 + ...c7edf311119358fdb59870ad9d51c369c3d.wai.hp | Bin 0 -> 117 bytes ...5e5ec6d228663a65e50cb0b95a8b0dfb4cc385.wai | Bin 0 -> 3026 bytes ...6d228663a65e50cb0b95a8b0dfb4cc385.wai.etag | 1 + ...ec6d228663a65e50cb0b95a8b0dfb4cc385.wai.hp | Bin 0 -> 117 bytes ...bc60aa8314878a602de85cfc9c3c0d33c0e03f.wai | Bin 0 -> 2962 bytes ...a8314878a602de85cfc9c3c0d33c0e03f.wai.etag | 1 + ...0aa8314878a602de85cfc9c3c0d33c0e03f.wai.hp | Bin 0 -> 117 bytes ...fe5154e7ef541554a0550ea8edb796bde725ce.wai | Bin 0 -> 3222 bytes ...4e7ef541554a0550ea8edb796bde725ce.wai.etag | 1 + ...154e7ef541554a0550ea8edb796bde725ce.wai.hp | Bin 0 -> 117 bytes ...0da1354b148c3920489e345201a8d3c2c72bed.wai | Bin 0 -> 3238 bytes ...54b148c3920489e345201a8d3c2c72bed.wai.etag | 1 + ...1354b148c3920489e345201a8d3c2c72bed.wai.hp | Bin 0 -> 117 bytes ...b2100a3491b6202e935a1d4101aae8062d9d6f.wai | Bin 0 -> 3018 bytes ...a3491b6202e935a1d4101aae8062d9d6f.wai.etag | 1 + ...00a3491b6202e935a1d4101aae8062d9d6f.wai.hp | Bin 0 -> 117 bytes ...336ef3257c8c755dbeb69314bce44fcc0947ac.wai | Bin 0 -> 2994 bytes ...3257c8c755dbeb69314bce44fcc0947ac.wai.etag | 1 + ...ef3257c8c755dbeb69314bce44fcc0947ac.wai.hp | Bin 0 -> 117 bytes ...8962773a620b1fabb1d17acfc7f52693ce830a.wai | Bin 0 -> 20986 bytes ...73a620b1fabb1d17acfc7f52693ce830a.wai.etag | 1 + ...2773a620b1fabb1d17acfc7f52693ce830a.wai.hp | Bin 0 -> 213 bytes ...58d1cd9a3f262ce9b44bbb16248142037bf32d.wai | Bin 0 -> 3222 bytes ...d9a3f262ce9b44bbb16248142037bf32d.wai.etag | 1 + ...1cd9a3f262ce9b44bbb16248142037bf32d.wai.hp | Bin 0 -> 117 bytes .../players/classical/init.mp4 | Bin 0 -> 755 bytes .../players/classical/seg0.m4s | Bin 0 -> 1194 bytes .../players/classical/seg1.m4s | Bin 0 -> 1319 bytes .../players/classical/seg2.m4s | Bin 0 -> 1355 bytes .../players/classical/v0.m3u8 | 13 + wai/http-conformance/players/master.m3u8 | 6 + ...d3d2a6b2e2b9cba605f1ba43b51f900eeb351.json | 1 + wai/http-conformance/players/wai-v0.m3u8 | 11 + wai/http-conformance/players/wai.m3u8 | 3 + wai/http-conformance/players/wai.mpd | 21 + ...f30fc105274bfae7e065ecbfa41446b78d296.json | 1 + wai/http-conformance/playlists/o10/wai-a.m3u8 | 9 + wai/http-conformance/playlists/o10/wai.m3u8 | 3 + ...2233eca247c07d0ccf4fd30f6eb633d87acb6.json | 1 + wai/http-conformance/playlists/o11/wai-v.m3u8 | 10 + wai/http-conformance/playlists/o11/wai.m3u8 | 3 + ...885627ec17670de4fc42d88f2513d0d5bb85f.json | 1 + .../playlists/o9/wai-180.m3u8 | 11 + wai/http-conformance/playlists/o9/wai.m3u8 | 3 + wai/http-conformance/playlists/o9/wai.mpd | 21 + wai/http-conformance/src/SOURCES.json | 210 +++++ .../src/live/classical-r0.m3u8 | 14 + .../src/live/classical-r1.m3u8 | 26 + .../src/live/classical-r2.m3u8 | 37 + .../src/live/classical-r3.m3u8 | 13 + wai/http-conformance/src/live/s0.wiv1 | Bin 0 -> 16424 bytes wai/http-conformance/src/live/s0p00.wiv1 | Bin 0 -> 2844 bytes wai/http-conformance/src/live/s0p01.wiv1 | Bin 0 -> 2780 bytes wai/http-conformance/src/live/s0p02.wiv1 | Bin 0 -> 2812 bytes wai/http-conformance/src/live/s0p03.wiv1 | Bin 0 -> 2836 bytes wai/http-conformance/src/live/s0p04.wiv1 | Bin 0 -> 2844 bytes wai/http-conformance/src/live/s0p05.wiv1 | Bin 0 -> 2780 bytes wai/http-conformance/src/live/s0p06.wiv1 | Bin 0 -> 2812 bytes wai/http-conformance/src/live/s0p07.wiv1 | Bin 0 -> 2836 bytes wai/http-conformance/src/live/s0p08.wiv1 | Bin 0 -> 2844 bytes wai/http-conformance/src/live/s0p09.wiv1 | Bin 0 -> 2780 bytes wai/http-conformance/src/live/s0p10.wiv1 | Bin 0 -> 2812 bytes wai/http-conformance/src/live/s0p11.wiv1 | Bin 0 -> 2832 bytes wai/http-conformance/src/live/s1.wiv1 | Bin 0 -> 18936 bytes wai/http-conformance/src/live/s1p00.wiv1 | Bin 0 -> 3008 bytes wai/http-conformance/src/live/s1p01.wiv1 | Bin 0 -> 2860 bytes wai/http-conformance/src/live/s1p02.wiv1 | Bin 0 -> 2940 bytes wai/http-conformance/src/live/s1p03.wiv1 | Bin 0 -> 3008 bytes wai/http-conformance/src/live/s1p04.wiv1 | Bin 0 -> 3056 bytes wai/http-conformance/src/live/s1p05.wiv1 | Bin 0 -> 2964 bytes wai/http-conformance/src/live/s1p06.wiv1 | Bin 0 -> 3040 bytes wai/http-conformance/src/live/s1p07.wiv1 | Bin 0 -> 3040 bytes wai/http-conformance/src/live/s1p08.wiv1 | Bin 0 -> 3056 bytes wai/http-conformance/src/live/s1p09.wiv1 | Bin 0 -> 2988 bytes wai/http-conformance/src/live/s1p10.wiv1 | Bin 0 -> 3040 bytes wai/http-conformance/src/live/s1p11.wiv1 | Bin 0 -> 3040 bytes wai/http-conformance/src/players/master.m3u8 | 5 + wai/http-conformance/src/players/wai0.wiv1 | Bin 0 -> 21576 bytes wai/http-conformance/src/players/wai1.wiv1 | Bin 0 -> 20804 bytes wai/http-conformance/src/players/wai2.wiv1 | Bin 0 -> 21156 bytes .../src/playlists/o10-classical.m3u8 | 7 + .../src/playlists/o11-classical.m3u8 | 10 + .../src/playlists/o9-classical.m3u8 | 13 + wai/wai-rs/src/bin/wai_cli/http_vectors.rs | 24 +- .../src/bin/wai_cli/http_vectors_playlists.rs | 823 ++++++++++++++++++ wai/wai-rs/src/http_binding/abr.rs | 2 + wai/wai-rs/src/http_binding/hls.rs | 32 +- 170 files changed, 2089 insertions(+), 15 deletions(-) create mode 100644 wai/http-conformance/cases/live-r2-orphan.json create mode 100644 wai/http-conformance/live/mr/2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5.json create mode 100644 wai/http-conformance/live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json create mode 100644 wai/http-conformance/live/mr/c06453e032118886f29548eda03331427f731719277601cca4359985b7103a4d.json create mode 100644 wai/http-conformance/live/mr/fea90b39e7bfdfb5e9e70feff146632b7516d833b969b6549f6f8e5f7b3c8b11.json create mode 100644 wai/http-conformance/live/wai-r0.m3u8 create mode 100644 wai/http-conformance/live/wai-r1.m3u8 create mode 100644 wai/http-conformance/live/wai-r2.m3u8 create mode 100644 wai/http-conformance/live/wai-r3.m3u8 create mode 100644 wai/http-conformance/live/wai.m3u8 create mode 100644 wai/http-conformance/objects/00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai create mode 100644 wai/http-conformance/objects/00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai.etag create mode 100644 wai/http-conformance/objects/00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai.hp create mode 100644 wai/http-conformance/objects/0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008.wai create mode 100644 wai/http-conformance/objects/0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008.wai.etag create mode 100644 wai/http-conformance/objects/0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008.wai.hp create mode 100644 wai/http-conformance/objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai create mode 100644 wai/http-conformance/objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai.etag create mode 100644 wai/http-conformance/objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai.hp create mode 100644 wai/http-conformance/objects/12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a.wai create mode 100644 wai/http-conformance/objects/12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a.wai.etag create mode 100644 wai/http-conformance/objects/12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a.wai.hp create mode 100644 wai/http-conformance/objects/173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f.wai create mode 100644 wai/http-conformance/objects/173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f.wai.etag create mode 100644 wai/http-conformance/objects/173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f.wai.hp create mode 100644 wai/http-conformance/objects/27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815.wai create mode 100644 wai/http-conformance/objects/27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815.wai.etag create mode 100644 wai/http-conformance/objects/27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815.wai.hp create mode 100644 wai/http-conformance/objects/2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai create mode 100644 wai/http-conformance/objects/2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai.etag create mode 100644 wai/http-conformance/objects/2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai.hp create mode 100644 wai/http-conformance/objects/35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0.wai create mode 100644 wai/http-conformance/objects/35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0.wai.etag create mode 100644 wai/http-conformance/objects/35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0.wai.hp create mode 100644 wai/http-conformance/objects/48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai create mode 100644 wai/http-conformance/objects/48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai.etag create mode 100644 wai/http-conformance/objects/48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai.hp create mode 100644 wai/http-conformance/objects/55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676.wai create mode 100644 wai/http-conformance/objects/55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676.wai.etag create mode 100644 wai/http-conformance/objects/55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676.wai.hp create mode 100644 wai/http-conformance/objects/5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304.wai create mode 100644 wai/http-conformance/objects/5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304.wai.etag create mode 100644 wai/http-conformance/objects/5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304.wai.hp create mode 100644 wai/http-conformance/objects/62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659.wai create mode 100644 wai/http-conformance/objects/62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659.wai.etag create mode 100644 wai/http-conformance/objects/62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659.wai.hp create mode 100644 wai/http-conformance/objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai create mode 100644 wai/http-conformance/objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai.etag create mode 100644 wai/http-conformance/objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai.hp create mode 100644 wai/http-conformance/objects/6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70.wai create mode 100644 wai/http-conformance/objects/6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70.wai.etag create mode 100644 wai/http-conformance/objects/6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70.wai.hp create mode 100644 wai/http-conformance/objects/7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai create mode 100644 wai/http-conformance/objects/7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai.etag create mode 100644 wai/http-conformance/objects/7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai.hp create mode 100644 wai/http-conformance/objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai create mode 100644 wai/http-conformance/objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai.etag create mode 100644 wai/http-conformance/objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai.hp create mode 100644 wai/http-conformance/objects/7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e.wai create mode 100644 wai/http-conformance/objects/7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e.wai.etag create mode 100644 wai/http-conformance/objects/7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e.wai.hp create mode 100644 wai/http-conformance/objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai create mode 100644 wai/http-conformance/objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai.etag create mode 100644 wai/http-conformance/objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai.hp create mode 100644 wai/http-conformance/objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai create mode 100644 wai/http-conformance/objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai.etag create mode 100644 wai/http-conformance/objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai.hp create mode 100644 wai/http-conformance/objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai create mode 100644 wai/http-conformance/objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai.etag create mode 100644 wai/http-conformance/objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai.hp create mode 100644 wai/http-conformance/objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai create mode 100644 wai/http-conformance/objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai.etag create mode 100644 wai/http-conformance/objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai.hp create mode 100644 wai/http-conformance/objects/a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b.wai create mode 100644 wai/http-conformance/objects/a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b.wai.etag create mode 100644 wai/http-conformance/objects/a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b.wai.hp create mode 100644 wai/http-conformance/objects/a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf.wai create mode 100644 wai/http-conformance/objects/a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf.wai.etag create mode 100644 wai/http-conformance/objects/a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf.wai.hp create mode 100644 wai/http-conformance/objects/ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d.wai create mode 100644 wai/http-conformance/objects/ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d.wai.etag create mode 100644 wai/http-conformance/objects/ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d.wai.hp create mode 100644 wai/http-conformance/objects/b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385.wai create mode 100644 wai/http-conformance/objects/b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385.wai.etag create mode 100644 wai/http-conformance/objects/b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385.wai.hp create mode 100644 wai/http-conformance/objects/c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f.wai create mode 100644 wai/http-conformance/objects/c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f.wai.etag create mode 100644 wai/http-conformance/objects/c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f.wai.hp create mode 100644 wai/http-conformance/objects/cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce.wai create mode 100644 wai/http-conformance/objects/cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce.wai.etag create mode 100644 wai/http-conformance/objects/cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce.wai.hp create mode 100644 wai/http-conformance/objects/db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed.wai create mode 100644 wai/http-conformance/objects/db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed.wai.etag create mode 100644 wai/http-conformance/objects/db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed.wai.hp create mode 100644 wai/http-conformance/objects/dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f.wai create mode 100644 wai/http-conformance/objects/dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f.wai.etag create mode 100644 wai/http-conformance/objects/dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f.wai.hp create mode 100644 wai/http-conformance/objects/f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac.wai create mode 100644 wai/http-conformance/objects/f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac.wai.etag create mode 100644 wai/http-conformance/objects/f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac.wai.hp create mode 100644 wai/http-conformance/objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai create mode 100644 wai/http-conformance/objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai.etag create mode 100644 wai/http-conformance/objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai.hp create mode 100644 wai/http-conformance/objects/fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d.wai create mode 100644 wai/http-conformance/objects/fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d.wai.etag create mode 100644 wai/http-conformance/objects/fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d.wai.hp create mode 100644 wai/http-conformance/players/classical/init.mp4 create mode 100644 wai/http-conformance/players/classical/seg0.m4s create mode 100644 wai/http-conformance/players/classical/seg1.m4s create mode 100644 wai/http-conformance/players/classical/seg2.m4s create mode 100644 wai/http-conformance/players/classical/v0.m3u8 create mode 100644 wai/http-conformance/players/master.m3u8 create mode 100644 wai/http-conformance/players/mr/afbdb9d6d90f0b36501b0ff7194d3d2a6b2e2b9cba605f1ba43b51f900eeb351.json create mode 100644 wai/http-conformance/players/wai-v0.m3u8 create mode 100644 wai/http-conformance/players/wai.m3u8 create mode 100644 wai/http-conformance/players/wai.mpd create mode 100644 wai/http-conformance/playlists/o10/mr/89f7c647fb786e14ced2c1c8250f30fc105274bfae7e065ecbfa41446b78d296.json create mode 100644 wai/http-conformance/playlists/o10/wai-a.m3u8 create mode 100644 wai/http-conformance/playlists/o10/wai.m3u8 create mode 100644 wai/http-conformance/playlists/o11/mr/bc3327099dc8f1121c168b9b9872233eca247c07d0ccf4fd30f6eb633d87acb6.json create mode 100644 wai/http-conformance/playlists/o11/wai-v.m3u8 create mode 100644 wai/http-conformance/playlists/o11/wai.m3u8 create mode 100644 wai/http-conformance/playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json create mode 100644 wai/http-conformance/playlists/o9/wai-180.m3u8 create mode 100644 wai/http-conformance/playlists/o9/wai.m3u8 create mode 100644 wai/http-conformance/playlists/o9/wai.mpd create mode 100644 wai/http-conformance/src/live/classical-r0.m3u8 create mode 100644 wai/http-conformance/src/live/classical-r1.m3u8 create mode 100644 wai/http-conformance/src/live/classical-r2.m3u8 create mode 100644 wai/http-conformance/src/live/classical-r3.m3u8 create mode 100644 wai/http-conformance/src/live/s0.wiv1 create mode 100644 wai/http-conformance/src/live/s0p00.wiv1 create mode 100644 wai/http-conformance/src/live/s0p01.wiv1 create mode 100644 wai/http-conformance/src/live/s0p02.wiv1 create mode 100644 wai/http-conformance/src/live/s0p03.wiv1 create mode 100644 wai/http-conformance/src/live/s0p04.wiv1 create mode 100644 wai/http-conformance/src/live/s0p05.wiv1 create mode 100644 wai/http-conformance/src/live/s0p06.wiv1 create mode 100644 wai/http-conformance/src/live/s0p07.wiv1 create mode 100644 wai/http-conformance/src/live/s0p08.wiv1 create mode 100644 wai/http-conformance/src/live/s0p09.wiv1 create mode 100644 wai/http-conformance/src/live/s0p10.wiv1 create mode 100644 wai/http-conformance/src/live/s0p11.wiv1 create mode 100644 wai/http-conformance/src/live/s1.wiv1 create mode 100644 wai/http-conformance/src/live/s1p00.wiv1 create mode 100644 wai/http-conformance/src/live/s1p01.wiv1 create mode 100644 wai/http-conformance/src/live/s1p02.wiv1 create mode 100644 wai/http-conformance/src/live/s1p03.wiv1 create mode 100644 wai/http-conformance/src/live/s1p04.wiv1 create mode 100644 wai/http-conformance/src/live/s1p05.wiv1 create mode 100644 wai/http-conformance/src/live/s1p06.wiv1 create mode 100644 wai/http-conformance/src/live/s1p07.wiv1 create mode 100644 wai/http-conformance/src/live/s1p08.wiv1 create mode 100644 wai/http-conformance/src/live/s1p09.wiv1 create mode 100644 wai/http-conformance/src/live/s1p10.wiv1 create mode 100644 wai/http-conformance/src/live/s1p11.wiv1 create mode 100644 wai/http-conformance/src/players/master.m3u8 create mode 100644 wai/http-conformance/src/players/wai0.wiv1 create mode 100644 wai/http-conformance/src/players/wai1.wiv1 create mode 100644 wai/http-conformance/src/players/wai2.wiv1 create mode 100644 wai/http-conformance/src/playlists/o10-classical.m3u8 create mode 100644 wai/http-conformance/src/playlists/o11-classical.m3u8 create mode 100644 wai/http-conformance/src/playlists/o9-classical.m3u8 create mode 100644 wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index f9b0d6ca5..234f96bf3 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -16,6 +16,38 @@ {"name":"head_proof_ok_O10","op":"head_proof","input":{"object":"O10"},"expect":{"covered_len":1009,"n_cv":1,"envelope_len":1825}}, {"name":"head_proof_ok_O10L1","op":"head_proof","input":{"object":"O10L1"},"expect":{"covered_len":1009,"n_cv":4,"envelope_len":12711}}, {"name":"head_proof_ok_O10L2","op":"head_proof","input":{"object":"O10L2"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":18367}}, +{"name":"head_proof_ok_O11a","op":"head_proof","input":{"object":"O11a"},"expect":{"covered_len":551,"n_cv":0,"envelope_len":551}}, +{"name":"head_proof_ok_O11b","op":"head_proof","input":{"object":"O11b"},"expect":{"covered_len":523,"n_cv":0,"envelope_len":523}}, +{"name":"head_proof_ok_O11bL1","op":"head_proof","input":{"object":"O11bL1"},"expect":{"covered_len":535,"n_cv":0,"envelope_len":535}}, +{"name":"head_proof_ok_O12p0_00","op":"head_proof","input":{"object":"O12p0_00"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3026}}, +{"name":"head_proof_ok_O12p0_01","op":"head_proof","input":{"object":"O12p0_01"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":2962}}, +{"name":"head_proof_ok_O12p0_02","op":"head_proof","input":{"object":"O12p0_02"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":2994}}, +{"name":"head_proof_ok_O12p0_03","op":"head_proof","input":{"object":"O12p0_03"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3018}}, +{"name":"head_proof_ok_O12p0_04","op":"head_proof","input":{"object":"O12p0_04"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3026}}, +{"name":"head_proof_ok_O12p0_05","op":"head_proof","input":{"object":"O12p0_05"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":2962}}, +{"name":"head_proof_ok_O12p0_06","op":"head_proof","input":{"object":"O12p0_06"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":2994}}, +{"name":"head_proof_ok_O12p0_07","op":"head_proof","input":{"object":"O12p0_07"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3018}}, +{"name":"head_proof_ok_O12p0_08","op":"head_proof","input":{"object":"O12p0_08"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3026}}, +{"name":"head_proof_ok_O12p0_09","op":"head_proof","input":{"object":"O12p0_09"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":2962}}, +{"name":"head_proof_ok_O12p0_10","op":"head_proof","input":{"object":"O12p0_10"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":2994}}, +{"name":"head_proof_ok_O12p0_11","op":"head_proof","input":{"object":"O12p0_11"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3014}}, +{"name":"head_proof_ok_O12p1_00","op":"head_proof","input":{"object":"O12p1_00"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3190}}, +{"name":"head_proof_ok_O12p1_01","op":"head_proof","input":{"object":"O12p1_01"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3042}}, +{"name":"head_proof_ok_O12p1_02","op":"head_proof","input":{"object":"O12p1_02"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3122}}, +{"name":"head_proof_ok_O12p1_03","op":"head_proof","input":{"object":"O12p1_03"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3190}}, +{"name":"head_proof_ok_O12p1_04","op":"head_proof","input":{"object":"O12p1_04"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3238}}, +{"name":"head_proof_ok_O12p1_05","op":"head_proof","input":{"object":"O12p1_05"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3146}}, +{"name":"head_proof_ok_O12p1_06","op":"head_proof","input":{"object":"O12p1_06"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3222}}, +{"name":"head_proof_ok_O12p1_07","op":"head_proof","input":{"object":"O12p1_07"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3222}}, +{"name":"head_proof_ok_O12p1_08","op":"head_proof","input":{"object":"O12p1_08"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3238}}, +{"name":"head_proof_ok_O12p1_09","op":"head_proof","input":{"object":"O12p1_09"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3170}}, +{"name":"head_proof_ok_O12p1_10","op":"head_proof","input":{"object":"O12p1_10"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3222}}, +{"name":"head_proof_ok_O12p1_11","op":"head_proof","input":{"object":"O12p1_11"},"expect":{"covered_len":1009,"n_cv":2,"envelope_len":3222}}, +{"name":"head_proof_ok_O12s0","op":"head_proof","input":{"object":"O12s0"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":16606}}, +{"name":"head_proof_ok_O12s1","op":"head_proof","input":{"object":"O12s1"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":19118}}, +{"name":"head_proof_ok_O13a","op":"head_proof","input":{"object":"O13a"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":21758}}, +{"name":"head_proof_ok_O13b","op":"head_proof","input":{"object":"O13b"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":20986}}, +{"name":"head_proof_ok_O13c","op":"head_proof","input":{"object":"O13c"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":21338}}, {"name":"head_proof_ok_O2","op":"head_proof","input":{"object":"O2"},"expect":{"covered_len":1009,"n_cv":5,"envelope_len":20250}}, {"name":"head_proof_ok_O3","op":"head_proof","input":{"object":"O3"},"expect":{"covered_len":1009,"n_cv":7,"envelope_len":98613}}, {"name":"head_proof_ok_O4","op":"head_proof","input":{"object":"O4"},"expect":{"covered_len":1009,"n_cv":6,"envelope_len":39361}}, @@ -318,5 +350,359 @@ {"name":"supports_union_only","op":"supports","input":{"floor":"wai2.video.av1","containers":["WAI1","WAI2"],"caps":["wai.video.int_hyper"]},"expect":{"supports":false}}, {"name":"supports_container_gate","op":"supports","input":{"floor":"wai2.video.av1","containers":["WAI1"],"caps":["wai.video.av1"]},"expect":{"supports":false}}, {"name":"supports_wai1","op":"supports","input":{"floor":"wai1.video.av1","containers":["WAI1"],"caps":["wai.video.av1"]},"expect":{"supports":true}}, -{"name":"supports_companion_ignored","op":"supports","input":{"floor":"wai2.video.av1+meta.hdr10plus","containers":["WAI1","WAI2"],"caps":["wai.video.av1"]},"expect":{"supports":true}} +{"name":"supports_companion_ignored","op":"supports","input":{"floor":"wai2.video.av1+meta.hdr10plus","containers":["WAI1","WAI2"],"caps":["wai.video.av1"]},"expect":{"supports":true}}, +{"name":"media_playlist_playlists_o9_wai_180_m3u8","op":"media_playlist","input":{"file":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","uri_prefix":"../../objects/","segments":[{"object":"O9a"},{"object":"O9b"},{"object":"O9c"}]},"expect":{"blake3":"01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f","bytes":427,"same_as_file":true}}, +{"name":"media_playlist_playlists_o10_wai_a_m3u8","op":"media_playlist","input":{"file":"playlists/o10/wai-a.m3u8","classical":"src/playlists/o10-classical.m3u8","uri_prefix":"../../objects/","segments":[{"object":"O10","layers":["O10L1","O10L2"]}]},"expect":{"blake3":"89f7c647fb786e14ced2c1c8250f30fc105274bfae7e065ecbfa41446b78d296","bytes":424,"same_as_file":true}}, +{"name":"media_playlist_playlists_o11_wai_v_m3u8","op":"media_playlist","input":{"file":"playlists/o11/wai-v.m3u8","classical":"src/playlists/o11-classical.m3u8","uri_prefix":"../../objects/","segments":[{"object":"O11a"},{"object":"O11b","layers":["O11bL1"]}]},"expect":{"blake3":"bc3327099dc8f1121c168b9b9872233eca247c07d0ccf4fd30f6eb633d87acb6","bytes":418,"same_as_file":true}}, +{"name":"media_playlist_live_wai_r0_m3u8","op":"media_playlist","input":{"file":"live/wai-r0.m3u8","classical":"src/live/classical-r0.m3u8","uri_prefix":"../objects/","segments":[],"open":{"parts":[{"object":"O12p0_00","alias":"720/1200.0.wai","independent":true},{"object":"O12p0_01","alias":"720/1200.1.wai","independent":true},{"object":"O12p0_02","alias":"720/1200.2.wai","independent":true},{"object":"O12p0_03","alias":"720/1200.3.wai","independent":true},{"object":"O12p0_04","alias":"720/1200.4.wai","independent":true}],"preload_hint":"720/1200.5.wai"}},"expect":{"blake3":"6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9","bytes":975,"same_as_file":true}}, +{"name":"media_playlist_live_wai_r1_m3u8","op":"media_playlist","input":{"file":"live/wai-r1.m3u8","classical":"src/live/classical-r1.m3u8","uri_prefix":"../objects/","segments":[{"object":"O12s0","parts":[{"object":"O12p0_00","alias":"720/1200.0.wai","independent":true},{"object":"O12p0_01","alias":"720/1200.1.wai","independent":true},{"object":"O12p0_02","alias":"720/1200.2.wai","independent":true},{"object":"O12p0_03","alias":"720/1200.3.wai","independent":true},{"object":"O12p0_04","alias":"720/1200.4.wai","independent":true},{"object":"O12p0_05","alias":"720/1200.5.wai","independent":true},{"object":"O12p0_06","alias":"720/1200.6.wai","independent":true},{"object":"O12p0_07","alias":"720/1200.7.wai","independent":true},{"object":"O12p0_08","alias":"720/1200.8.wai","independent":true},{"object":"O12p0_09","alias":"720/1200.9.wai","independent":true},{"object":"O12p0_10","alias":"720/1200.10.wai","independent":true},{"object":"O12p0_11","alias":"720/1200.11.wai","independent":true}]}],"open":{"parts":[{"object":"O12p1_00","alias":"720/1201.0.wai","independent":true},{"object":"O12p1_01","alias":"720/1201.1.wai","independent":true},{"object":"O12p1_02","alias":"720/1201.2.wai","independent":true}],"preload_hint":"720/1201.3.wai"}},"expect":{"blake3":"2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5","bytes":2487,"same_as_file":true}}, +{"name":"media_playlist_live_wai_r2_m3u8","op":"media_playlist","input":{"file":"live/wai-r2.m3u8","classical":"src/live/classical-r2.m3u8","uri_prefix":"../objects/","segments":[{"object":"O12s0","parts":[{"object":"O12p0_00","alias":"720/1200.0.wai","independent":true},{"object":"O12p0_01","alias":"720/1200.1.wai","independent":true},{"object":"O12p0_02","alias":"720/1200.2.wai","independent":true},{"object":"O12p0_03","alias":"720/1200.3.wai","independent":true},{"object":"O12p0_04","alias":"720/1200.4.wai","independent":true},{"object":"O12p0_05","alias":"720/1200.5.wai","independent":true},{"object":"O12p0_06","alias":"720/1200.6.wai","independent":true},{"object":"O12p0_07","alias":"720/1200.7.wai","independent":true},{"object":"O12p0_08","alias":"720/1200.8.wai","independent":true},{"object":"O12p0_09","alias":"720/1200.9.wai","independent":true},{"object":"O12p0_10","alias":"720/1200.10.wai","independent":true},{"object":"O12p0_11","alias":"720/1200.11.wai","independent":true}]},{"object":"O12s1","parts":[{"object":"O12p1_00","alias":"720/1201.0.wai","independent":true},{"object":"O12p1_01","alias":"720/1201.1.wai","independent":true},{"object":"O12p1_02","alias":"720/1201.2.wai","independent":true},{"object":"O12p1_03","alias":"720/1201.3.wai","independent":true},{"object":"O12p1_04","alias":"720/1201.4.wai","independent":true},{"object":"O12p1_05","alias":"720/1201.5.wai","independent":true},{"object":"O12p1_06","alias":"720/1201.6.wai","independent":true},{"object":"O12p1_07","alias":"720/1201.7.wai","independent":true},{"object":"O12p1_08","alias":"720/1201.8.wai","independent":true},{"object":"O12p1_09","alias":"720/1201.9.wai","independent":true},{"object":"O12p1_10","alias":"720/1201.10.wai","independent":true},{"object":"O12p1_11","alias":"720/1201.11.wai","independent":true}]}],"open":{"parts":[],"preload_hint":"720/1202.0.wai"}},"expect":{"blake3":"fea90b39e7bfdfb5e9e70feff146632b7516d833b969b6549f6f8e5f7b3c8b11","bytes":3859,"same_as_file":true}}, +{"name":"media_playlist_live_wai_r3_m3u8","op":"media_playlist","input":{"file":"live/wai-r3.m3u8","classical":"src/live/classical-r3.m3u8","uri_prefix":"../objects/","segments":[{"object":"O12s0"},{"object":"O12s1"}],"end_list":true},"expect":{"blake3":"c06453e032118886f29548eda03331427f731719277601cca4359985b7103a4d","bytes":459,"same_as_file":true}}, +{"name":"media_playlist_players_wai_v0_m3u8","op":"media_playlist","input":{"file":"players/wai-v0.m3u8","classical":"players/classical/v0.m3u8","uri_prefix":"../objects/","segments":[{"object":"O13a"},{"object":"O13b"},{"object":"O13c"}]},"expect":{"blake3":"afbdb9d6d90f0b36501b0ff7194d3d2a6b2e2b9cba605f1ba43b51f900eeb351","bytes":418,"same_as_file":true}}, +{"name":"multivariant_playlists_o9_wai_m3u8","op":"multivariant","input":{"file":"playlists/o9/wai.m3u8","variants":[{"uri":"wai-180.m3u8","playlist":"playlists/o9/wai-180.m3u8","resolution":[320,180],"frame_rate_milli":25000}]},"expect":{"blake3":"5380eb2ce76df5d61a115dcb15ad64764731d80c3a8ced7c24ee19b680e99366","bytes":187,"same_as_file":true}}, +{"name":"multivariant_playlists_o10_wai_m3u8","op":"multivariant","input":{"file":"playlists/o10/wai.m3u8","variants":[{"uri":"wai-a.m3u8","playlist":"playlists/o10/wai-a.m3u8"}]},"expect":{"blake3":"5e28ad95a617934cbdea7d3a276c44c613b5ac139cb98ec7b4140f2ef073f741","bytes":168,"same_as_file":true}}, +{"name":"multivariant_playlists_o11_wai_m3u8","op":"multivariant","input":{"file":"playlists/o11/wai.m3u8","variants":[{"uri":"wai-v.m3u8","playlist":"playlists/o11/wai-v.m3u8"}]},"expect":{"blake3":"460af62f80230ac1040f350affba7de2640b22b0b3f47c96b2b7e8e30d1a91de","bytes":161,"same_as_file":true}}, +{"name":"multivariant_live_wai_m3u8","op":"multivariant","input":{"file":"live/wai.m3u8","variants":[{"uri":"wai-720.m3u8","playlist":"live/wai-r3.m3u8","resolution":[32,18],"frame_rate_milli":25000}]},"expect":{"blake3":"43d8a9cac1a662298e42fe9f315a9a5101adfd51a23349d87d68f1c1f76d1b1a","bytes":181,"same_as_file":true}}, +{"name":"multivariant_players_wai_m3u8","op":"multivariant","input":{"file":"players/wai.m3u8","variants":[{"uri":"wai-v0.m3u8","playlist":"players/wai-v0.m3u8","resolution":[64,36],"frame_rate_milli":25000}]},"expect":{"blake3":"48967c4eb624a91ac9fb3a2849de4609f02fa093eced0aa31842189ea1793db7","bytes":182,"same_as_file":true}}, +{"name":"mpd_playlists_o9_wai_mpd","op":"mpd","input":{"file":"playlists/o9/wai.mpd","playlist":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","period_id":"p0","set_id":1,"rep_id":"wai-180"},"expect":{"blake3":"785aff9bc3b19bae7e0383c0156786500211621dc288e7e2b661d2171b8a0c11","bytes":1288,"same_as_file":true}}, +{"name":"mpd_players_wai_mpd","op":"mpd","input":{"file":"players/wai.mpd","playlist":"players/wai-v0.m3u8","classical":"players/classical/v0.m3u8","period_id":"p0","set_id":1,"rep_id":"wai-v0"},"expect":{"blake3":"d3e43e42b871ea022eca4f7231af729e546300480217f6781f9906e94e32d66f","bytes":1280,"same_as_file":true}}, +{"name":"media_playlist_short_O9","op":"media_playlist","input":{"classical":"src/playlists/o9-classical.m3u8","uri_prefix":"","segments":[{"object":"O9a"},{"object":"O9b"}]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"media_playlist_parts_beside_no_target","op":"media_playlist","input":{"classical":"src/playlists/o9-classical.m3u8","uri_prefix":"","segments":[{"object":"O9a","parts":[{"object":"O12p0_00","alias":"p0.wai","independent":true,"duration_ms":320},{"object":"O12p0_01","alias":"p1.wai","independent":true,"duration_ms":320}]},{"object":"O9b"},{"object":"O9c"}]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"media_playlist_layer_not_a_layer","op":"media_playlist","input":{"classical":"src/playlists/o9-classical.m3u8","uri_prefix":"","segments":[{"object":"O9a","layers":["O9b"]},{"object":"O9b"},{"object":"O9c"}]},"expect":{"error":"not_a_layer"}}, +{"name":"media_playlist_open_without_classical","op":"media_playlist","input":{"classical":"src/playlists/o9-classical.m3u8","uri_prefix":"","segments":[{"object":"O9a"},{"object":"O9b"},{"object":"O9c"}],"open":{"parts":[{"object":"O12p1_00","alias":"p.wai","independent":true,"duration_ms":320}]}},"expect":{"error":"adjacency_misaligned"}}, +{"name":"playlist_parse_playlists_o9_wai_180_m3u8","op":"playlist_parse","input":{"file":"playlists/o9/wai-180.m3u8"},"expect":{"version":7,"target_duration_s":1,"media_sequence":0,"discontinuity_sequence":0,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"segments":[[640,false,null,[],[],"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009"],[640,false,null,[],[],"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004&h=1009"],[640,false,null,[],[],"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381&h=1009"]],"open":null,"preload_hint":null,"rendered_form":true}}, +{"name":"playlist_parse_playlists_o10_wai_a_m3u8","op":"playlist_parse","input":{"file":"playlists/o10/wai-a.m3u8"},"expect":{"version":7,"target_duration_s":1,"media_sequence":0,"discontinuity_sequence":0,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"segments":[[50,false,null,[],[1,2],"../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825&h=1009"]],"open":null,"preload_hint":null,"rendered_form":true}}, +{"name":"playlist_parse_playlists_o11_wai_v_m3u8","op":"playlist_parse","input":{"file":"playlists/o11/wai-v.m3u8"},"expect":{"version":7,"target_duration_s":1,"media_sequence":0,"discontinuity_sequence":0,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"segments":[[200,false,null,[],[],"../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551&h=551"],[200,false,null,[],[1],"../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523&h=523"]],"open":null,"preload_hint":null,"rendered_form":true}}, +{"name":"playlist_parse_live_wai_r0_m3u8","op":"playlist_parse","input":{"file":"live/wai-r0.m3u8"},"expect":{"version":9,"target_duration_s":4,"media_sequence":1200,"discontinuity_sequence":0,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"segments":[],"open":[false,"2026-10-01T12:00:00.000Z",[[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]]]],"preload_hint":"720/1200.5.wai","rendered_form":true}}, +{"name":"playlist_parse_live_wai_r1_m3u8","op":"playlist_parse","input":{"file":"live/wai-r1.m3u8"},"expect":{"version":9,"target_duration_s":4,"media_sequence":1200,"discontinuity_sequence":0,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"segments":[[3840,false,"2026-10-01T12:00:00.000Z",[[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]]],[],"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606&h=1009"]],"open":[false,null,[[320,true,[]],[320,true,[]],[320,true,[]]]],"preload_hint":"720/1201.3.wai","rendered_form":true}}, +{"name":"playlist_parse_live_wai_r2_m3u8","op":"playlist_parse","input":{"file":"live/wai-r2.m3u8"},"expect":{"version":9,"target_duration_s":4,"media_sequence":1200,"discontinuity_sequence":0,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"segments":[[3840,false,"2026-10-01T12:00:00.000Z",[[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]]],[],"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606&h=1009"],[3840,false,null,[[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]]],[],"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118&h=1009"]],"open":null,"preload_hint":"720/1202.0.wai","rendered_form":true}}, +{"name":"playlist_parse_live_wai_r3_m3u8","op":"playlist_parse","input":{"file":"live/wai-r3.m3u8"},"expect":{"version":9,"target_duration_s":4,"media_sequence":1200,"discontinuity_sequence":0,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"segments":[[3840,false,"2026-10-01T12:00:00.000Z",[],[],"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606&h=1009"],[3840,false,null,[],[],"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118&h=1009"]],"open":null,"preload_hint":null,"rendered_form":true}}, +{"name":"playlist_parse_players_wai_v0_m3u8","op":"playlist_parse","input":{"file":"players/wai-v0.m3u8"},"expect":{"version":7,"target_duration_s":1,"media_sequence":0,"discontinuity_sequence":0,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"segments":[[1000,false,null,[],[],"../objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai#n=21758&h=1009"],[1000,false,null,[],[],"../objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai#n=20986&h=1009"],[1000,false,null,[],[],"../objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai#n=21338&h=1009"]],"open":null,"preload_hint":null,"rendered_form":true}}, +{"name":"playlist_parse_src_live_classical_r1_m3u8","op":"playlist_parse","input":{"file":"src/live/classical-r1.m3u8"},"expect":{"version":9,"target_duration_s":4,"media_sequence":1200,"discontinuity_sequence":0,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"segments":[[3840,false,"2026-10-01T12:00:00.000Z",[[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]],[320,true,[]]],[],"1200.m4s"]],"open":[false,null,[[320,true,[]],[320,true,[]],[320,true,[]]]],"preload_hint":"1201.3.m4s","rendered_form":false}}, +{"name":"playlist_parse_players_classical_v0_m3u8","op":"playlist_parse","input":{"file":"players/classical/v0.m3u8"},"expect":{"version":7,"target_duration_s":1,"media_sequence":0,"discontinuity_sequence":0,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"segments":[[1000,false,null,[],[],"seg0.m4s"],[1000,false,null,[],[],"seg1.m4s"],[1000,false,null,[],[],"seg2.m4s"]],"open":null,"preload_hint":null,"rendered_form":false}}, +{"name":"playlist_parse_src_playlists_o9_classical_m3u8","op":"playlist_parse","input":{"file":"src/playlists/o9-classical.m3u8"},"expect":{"version":7,"target_duration_s":1,"media_sequence":0,"discontinuity_sequence":0,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"segments":[[640,false,null,[],[],"seg-180-0.m4s"],[640,false,null,[],[],"seg-180-1.m4s"],[640,false,null,[],[],"seg-180-2.m4s"]],"open":null,"preload_hint":null,"rendered_form":false}}, +{"name":"playlist_parse_no_header","op":"playlist_parse","input":{"text":"#EXT-X-VERSION:3\n#EXTINF:1,\na\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_extinf_no_uri","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXTINF:1,\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_uri_no_extinf","op":"playlist_parse","input":{"text":"#EXTM3U\na.wai\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_skip","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXT-X-SKIP:SKIPPED-SEGMENTS=3\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_variant","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1\na.m3u8\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_sequence_late","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXTINF:1,\na\n#EXT-X-MEDIA-SEQUENCE:3\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_layer_after_uri","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXTINF:1,\na\n#WAI-LAYER:N=1,URI=\"l.wai\"\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_layer_order","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXTINF:1,\n#WAI-LAYER:N=2,URI=\"l.wai\"\n#WAI-LAYER:N=1,URI=\"m.wai\"\na\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_layer_comment_between","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXTINF:1,\n# a note\n#WAI-LAYER:N=1,URI=\"l.wai\"\na\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_part_no_uri","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXT-X-PART:DURATION=0.5\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_two_hints","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"a\"\n#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"b\"\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_negative","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXTINF:-1,\na\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_exponent","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXTINF:1e3,\na\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_block_maybe","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=MAYBE\n"},"expect":{"error":"playlist_malformed"}}, +{"name":"playlist_parse_layer_zero","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXTINF:1,\n#WAI-LAYER:N=0,URI=\"l.wai\"\na\n"},"expect":{"error":"layer_tag_malformed"}}, +{"name":"playlist_parse_rounding","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXTINF:3.9995,\na\n#EXTINF:3.99949,\nb\n#EXTINF:4.00008,\nc\n#EXTINF:4,\nd\n"},"expect":{"version":null,"target_duration_s":null,"media_sequence":0,"discontinuity_sequence":0,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":false,"segments":[[4000,false,null,[],[],"a"],[3999,false,null,[],[],"b"],[4000,false,null,[],[],"c"],[4000,false,null,[],[],"d"]],"open":null,"preload_hint":null,"rendered_form":false}}, +{"name":"playlist_parse_crlf_title","op":"playlist_parse","input":{"text":"#EXTM3U\r\n#EXTINF:0.64,a title\r\nseg.m4s\r\n#EXT-X-DISCONTINUITY\r\n#EXTINF:1,\r\nseg2.m4s\r\n"},"expect":{"version":null,"target_duration_s":null,"media_sequence":0,"discontinuity_sequence":0,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":false,"segments":[[640,false,null,[],[],"seg.m4s"],[1000,true,null,[],[],"seg2.m4s"]],"open":null,"preload_hint":null,"rendered_form":false}}, +{"name":"playlist_parse_hint_only","op":"playlist_parse","input":{"text":"#EXTM3U\n#EXT-X-PART-INF:PART-TARGET=1\n#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"p.0\"\n#EXT-X-PRELOAD-HINT:TYPE=MAP,URI=\"i\"\n"},"expect":{"version":null,"target_duration_s":null,"media_sequence":0,"discontinuity_sequence":0,"part_target_ms":1000,"part_hold_back_ms":null,"can_block_reload":false,"end_list":false,"segments":[],"open":null,"preload_hint":"p.0","rendered_form":false}}, +{"name":"adjacency_playlists_o9_wai_180_m3u8","op":"adjacency","input":{"wai":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8"},"expect":{"adjacent":true}}, +{"name":"adjacency_playlists_o10_wai_a_m3u8","op":"adjacency","input":{"wai":"playlists/o10/wai-a.m3u8","classical":"src/playlists/o10-classical.m3u8"},"expect":{"adjacent":true}}, +{"name":"adjacency_playlists_o11_wai_v_m3u8","op":"adjacency","input":{"wai":"playlists/o11/wai-v.m3u8","classical":"src/playlists/o11-classical.m3u8"},"expect":{"adjacent":true}}, +{"name":"adjacency_live_wai_r0_m3u8","op":"adjacency","input":{"wai":"live/wai-r0.m3u8","classical":"src/live/classical-r0.m3u8"},"expect":{"adjacent":true}}, +{"name":"adjacency_live_wai_r1_m3u8","op":"adjacency","input":{"wai":"live/wai-r1.m3u8","classical":"src/live/classical-r1.m3u8"},"expect":{"adjacent":true}}, +{"name":"adjacency_live_wai_r2_m3u8","op":"adjacency","input":{"wai":"live/wai-r2.m3u8","classical":"src/live/classical-r2.m3u8"},"expect":{"adjacent":true}}, +{"name":"adjacency_live_wai_r3_m3u8","op":"adjacency","input":{"wai":"live/wai-r3.m3u8","classical":"src/live/classical-r3.m3u8"},"expect":{"adjacent":true}}, +{"name":"adjacency_players_wai_v0_m3u8","op":"adjacency","input":{"wai":"players/wai-v0.m3u8","classical":"players/classical/v0.m3u8"},"expect":{"adjacent":true}}, +{"name":"adjacency_extinf_shifted","op":"adjacency","input":{"wai":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","edit_wai":[["#EXTINF:0.640,","#EXTINF:0.641,"]]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"adjacency_discontinuity_one_side","op":"adjacency","input":{"wai":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","edit_wai":[["#EXTINF:0.640,","#EXT-X-DISCONTINUITY\n#EXTINF:0.640,"]]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"adjacency_media_sequence","op":"adjacency","input":{"wai":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","edit_wai":[["#EXT-X-MEDIA-SEQUENCE:0","#EXT-X-MEDIA-SEQUENCE:1"]]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"adjacency_discontinuity_sequence","op":"adjacency","input":{"wai":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","edit_wai":[["#EXT-X-MEDIA-SEQUENCE:0\n","#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-DISCONTINUITY-SEQUENCE:1\n"]]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"adjacency_segment_missing","op":"adjacency","input":{"wai":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","edit_wai":[["#EXTINF:0.640,\n../../objects/","#EXT-X-BYTERANGE:1\n#EXT-X-GAP\n#EXTINF:0.640,\n../../objects/"],["#EXT-X-ENDLIST","#EXTINF:0.640,\nextra.wai\n#EXT-X-ENDLIST"]]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"adjacency_classical_discontinuity","op":"adjacency","input":{"wai":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","edit_classical":[["#EXTINF:0.64,\nseg-180-2","#EXT-X-DISCONTINUITY\n#EXTINF:0.64,\nseg-180-2"]]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"adjacency_part_duration","op":"adjacency","input":{"wai":"live/wai-r1.m3u8","classical":"src/live/classical-r1.m3u8","edit_wai":[["#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI=\"720/1200.4.wai","#EXT-X-PART:DURATION=0.319,INDEPENDENT=YES,URI=\"720/1200.4.wai"]]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"adjacency_open_part_duration","op":"adjacency","input":{"wai":"live/wai-r1.m3u8","classical":"src/live/classical-r1.m3u8","edit_wai":[["#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI=\"720/1201.2.wai","#EXT-X-PART:DURATION=0.330,INDEPENDENT=YES,URI=\"720/1201.2.wai"]]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"adjacency_part_target","op":"adjacency","input":{"wai":"live/wai-r1.m3u8","classical":"src/live/classical-r1.m3u8","edit_wai":[["PART-TARGET=0.320","PART-TARGET=0.330"]]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"adjacency_program_date_time","op":"adjacency","input":{"wai":"live/wai-r1.m3u8","classical":"src/live/classical-r1.m3u8","edit_wai":[["2026-10-01T12:00:00.000Z","2026-10-01T12:00:00.000+00:00"]]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"adjacency_layer_line_ignored","op":"adjacency","input":{"wai":"live/wai-r1.m3u8","classical":"src/live/classical-r1.m3u8","edit_wai":[["#EXTINF:3.840,\n","#EXTINF:3.840,\n#WAI-LAYER:N=1,URI=\"../objects/l.wai#n=9\"\n"]]},"expect":{"adjacent":true}}, +{"name":"adjacency_live_s_beside_live_p","op":"adjacency","input":{"wai":"live/wai-r3.m3u8","classical":"src/live/classical-r3.m3u8","edit_wai":[["#EXT-X-ENDLIST\n",""]],"edit_classical":[["#EXT-X-ENDLIST\n",""]]},"expect":{"adjacent":true}}, +{"name":"adjacency_malformed_wai","op":"adjacency","input":{"wai":"live/wai-r1.m3u8","classical":"src/live/classical-r1.m3u8","edit_wai":[["#EXTM3U","#EXTM3X"]]},"expect":{"error":"playlist_malformed"}}, +{"name":"unchanged_players","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[]},"expect":{"unchanged":true}}, +{"name":"unchanged_original_itself","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"src/players/master.m3u8"},"expect":{"unchanged":true}}, +{"name":"unchanged_two_lines","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n","#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n"]]},"expect":{"error":"classical_changed"}}, +{"name":"unchanged_bandwidth_changed","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["BANDWIDTH=10840","BANDWIDTH=10841"]]},"expect":{"error":"classical_changed"}}, +{"name":"unchanged_other_data_id","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["science.transaction.wai.multivariant","science.transaction.wai.other"]]},"expect":{"error":"classical_changed"}}, +{"name":"unchanged_trailing_space","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["VALUE=\"wai.m3u8\"\n","VALUE=\"wai.m3u8\" \n"]]},"expect":{"error":"classical_changed"}}, +{"name":"unchanged_first_line","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n",""],["#EXTM3U\n","#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n#EXTM3U\n"]]},"expect":{"error":"classical_changed"}}, +{"name":"playlist_receipt_playlists_o9_wai_180_m3u8","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json"},"expect":{"signed":"5a9e817bd6016a698316663d13b38ab925e6013148b3f452a2fd7368c250444e","parent":null}}, +{"name":"playlist_receipt_playlists_o10_wai_a_m3u8","op":"playlist_receipt","input":{"playlist":"playlists/o10/wai-a.m3u8","receipt":"playlists/o10/mr/89f7c647fb786e14ced2c1c8250f30fc105274bfae7e065ecbfa41446b78d296.json"},"expect":{"signed":"58329cac50310f1ede84e0d375db64a3f6e67003c6165ce6b00a3dedefc56e6e","parent":null}}, +{"name":"playlist_receipt_playlists_o11_wai_v_m3u8","op":"playlist_receipt","input":{"playlist":"playlists/o11/wai-v.m3u8","receipt":"playlists/o11/mr/bc3327099dc8f1121c168b9b9872233eca247c07d0ccf4fd30f6eb633d87acb6.json"},"expect":{"signed":"e651c9d1630c752917ffbe2f156cbf1da6b95e03e8532e748b9addb72de5fd99","parent":null}}, +{"name":"playlist_receipt_live_wai_r0_m3u8","op":"playlist_receipt","input":{"playlist":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json"},"expect":{"signed":"c378db4174631a36f2aadb6a77e49dbcb218aff10f380a67a41b2566edcf7c61","parent":null}}, +{"name":"playlist_receipt_live_wai_r1_m3u8","op":"playlist_receipt","input":{"playlist":"live/wai-r1.m3u8","receipt":"live/mr/2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5.json"},"expect":{"signed":"069792acc477710a2ad7b5404c7520de5a2e5e4e481108c1f84f49af9600f64f","parent":"c378db4174631a36f2aadb6a77e49dbcb218aff10f380a67a41b2566edcf7c61"}}, +{"name":"playlist_receipt_live_wai_r2_m3u8","op":"playlist_receipt","input":{"playlist":"live/wai-r2.m3u8","receipt":"live/mr/fea90b39e7bfdfb5e9e70feff146632b7516d833b969b6549f6f8e5f7b3c8b11.json"},"expect":{"signed":"3c7c1decf737c48ff86a4e8a4eee1557e206406f2be6b33aae20dd56510210fa","parent":"069792acc477710a2ad7b5404c7520de5a2e5e4e481108c1f84f49af9600f64f"}}, +{"name":"playlist_receipt_live_wai_r3_m3u8","op":"playlist_receipt","input":{"playlist":"live/wai-r3.m3u8","receipt":"live/mr/c06453e032118886f29548eda03331427f731719277601cca4359985b7103a4d.json"},"expect":{"signed":"e4598c7e3c87620786f1cea0014cf0864feb715a392ead70a81e536c4115b088","parent":"3c7c1decf737c48ff86a4e8a4eee1557e206406f2be6b33aae20dd56510210fa"}}, +{"name":"playlist_receipt_players_wai_v0_m3u8","op":"playlist_receipt","input":{"playlist":"players/wai-v0.m3u8","receipt":"players/mr/afbdb9d6d90f0b36501b0ff7194d3d2a6b2e2b9cba605f1ba43b51f900eeb351.json"},"expect":{"signed":"146f4128ce4f42c82d3a050ce1c769094ebe796b60af739abb92a5fe61be083f","parent":null}}, +{"name":"playlist_receipt_name","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json","name":"0000.json"},"expect":{"error":"receipt_name_mismatch"}}, +{"name":"playlist_receipt_playlist_changed","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json","edit_playlist":[["#EXTINF:0.640,","#EXTINF:0.641,"]]},"expect":{"error":"playlist_receipt_mismatch"}}, +{"name":"playlist_receipt_viewers","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json","edit_receipt":[["\"viewers_served\":1","\"viewers_served\":2"]]},"expect":{"error":"playlist_receipt_invalid"}}, +{"name":"playlist_receipt_class","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json","edit_receipt":[["\"entitlement\":\"public\"","\"entitlement\":\"svod\""]]},"expect":{"error":"playlist_receipt_invalid"}}, +{"name":"playlist_receipt_extras_equivalent","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json","edit_receipt":[["\"extras\":[]","\"extras\":[{\"discontinuity\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null},{\"discontinuity\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null},{\"discontinuity\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null}]"]]},"expect":{"error":"playlist_receipt_invalid"}}, +{"name":"playlist_receipt_kind","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json","edit_receipt":[["\"kind\":\"wai.video.manifest\"","\"kind\":\"wai.video.package\""]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_unknown_member","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json","edit_receipt":[["{\"kind\"","{\"note\":1,\"kind\""]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_part_independent","op":"playlist_receipt","input":{"playlist":"live/wai-r1.m3u8","receipt":"live/mr/2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5.json","edit_playlist":[["DURATION=0.320,INDEPENDENT=YES,URI=\"720/1200.3","DURATION=0.320,URI=\"720/1200.3"]]},"expect":{"error":"playlist_receipt_mismatch"}}, +{"name":"playlist_receipt_parts_reordered","op":"playlist_receipt","input":{"playlist":"live/wai-r1.m3u8","receipt":"live/mr/2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5.json","edit_receipt":[["\"uri\":\"720/1200.3.wai","\"uri\":\"720/1200.X.wai"],["\"uri\":\"720/1200.4.wai","\"uri\":\"720/1200.3.wai"],["\"uri\":\"720/1200.X.wai","\"uri\":\"720/1200.4.wai"]]},"expect":{"error":"playlist_receipt_mismatch"}}, +{"name":"live_edge","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/fea90b39e7bfdfb5e9e70feff146632b7516d833b969b6549f6f8e5f7b3c8b11.json","classical":"src/live/classical-r2.m3u8","edit_wai":[]},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/c06453e032118886f29548eda03331427f731719277601cca4359985b7103a4d.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1200.5.wai","object":"O12p0_05"},{"alias":"720/1201.3.wai","object":"O12p1_03"},{"alias":"720/1201.7.wai","object":"O12p1_08"},{"alias":"720/1200.2.wai","object":"O12p0_02","patch_object":[[400,"00"]]},{"alias":"720/1202.0.wai","object":"O12p1_11"},{"alias":"1200.m4s","object":"O12s0"}]},"expect":{"revisions":[{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1200.5.wai","listed_in":1,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"},{"alias":"720/1201.7.wai","listed_in":2,"outcome":"object_digest_mismatch","held":null},{"alias":"720/1200.2.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1202.0.wai","listed_in":null,"outcome":"not_listed","held":null},{"alias":"1200.m4s","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_edge_revision_changed","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/fea90b39e7bfdfb5e9e70feff146632b7516d833b969b6549f6f8e5f7b3c8b11.json","classical":"src/live/classical-r2.m3u8","edit_wai":[["720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f","720/1201.7.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed"]]},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/c06453e032118886f29548eda03331427f731719277601cca4359985b7103a4d.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1201.7.wai","object":"O12p1_08"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"playlist_receipt_mismatch","chained":null},{"adjacent":true,"receipt":"signed","chained":null}],"fetches":[{"alias":"720/1201.7.wai","listed_in":2,"outcome":"accepted","held":"listed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"listed"}]}}, +{"name":"live_edge_unchained","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"cases/live-r2-orphan.json","classical":"src/live/classical-r2.m3u8","edit_wai":[]},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/c06453e032118886f29548eda03331427f731719277601cca4359985b7103a4d.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1201.3.wai","object":"O12p1_03"},{"alias":"720/1200.0.wai","object":"O12p0_00"}]},"expect":{"revisions":[{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":"playlist_receipt_unchained"},{"adjacent":true,"receipt":"signed","chained":"playlist_receipt_unchained"}],"fetches":[{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"listed"},{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"}]}}, +{"name":"abr_throughput_00","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":28203,"samples":[[1000,1259305,570]],"last":2,"option_bytes":[229507,231557,344895],"option_joules_micro":[3913,null,3815],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_throughput_01","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":27557,"samples":[],"last":0,"option_bytes":[48543,69733,178222,189003,255156,340556],"option_joules_micro":[250,4083,4219,null,null,null],"joule_budget_micro":1274,"segment_ms":2000}},"expect":{"choice":0,"budget":"within"}}, +{"name":"abr_throughput_02","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":27339,"samples":[],"last":null,"option_bytes":[132854,232413],"option_joules_micro":[null,315],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_throughput_03","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":5325,"samples":[[1000,691100,1108],[2000,1798923,186],[3000,499287,1537],[4000,729119,142]],"last":null,"option_bytes":[47614],"option_joules_micro":[2441],"joule_budget_micro":75,"segment_ms":3840}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_throughput_04","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":29234,"samples":[[1000,96310,457],[2000,692823,1361],[3000,532395,2859],[4000,1569349,2674],[5000,309913,2047]],"last":2,"option_bytes":[31692,61260,114256,118871,321713],"option_joules_micro":[3464,1013,null,1819,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_throughput_05","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":38786,"samples":[[1000,1346651,872],[2000,1898768,937],[3000,1172638,1448]],"last":3,"option_bytes":[96715,141611,162979,171720,184391,300283],"option_joules_micro":[null,1397,3978,58,3341,null],"joule_budget_micro":3328,"segment_ms":1000}},"expect":{"choice":5,"budget":"not_applied"}}, +{"name":"abr_throughput_06","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":39560,"samples":[],"last":null,"option_bytes":[82831,144942],"option_joules_micro":[null,1121],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_throughput_07","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":38581,"samples":[[1000,751117,26]],"last":null,"option_bytes":[237209],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_throughput_08","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":19262,"samples":[[1000,597546,2693],[2000,684159,2225]],"last":0,"option_bytes":[168294],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_throughput_09","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":8381,"samples":[[1000,1385410,319],[2000,1460612,2797]],"last":1,"option_bytes":[65202,65696,184785,346631,371704],"option_joules_micro":[1624,3288,4389,3212,null],"joule_budget_micro":4577,"segment_ms":4000}},"expect":{"choice":4,"budget":"not_applied"}}, +{"name":"abr_throughput_10","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":6439,"samples":[[1000,1642862,2072],[2000,1724810,2415]],"last":null,"option_bytes":[221303,254054,282799,397258],"option_joules_micro":[2984,4714,951,2746],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_throughput_11","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":12659,"samples":[[1000,1332812,1319],[2000,939957,2721],[3000,1367969,837],[4000,1599764,1867]],"last":2,"option_bytes":[80403,120456,245927],"option_joules_micro":[3147,null,2885],"joule_budget_micro":5230,"segment_ms":200}},"expect":{"choice":1,"budget":"not_applied"}}, +{"name":"abr_throughput_12","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":16498,"samples":[[1000,495433,2847],[2000,1138099,1332],[3000,118438,752]],"last":null,"option_bytes":[5136,209878,237512,306265,314855],"option_joules_micro":[2083,1131,2370,3206,2827],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_throughput_13","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":23508,"samples":[],"last":null,"option_bytes":[106126,244473,290603,332362,353222,363306],"option_joules_micro":[null,2545,null,4788,4650,null],"joule_budget_micro":5986,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_applied"}}, +{"name":"abr_throughput_14","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":25907,"samples":[[1000,1865109,2358],[2000,1599056,2350]],"last":null,"option_bytes":[27081,34658],"option_joules_micro":[789,4649],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_throughput_15","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":34250,"samples":[],"last":null,"option_bytes":[35851,202585,220271,236085,248931,345739],"option_joules_micro":[1723,1554,null,199,781,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_throughput_16","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":23347,"samples":[],"last":null,"option_bytes":[200798,293314,359455],"option_joules_micro":[1339,null,2473],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_throughput_17","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":600,"samples":[[1000,1896617,2224],[2000,1558447,1692],[3000,765327,1419]],"last":1,"option_bytes":[163436,186366,272666,275011,340603],"option_joules_micro":[3991,4424,null,3790,3355],"joule_budget_micro":1411,"segment_ms":2000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_throughput_18","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":13891,"samples":[[1000,954290,1322],[2000,1359600,1307],[3000,579442,570],[4000,1316832,1762],[5000,1974497,2440]],"last":null,"option_bytes":[73894,79372,276740],"option_joules_micro":[2593,1518,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_throughput_19","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":24789,"samples":[[1000,560355,44],[2000,1168366,2494],[3000,1768747,727],[4000,1437641,603]],"last":null,"option_bytes":[163014,260410],"option_joules_micro":[null,1131],"joule_budget_micro":4590,"segment_ms":200}},"expect":{"choice":0,"budget":"not_applied"}}, +{"name":"abr_throughput_20","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":15721,"samples":[[1000,1211483,39],[2000,301375,2007]],"last":0,"option_bytes":[10369,280040],"option_joules_micro":[null,668],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_throughput_21","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":33334,"samples":[[1000,1447610,2507],[2000,1657478,2231],[3000,450998,1363]],"last":null,"option_bytes":[183723,251782,272819,337959,393865,395099],"option_joules_micro":[21,null,1077,4664,303,null],"joule_budget_micro":4277,"segment_ms":3840}},"expect":{"choice":5,"budget":"not_applied"}}, +{"name":"abr_throughput_22","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":35031,"samples":[[1000,324127,2144],[2000,1092870,1089]],"last":null,"option_bytes":[352081],"option_joules_micro":[1053],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_throughput_23","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":19071,"samples":[[1000,287343,1171],[2000,964313,2942],[3000,1704757,2128],[4000,1385776,95]],"last":null,"option_bytes":[72845,114716,340475,374581,379279],"option_joules_micro":[4160,2173,704,null,4235],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_throughput_24","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":6320,"samples":[[1000,180442,1433],[2000,1296876,1896],[3000,1459973,2827],[4000,906413,1788],[5000,708548,914]],"last":2,"option_bytes":[18928,32680,119510,244405,348963],"option_joules_micro":[4323,null,3719,334,2024],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_throughput_25","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":21905,"samples":[[1000,1343712,1367],[2000,469695,2726],[3000,1904792,2871],[4000,1325799,1677],[5000,250625,1526]],"last":null,"option_bytes":[45241,215743,242203,249812],"option_joules_micro":[4983,null,4421,2631],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_throughput_26","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":12394,"samples":[[1000,1228723,1636],[2000,1510412,794],[3000,100188,1057]],"last":3,"option_bytes":[10898,111800,122856,238090],"option_joules_micro":[4652,null,null,3096],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_throughput_27","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":28905,"samples":[[1000,694916,82],[2000,112506,981],[3000,1300898,439],[4000,1895031,165]],"last":null,"option_bytes":[256221],"option_joules_micro":[1154],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_throughput_28","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":10426,"samples":[[1000,1429554,3],[2000,119924,2406],[3000,692890,1773],[4000,1863026,2411]],"last":null,"option_bytes":[118910,163595,350359],"option_joules_micro":[null,2770,3426],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_throughput_29","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":9070,"samples":[[1000,1012061,1185]],"last":null,"option_bytes":[18854,33308,77115,281011],"option_joules_micro":[1937,1674,2859,1446],"joule_budget_micro":371,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_throughput_30","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":15557,"samples":[[1000,756365,795],[2000,1757679,1638]],"last":null,"option_bytes":[368004],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_throughput_31","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":26456,"samples":[[1000,1148772,1885],[2000,1378089,1925],[3000,1488056,561],[4000,615024,2153],[5000,203736,1988]],"last":null,"option_bytes":[25456,55188,175334,372052,395592],"option_joules_micro":[2982,null,1043,null,null],"joule_budget_micro":1948,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_applied"}}, +{"name":"abr_throughput_32","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":13482,"samples":[[1000,353535,461],[2000,100125,2264],[3000,1549597,940]],"last":null,"option_bytes":[71315,160507,171934,384830],"option_joules_micro":[null,4992,null,4405],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_throughput_33","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":33100,"samples":[[1000,1213885,1167],[2000,361434,1921]],"last":null,"option_bytes":[86339,106234,179258,247949],"option_joules_micro":[null,123,null,null],"joule_budget_micro":5271,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_applied"}}, +{"name":"abr_throughput_34","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":9326,"samples":[[1000,49874,1866],[2000,429238,1510],[3000,1332114,425],[4000,763830,1566],[5000,1149134,46]],"last":null,"option_bytes":[69890,101912,107255,206376,244677,356206],"option_joules_micro":[373,740,2549,383,2834,4872],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_throughput_35","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":2542,"samples":[[1000,293117,2313]],"last":0,"option_bytes":[63930,141450,153125,187898,230096,347497],"option_joules_micro":[58,3606,4416,null,null,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_throughput_36","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":33929,"samples":[[1000,59337,1305]],"last":2,"option_bytes":[160052,162794,223319,364293,366948],"option_joules_micro":[2111,2846,2816,774,null],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_throughput_37","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":23972,"samples":[[1000,1365119,332]],"last":1,"option_bytes":[26256,32808,162909,197504,315792,317227],"option_joules_micro":[null,1960,1542,null,1314,623],"joule_budget_micro":5741,"segment_ms":3840}},"expect":{"choice":5,"budget":"within"}}, +{"name":"abr_throughput_38","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":34202,"samples":[[1000,2498,1932],[2000,740286,1424],[3000,1964512,2166],[4000,1463791,994],[5000,833109,438]],"last":null,"option_bytes":[4006,177173,200028,293451,296992,351459],"option_joules_micro":[null,2760,3007,4566,1624,2408],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_throughput_39","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":6714,"samples":[[1000,1739427,69],[2000,1968256,622],[3000,1020785,2536],[4000,1578878,2178],[5000,456040,2675]],"last":null,"option_bytes":[113412,155507,211677,215466,331531,346524],"option_joules_micro":[4643,4087,1312,2992,null,4122],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_buffer_based_00","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":37166,"samples":[[1000,1979410,9]],"last":1,"option_bytes":[210016,308333,345583],"option_joules_micro":[null,2474,null],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_buffer_based_01","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":16121,"samples":[[1000,570802,767]],"last":5,"option_bytes":[204621,284313,289434,306386,318982,351290],"option_joules_micro":[null,1352,null,null,3924,null],"joule_budget_micro":4692,"segment_ms":200}},"expect":{"choice":5,"budget":"not_applied"}}, +{"name":"abr_buffer_based_02","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":29777,"samples":[[1000,1565417,2877],[2000,1095405,1838],[3000,1213452,1830],[4000,1561082,2035],[5000,514815,2890]],"last":null,"option_bytes":[125752,240457],"option_joules_micro":[null,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_buffer_based_03","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":2271,"samples":[[1000,1844685,1561],[2000,1660953,1396],[3000,1046634,1653],[4000,572996,984]],"last":null,"option_bytes":[78556,128536,136533,216821,223315],"option_joules_micro":[null,null,null,563,4718],"joule_budget_micro":4470,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_applied"}}, +{"name":"abr_buffer_based_04","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":10913,"samples":[[1000,1293814,1998],[2000,1252255,1484],[3000,443443,1964]],"last":null,"option_bytes":[170679],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_buffer_based_05","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":952,"samples":[[1000,787637,1089],[2000,1884870,2269],[3000,1428241,684],[4000,663641,1609],[5000,512905,917]],"last":null,"option_bytes":[27588,215824,225817,302822,314142,359134],"option_joules_micro":[286,2738,439,519,2188,null],"joule_budget_micro":3150,"segment_ms":200}},"expect":{"choice":0,"budget":"within"}}, +{"name":"abr_buffer_based_06","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":4978,"samples":[[1000,331389,564],[2000,827587,787],[3000,797362,361],[4000,253656,2761],[5000,772285,657]],"last":0,"option_bytes":[145164],"option_joules_micro":[2801],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_buffer_based_07","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":29335,"samples":[],"last":null,"option_bytes":[70464,132583,186315],"option_joules_micro":[2199,4129,null],"joule_budget_micro":662,"segment_ms":4000}},"expect":{"choice":2,"budget":"not_applied"}}, +{"name":"abr_buffer_based_08","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":37749,"samples":[[1000,1709563,19]],"last":null,"option_bytes":[69175,85110,139703,199776,217765],"option_joules_micro":[4958,null,2129,null,2528],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_buffer_based_09","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":39078,"samples":[[1000,1551825,1508],[2000,654267,1691],[3000,338056,1616]],"last":null,"option_bytes":[2240,35831,107259,143553,219593],"option_joules_micro":[2163,238,3509,2438,3908],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_buffer_based_10","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":36561,"samples":[[1000,271282,2798],[2000,430309,868],[3000,46995,1685]],"last":null,"option_bytes":[27471,245810],"option_joules_micro":[1924,102],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_buffer_based_11","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":28890,"samples":[[1000,1226472,2092],[2000,737898,352],[3000,1925300,2584],[4000,270282,2632],[5000,1390372,242]],"last":null,"option_bytes":[31361,140603,176358,281536],"option_joules_micro":[397,2368,null,595],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_buffer_based_12","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":11976,"samples":[[1000,539711,1921],[2000,502892,1851],[3000,501448,2190],[4000,1799944,1647],[5000,1645324,2591]],"last":0,"option_bytes":[321858],"option_joules_micro":[2279],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_buffer_based_13","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":35463,"samples":[[1000,294543,579]],"last":null,"option_bytes":[62094,85789],"option_joules_micro":[4936,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_buffer_based_14","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":1866,"samples":[[1000,1377500,831],[2000,187295,2750],[3000,292627,1265],[4000,442366,702],[5000,1924865,1255]],"last":4,"option_bytes":[3210,213072,217510,287820,295523,356470],"option_joules_micro":[null,817,null,2337,3507,3573],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_buffer_based_15","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":27309,"samples":[[1000,111715,2526],[2000,231034,979]],"last":null,"option_bytes":[314026,391252],"option_joules_micro":[2964,4898],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_buffer_based_16","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":36242,"samples":[[1000,443958,2187],[2000,807690,546],[3000,208080,638]],"last":4,"option_bytes":[168938,185911,280205,302365,376909,391121],"option_joules_micro":[1840,3254,3948,2221,1166,2731],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_buffer_based_17","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":24891,"samples":[[1000,829665,2311],[2000,714803,2411],[3000,225237,1950],[4000,1067161,1421]],"last":null,"option_bytes":[59468,110431,164038,234549,259436,283866],"option_joules_micro":[null,null,null,3929,null,620],"joule_budget_micro":4362,"segment_ms":1000}},"expect":{"choice":5,"budget":"within"}}, +{"name":"abr_buffer_based_18","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":35958,"samples":[[1000,1431039,155],[2000,683213,535],[3000,513515,939],[4000,1101294,1059]],"last":3,"option_bytes":[8921,48253,59392,199835,323599],"option_joules_micro":[3123,null,22,589,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_buffer_based_19","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":20294,"samples":[[1000,551462,1444],[2000,1791244,1418],[3000,1245642,296],[4000,1987993,542],[5000,800295,2462]],"last":1,"option_bytes":[204577,289889],"option_joules_micro":[1025,2509],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_buffer_based_20","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":14345,"samples":[[1000,1036116,1218]],"last":5,"option_bytes":[83386,133160,160689,161213,221932,264986],"option_joules_micro":[null,3026,3427,null,989,1697],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_buffer_based_21","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":23761,"samples":[[1000,764827,1975],[2000,900986,2110],[3000,821380,785]],"last":2,"option_bytes":[9880,45476,64655,65611,85145,134840],"option_joules_micro":[90,null,2781,null,1005,3558],"joule_budget_micro":5306,"segment_ms":200}},"expect":{"choice":5,"budget":"within"}}, +{"name":"abr_buffer_based_22","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":21610,"samples":[[1000,1908393,1562],[2000,383657,466],[3000,663699,1533]],"last":5,"option_bytes":[3283,41158,118561,140465,210226,241740],"option_joules_micro":[null,null,2555,2681,435,1392],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_buffer_based_23","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":38379,"samples":[[1000,809805,2288],[2000,765096,882]],"last":null,"option_bytes":[86472,152234,248870,302446,366421,401120],"option_joules_micro":[null,1913,1140,1723,null,2002],"joule_budget_micro":5919,"segment_ms":200}},"expect":{"choice":5,"budget":"within"}}, +{"name":"abr_buffer_based_24","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":379,"samples":[[1000,1356046,2126],[2000,1074031,1592],[3000,999666,807],[4000,1433111,704]],"last":null,"option_bytes":[61493,112368,201303,359641],"option_joules_micro":[1956,1863,92,null],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_buffer_based_25","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":34646,"samples":[[1000,1525189,98],[2000,1749823,264],[3000,1935984,2297],[4000,1624790,2332],[5000,362616,1156]],"last":null,"option_bytes":[158338,237699,247119,263972,306843],"option_joules_micro":[1459,3809,1529,870,4339],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_buffer_based_26","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":16817,"samples":[[1000,1886518,1146]],"last":0,"option_bytes":[186622,302800,354224],"option_joules_micro":[null,null,4044],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_buffer_based_27","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":37746,"samples":[[1000,1825008,1445],[2000,1662378,2169],[3000,1309264,253]],"last":5,"option_bytes":[10202,49128,135356,301136,307742,318281],"option_joules_micro":[1963,647,null,4351,2310,null],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_buffer_based_28","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":27041,"samples":[[1000,875003,2914],[2000,475096,1811]],"last":0,"option_bytes":[190765,197897,220330,321218,332550],"option_joules_micro":[1384,628,null,5000,4239],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_buffer_based_29","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":28564,"samples":[[1000,1903050,1616],[2000,805741,715],[3000,1156735,928]],"last":0,"option_bytes":[132147,391510],"option_joules_micro":[3736,2596],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_buffer_based_30","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":21410,"samples":[[1000,1469834,659]],"last":null,"option_bytes":[283475,308777,311223],"option_joules_micro":[765,2079,4078],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_buffer_based_31","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":23415,"samples":[[1000,442019,2287],[2000,1783989,771],[3000,1466540,996]],"last":null,"option_bytes":[32947,125397,157859,246761,271145],"option_joules_micro":[null,null,2092,1535,4449],"joule_budget_micro":1292,"segment_ms":3840}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_buffer_based_32","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":11395,"samples":[[1000,343990,2406],[2000,777840,551]],"last":0,"option_bytes":[270717],"option_joules_micro":[2843],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_buffer_based_33","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":22365,"samples":[[1000,17516,1911],[2000,1837902,2638]],"last":0,"option_bytes":[102200,185038,186610],"option_joules_micro":[2128,4256,1647],"joule_budget_micro":2909,"segment_ms":1000}},"expect":{"choice":2,"budget":"within"}}, +{"name":"abr_buffer_based_34","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":2245,"samples":[],"last":null,"option_bytes":[137626,366867],"option_joules_micro":[2133,32],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_buffer_based_35","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":13932,"samples":[[1000,1775327,142],[2000,740968,1291],[3000,1979467,186]],"last":0,"option_bytes":[26057],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_buffer_based_36","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":39383,"samples":[[1000,353676,1366],[2000,1490365,871],[3000,218806,2455],[4000,681126,1379],[5000,1439922,1626]],"last":0,"option_bytes":[45541,153009,213271,289249],"option_joules_micro":[3119,null,null,null],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_buffer_based_37","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":16367,"samples":[[1000,89442,713],[2000,695745,720],[3000,866081,2300],[4000,526205,617]],"last":null,"option_bytes":[44680,102214,198951,382909],"option_joules_micro":[null,null,null,null],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_buffer_based_38","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":19131,"samples":[[1000,353051,2579],[2000,1414361,57],[3000,577597,1301],[4000,1885009,776]],"last":1,"option_bytes":[135357,390901],"option_joules_micro":[null,2104],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_buffer_based_39","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":22639,"samples":[[1000,807717,2076],[2000,1374312,2528]],"last":0,"option_bytes":[72616],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_bola_like_00","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":33677,"samples":[[1000,829395,2963],[2000,1546059,2983]],"last":null,"option_bytes":[31524,86039,143379,169081],"option_joules_micro":[null,327,81,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_bola_like_01","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":3645,"samples":[[1000,1466360,26],[2000,1620323,2415],[3000,1649943,2525]],"last":0,"option_bytes":[345399],"option_joules_micro":[1409],"joule_budget_micro":3910,"segment_ms":1000}},"expect":{"choice":0,"budget":"within"}}, +{"name":"abr_bola_like_02","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":24831,"samples":[],"last":1,"option_bytes":[111510,190965],"option_joules_micro":[1410,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_bola_like_03","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":28818,"samples":[[1000,393581,121],[2000,1857427,184],[3000,1438715,1969],[4000,462861,507],[5000,1779704,732]],"last":null,"option_bytes":[55476,242754],"option_joules_micro":[null,1180],"joule_budget_micro":1617,"segment_ms":4000}},"expect":{"choice":1,"budget":"within"}}, +{"name":"abr_bola_like_04","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":24993,"samples":[[1000,628261,266],[2000,1311093,2490],[3000,1896231,2508],[4000,1899946,1549],[5000,1473270,521]],"last":3,"option_bytes":[12156,93174,102688,115312,200006],"option_joules_micro":[null,null,null,null,3620],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_bola_like_05","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":36964,"samples":[[1000,1635778,1330]],"last":0,"option_bytes":[180898,209837,230082,260672],"option_joules_micro":[2254,null,null,3133],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_bola_like_06","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":33333,"samples":[[1000,1928993,2256],[2000,1990635,1460],[3000,985446,157],[4000,1448177,1178]],"last":null,"option_bytes":[174914,277374,359614,400433],"option_joules_micro":[3301,2076,null,2638],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_bola_like_07","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":14770,"samples":[[1000,1194018,1373],[2000,259042,1066],[3000,1462106,1136]],"last":null,"option_bytes":[243135,292490],"option_joules_micro":[4054,null],"joule_budget_micro":1151,"segment_ms":4000}},"expect":{"choice":1,"budget":"not_applied"}}, +{"name":"abr_bola_like_08","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":27980,"samples":[[1000,141644,2588],[2000,1022414,964],[3000,388313,362],[4000,900844,2246]],"last":null,"option_bytes":[35175,224251,379021],"option_joules_micro":[3222,3467,1675],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_bola_like_09","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":25379,"samples":[[1000,1891490,2917],[2000,1154608,571],[3000,919253,627],[4000,77454,1835]],"last":null,"option_bytes":[33791,62122,140623,207294,208269,277356],"option_joules_micro":[1522,2250,1056,null,3249,4928],"joule_budget_micro":854,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_bola_like_10","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":32840,"samples":[[1000,1081027,2618]],"last":0,"option_bytes":[104494,230609],"option_joules_micro":[1662,62],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_bola_like_11","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":36145,"samples":[[1000,1966685,548]],"last":null,"option_bytes":[53981,203768],"option_joules_micro":[1104,777],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_bola_like_12","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":25512,"samples":[[1000,1037585,2300],[2000,1988690,1445],[3000,258576,2439]],"last":1,"option_bytes":[2816,214121,330531],"option_joules_micro":[3106,4288,2871],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_bola_like_13","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":15328,"samples":[[1000,494435,71],[2000,1770296,457],[3000,1901828,1981],[4000,1203235,2930],[5000,801814,2007]],"last":null,"option_bytes":[50866,121719,302006,333897,379200],"option_joules_micro":[346,null,1998,2201,2086],"joule_budget_micro":3860,"segment_ms":200}},"expect":{"choice":4,"budget":"within"}}, +{"name":"abr_bola_like_14","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":1929,"samples":[[1000,79637,15],[2000,875308,2757],[3000,4315,1389]],"last":1,"option_bytes":[49512,283176,330852],"option_joules_micro":[3828,1327,3321],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_bola_like_15","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":37908,"samples":[[1000,177163,2609],[2000,1224028,1052],[3000,1827069,573],[4000,92334,871],[5000,1925035,392]],"last":0,"option_bytes":[134154,163996],"option_joules_micro":[null,1026],"joule_budget_micro":1362,"segment_ms":3840}},"expect":{"choice":1,"budget":"within"}}, +{"name":"abr_bola_like_16","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":18539,"samples":[],"last":1,"option_bytes":[32923,149086,206071,219296,344716,350512],"option_joules_micro":[null,null,null,null,4330,3386],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_bola_like_17","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":18521,"samples":[[1000,1115263,2269],[2000,99390,476],[3000,1631954,879],[4000,980981,1528]],"last":null,"option_bytes":[63054,204900,241266,293744,294103,367915],"option_joules_micro":[null,3000,4574,2603,null,1817],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_bola_like_18","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":37732,"samples":[[1000,43283,2145]],"last":4,"option_bytes":[19044,89091,89950,155865,158194,354222],"option_joules_micro":[1653,null,926,null,1413,4822],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_bola_like_19","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":7048,"samples":[[1000,395218,2621],[2000,420999,1489],[3000,875357,2469],[4000,1590390,33]],"last":null,"option_bytes":[76058,110659],"option_joules_micro":[null,null],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_bola_like_20","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":6286,"samples":[[1000,1697883,490],[2000,653689,2821]],"last":2,"option_bytes":[93697,103897,166943,395655],"option_joules_micro":[808,2175,3720,1722],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_bola_like_21","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":1637,"samples":[[1000,1326660,453],[2000,623250,595],[3000,190373,496],[4000,545816,2879]],"last":null,"option_bytes":[45513,140712,285270],"option_joules_micro":[null,3444,2169],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_bola_like_22","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":33416,"samples":[[1000,1522501,2293],[2000,1367474,843],[3000,28876,258],[4000,348102,817]],"last":2,"option_bytes":[135561,164095,199476],"option_joules_micro":[null,542,2243],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_bola_like_23","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":25717,"samples":[[1000,1178437,1629],[2000,447799,476],[3000,630878,2096],[4000,1880066,1857]],"last":null,"option_bytes":[36369,174153,224525,369179,377169],"option_joules_micro":[null,1811,647,1106,1034],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_bola_like_24","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":10540,"samples":[],"last":null,"option_bytes":[71451,95462,143083,246735,253167,318686],"option_joules_micro":[4747,4813,4715,null,4515,135],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_bola_like_25","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":20951,"samples":[[1000,130708,887]],"last":null,"option_bytes":[156031],"option_joules_micro":[4035],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_bola_like_26","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":38816,"samples":[[1000,675201,2563],[2000,1747486,468],[3000,804995,2476],[4000,555929,1323]],"last":null,"option_bytes":[97255,144171,144983,212778,337879],"option_joules_micro":[1878,2427,3802,3359,3138],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_bola_like_27","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":29120,"samples":[[1000,205557,75],[2000,1600494,1251],[3000,615667,1422]],"last":1,"option_bytes":[15448,80716,314237],"option_joules_micro":[1783,null,983],"joule_budget_micro":2446,"segment_ms":1000}},"expect":{"choice":2,"budget":"within"}}, +{"name":"abr_bola_like_28","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":3895,"samples":[[1000,1642417,488]],"last":0,"option_bytes":[178847,197381],"option_joules_micro":[805,1330],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_bola_like_29","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":5043,"samples":[[1000,320516,856],[2000,455944,2400],[3000,1090390,307],[4000,1503545,2066],[5000,1571448,2156]],"last":3,"option_bytes":[118399,166307,207779,350673],"option_joules_micro":[3407,null,4989,4316],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_bola_like_30","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":10880,"samples":[[1000,496251,2984],[2000,1410869,227]],"last":5,"option_bytes":[53586,190914,212753,222066,293341,388656],"option_joules_micro":[null,null,2722,1228,2710,null],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_bola_like_31","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":32168,"samples":[[1000,1586751,2771],[2000,550958,2532],[3000,281722,1964],[4000,1084201,924]],"last":0,"option_bytes":[95722,138681,162936,182448,240430,352697],"option_joules_micro":[203,917,3136,4882,3788,null],"joule_budget_micro":2702,"segment_ms":4000}},"expect":{"choice":5,"budget":"not_applied"}}, +{"name":"abr_bola_like_32","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":27832,"samples":[],"last":null,"option_bytes":[217137,290770],"option_joules_micro":[null,null],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_bola_like_33","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":1133,"samples":[[1000,1883348,310],[2000,1503581,219],[3000,257624,2456]],"last":null,"option_bytes":[376873],"option_joules_micro":[3600],"joule_budget_micro":2937,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_bola_like_34","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":660,"samples":[],"last":2,"option_bytes":[35333,90913,214404,288921,307074,359893],"option_joules_micro":[null,1183,null,3405,4668,1069],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_bola_like_35","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":33019,"samples":[[1000,1196634,2933],[2000,1237240,1474]],"last":0,"option_bytes":[175597],"option_joules_micro":[1574],"joule_budget_micro":5374,"segment_ms":1000}},"expect":{"choice":0,"budget":"within"}}, +{"name":"abr_bola_like_36","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":9288,"samples":[[1000,834878,1601],[2000,423950,2676],[3000,1358696,1710],[4000,388812,1714]],"last":0,"option_bytes":[312330],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_bola_like_37","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":29838,"samples":[[1000,89024,23],[2000,1472164,2118],[3000,1201310,540]],"last":0,"option_bytes":[116439],"option_joules_micro":[2398],"joule_budget_micro":3382,"segment_ms":4000}},"expect":{"choice":0,"budget":"within"}}, +{"name":"abr_bola_like_38","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":18295,"samples":[[1000,546049,735],[2000,1918070,1160],[3000,347471,22],[4000,1299017,2171]],"last":0,"option_bytes":[34244,227894,279287,285354,307348,350156],"option_joules_micro":[null,null,2081,944,964,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_bola_like_39","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":15971,"samples":[[1000,102625,1856],[2000,266131,383],[3000,1974116,1755],[4000,1735286,1269]],"last":0,"option_bytes":[135545],"option_joules_micro":[2776],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_00","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":34246,"samples":[[1000,865389,2193],[2000,1385529,1216],[3000,1052936,2170]],"last":null,"option_bytes":[154175,157964],"option_joules_micro":[2967,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_mpc_01","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":1554,"samples":[[1000,36156,661],[2000,744758,716],[3000,20268,2358],[4000,989327,2256],[5000,1615968,16]],"last":null,"option_bytes":[155353,199999],"option_joules_micro":[2643,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_mpc_02","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":10836,"samples":[[1000,1951248,1523]],"last":null,"option_bytes":[9115,50612,284928,360073],"option_joules_micro":[912,2198,null,3974],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_mpc_03","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":12630,"samples":[],"last":null,"option_bytes":[343298],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_04","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":2863,"samples":[[1000,101317,1440]],"last":null,"option_bytes":[88174,103129,280755,300194,358240],"option_joules_micro":[4214,301,2260,4648,632],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_05","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":4727,"samples":[],"last":2,"option_bytes":[10157,104678,132143,203523,210330,220727],"option_joules_micro":[null,null,null,3069,896,null],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_06","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":32209,"samples":[[1000,1821999,2655],[2000,797338,1714]],"last":null,"option_bytes":[74005,89236,196978,306264,327164,352684],"option_joules_micro":[1377,null,null,3179,null,4221],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_mpc_07","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":30520,"samples":[[1000,517266,1308],[2000,434505,844],[3000,1638568,2657]],"last":null,"option_bytes":[21461,164937,178059,202715,217230],"option_joules_micro":[2138,3387,null,247,1764],"joule_budget_micro":3581,"segment_ms":3840}},"expect":{"choice":4,"budget":"within"}}, +{"name":"abr_mpc_08","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":12319,"samples":[[1000,340263,1392]],"last":null,"option_bytes":[12541,87847,186801,226775,309419,349923],"option_joules_micro":[null,3307,879,547,null,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_mpc_09","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":30313,"samples":[[1000,933644,1257],[2000,1814434,884],[3000,229375,266],[4000,446821,776],[5000,1102641,2517]],"last":null,"option_bytes":[291171,371171],"option_joules_micro":[1237,275],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_mpc_10","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":35123,"samples":[[1000,983211,2249]],"last":0,"option_bytes":[49079,310795],"option_joules_micro":[1996,2692],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_mpc_11","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":1570,"samples":[[1000,344542,446],[2000,1693018,1164]],"last":1,"option_bytes":[71246,95686,134267,155112,221156],"option_joules_micro":[null,3757,2827,2809,1525],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_mpc_12","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":35549,"samples":[[1000,1673989,696],[2000,1035206,2893],[3000,1016484,39]],"last":null,"option_bytes":[113914,156377],"option_joules_micro":[3044,1235],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_mpc_13","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":10338,"samples":[[1000,1224110,481],[2000,955881,1007]],"last":null,"option_bytes":[195816],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_14","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":17127,"samples":[[1000,1369275,1143]],"last":null,"option_bytes":[79050,79475,97585,272705,398504],"option_joules_micro":[null,null,2589,2622,4835],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_mpc_15","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":9841,"samples":[[1000,1168458,1567],[2000,1869218,627],[3000,138851,1938],[4000,1432156,427]],"last":null,"option_bytes":[78956,316949],"option_joules_micro":[4624,null],"joule_budget_micro":5428,"segment_ms":200}},"expect":{"choice":1,"budget":"not_applied"}}, +{"name":"abr_mpc_16","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":34129,"samples":[[1000,696042,877]],"last":0,"option_bytes":[227388],"option_joules_micro":[2005],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_17","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":34612,"samples":[[1000,1113860,2847],[2000,1744199,781],[3000,1458008,2986],[4000,699431,503]],"last":null,"option_bytes":[87198,305540],"option_joules_micro":[535,2971],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":1,"budget":"not_set"}}, +{"name":"abr_mpc_18","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":11904,"samples":[[1000,1738597,2894],[2000,742606,673],[3000,758290,562],[4000,585883,266]],"last":1,"option_bytes":[175744,300621,311244],"option_joules_micro":[null,null,2800],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_mpc_19","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":19031,"samples":[],"last":0,"option_bytes":[218075],"option_joules_micro":[551],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_20","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":3015,"samples":[[1000,5944,953]],"last":0,"option_bytes":[209315,213815,256352],"option_joules_micro":[1099,2001,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_21","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":31340,"samples":[[1000,932838,1851],[2000,1898763,2714]],"last":null,"option_bytes":[31577,118773,121268],"option_joules_micro":[4672,null,2373],"joule_budget_micro":2542,"segment_ms":200}},"expect":{"choice":2,"budget":"within"}}, +{"name":"abr_mpc_22","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":27341,"samples":[[1000,1953438,659],[2000,1690928,406],[3000,119497,1548],[4000,1630137,1917],[5000,1253443,1424]],"last":0,"option_bytes":[9460,97210,282937,288947,344833,365350],"option_joules_micro":[4234,780,653,290,null,2585],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_mpc_23","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":38259,"samples":[[1000,788960,2831],[2000,697389,1164]],"last":4,"option_bytes":[58628,154371,256000,316213,364477],"option_joules_micro":[4711,1019,2212,1394,2744],"joule_budget_micro":3324,"segment_ms":4000}},"expect":{"choice":4,"budget":"within"}}, +{"name":"abr_mpc_24","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":16941,"samples":[[1000,879707,348]],"last":null,"option_bytes":[84996,97628,193104,223533],"option_joules_micro":[2748,870,4434,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_mpc_25","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":32826,"samples":[[1000,1940772,2321]],"last":3,"option_bytes":[111101,141461,173292,287950],"option_joules_micro":[263,null,2349,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_mpc_26","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":7985,"samples":[[1000,679797,1014],[2000,1388646,1629],[3000,1287532,995],[4000,673199,2771],[5000,1693506,6]],"last":null,"option_bytes":[148612,182814,263724],"option_joules_micro":[1240,3043,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_mpc_27","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":25285,"samples":[],"last":2,"option_bytes":[205132,209606,216912,263414,353879,372690],"option_joules_micro":[3514,1145,1369,4951,null,736],"joule_budget_micro":5625,"segment_ms":1000}},"expect":{"choice":0,"budget":"within"}}, +{"name":"abr_mpc_28","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":19124,"samples":[[1000,331098,1453],[2000,536721,2331]],"last":null,"option_bytes":[80660,128098,193362,237934,287501,324214],"option_joules_micro":[null,null,4493,null,3390,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":5,"budget":"not_set"}}, +{"name":"abr_mpc_29","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":13960,"samples":[[1000,1446941,305],[2000,1491488,581],[3000,765645,743],[4000,179479,1628],[5000,1937348,312]],"last":null,"option_bytes":[166082,190860],"option_joules_micro":[null,4831],"joule_budget_micro":457,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_mpc_30","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":6763,"samples":[],"last":3,"option_bytes":[114132,121182,345843,346021,355368],"option_joules_micro":[3690,null,3581,1578,1526],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_31","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":12795,"samples":[[1000,904626,2824],[2000,712775,1599],[3000,884425,2063]],"last":null,"option_bytes":[28835,223843,227852,310268],"option_joules_micro":[null,893,3370,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_mpc_32","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":28372,"samples":[],"last":0,"option_bytes":[27801,54184],"option_joules_micro":[1568,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_33","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":976,"samples":[[1000,95753,2800],[2000,642094,319],[3000,927665,2262],[4000,179629,847]],"last":null,"option_bytes":[61929,144961,184676,221710,336112,398207],"option_joules_micro":[4302,3376,560,2370,4640,3510],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_mpc_34","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":23052,"samples":[],"last":null,"option_bytes":[80227,338392],"option_joules_micro":[3874,1539],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_35","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":34234,"samples":[],"last":0,"option_bytes":[14712,153962,374129],"option_joules_micro":[968,3807,1420],"joule_budget_micro":5207,"segment_ms":3840}},"expect":{"choice":0,"budget":"within"}}, +{"name":"abr_mpc_36","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":15197,"samples":[],"last":null,"option_bytes":[8555,15488,45559,129577,145333,393613],"option_joules_micro":[298,771,null,4210,null,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_37","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":22058,"samples":[[1000,1492222,2574],[2000,955765,723],[3000,44230,1292],[4000,1643204,1569]],"last":2,"option_bytes":[3419,61005,155199,251344,325134],"option_joules_micro":[2989,994,4448,1986,2340],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":4,"budget":"not_set"}}, +{"name":"abr_mpc_38","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":4614,"samples":[],"last":2,"option_bytes":[124032,147831,179787,264257,317602,373533],"option_joules_micro":[464,3627,null,2541,null,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_mpc_39","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":2169,"samples":[[1000,1664812,2029],[2000,1199226,396],[3000,1066747,1306],[4000,934893,1177],[5000,937707,462]],"last":2,"option_bytes":[69029,158033,286806,379267],"option_joules_micro":[null,null,1240,4653],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_budget_not_set","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,500],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_budget_not_applied","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,null],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":2,"budget":"not_applied"}}, +{"name":"abr_budget_within","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,100],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":2,"budget":"within"}}, +{"name":"abr_budget_lowered","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":1,"budget":"lowered"}}, +{"name":"abr_budget_lowered_past_unlabelled","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,null,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":0,"budget":"lowered"}}, +{"name":"abr_budget_floor","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[200,null,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_order_O3_throughput_0","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2]}}, +{"name":"abr_order_O3_throughput_1","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_O3_throughput_2","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_O3_buffer_based_0","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2]}}, +{"name":"abr_order_O3_buffer_based_1","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2]}}, +{"name":"abr_order_O3_buffer_based_2","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_O3_bola_like_0","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2]}}, +{"name":"abr_order_O3_bola_like_1","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_O3_bola_like_2","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_O3_mpc_0","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2]}}, +{"name":"abr_order_O3_mpc_1","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_O3_mpc_2","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_OD_throughput_0","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2]}}, +{"name":"abr_order_OD_throughput_1","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_OD_throughput_2","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_OD_buffer_based_0","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2]}}, +{"name":"abr_order_OD_buffer_based_1","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2]}}, +{"name":"abr_order_OD_buffer_based_2","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_OD_bola_like_0","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2]}}, +{"name":"abr_order_OD_bola_like_1","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_OD_bola_like_2","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_OD_mpc_0","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2]}}, +{"name":"abr_order_OD_mpc_1","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_OD_mpc_2","op":"abr_order","input":{"object":"OD","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, +{"name":"abr_order_O9a_throughput_0","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O9a_throughput_1","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O9a_throughput_2","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O9a_buffer_based_0","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O9a_buffer_based_1","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O9a_buffer_based_2","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O9a_bola_like_0","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O9a_bola_like_1","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O9a_bola_like_2","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O9a_mpc_0","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O9a_mpc_1","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O9a_mpc_2","op":"abr_order","input":{"object":"O9a","advertises":["wai.video.av1","wai.video.int_hyper"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1]}}, +{"name":"abr_order_O2_throughput_0","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O2_throughput_1","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O2_throughput_2","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O2_buffer_based_0","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O2_buffer_based_1","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O2_buffer_based_2","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O2_bola_like_0","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O2_bola_like_1","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O2_bola_like_2","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O2_mpc_0","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O2_mpc_1","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O2_mpc_2","op":"abr_order","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2,3]}}, +{"name":"abr_order_O8_throughput_0","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"abr_order_O8_throughput_1","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"abr_order_O8_throughput_2","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"abr_order_O8_buffer_based_0","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"abr_order_O8_buffer_based_1","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"abr_order_O8_buffer_based_2","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"abr_order_O8_bola_like_0","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"abr_order_O8_bola_like_1","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"abr_order_O8_bola_like_2","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"abr_order_O8_mpc_0","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"abr_order_O8_mpc_1","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"abr_order_O8_mpc_2","op":"abr_order","input":{"object":"O8","advertises":["wai.video.avc","wai.video.av1"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,2,1]}}, +{"name":"depth_O10_throughput_0","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,2500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":0,"budget":"not_set"}}, +{"name":"depth_O10_throughput_1","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":6000,"samples":[[1000,37500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":2,"budget":"not_set"}}, +{"name":"depth_O10_throughput_2","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":30000,"samples":[[1000,625000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":2,"budget":"not_set"}}, +{"name":"depth_O10_buffer_based_0","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":0,"samples":[[1000,2500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":0,"budget":"not_set"}}, +{"name":"depth_O10_buffer_based_1","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":6000,"samples":[[1000,37500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":0,"budget":"not_set"}}, +{"name":"depth_O10_buffer_based_2","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":30000,"samples":[[1000,625000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":2,"budget":"not_set"}}, +{"name":"depth_O10_bola_like_0","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":0,"samples":[[1000,2500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":0,"budget":"not_set"}}, +{"name":"depth_O10_bola_like_1","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":6000,"samples":[[1000,37500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, +{"name":"depth_O10_bola_like_2","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":30000,"samples":[[1000,625000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":2,"budget":"not_set"}}, +{"name":"depth_O10_mpc_0","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":0,"samples":[[1000,2500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":0,"budget":"not_set"}}, +{"name":"depth_O10_mpc_1","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":6000,"samples":[[1000,37500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":2,"budget":"not_set"}}, +{"name":"depth_O10_mpc_2","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":30000,"samples":[[1000,625000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":2,"budget":"not_set"}}, +{"name":"depth_O11b_throughput_0","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,2500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, +{"name":"depth_O11b_throughput_1","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":6000,"samples":[[1000,37500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, +{"name":"depth_O11b_throughput_2","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":30000,"samples":[[1000,625000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, +{"name":"depth_O11b_buffer_based_0","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":0,"samples":[[1000,2500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":0,"budget":"not_set"}}, +{"name":"depth_O11b_buffer_based_1","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":6000,"samples":[[1000,37500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":0,"budget":"not_set"}}, +{"name":"depth_O11b_buffer_based_2","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":30000,"samples":[[1000,625000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, +{"name":"depth_O11b_bola_like_0","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":0,"samples":[[1000,2500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":0,"budget":"not_set"}}, +{"name":"depth_O11b_bola_like_1","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":6000,"samples":[[1000,37500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, +{"name":"depth_O11b_bola_like_2","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":30000,"samples":[[1000,625000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, +{"name":"depth_O11b_mpc_0","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":0,"samples":[[1000,2500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, +{"name":"depth_O11b_mpc_1","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":6000,"samples":[[1000,37500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, +{"name":"depth_O11b_mpc_2","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":30000,"samples":[[1000,625000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, +{"name":"depth_O10_budget_unlabelled_layer","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,10000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[5,null,20],"joule_budget_micro":30,"segment_ms":1000}},"expect":{"depth":2,"budget":"not_applied"}}, +{"name":"depth_O10_budget_lowered","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,10000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[5,10,20],"joule_budget_micro":30,"segment_ms":1000}},"expect":{"depth":1,"budget":"lowered"}}, +{"name":"fetch_abr_O3_low","op":"fetch","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"applies":["wai.meta.hdr10plus"],"abr":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"fragment":"#h=1009"},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":21650,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"24003cee2a7bf7fa45caa412ab4ea9f5f08b6bf44842a73d5a11c2bcf36df464"}}, +{"name":"same_pick_abr_O3_low","op":"same_pick","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"applies":["wai.meta.hdr10plus"],"abr":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"fragment":"#h=1009"},"expect":{"pick":0}}, +{"name":"same_pick_abr_O7_low","op":"same_pick","input":{"object":"O7","advertises":["wai.video.av1","wai.meta.hdr10plus"],"abr":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}}},"expect":{"pick":1}}, +{"name":"fetch_abr_O3_high","op":"fetch","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"applies":["wai.meta.hdr10plus"],"abr":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1250000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"fragment":"#h=1009"},"expect":{"role":"base","pick":1,"verified_by":"head-proof","why_whole":null,"body_bytes":78526,"requests":3,"companions":[2],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"68a51be787c439226eb7bb1cf434e913fc1f0e221870da87f70615d667a7ab12"}}, +{"name":"same_pick_abr_O3_high","op":"same_pick","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"applies":["wai.meta.hdr10plus"],"abr":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1250000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"fragment":"#h=1009"},"expect":{"pick":1}}, +{"name":"same_pick_abr_O7_high","op":"same_pick","input":{"object":"O7","advertises":["wai.video.av1","wai.meta.hdr10plus"],"abr":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1250000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}}},"expect":{"pick":1}} ] diff --git a/wai/http-conformance/cases/live-r2-orphan.json b/wai/http-conformance/cases/live-r2-orphan.json new file mode 100644 index 000000000..25d7ec1a2 --- /dev/null +++ b/wai/http-conformance/cases/live-r2-orphan.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"fea90b39e7bfdfb5e9e70feff146632b7516d833b969b6549f6f8e5f7b3c8b11","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606&h=1009","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118&h=1009","duration_ms":3840,"ad_id":null}],"merkle_root":"249c258662a60c975a99fe76cfca79d1a6d90e82732f9c3996868d960fdb4c5d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"dfefdb110398bc23519962164bc4cb1aae989f1bf36e50fcdc6820dbe84622480c5c4cb0c55b186b2f75a6c67f033155f417b3d6dd6f252836a3d7a145f4dc01","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false},"extras":[{"discontinuity":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/live/mr/2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5.json b/wai/http-conformance/live/mr/2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5.json new file mode 100644 index 000000000..589df7ab2 --- /dev/null +++ b/wai/http-conformance/live/mr/2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"2042f95e8bea7b95a2a8617a4f29b1279a07e3a16b7189c4df9ed150bc369ac5","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606&h=1009","duration_ms":3840,"ad_id":null}],"merkle_root":"bab509dcd51346560934078e77a5076b3e4331ccc0eda9b4e97f59c37383f74c","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"c378db4174631a36f2aadb6a77e49dbcb218aff10f380a67a41b2566edcf7c61","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"abde97cc6400505bada543ac27311d9f31fd2398eb3633d17219ea938329b4132199101b6a5265b42ad7c3788016ba0e263cf036257fc49612a0bbef52d3760d","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false},"extras":[{"discontinuity":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]}],"layers":[],"preload_hint":"720/1201.3.wai"}]} diff --git a/wai/http-conformance/live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json b/wai/http-conformance/live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json new file mode 100644 index 000000000..6322d2d85 --- /dev/null +++ b/wai/http-conformance/live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[],"merkle_root":"0000000000000000000000000000000000000000000000000000000000000000","work_compose_ops":0,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"2478c7a9f382d31714923abfbf1564ba582f1be10f4801bcd4bc82a9677fac8b9d8037a12b151833055e86bf73e2f636fec46ded59deb9d46f93c4e4f8f99a05","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false},"extras":[{"discontinuity":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]}],"layers":[],"preload_hint":"720/1200.5.wai"}]} diff --git a/wai/http-conformance/live/mr/c06453e032118886f29548eda03331427f731719277601cca4359985b7103a4d.json b/wai/http-conformance/live/mr/c06453e032118886f29548eda03331427f731719277601cca4359985b7103a4d.json new file mode 100644 index 000000000..62e7c1a8c --- /dev/null +++ b/wai/http-conformance/live/mr/c06453e032118886f29548eda03331427f731719277601cca4359985b7103a4d.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"c06453e032118886f29548eda03331427f731719277601cca4359985b7103a4d","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606&h=1009","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118&h=1009","duration_ms":3840,"ad_id":null}],"merkle_root":"249c258662a60c975a99fe76cfca79d1a6d90e82732f9c3996868d960fdb4c5d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"3c7c1decf737c48ff86a4e8a4eee1557e206406f2be6b33aae20dd56510210fa","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"5a0f9fa0eadde532b0aa1e2b6546fc4c086a396680859e7aa6420df3012d4c0181244fb97d4580271d0025c7a66a9e4b2689196bf6d047625a01c9159e8fbe05","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true},"extras":[{"discontinuity":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/live/mr/fea90b39e7bfdfb5e9e70feff146632b7516d833b969b6549f6f8e5f7b3c8b11.json b/wai/http-conformance/live/mr/fea90b39e7bfdfb5e9e70feff146632b7516d833b969b6549f6f8e5f7b3c8b11.json new file mode 100644 index 000000000..94ce16449 --- /dev/null +++ b/wai/http-conformance/live/mr/fea90b39e7bfdfb5e9e70feff146632b7516d833b969b6549f6f8e5f7b3c8b11.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"fea90b39e7bfdfb5e9e70feff146632b7516d833b969b6549f6f8e5f7b3c8b11","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606&h=1009","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118&h=1009","duration_ms":3840,"ad_id":null}],"merkle_root":"249c258662a60c975a99fe76cfca79d1a6d90e82732f9c3996868d960fdb4c5d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"069792acc477710a2ad7b5404c7520de5a2e5e4e481108c1f84f49af9600f64f","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"243f289eb7c0c106781b74ec297d057559d36810d41c8aff2ae7221cbbd64b2803ecfe5ebfaa56527f986dae60cb79eb91fe6c3a2a6be5f5fa4711e6e1bb880c","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false},"extras":[{"discontinuity":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/live/wai-r0.m3u8 b/wai/http-conformance/live/wai-r0.m3u8 new file mode 100644 index 000000000..7f5703f34 --- /dev/null +++ b/wai/http-conformance/live/wai-r0.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.320 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026" +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="720/1200.5.wai" diff --git a/wai/http-conformance/live/wai-r1.m3u8 b/wai/http-conformance/live/wai-r1.m3u8 new file mode 100644 index 000000000..4c176cc80 --- /dev/null +++ b/wai/http-conformance/live/wai-r1.m3u8 @@ -0,0 +1,25 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.320 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014" +#EXTINF:3.840, +../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606&h=1009 +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122" +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="720/1201.3.wai" diff --git a/wai/http-conformance/live/wai-r2.m3u8 b/wai/http-conformance/live/wai-r2.m3u8 new file mode 100644 index 000000000..4a0e49681 --- /dev/null +++ b/wai/http-conformance/live/wai-r2.m3u8 @@ -0,0 +1,36 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.320 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014" +#EXTINF:3.840, +../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606&h=1009 +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222" +#EXTINF:3.840, +../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118&h=1009 +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="720/1202.0.wai" diff --git a/wai/http-conformance/live/wai-r3.m3u8 b/wai/http-conformance/live/wai-r3.m3u8 new file mode 100644 index 000000000..3ce5c1f29 --- /dev/null +++ b/wai/http-conformance/live/wai-r3.m3u8 @@ -0,0 +1,12 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.320 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXTINF:3.840, +../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606&h=1009 +#EXTINF:3.840, +../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118&h=1009 +#EXT-X-ENDLIST diff --git a/wai/http-conformance/live/wai.m3u8 b/wai/http-conformance/live/wai.m3u8 new file mode 100644 index 000000000..ab5d1f04e --- /dev/null +++ b/wai/http-conformance/live/wai.m3u8 @@ -0,0 +1,3 @@ +#EXTM3U +#EXT-X-STREAM-INF:BANDWIDTH=39830,AVERAGE-BANDWIDTH=37213,CODECS="wai1.video.int_motion",X-WAI-FLOOR="wai1.video.int_motion",RESOLUTION=32x18,FRAME-RATE=25.000 +wai-720.m3u8 diff --git a/wai/http-conformance/objects.json b/wai/http-conformance/objects.json index e342c4c46..da93febb9 100644 --- a/wai/http-conformance/objects.json +++ b/wai/http-conformance/objects.json @@ -222,5 +222,261 @@ "h": 1009, "codecs": "wai2.video.av1+video.int_refine", "what": "WAI2: an AV1 base and a refinement stating no sha256" + }, + "O11a": { + "cid": "8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7", + "path": "objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai", + "n": 551, + "h": 551, + "codecs": "wai1.video.int_motion", + "what": "a staged-delivery base: the first two frames of a clip (WAI1 int_motion), segment 1 of 2" + }, + "O11b": { + "cid": "9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486", + "path": "objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai", + "n": 523, + "h": 523, + "codecs": "wai1.video.int_motion", + "what": "the next two frames, segment 2 of 2" + }, + "O11bL1": { + "cid": "8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504", + "path": "objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai", + "n": 535, + "h": 535, + "codecs": null, + "what": "layer 1 over O11b: listed after segment 2 only" + }, + "O12s0": { + "cid": "9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716", + "path": "objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai", + "n": 16606, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "live segment 1200 (WAI1 int_motion, 3.84 s): the object behind the live edge" + }, + "O12p0_00": { + "cid": "b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385", + "path": "objects/b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385.wai", + "n": 3026, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 0 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12p0_01": { + "cid": "c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f", + "path": "objects/c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f.wai", + "n": 2962, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 1 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12p0_02": { + "cid": "12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a", + "path": "objects/12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a.wai", + "n": 2994, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 2 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12p0_03": { + "cid": "00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4", + "path": "objects/00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai", + "n": 3018, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 3 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12p0_04": { + "cid": "27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815", + "path": "objects/27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815.wai", + "n": 3026, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 4 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12p0_05": { + "cid": "2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778", + "path": "objects/2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai", + "n": 2962, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 5 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12p0_06": { + "cid": "0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008", + "path": "objects/0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008.wai", + "n": 2994, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 6 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12p0_07": { + "cid": "dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f", + "path": "objects/dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f.wai", + "n": 3018, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 7 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12p0_08": { + "cid": "7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6", + "path": "objects/7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai", + "n": 3026, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 8 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12p0_09": { + "cid": "55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676", + "path": "objects/55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676.wai", + "n": 2962, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 9 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12p0_10": { + "cid": "f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac", + "path": "objects/f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac.wai", + "n": 2994, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 10 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12p0_11": { + "cid": "62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659", + "path": "objects/62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659.wai", + "n": 3014, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 11 of live segment 1200 (0.32 s), served under its alias and decoded alone" + }, + "O12s1": { + "cid": "63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17", + "path": "objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai", + "n": 19118, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "live segment 1201 (WAI1 int_motion, 3.84 s): the object behind the live edge" + }, + "O12p1_00": { + "cid": "7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e", + "path": "objects/7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e.wai", + "n": 3190, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 0 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O12p1_01": { + "cid": "ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d", + "path": "objects/ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d.wai", + "n": 3042, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 1 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O12p1_02": { + "cid": "a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b", + "path": "objects/a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b.wai", + "n": 3122, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 2 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O12p1_03": { + "cid": "a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf", + "path": "objects/a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf.wai", + "n": 3190, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 3 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O12p1_04": { + "cid": "6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70", + "path": "objects/6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70.wai", + "n": 3238, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 4 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O12p1_05": { + "cid": "35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0", + "path": "objects/35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0.wai", + "n": 3146, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 5 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O12p1_06": { + "cid": "cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce", + "path": "objects/cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce.wai", + "n": 3222, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 6 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O12p1_07": { + "cid": "173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f", + "path": "objects/173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f.wai", + "n": 3222, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 7 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O12p1_08": { + "cid": "db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed", + "path": "objects/db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed.wai", + "n": 3238, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 8 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O12p1_09": { + "cid": "48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312", + "path": "objects/48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai", + "n": 3170, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 9 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O12p1_10": { + "cid": "fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d", + "path": "objects/fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d.wai", + "n": 3222, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 10 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O12p1_11": { + "cid": "5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304", + "path": "objects/5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304.wai", + "n": 3222, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "part 11 of live segment 1201 (0.32 s), served under its alias and decoded alone" + }, + "O13a": { + "cid": "0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59", + "path": "objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai", + "n": 21758, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "player fixture: the WAI object for classical segment 0 (1 s)" + }, + "O13b": { + "cid": "f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a", + "path": "objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai", + "n": 20986, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "player fixture: the WAI object for classical segment 1 (1 s)" + }, + "O13c": { + "cid": "761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543", + "path": "objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai", + "n": 21338, + "h": 1009, + "codecs": "wai1.video.int_motion", + "what": "player fixture: the WAI object for classical segment 2 (1 s)" } } diff --git a/wai/http-conformance/objects/00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai b/wai/http-conformance/objects/00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai new file mode 100644 index 0000000000000000000000000000000000000000..b0145e207773f28305360db9a2baba60c61fce02 GIT binary patch literal 3018 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai.etag b/wai/http-conformance/objects/00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai.etag new file mode 100644 index 000000000..e4948a5d2 --- /dev/null +++ b/wai/http-conformance/objects/00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai.etag @@ -0,0 +1 @@ +"00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4" diff --git a/wai/http-conformance/objects/00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai.hp b/wai/http-conformance/objects/00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..e755b260907fe3fc01cbeccfdcf948c5b526097c GIT binary patch literal 117 zcmWId2ry(=Z_1M#dpVIowL`>i)gf`YYU{=G`kO;uTOIzj*Lc#EQ``(-@R1opGfjQ5 z^rH5}qW<%O?zYa>g{^N>n3r}xEzp{<)Opsv|2qHYOt~czeN92wx{9xA)4h$WzHGbu S=a<)lKc^Pnx}A9=(+>cHyfm)> literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008.wai b/wai/http-conformance/objects/0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008.wai new file mode 100644 index 0000000000000000000000000000000000000000..dcef1e3b38bdc45e69954bb5f74a8a4d1f640701 GIT binary patch literal 2994 zcmdUsc{CJiAIG0D`(jqfRxvkqiz_2iB1>f%*@i4h*13#jXbeSHX_GBcW4Ve{(nwjO za6_bUEiX6fZF7lGE(&8-bEo^BbI*DIde8g*_x{fLp6B=bJ)htAd7krlSv%0~0su}> zj|H-*W>lJiAyuCm#SCEuDyn!^2s2tyv)EiFo2zJWm@$#8;6N_(8x$SFjP&O)kH)b$ z%&5QiPf&vcV*-O%kt}Y4f>u}>{AFRFFz}Cx=CY#M)Fgdsa5OuF^{rsBL#bvbs1Yo7 zhywq2h#SZWWh%~8+>C0r-AJDrMm3}DOiGes0Pu3~q(LA6pcFSJE2OQ^w;ZR?vRp1# zSFGP+PN97YEPtXg7ftlrF$kXeyWke4YeAf^DP;GgKcsGMOi6tv1%Wt#RVpYcCK#u_LFYU4 zL8`XI<7Jnm(UEICC=1alvg1Jeur^;V-$VWA0>p@k+7wC=~3XtzQeZd612QkrBg+kb+J)Bve_b2_g1 zc$7E`eikvk&;vc=G$}peMk97w!C}@D(>R%{7pz9h7{A}6A69U`1If0#jyEr`h0)$O zQjV@gp@he18iH27Ql7g7Ik9)|>ReKqC}+<$7_f1cRIc?^PQUpHcH1Hk2`#)1pRYh- zh8{kL?`A$gmyb8Vhi`vHowSWdrvK3k-;p1LbJZf=^q&Ui1eXHz(=Zf+f((e;?f6Qz@F zyWgHok;cuFA?b#fE0EpSlZrRgI)@cBps?s!fjA~ zOHyf$L(nE0$)QRQ(au%{q?;-Yl=pQ+y64kds5zsPXq>|# z+qpatd-Rne$_$C*?v7_jjrvJa>>5>o2|i5G8+24UvoQ<1?nn{E`?Np$F-wlB!PHV7 zH`hBT+2oQT!U~A=TjCK+* zC+q6UR)hmI&ny$eI~4`L}}ymEwWcU_9iEegP4f2%|m78kD^%UvQU>l+BE zev!CqJ~Jyh4W(#ZI$2oc|9ItAdsETVqjxG-|z4E~_ekkH2+E17!eCK4fvWIE@f5$i4b#2uOr z_-hd`v8OQ_=J?AbVvkcT+7xC*8qee)J`cSXZCZLgv8978-lowY7;c~tju7sNzZ(-H z^e%RaQnZgCHy+(04y`m5$5@JS*1fe7_mUZGr7>HwhptKtiD5|2BSr~%lsUYM8IU|m z!{JM7%$o-o-YevgdMvsjKvEZZ6j4McNHD`#;%ClA2_NM}xY6V;G5;6@=F92Qh?Zc; zunr3KT=xk~*RGUcO8qK{>U~1`ZPO3L^NA)>^$>IANctV=E!8#3jLU7(UbVZT0J>aq zn#YzjyS>Fd8jF`wLZnF5U6!(WMm>({k|e9BG=fDXixqF3l?ujkWsj*MlATU2>WP{L zCYW+l5YAshM{Kck$2ilIdZ9Kk>Kig9as}nq$nu?5LMh##p!SA@jG7| z$#-;5AWc=r<=q7bEYCy9bmy(*E)RXTFW9`@Hp@70j8QrV3mWH^Rj0X}&pCR}`=%{h zTDW!OMZt`1T>D~$+2QVs|J)EW(bNQ2$bgTDPhMup5fKI@dvv; z6=W7ETX1{|8MI3;Z}ZcXy%)}nFi;eG4>qYH5id50B+)xh)1f(-se?hi~?X9#X&4$#1jx>`u2+?WyzaxbCx3mHumY zEI)4leA+D&oHm&nL>+J2V z^&JflX{G)l?5dh**gMRfsbwc#dfSx8%?Prr&R>Gvw$qU3M}9WSw9QK`%Djco&wcn2 z`~Ig!yTdT-@6ri^>Ykde&9fSoo6l?l%Wk3ByYL^k2<{ftaXH;b?C%V^RvH$m3Q zL(?@}cJ$iU^OpG84hebI{-?^`#;N+~=?N_ZsM*IESu}>hWNoQZP_)xCKh?UhH%?bx z^DpyIr%lpz@0uDh(@cCiCu92$((n0X-r*mQ?;Gv!E7Iydxn;+m48C!ER>Dr|_R9xN XZ2fFLb>4nps}Vj_*7Qhoq$}c2&3VA8 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008.wai.etag b/wai/http-conformance/objects/0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008.wai.etag new file mode 100644 index 000000000..556a7546c --- /dev/null +++ b/wai/http-conformance/objects/0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008.wai.etag @@ -0,0 +1 @@ +"0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008" diff --git a/wai/http-conformance/objects/0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008.wai.hp b/wai/http-conformance/objects/0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..267a1ba394f0bf77d8c227d65f42b7a4b71c9a6f GIT binary patch literal 117 zcmWId2r%R-nH2W_{I&C&H&6TE^UAh3NjpZFS(P*It!eA3_(lVcP23D%@R1opGwsO? z&d8a5e0kBIT@C`$QlCC)JlNZv^C)HF^@_{hjzaU_uHRVkNus_wLR0?gN8M-ATD}V3 RRTPV|*TscD^pDWi2LSq3Fu4E# literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai b/wai/http-conformance/objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai new file mode 100644 index 0000000000000000000000000000000000000000..52f8ccaaab1bc05732b56e71a68e6379cd3045b9 GIT binary patch literal 21758 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a.wai.etag b/wai/http-conformance/objects/12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a.wai.etag new file mode 100644 index 000000000..3f7c6b708 --- /dev/null +++ b/wai/http-conformance/objects/12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a.wai.etag @@ -0,0 +1 @@ +"12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a" diff --git a/wai/http-conformance/objects/12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a.wai.hp b/wai/http-conformance/objects/12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..c1b7576847f2c49256fa660bef3eecbdf5a9681e GIT binary patch literal 117 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f.wai.etag b/wai/http-conformance/objects/173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f.wai.etag new file mode 100644 index 000000000..e404d2b14 --- /dev/null +++ b/wai/http-conformance/objects/173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f.wai.etag @@ -0,0 +1 @@ +"173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f" diff --git a/wai/http-conformance/objects/173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f.wai.hp b/wai/http-conformance/objects/173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..70407fdf863ff106136c4dc9a10f54cd3ae5ae56 GIT binary patch literal 117 zcmWId2rv}4QL0{&@uFeg>}tlOl)Y_tgawXgNEPeN^!-JP%5Wls)!1M+#t(99$z;7{f zL_asnAYNXwMU5PLF1x5(gBY4UOVKIWi#rgMi+xaFM6m91Lf&_MjlRQ`MLtret$5gG zql6fDQ6<)jxMO@X@_uomtU_Vq8g3;H&afegYmVn34rsqZc5W*oD(`7R`j=Q!4i(R# z^Su#hwCpof@O%u4*t;2rD{er3scXfgM0le*{coVN1rtb0(zGNp_&ug~^0uTXesIk% zAwX2j#!F`F+lkprN6E$4SmZQwK%(c3k+n>w2aIgql8@d^Lm#YuL{^H;7c?)QBEQJp zxT@0YL&9n86xZ#FC8{Q*l39srh`t+&62k|DaIZiEQNBh6qQ75Uc1Gu20=CHvJ4b)l1 zQg}5lUZxs0NKJ~3MyK=}sGnqm2?93+wGZ^#dIvXhM2i>;_)XO?_SPILAXD@vGy+_uf%3WEoOjvYs?Ab zkmI}b#YY=cpX;+yN%s}D>z3e9zCJ72+k*)vXUtYTj4LJJ{d#h!zA>!gTQeXS2AB{UYCCw+dfB%(;|2?OTnwLNjV zST$872^4nn_4Ui}@SUb>?-1&EP3P01z3(b zV_33Yu!-IF&dw$MCRq(FxaV&hNoacn`E&(;^boNMpb$0Es-5^~O)33C(b(%YE z3EFs-BWk~c8ofb;!`mrW-(V}9s}nh-QrR8@nuY${M+KdjHUz7nLxq%)oqq28C5EQh zzPxgOlf%oxtLp;G(o4Sf=^x=QyrWtUsfVqDm%d-*PnfmUB=2^-_H)vage`G4;?Q4Q z)PFR~H8?mhg>DPJ+dBC^T{$RaKCJ1niEctiq~nA$JlXFq_u!q$!OfgW*PoPn0uJ?7 z*|YsI=2H!gEo`e>LZce(Q=*`?*IPXxZO;jC@$5*)>|+HbUmM+%r}0^pK5oNyI|nS#DM|Tto8g5>Gl>6m)nu1Ese61MPDywur~BvEUSNDU#*y-*p}VGaqgYn%Nn+}pAZ2#QXP<3wb3fzgVBJ!wsXVd0`_8Qb_o~TG-2RaE1`=0= zegERF1yf#=d{d99TC-_tT-Ztq|^Xd564DPL?eU`Y?nq;b6_cPMS@hbzl9~p(=qs;?s z@{_tv+PsXa)2dm(zx90Y&1j~oD z)6Y+iMS6Oq^{yTU!-yxAO1yEKv{>5=`h3EMEvCcFE8e4;5!`e1vR^;COt|@&Tap)I l$0kFJQYs8b5AkdMusQLN3dO_pTzW^1Uy?6is literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815.wai.etag b/wai/http-conformance/objects/27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815.wai.etag new file mode 100644 index 000000000..e0baad5ca --- /dev/null +++ b/wai/http-conformance/objects/27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815.wai.etag @@ -0,0 +1 @@ +"27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815" diff --git a/wai/http-conformance/objects/27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815.wai.hp b/wai/http-conformance/objects/27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..fab1b877b2f2048a5880af110d62860778a3c315 GIT binary patch literal 117 zcmWId2ryK4*6ZngTUD|k{KEeg<{6hfXXxElH4pS$paBk3FjvdbB*;n%TZ=%c{33*Sl^^I@+^UFM&DLZZp&7 S+@%W=#V0w$3H&nD5eERGjx(|V literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai b/wai/http-conformance/objects/2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai new file mode 100644 index 0000000000000000000000000000000000000000..fbe37c2cc0c8a6da63fd74e3efd9d986dead1364 GIT binary patch literal 2962 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai.etag b/wai/http-conformance/objects/2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai.etag new file mode 100644 index 000000000..e71b77de2 --- /dev/null +++ b/wai/http-conformance/objects/2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai.etag @@ -0,0 +1 @@ +"2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778" diff --git a/wai/http-conformance/objects/2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai.hp b/wai/http-conformance/objects/2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..fb2884c9c554680a965396310e3e935b0ee6bdd8 GIT binary patch literal 117 zcmWId2r$&Qc-bQq#(1OS&KvEhVKCUhcwLq literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0.wai b/wai/http-conformance/objects/35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0.wai new file mode 100644 index 0000000000000000000000000000000000000000..f96f2674e8b198dcbc2ca810c7244ff426ecc8b7 GIT binary patch literal 3146 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0.wai.etag b/wai/http-conformance/objects/35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0.wai.etag new file mode 100644 index 000000000..a8ca0e2e3 --- /dev/null +++ b/wai/http-conformance/objects/35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0.wai.etag @@ -0,0 +1 @@ +"35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0" diff --git a/wai/http-conformance/objects/35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0.wai.hp b/wai/http-conformance/objects/35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..d9abd429f431803397e3e5587e3a1fdd3d36eeb8 GIT binary patch literal 117 zcmWId2rxALDe!Q?k~P17yj76vIqS^K^m(cV*Yf$(!Z)85`((XzgBK427<^=g&`iHJ z=Kr%#SD3nMp?RuG__9h>aSmxE(c}F_X+BFkgG9GX^Eg{FtDoiBChMnir|(?&aQC39 S(46XZ>lXg>H-7u@{(1luIWwdH literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai b/wai/http-conformance/objects/48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai new file mode 100644 index 0000000000000000000000000000000000000000..479c4c5363ed133e9b0ef86ee1cd859b9600a0b3 GIT binary patch literal 3170 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 AnE(I) literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai.etag b/wai/http-conformance/objects/48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai.etag new file mode 100644 index 000000000..e322120ca --- /dev/null +++ b/wai/http-conformance/objects/48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai.etag @@ -0,0 +1 @@ +"48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312" diff --git a/wai/http-conformance/objects/48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai.hp b/wai/http-conformance/objects/48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..7f48ae0880722400bcf2d1803f2af91902829efa GIT binary patch literal 117 zcmWId2r%?GHt`sfbT<3Cr~`CQCwJ(17`<~OCWS%S2;uRe) Qu*Rrg-pNO}^FVng0A=SbWdHyG literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676.wai b/wai/http-conformance/objects/55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676.wai new file mode 100644 index 0000000000000000000000000000000000000000..5151e8e35982d2ed51155f723130f0d2b4b864a6 GIT binary patch literal 2962 zcmdUpc{tSV8pnULZ$D#ZVv;R}@+M@Q#g+rBIeP zOQnsnFO{rivg9SHOfzG~%$eSE{yG1hbN)N`b$ze<`99C*dq20^&b04?2|B$b*}zs{gkT!}=oPTGIf zqEx%3+!-hUz~BI&jQ{|VRN({w&>#W;Q5tTOYL_(d{Spo=K?&!sDiichLJOghe3oh3;_I91aGP<&`WJ{ zE4%Sb{8`6%AZR2OEEtdln3PqN$S4uAHm4fhy4?Ueq^$~fGklHXWF7}Chmj6KJV|J$uPOm@BcRSI(nt8Du`uMi{ z>dChmVD>;8{GERl_R^v|d>x~Q-?ZCW7O9R$45UM_je*AS!LDYpcBDGuyB`4QD6y8c zM9;#y=L7ID(PfCJnNzUez7_L@-qSFl+6?#MG!=C6yD96C_C&@%?Ep_s0f$N*dXCsO zy#u)~q7;#2tc@Mj?LwT-4MUAx0*Ry>p=gwmGBG%O94T*HPLQ);3zlOJiTlv;uqmGY zYW=A`Xv5Mu{L3rI5 z4zUzX!L>Mf5;dp4BaeRO5)s!QOC-rYr1q$;aF;oE`u_+{XpAo@@{G$Pfaf?@AKX71?>9CYKDpq_a}eN=^5Q`Py4!}w*c4Bir+|B0$p9 z75QL^A9_$3@DN6A`w667MXD`7Qe+eIB#I-vMBx{U?tZpx%onskpzA;m{pHwesF)jo>>&}6;*q2TKi7)yrZA^V_gT!Y+-n?8Ldp_{p%7?s>%y9je2M1Ep zA4i#$EODM@ryx&Nx;f90v~53a?a(V}&-S;jWXOJq@*{0yeVnim#3%;4E=T6TKC~5> zJ!jN%SYZELLD!6>BCS$!Y&vAnTHu>;kXoA&9q;O)(4*09oYIj*FM8=SPEXi+YI}Cb z1@6e$IdM_(pQa`OhA-?bumc5TUEkYW7i7_zc+DPF=i2;oVvybUNwd76d(Bh5?1I$1 zHMhz>Di7Bvc9+j7PDQi2zh*`IFFo5J#FC3{C{Eq1T|IDfBkL!}-P7F->ms9hI{5C{ zeZrih>Tg=BQ<^6a@|*nfA9S$S3%k!>xMR|jV;e>un|k3)GkQeLbq{c66p5PLDy;IN zLi7@ql`xmHsxF=SoLj46y?)%bto%#3R$k%3183Km{;;T!@A0w0Ji9!=)yf5`S`6h= zY%h=(Z5Q3=jzvVUJ{3JF)o(WZ#$6A!S+9_;=pEDP)TYOlKkJr< z89O`s?Acs|lCOsXaJ91IKt^!Dp_~Y9X6fq#iS-XN#8I_tF}>w6>rPPK-+p`H{fGUx z!WeNT*r!if+lC%q-MS{w#@~;lJ#pdimwD~?yqXo=V-uZ=k@gRYsGX|Hv!z|ptg??f zM~CfukD2wT*zM0AOOc&&feOx_L=c0#oXD|0x#ChG@(unewD>ZIw1Xgx?e5bn71I5jInYf$s+ox7bj zo9iI9AtR{7^zu*9>79x6(li^8yydOg9Gj^5W}q;O*c|u!lJypMGHtiJ*BP$)wJrHp zT0uV1SwD}1o*ae+EdONMd=bG-BEcHrtrc-aO>%Oe~ S7?yKq< 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 R@70PXS_698w(GA954 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai b/wai/http-conformance/objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai new file mode 100644 index 0000000000000000000000000000000000000000..788a113fbbe7e4feaf294ec0f2b82f4d1a908dcb GIT binary patch literal 19118 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°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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai.etag b/wai/http-conformance/objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai.etag new file mode 100644 index 000000000..8a0a8241f --- /dev/null +++ b/wai/http-conformance/objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai.etag @@ -0,0 +1 @@ +"63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17" diff --git a/wai/http-conformance/objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai.hp b/wai/http-conformance/objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..b9ca2056b2d941f9612f7acd081c33832ad4a9ab GIT binary patch literal 213 zcmV;`04o1iNKi3jz>5WCpVwFiPxKlSc%zZG)Jd=b3QgS@l<&3xCc77|N&o-=0002- z0{{R300005#v`JtMTD0oxx_{&9c?j2h(Ba0#WXaAC%xcaQ@x&4u6Z=I~bFTRSD>d|^ Pt}a^Za;%U7PDaNOp<9NLNr`tQc^mSkX@D>%V3x+W$C0y97h{Ugir=qvI|+t zz9vReAz6y79ZR#A8Rq^v=icYJxBDO5@AJH#@8|vfyua`7;$UP-ECv7|f)MUbA&>|} zjRORA!g;b6#T`^(6fbfhs3`#< zp6)^J9uz7iI7c`iFokp)m01tIGEZKArJsSf;Sfj!Grv$g+P}0e7+(${}}Qh zyFe5`@F2TE2p}pT`#>~7bU-j*)+rEM0RR|*0ss#N00VFUxFia$ivd6|c*rpc07$?A zK-~r*I8vqy}d_EWsfWiN&eyG34-<^M} z|DAvC|Ez!B@uUBro}cyqmwy=r026>a-UocvVW9A83nY3p256`G!<@y)$h<=q=pv~r zqDs9Mwl+Gb z)WBHS)W%)Tfs}b{RJ1s(`jHbNM=XPPa7zk0uYlcBrs+XHYNBD!zvc;A#j3)!XcRGB zce;ob1TPIyYBGn$X~DnM^3l-F`|xoicfs){Yq-{fGQ{1qn{dCiL0+QCDN)nir}$~B zTfAzSk}WQNi#N-eglUmBMcRBRGmtjQ)yx6y|tirvL;Pz59i&6QF<_WDn={3{vp!hqn83Vn$$ zRbg1k(K_+zXbG%E=ne@TbK&i>^U@MN8Jz8Dfl6Mw!fRf}_R^N0TqsPnI9n#T0*Y}; z(3L8q$4E;MGlbHY!x(dSCs422QJ9e#oTMV9UN$oOD9>nS2rkHAqO=N-+x{yT!7qkw zA9&X%uqw7|jbZ!={eD;4@~-BR)e+^=_4Y@BLOY!bkq!d(!q@e**R#eJKhG9YzGXMv z`PTRP@?!tz1jhYwJUY^Ik?}#*0QKd?1mldz7Ne9@jX0EQ%8)&iBE_12fepwgU_=Rr zG1Ho8sE(l)w(`de;r0_W#_B|&piig_voK;m-vvt+`bSR*B=K+2z)JMipU#!otavZ)Q7v!^y=Z$~sm86wM*Pm6vzSHe*s z_9F8qCphGZUl4Em47kNN+O`^7S4145EVoC6T*=3ns|r4kO5`UB1;Y?0a|9nP9^_yC&Jm^6e8-&p zror#iXTjC3!};A*95ea-Fkcea$y!~_M(RZx^1UXYh`xMdzGmu^ZRpK8{+|zN#ryJ2 zKU}=Tt+iLAcLlvYk`3=IkX3hP29<{Ek+{MUOob}lD>pmjd+JT8Oh4nb>R^Xq=QubK z7_@PB%@@fUx;R{@xok+jE+jvFo7&XRnHs+aF^2??itZ;nN2Tiq^(t#=*1Oj6GF+c$<{3*89n}cAW~rm zdye6rQe13%T-;2_b(HE5gLX1{?0nC{{?f|}BUPiFVQ>6CA8(wmh~2josMXa@DJhf1 zHzayDI$k%<>_4K2{~bRw=Q202MC`~&Vt#MdH1GS|FJtE9v?ob@ZGAXS)od!T($zB0 zO?s!d`#R;lY_)FxZu^Qc&#_WHY}5{Y*q^yWLCo?**oaY=QJs3#?Kv&v(`Q7vwiD@} znho_o-EXpq>#`&T8$%lP1#Z;R@&VbySeh2Tdoe2c zs{h)O)%X(OLJOmpD(w(y z6WU6+$D3X*pMu9zr#x$Q62@G;H_Dr`AM#F`+l=mQ(QM6pTy#()ukwedoyh?Vux;R*&>NU<4z^PHHp3(?PuTd>6&%{S~&SprMtr!RT3D@>UFakEH;dYd;*!@+7856%~Cv`2P4;hu7MQ6ND5C32Pb()y0Z z(PQnCjje~Itm$xg0-aTO=Z_;bJ`GjYo2iEl@i)u2#a>#Sdhr~xAk`c_a(ddkbWy^e zq+5wP`>4i-7c3$F__q+mb_+-s{YT3lHldZF5Er`SQ4g;K`TN~f=% SyT3y1)+gB+-(;HkHvs^zbu%OY literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai b/wai/http-conformance/objects/7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai new file mode 100644 index 0000000000000000000000000000000000000000..5f6e32955922032dd656d46f53d6a2cd42a0b570 GIT binary patch literal 3026 zcmdUrc|6qX9>;&P&zOCXxaSlzh_XjzbjvRLRw6OBktvKc$WkJa&_R}@WJ`%s*~_8G z5=Ba-tf6p{oHEAv&6xY^-22b{@7zD{^Lo8s&-eR0pYP}UeV&6R*1ER=0EneW`Euw? zx~`TEU4tIN4&eAgStKWb&4qGKD32Y=gAT&kVZj`KUmp9Xh#SBTW`(nlM{vT~A%ES+ z(*1qIeEm4V99}e}hAg%Ive1GISRq^64fTX3qcgYbY0!h{Ox<0lPW^%hfP>a8SMr@KJxQE#Qv9)qI zZI5~&#Dauho=yauD+~oMlY)qUZm9?EJwyIjQ8xGU)8^D)CV~Ohtzw z6ka|}yfKvk8&4|0x7g^yWj|eA4?!x6n}dIY7n_ZUk3Migzl;t*Y?sP}fA8hs7wuW_ zQA+zA}K3%5Fgp?zuwwkAf655AVI(vWpbzyGiIBOpUTKYDK!OS zufQU3HgR_djAx0cq4gMny5}8o=e*Qf%QYTp3n~WH{q;32Z+-;HK46ILpJ9tF>&y_v zClrEy;DdCJz(%jCtF4oYs#7|E$S#9lq%zl_1f~tbv$>AsDG!exQBmn0jTKiE$+avJjeEX?y)T~9#ST!lDm}zQ0E`glK^%OsfmPcIp ze0trYt_iVEkb(-tSQ497OlU>QL|Qqclct6?B7?OVxW~C}G?q^)x=s9yc4#tE%1@_` zroFU8+Kfa?>y*A&?+r4gDNlTr8^bl>_Wo#>VmlX0=V;EzG^JHZbJtv?bDlcN?lI_AzZm|M;XG^s zs7IeX<+`HLrA@lC|07?X?8A4ROvRHNHTk4hMiCB`O4y7k2Y$$z+Zg}034XU$p)h=> zD`8*u1u)7ZQ%Wt3$v0E?2Re1~>X?)kP)!2gE{ zrP^%=er7mvrKdS>Mg4A|z(CZ$y4SUi;Ju6!sLU8)nsW{a5DICSGfBHwCq)Ngqlx7L zufl1BmW(y2x-(obGH<^++o(xVv4pQ)IqP75)};_@Qf9Ck#~vng(`p1Vk9JTp=H>-U zV`-oos3p}MTP=78Ok%T@|0zsXbbuG6YORgMfQTDLYore)fkZtTX)SKL2$hYf7fz^F zkoM-=V^n-mYaET6&@zFC`)Ilu1^v8e8#Ju8cP#=Rf)v|LrTA&V|bF^&eo z7uAGE-J_t(>bq4uZyjt=asp=xr%_^;EYNiABF+abFFty75bLRDvfg?#K9nIIjNW{B>2_^`#cK(&@bM7FC8G^LQZ!Fhvr-}H%w5C}CwikL@i~&*@E*av zzGZaYg0qlG%SB~fj*^^H)yLcQ)k*e%^O0{~=PQNS+{{+(e9!8NjBhXpNV3zN$-%!r z4Eg7g9ft&9y*;~L?(C?o**<})t!+iBt%=X(E(2h5<;&}(%*!R-x0!&VdQ9}Ek*5jY zvAd4?1olOzb(JtvF8|puW)+(Dqj6gA<@>T%d}eBM?Rcqqy5zCB_gVQFvZt43;hcr; zfH|-FH}$GU&#>-LRlzm0@E-rPg{cLVjQHgCT?0{7Mw^42G*jR2K0KJ-ZXb%jaXbiq zuCn(>nj3?gl4+Rvs)Z*y>A>Kn#hyUKcRG8H+sB8A%b)G9Y#6#IXPYoW zIPCjUY2DCUIiYcnv~}C-P6r(ev6+#OVwHIFbX<=5>$0u8Ek0uTF52ElShiw=QW_uENe%42G7>K@k zfUP{|wb59u>~MEN#G}aVK%3fLQ786 zm_M7{HOp(wa@_AWcP2j|D8gY;MdmHIVMl29Qsno2yLYRdk7zOt$cEX}dc8~j6!O%3 z|K+}0Rp|sy{56#PtwO(Q+FkIGATaasL)D6|rO3FuQG^-twTdf4M|oc1`|S`5!Q+ zt$Kv#CK^@ep170ELWnsJ4^$X4f**{_Z8QA9$@dyn4mov>Sy4IbI_1XNV@z2(IX-hl hFS%NG)OO|m6Z42hp5t1kc17B~FFhf8YuSw4e*s~=zkdJ# literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai.etag b/wai/http-conformance/objects/7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai.etag new file mode 100644 index 000000000..54e167b8d --- /dev/null +++ b/wai/http-conformance/objects/7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai.etag @@ -0,0 +1 @@ +"7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6" diff --git a/wai/http-conformance/objects/7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai.hp b/wai/http-conformance/objects/7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..1115ac8b0eb722a31ddb5747bb690387d073e901 GIT binary patch literal 117 zcmWId2rw*|dg$T@YeDDO?-M&y_=^p!=ZH-DWuPm2`nKA~pxQalE^#w}!AE8Y%`|PB z_$v+G)!jL-9~K8LkM^p3GO-}ZEy#oKl7{Ldmx?9+X1^MR!n5}uDB0cGpVe#gQTU(o Rzixqh``h-YI|}wm0s!iKF(Uu~ literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai b/wai/http-conformance/objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai new file mode 100644 index 0000000000000000000000000000000000000000..479e4f1eb668622fe7db8131ab4903b8938ba821 GIT binary patch literal 21338 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 R5ClbDclrhx`9IE!tvg!AE`0z1 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai.etag b/wai/http-conformance/objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai.etag new file mode 100644 index 000000000..e702e129f --- /dev/null +++ b/wai/http-conformance/objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai.etag @@ -0,0 +1 @@ +"761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543" diff --git a/wai/http-conformance/objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai.hp b/wai/http-conformance/objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..7c73161742b10245928693c9e5c0ad0f3d0ddd85 GIT binary patch literal 213 zcmV;`04o1iNKi3$9xek2JzyLgf=l#-&S0X3%hC=KPgreS1dM>sP!p7)*j2v z^S0eeOqP&{7;Z%mQ&{jr`2tT P+5&h|)MaP4Ih<3Gz%^a0 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e.wai b/wai/http-conformance/objects/7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e.wai new file mode 100644 index 0000000000000000000000000000000000000000..4da808ffd494615ed43725751142f8b5f13ba029 GIT binary patch literal 3190 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e.wai.etag b/wai/http-conformance/objects/7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e.wai.etag new file mode 100644 index 000000000..19c80d38a --- /dev/null +++ b/wai/http-conformance/objects/7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e.wai.etag @@ -0,0 +1 @@ +"7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e" diff --git a/wai/http-conformance/objects/7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e.wai.hp b/wai/http-conformance/objects/7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..b9976dfb81e82aa7568a8eddff243db2756ac18a GIT binary patch literal 117 zcmWId2r#UAmc_Cy`PI+VcZEk>*B5I{PS)B`XmhI3a@t%|%@=R_%6J&S;3G4HX3ALi zPiWTZ6KdUuUhocGS>J0r_-5hgO8bb7jkJxh5bD4)Qbalx`AU0@Ph=vkAW{UPX*?74QH_@Q8;d*| zpt5DGCc_+&zEvhNzBP7*lm}$hSe436$Wr>hu$wYX(bdS5w4*hrf-+X1j%2h&dC_gU z(k-==a+ngFNtdhA3>DLYJIdu$axM#;EAF_{*liH%-u*avqOU_zW;xOggiX6_g?^iq zX*yV(u89qCib#Yj8i9>HZ@2>h%kx#1!Byjh$G4Hi_wGEv&qb0XPj3tLhXD0wsLugx zR{@qj0DSw7u?KK+1mT+R{Z-*fA7bm$N)@bpOwJeR6K&)88SJ!RX_7{O@4hfAQovDiu#Re=WF&6Sd0jHS86n7XSbN literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai.etag b/wai/http-conformance/objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai.etag new file mode 100644 index 000000000..3ff83e1e5 --- /dev/null +++ b/wai/http-conformance/objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai.etag @@ -0,0 +1 @@ +"8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504" diff --git a/wai/http-conformance/objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai.hp b/wai/http-conformance/objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..919bcff5df68383c948cc54efcc5994b7c67c3f7 GIT binary patch literal 53 zcmWId2rz6mGML}-Q96j(@dV$kXGwW6iSMSWu~O&1NoVGC8>EOK!Kvvf}G6c z#FA8yqWqN9ocN;D!qUv5)LgK9wNi3oL1I#7PG(6ZP&Lp2H7#SGCJ;Mwcfix4)xg2Qt zyY#Iy<8?}JgfPrC? z5zss{28LK?28Q72Knx6ze@ht{R<2-R_`ecJZvpc60>zI5@o5Hz6*qwNU7*-gAbt&$ ze+R^07#Kpn1J(Zrxd7-$AUFZUJuK_j7%pUF*vgt`xLfjH^)CHrReim<%2e^vw7bel z^SP!q^0HYZIDY+32 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai b/wai/http-conformance/objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai new file mode 100644 index 0000000000000000000000000000000000000000..ed70fc05b9886eaa52022acb099e6178308785ec GIT binary patch literal 16606 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai.etag b/wai/http-conformance/objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai.etag new file mode 100644 index 000000000..594379430 --- /dev/null +++ b/wai/http-conformance/objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai.etag @@ -0,0 +1 @@ +"9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486" diff --git a/wai/http-conformance/objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai.hp b/wai/http-conformance/objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..1abe71136e0e2ba6e224c0707c5d1cbde4df0481 GIT binary patch literal 53 zcmWId2r!)I_mqoKh~*$ZtHq8J^Y1#|3Oo@zohvKkM!?Y+)!2|WZYBmWfYA&9_>>OI literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b.wai b/wai/http-conformance/objects/a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b.wai new file mode 100644 index 0000000000000000000000000000000000000000..5e2aeecb1d70e985ab01bc70c4b24a8b01a93dfc GIT binary patch literal 3122 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 J6s*)(`FCfB2i*Vw literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b.wai.etag b/wai/http-conformance/objects/a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b.wai.etag new file mode 100644 index 000000000..1b98feac0 --- /dev/null +++ b/wai/http-conformance/objects/a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b.wai.etag @@ -0,0 +1 @@ +"a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b" diff --git a/wai/http-conformance/objects/a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b.wai.hp b/wai/http-conformance/objects/a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..c78332b6d8cd06281e61c80ee865cd5e189f96f5 GIT binary patch literal 117 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d.wai.etag b/wai/http-conformance/objects/ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d.wai.etag new file mode 100644 index 000000000..0dd1118ca --- /dev/null +++ b/wai/http-conformance/objects/ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d.wai.etag @@ -0,0 +1 @@ +"ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d" diff --git a/wai/http-conformance/objects/ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d.wai.hp b/wai/http-conformance/objects/ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..079e61eddeb63b765ede9fa47488fa308d7795ce GIT binary patch literal 117 zcmWId2ryhdbKZK{zkA<_w`{(b?JrYz-%wD}wEuQwJJ-#tGG=pZA8|8)!AE8Y&9s@( zs*!U?u=kW1cZ^b4cK;2XDDKWtD*7z&(h-l`YO6%9#7}GP&(M+-Klk)jrh1ieLFo7O RS`Ulr&3~_&8tRg@4gjNmFgXAK literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385.wai b/wai/http-conformance/objects/b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385.wai new file mode 100644 index 0000000000000000000000000000000000000000..7d79090f2ed01781c04812897fd91eadf4a0bced GIT binary patch literal 3026 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 QI3u#iW1oLzyOgRQ0DXfmjQ{`u literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f.wai b/wai/http-conformance/objects/c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f.wai new file mode 100644 index 0000000000000000000000000000000000000000..a4468c82e30e9ac13b269f046aa1f44821a26953 GIT binary patch literal 2962 zcmdUpc{~*A8pq$+cQb?0NJ&$oL-wpigtFx5Bs*o9n3x$^LPW)pvYhN9Co0*BvQ-jF zB2%JNb~+A<5Hc9E+^Kv2x&PgJ|GUrU^ZPvS?|r||?|qz1tu)I408zAXF9yw!rm3bu zQ>8KKz6>w1JI(N=v&Ejl3Z=6`#TUW!pg@L?S1A1l#P+2J9t)T(6h#CHAZyPzbTMPE7W2AVA8(5g>t!OnzNSj(`3 z;Krf_^413%w89@Wkweuu#FXU#xlAu5hNJu;&)@muHyzayvb-Xp7lOA4p$7M%$98Ze zGv<_3m3Tf7Wzj+0{-vW3+l9ilsn&A@^A{VSZ>(f+PA9fNh0&+6_p&pfGQ}cD&7E|@ zhe9P7XJ;wS?AScNC-DzhNJ_FWENCxmBy2=-?~PG3x;+_opl^V?yRwQ{(TReU?U|N% z`H==|G@lpPe^o(CE$xHJbr=&IxPI`3`;V~+gL~oGu_Q#@DhoEfX0e)>5h&c3wZ43X zdjgJhx5h(lrAVXq)8R&cT*J~5Dex~oW!b~O=um&QsFGRQ#%kLDBL2!!ahJ) z6)X_;W*kCHdz=&^DuNI?Rz&dmX$&d%PAsG?shp(ZVgxsGc#jJyD_=EsHWo-|@V z(j9H?pN~03Ek-ADaFPhQ9=u0ImZV)|7m=w)kfaW%^6eX+O77?!5J!Tamk9Ca3<_r^vrw?*X7TL zM<>nScJWDE#dwcLB=5xy(?tFHcu=A-o^7G$=+%)Gj zWN((&N`yo|;&>Q!Mem>#Qm@fPBCz|n73B@hl85wHkq&`Li^}2h#F%*GilT-dzVg+n z739=6UOXa&@XH3Oa1Tlc74db1s~UY13s~}NkYjnUiSSQ^KFd3+*k^&*S@T$|g+%S@ zTwOm=VJx`0ZN`7u*g%oUpJ`v!uuocBy2L@bmG!U69dyFRZ^NyQ-wzS3h~fy(CyXI+ z^tULAiVd&lf(B;pLlfb#0h)JWJ_(8NROBUFqY3l*mx%p`>({h5N)S{lba@$w*K2wu z(wHC{2cEa9H74O@7cbf<0mw!Pc%PL6Axjf!*qW{5@JS^$u{N}E{fM6{*-v=|)u&)V zskwC?)tPW&efdEMQsCXOUQ*+}7Jfr|eO8&ezGg|m&KL-Je^y5mwRV*8yD#$z+pTE) z)GlX?)8$kAl0;whME@-;KK2@)bJ-TB?Ow?bLKNW&OH|Ri$1_%ryCZNtC$jkIcJ`PB z+4Xhj^sR(n3O)F*sWj0$^Kt=)VhT->d5FK(UM)1$EJF+orxC-J#_^HMs={Z@RAgGM zK{tm;6JB^2hLY*X5XK*!=flr!MjN4P1lPVZg*Lo?1T}r9z$aP{kF=W>NvT{x>oJ-| z*CO2!mI1%v+i`uOt26E7qv1B9Hm0kn#*=|{rrZ(@%1+_lv{;D}hhlM#5F63CLn-_V zlu2ajCxl4fdxykB+X7F#|DB=4PWe5h-4B%}5qoxI4d0m7kz1`aDDIGVC498d9&v2_ zJ`hKXyrflQUL(EdaA|vf!I;xg{nL)tK9?F|Gp{)^yYFpOvqBLDB6Xk>x>xPYv?Sdt zKG~eN@0y6{%o{t9T0fWS8e<=Ld+YQ{nSBf7P7Za<$;DJ7f^EGr^gv*^-MF3~ACu^= zs-dg% z;w*GE^E@k!PCZ{Kh<+1nF35}Tktv2sW!ktH>o(-D4quP9DGm2f+Gw^*ilk6Xwd>l< z_D;Oy6Q7r?Zy(jW+s@>!p4@#_Dd7d7@m?NYV3p2e^5Z8 ztNB>^-E(v}5_C4R`CiDgUeDVijV&KtiW&05;hrATdm}KL;z}32?p4>FR5(pBpm|$~ z7@QvKLc_VS`fl?xr6x}cM6da!8=QMbi{4lnWU4TiK9?2vf1gPT9yyIF;+bifoxHs7 z^;n&PK>l23rVu2j*(e z1`MF0<4eXv;yKZb;tTunGV1U3zhslQ_rATJN`4&S{%xPSvkcwb+2i7}!BzEqlRBeip%523J orT39xb?sHmFI``&`7SFEYjYUp!*``&m0?G;{y-uCvh`?!AE8Y%~bWA zY{7Hi`fM literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce.wai b/wai/http-conformance/objects/cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce.wai new file mode 100644 index 0000000000000000000000000000000000000000..17259d082917163d57481498969d20eeac57645b GIT binary patch literal 3222 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 Me#5ORV~OK`17YJ$HUIzs literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce.wai.etag b/wai/http-conformance/objects/cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce.wai.etag new file mode 100644 index 000000000..ccf2c1349 --- /dev/null +++ b/wai/http-conformance/objects/cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce.wai.etag @@ -0,0 +1 @@ +"cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce" diff --git a/wai/http-conformance/objects/cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce.wai.hp b/wai/http-conformance/objects/cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..2b0095edb146b4dd79612a597e19de84f6ad6767 GIT binary patch literal 117 zcmWId2rxX$8N2bz&M@|-_1()E{so3Se;*^)5F{@c9OC=0t%@STBDpI_*D0`YF>qImtWr^34B@AJdHQR(rmPE2; zixd(?sW%EQWvR?+#@y+>_dd_P-T&a8=lMM6e1G5X`JVGU=jU?BN~Z<@fJj0(iA*pe z=xAyaGzjN>yvZb?x=8l+2^MN{P?%3pnD9f0&-p;I7b(o=XDHa)Cy*H8L%Bc>@j3Um zA4%{cohO|k2a>}s3)8|%&A%%&g$2ZO!C~a!AVRbT!7Dh(oBUIdgZv0ak%Rzpkhd`Y z-=Q#4h@X$}OySK4M!I`72>t{k9mDA8H5352SUKy6hyVawcynNa$om;U@+?HD?X3r->hm!2 znXNztcn}uYLjYaI#A216FN^R*=n(gYKT+h&R(?Gj1_ zP4rvC?NMMC9`10p4QAgS661m+6=GYO z#y;^Q5Scjh8l1Z;0cXdyg&Z2C@+N*Og|)<8gQn{AAzb5zMdn8g;jOY3Q0UVExT$Nc zXpV9q)V!??do1J(RMkfe{&{8*`XwL7DtkSLDS?|qm3JzO&d7*DPhZ5LKHEHp#$KWc zEHC@>&pe{=7#uu*ztjpBn7@sC!rq0ZU~a)MgL3#k!bM@t6UDeAx*M33!zy@_IWbn@ zwI8_i&p;e?a3#*9gUH>dbsUz~`wDkB?WSll=n2nzeGa72s>DArQw?II#zPb}Z^E{r zWyGyEoABQ%hF}BpzW9O4Nc7OVXmQH44GS0h61CmJOgvf#2dhI$i8rWHklBw!uxcX0L#NFntu+1blRKMMEiLSfp$SkfkqS>5;BXvJQZuHkN zb-jMa+{zP|n7yb1A1wSXzCMrzucs+X7~pBhlTWGq@?CCV!zw1vL)8KNAmD;Dvi}nb zT%dvfkTQ!0!OBq@<@44l(|yu@A9qTg(uc5v&7-i>Z|;aOzYj|#j+#q)22cg3u2+Nf z>dtH)ztjSHZgrPCY{12+B$gwjDRlh9=UXHS{xVbD)-kj)To%9JG&#$fUVFsxR%+!w za$M(gsqZ!kjuisdeCTF&VI==3&up{geFWHQt2k3Jg#~~AsY>({jmF?bHAq+Yzm7TB z?#Q&y&Bw^4&M4$X>N;Z(&p|u_ zAr{R^Y)asua^>Koep1|c5~<)5r3v*-*@B~HsU`OHHI%b8;uHcp_T0pk-t)rZ}DBOlJGQMPoAqu>m!=-lW!E>*yazm+f z=8IOn@urJmUWUc&pFGaa>1r2So>SrFb{v6wx=n3>MdCKY zq#bzyOoT}00!h@k){CEeVja>pSs-SXGmHu+!ueLRd1B$jVz|@kyhU00s|=d9G}fq9 zoqsY>fY?70v^i=*;`a`pM{A?yHt!`d1Y-g52y*ss0==z~V)Bq2K^}#Qm9?Hj$7RY2 z-Y*@L2mx&(OE=D-c6iw%3ny{~pOy@82=yAlNAMGDyZ^i(V+!CORntMA3g8H~#2Cf+ zyzdVD;wCwSc572$B!Q|_cGj06dfN4!cUczIsw`H@SD{D|#5c^r@%#J5Yqq*Xo9qG3 zZeCFfb#v26{oVjvH~PG5%24jITU0AS)Wh@ik3HJ6Pt60zSIh(w^;9jp)1-N~IqOoH z0EWic+HzY~)RMnTc-ke`Jqc}nMdlXn>Y)p!CDGa%Id|q`^iP*tKJS|9_;@7#!HD<; z#qFZ|sb9&94dOF{y0m1+54i&(){bWuYYL4ss2$%`9edg)uNk`yXWkmdq<1vTmF4ZqpGuB5CDbhQJyE<16whq~rm-RkqPF{4V z+EO(|3!q4pC6?wJDvmAQywTXaXxxyfFumddi`#}KmL0?QpSaU)UJyye8F78O z59U?Y`_}a^jk?*b>iIgUA$+lR*Y_bqK;l}Qm2<+CX;9|T_gHgf>&_B>L#(c{oT`EbD0>a zC$3kuKU&zWr)=1rx)t%(w$Nl8QT+BBO}EN8(x7~0VjBJOz(F#-bYi?GpFnl*NyYy- zXmu_8dBaoBzA0DJp~?#dJR+mk~8ySlAD_XFQ%Q4!Hk#p-I=8koiJ^=F^|2 zm#a-&SF@36+ubgBleEo*xuob_)Ox94geE1UuszlzeapkQzdIEBcF~Hv?hlove9_wx zkiYM}-9u)J!!p|Q{dN2;zi#(@3l)vU&iH(}zT7)g&~4CD3R%3g@N;68r{%tVjyhWr z`}@I(aG3{(@;|~dR$Nvq=?b2_eftdTpK?OxM`k=+I)$@61V z$hU)choAeQ1FM>2H?9seR$pma&^2p(89;Up)DHKPYjPW2lImX7?cLW_eBiddmevPy z5#mFueVG~6`yoxE`y-{DY*F>DuAMUO$4M7+HrZ8(=I->Bc&(nyFOF)VUX%$EPHsi^KRnZh&PR^RffGkz)%%dO?r$AgmPpnT0WFZjn7(1k|rC+t;@?P zJZrS0ueyQ+^mqq&zsRIg literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed.wai.etag b/wai/http-conformance/objects/db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed.wai.etag new file mode 100644 index 000000000..dadef1e4b --- /dev/null +++ b/wai/http-conformance/objects/db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed.wai.etag @@ -0,0 +1 @@ +"db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed" diff --git a/wai/http-conformance/objects/db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed.wai.hp b/wai/http-conformance/objects/db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..d6c0969d2bdcf6a91b175df51f5cf322eb1728a0 GIT binary patch literal 117 zcmWId2r#@YzL$N2^4&9r8^Z6;F5_VUgOAJ*nyG%b z>5bsM0h2veCFkE*b2M+}hMT8nJTiN7N7O} literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f.wai b/wai/http-conformance/objects/dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f.wai new file mode 100644 index 0000000000000000000000000000000000000000..dd6632d3ba23f3230ced75c8499c2555acc6e141 GIT binary patch literal 3018 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f.wai.etag b/wai/http-conformance/objects/dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f.wai.etag new file mode 100644 index 000000000..37f71e785 --- /dev/null +++ b/wai/http-conformance/objects/dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f.wai.etag @@ -0,0 +1 @@ +"dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f" diff --git a/wai/http-conformance/objects/dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f.wai.hp b/wai/http-conformance/objects/dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..9e3da571f6bc3696cb6c27f7ffcb21eda342bb64 GIT binary patch literal 117 zcmWId2r#_M=*+(?>aO~a-q^?5n*_K_CT>&En;a$U$hhhSo9^8FQ``(-@R1opGkFM= z|K3{i>xNcMny1$Z=SR6uc5QLFQdyrMHmlj~z|!3_1?T)}(Fwo4QkVCYkx16rDVnTl Rk}qCvlq=A%R^Rqn9snU#F6saP literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac.wai b/wai/http-conformance/objects/f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac.wai new file mode 100644 index 0000000000000000000000000000000000000000..b94b13ab758b45d1c5689fbbc130a9ffcfae9da8 GIT binary patch literal 2994 zcmdUrdo&by8^?cRF6Lscl%c|oTdt9cMv5p)xs(tk1|!25Vi@C6?w3eH*;d37rA@Qh zB%x9Yi`tT?TyiNwp_nlpk!L-}{{NJ-_F9exK+2d(O|(#=*1}01!{% z&=?dd#njk@Vn_+22Qp}~DuxkA50}*p7Mspu%NmjN2qq(d#-@LV!UO3{-$?q2XhtMG z?C<+{N&qc_=Fea<*jyPcvo!wO!dPbD8y3!HgtI7#hLnJCRv_bh!C)PyP~$0~3|62F z|96N@i#$%3y(!y_LN(iLNC~D;O)U}=#b^LL9Xw1S5CD*}%}Fw8$n-r|kZDOOm8!_j z@9|kCI~kWL2LKcXfIb`mu}n1t0L~Ku7?2H*DFUFq4uF;!0Nj@W@Z|$wDgfZ{3IKI7 zEeHV^5dqL62H>6q0HPECsZ0RKjQ@#$b^a;;WAeZ7|IN&I_WzI9Kmkw%911jLU+b+9 z!ec|0Swh^`yk@X1XcGN6r3qwc)`I7c;*gzKB3Rdv+P}-u7PuJuE>}|!xE*dh0ZUng zZ5)6L<*f<`_3R)3)92uxP{^PoED4!f;td%*6aqc|p&C!+-GFSjHo}i*pH^s3`3h-O zUql(ae|m{~vvv9OmD8Bl{MV4aL#`OOm4lq0$yZ^#;3LxiRx6}>RRy`9+YCK1brxf6 zmI-a3slkj>o?#3wmqP2UugmF&Wh&_SqoFO8bi}j1Pf)69kfOW(?CQNE^#Vg)fm|-{ zp|IYMfOw{)vWkt`Bd_f>2EErN0BW2XSYX@`^iIIfh@lWH{I2U&^s!wfII@~1TsoPD z8*zU!8CmD6=tb~aT?n6);{;a=M3W8JecUCO^P`POCd&$z#C#)h8R>%!Zk-1L3Cy8sl2J+@wu2cnrlRuO+0Qrb_!>sYnA{r6)5Q0qi5tSD0h{;7F#30F@ zc+3iRFFr9)f}5;|_ML`GGC+}BPhR*+f$J9}dD55|u`?e3@+6fgnP0*OkG2vkt}Wv> zp^8XK;&vD{#R#(_X%DGcy@wdou8-_69wBbHLo2L;he#S38j9@}YLe+uGw7tb9%=m@ z3*3YLIMP&rp=9ymR&0I7fK;wg8O4knB6ZKUVf)Rfio5D!;Ynu}v7)k{6~{|olD4T< zE9MWZB3G{kL&bM{6tfN|Oa2I*lG{JrrRa723qJJ(Ns+tsvqY!a1-Dc4xYVqyoS>t5 zM(LY6XN|3sEli`;7TKSyOU!ecB~H9xp+kGgXq$`v_?4P>%3A%7%0H1}h5d(Ju(wL& zmGZ@I%2nMrRkiM~g{*`*qa#*Eamu7BgzlyuR6?;niZY9nf7IV1FMnYxGWOl%;=sAN z`E!=ev$}8g3p>QRVi+|7-Kz&fQ5pu99k@({W|bR%&1PMg)czPk>#PgEFlUA^eeF4> zT!+9f*D~+)i|Jq1ON8=$&SovYF8fVj1aHHovOW3ny2A<_(6_9i{6xZEKDQhZ^8*CF zAm;1(+ChUhe!=+ojKKrmu43U_gdh@ok2pE>2)~yZf<7xjARS9D^F2sID5h@^pP?zI zaH~YjUp%OYn(mI`zmarEd>_By|LLg%sj9po=)grnbqLq-PfVQ!I(IOr+csO#YZ)_w zXPhL3+6B*4fH=Z$Qh$!WfQ}WI@@5e24jwCeolc9m@B9S698*{|a|*>GHo6GKgXa)i zQbt$!N19-TCPqRR3kgPJ=trU-4~1A8J-ae+Xs*(2m?7j8%JH2aZzt^EpDpzM>aS4c zO%~3VTOf)RZwW77ke7Zwbrgy9S}#gKv|y*^EJd9MAOyWXzi#)vYK=x*lC{6GP z0-4UldH=2fizDpDARKsNnzt=!c>g?F*O4fenqx4hMi)igU-A_kiz3DJVCIT%SCp9Z zHdnMTR*KizP$FJES&V*4D3`PreZi4uI##_*VP5v#Onmc;T1j`UH@6-}_z z!XEsEzD#eskASB0R}iCC068Ke071rM=f1!6(9-WaAbzCxHZ@(rt6 zxaK5nMEu{HCkN?S+eRH_NrYhBZ|da5;T+(X`XcmAfW zg8P|g4DuIq=p87foc7-`Y>iukIS-J$(%#zgZvh|d6U#+$cb~pmQ+4G?^jt|->V@W< zqf?#Gpdw3ilU(i2+_JsSH<)F|?6z0Lh;~Kr1=q0AF(eCiCVAhu^Gpwja zLR>RdOQGmY$zYIUILB$T6{((c@NFNvT|38O>_nyun%wn49oE!PNHD_7`RB0DH<-N) z?rh9IY;FAP!glX7uRMa6bUeIE2H0As>ZxV1+R*1dwZ*NT<{vn^d7d_hwAJ57oiX-I z59{Bc(o**Zz0LAB`&4MJgMP*P29s<%Zcq=U%OJqJH(>6$*<-q0U{IEQlBUlE9rd$@ z(x$9j;mC_Wx5+7bZG{fl?`XU1dwCPa?$0&EX9?khPjqVP0#n^G>#eW4XEN4X+8-F9 z#_2gBV!-El^P%@9JnA z*F(p=@!T;HZKQK_v2USxOGHN3{AOG3`k@!0*G&bvT?@CjZR@q{Z}aHBwKkDF7;WyD zluD25!P~3G*iF!&{`NmU66e^3V`F*>dV5M=cbwFlv&y+T7oEqQ_`$3I Z$J*@ub-MjZ^j_7FgzBcoSU* literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac.wai.etag b/wai/http-conformance/objects/f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac.wai.etag new file mode 100644 index 000000000..ad6845de7 --- /dev/null +++ b/wai/http-conformance/objects/f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac.wai.etag @@ -0,0 +1 @@ +"f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac" diff --git a/wai/http-conformance/objects/f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac.wai.hp b/wai/http-conformance/objects/f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..6a5a9e84b2afd7b4769461591f7303f299db0333 GIT binary patch literal 117 zcmWId2r&HMH08kOk9u**oSWy%80URft?4O^-M4MB$et(uXE@#0Y~p4BgOAJ*no0fX zfjyB-K9?5??3mqms4?qW(gKrtcANZXPYzOH2*0RD RSbQc+w0fMpeR+ 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai.etag b/wai/http-conformance/objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai.etag new file mode 100644 index 000000000..008316c31 --- /dev/null +++ b/wai/http-conformance/objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai.etag @@ -0,0 +1 @@ +"f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a" diff --git a/wai/http-conformance/objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai.hp b/wai/http-conformance/objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai.hp new file mode 100644 index 0000000000000000000000000000000000000000..2dc505f65ea917f02ede3c6a47a71fb893a5d5c6 GIT binary patch literal 213 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 S_2r3h-h!jm$wn*d*8l*ZL^JLH literal 0 HcmV?d00001 diff --git a/wai/http-conformance/players/classical/init.mp4 b/wai/http-conformance/players/classical/init.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..b1aaf5e26c6e0893057d45d7dd2b0fd8f8f7f8d6 GIT binary patch literal 755 zcmaJ;u};G<5WO@YF)(zf1Ve>VsRIi_Aj*bR!O|aqi6yO58Z~aB*tLKKB*Z7M@+}N( z{Q$&RiGh_LpuBS%Y69XE``x>HzMOqTRPXDnELLfYs6vcbhcY|Z1AmceYKSP2W*~r$ z`R|r^MEI8WgCJbu?WOWBuYv=gIuD0Pp6j9gr|$?$)&9wplx>#s)?|m8k6c^^6p8h+H5=`R@Dn$mz3%oq7`C=Tbk$+JkYsCPI0^CmXF3lhlsaiWRZ0ZcM_Oh9%dOo*0-(2821;pv|1tn literal 0 HcmV?d00001 diff --git a/wai/http-conformance/players/classical/seg0.m4s b/wai/http-conformance/players/classical/seg0.m4s new file mode 100644 index 0000000000000000000000000000000000000000..99cfd0324acaa3098733c2bde5c4f1d2832bd257 GIT binary patch literal 1194 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 B%#Z*8 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/players/classical/seg2.m4s b/wai/http-conformance/players/classical/seg2.m4s new file mode 100644 index 0000000000000000000000000000000000000000..5d1e65711df7bfd87522eb4f1b61a80b1085074a GIT binary patch literal 1355 zcmXw(4@?tx6vuzHhjdVAyH~LWB`nT0shEvCz5}|G-dPBFULXVkePjh$12_No zgo9lI2!Tj|1+W2fT>vnE`2eY}0-^vip9uuMw;ZtZfCT^@fEsM2B1fi%Ud>x8b!EjG zp02aYj+hB|4z69AxB2(ckbsnDvXT<5*(^m8Y67BCzu9j%T)4{oc<3*eawfWr7gu4o z`y;<54!Xe@bPf(W9gc_QH*^ZMi$G#J!sd!Dhi*xw(xS1Ofz*s@$_3wMZ^T?e!wa3x zF~I4M1@KWY9MsYatx{Wgm$b)QTc)g35^9fO;j^8?c?ET%O769mft2&B2se@ z=JgcOg7@8CxY@t*D$1`D^&5f={rd@wK2I5edOoQnD9Q zM8cEJlGHO2pNhl=`8}iib;VS|Xr!=c&h^aKDWiW+@exMZB1Ym^FHyJ1wN>r4t=r*t z-{w}uNT!b38Y}tsY!oB;P;6J7>gwl9AQj3oj>^hEh&r$Zj)nmt8ziSrk=$)ko|1^&We?IN;IlZa?nmL*7{dTyY5H$ zUK$usw09Y`3zAe!?w4blQrm{D%|p;0&K(JyI@^uamW}hR>sgsKuZk{z7ru97GMEzW zKJ~&LzdxM(kmxh4H`|j1GZc)mLmai|w|5w)Z%va8xG*%q%Xpz5XQHu0m49I5 jv?433;OFs~n^st*!Fa-^8poC3!?~j66RftK#ys + + + + + + + + + + + + + + + + + + + + diff --git a/wai/http-conformance/playlists/o10/mr/89f7c647fb786e14ced2c1c8250f30fc105274bfae7e065ecbfa41446b78d296.json b/wai/http-conformance/playlists/o10/mr/89f7c647fb786e14ced2c1c8250f30fc105274bfae7e065ecbfa41446b78d296.json new file mode 100644 index 000000000..c3d64bd3b --- /dev/null +++ b/wai/http-conformance/playlists/o10/mr/89f7c647fb786e14ced2c1c8250f30fc105274bfae7e065ecbfa41446b78d296.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"89f7c647fb786e14ced2c1c8250f30fc105274bfae7e065ecbfa41446b78d296","session_class_hash":"28bd23d88c20332da1bf73dc84748f431ca1459b0f6edf73c2a083da2c48c77d","class":{"content_id":"o10","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825&h=1009","duration_ms":50,"ad_id":null}],"merkle_root":"7e79bdbd00dca71e6672e23d5bf604d75d48151cb8a0279dbba5ce023e27d067","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"0d17331fe52f1cda1f5a8564b346011e1050642258db19e8c7066e2e83b566b236719182ce608bf4665ab9fd5ef9b5c80ec0efc8bebc91ed8fbf4b07a21dc70f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true},"extras":[{"discontinuity":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab.wai#n=12711"},{"n":2,"uri":"../../objects/753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c.wai#n=18367"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o10/wai-a.m3u8 b/wai/http-conformance/playlists/o10/wai-a.m3u8 new file mode 100644 index 000000000..df7d021ae --- /dev/null +++ b/wai/http-conformance/playlists/o10/wai-a.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:1 +#EXT-X-MEDIA-SEQUENCE:0 +#EXTINF:0.050, +#WAI-LAYER:N=1,URI="../../objects/53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab.wai#n=12711" +#WAI-LAYER:N=2,URI="../../objects/753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c.wai#n=18367" +../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825&h=1009 +#EXT-X-ENDLIST diff --git a/wai/http-conformance/playlists/o10/wai.m3u8 b/wai/http-conformance/playlists/o10/wai.m3u8 new file mode 100644 index 000000000..4637cbe24 --- /dev/null +++ b/wai/http-conformance/playlists/o10/wai.m3u8 @@ -0,0 +1,3 @@ +#EXTM3U +#EXT-X-STREAM-INF:BANDWIDTH=292000,AVERAGE-BANDWIDTH=292000,CODECS="wai1.audio.flac+audio.int_refine",X-WAI-FLOOR="wai1.audio.flac+audio.int_refine" +wai-a.m3u8 diff --git a/wai/http-conformance/playlists/o11/mr/bc3327099dc8f1121c168b9b9872233eca247c07d0ccf4fd30f6eb633d87acb6.json b/wai/http-conformance/playlists/o11/mr/bc3327099dc8f1121c168b9b9872233eca247c07d0ccf4fd30f6eb633d87acb6.json new file mode 100644 index 000000000..b10c7cb81 --- /dev/null +++ b/wai/http-conformance/playlists/o11/mr/bc3327099dc8f1121c168b9b9872233eca247c07d0ccf4fd30f6eb633d87acb6.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"bc3327099dc8f1121c168b9b9872233eca247c07d0ccf4fd30f6eb633d87acb6","session_class_hash":"7ecee2c182c9807140e70aa35a6abc8888ea610c8497def214626e2cdc30102b","class":{"content_id":"o11","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551&h=551","duration_ms":200,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523&h=523","duration_ms":200,"ad_id":null}],"merkle_root":"e8ad08a2388f7a1c781ee512230eeefb5b0e7f2d3adfe861101b15edcd01b43a","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"915116ea2e9e9f317cd26b716c7cd112f68dde83adce981327d15c3d500630100806c9233093353ae71aba95ecce760fb4c1183c3463d9f607d6135a8f2ade06","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true},"extras":[{"discontinuity":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai#n=535"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o11/wai-v.m3u8 b/wai/http-conformance/playlists/o11/wai-v.m3u8 new file mode 100644 index 000000000..4f112f798 --- /dev/null +++ b/wai/http-conformance/playlists/o11/wai-v.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:1 +#EXT-X-MEDIA-SEQUENCE:0 +#EXTINF:0.200, +../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551&h=551 +#EXTINF:0.200, +#WAI-LAYER:N=1,URI="../../objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai#n=535" +../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523&h=523 +#EXT-X-ENDLIST diff --git a/wai/http-conformance/playlists/o11/wai.m3u8 b/wai/http-conformance/playlists/o11/wai.m3u8 new file mode 100644 index 000000000..427218d58 --- /dev/null +++ b/wai/http-conformance/playlists/o11/wai.m3u8 @@ -0,0 +1,3 @@ +#EXTM3U +#EXT-X-STREAM-INF:BANDWIDTH=22040,AVERAGE-BANDWIDTH=21480,CODECS="wai1.video.int_motion+video.int_refine",X-WAI-FLOOR="wai1.video.int_motion" +wai-v.m3u8 diff --git a/wai/http-conformance/playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json b/wai/http-conformance/playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json new file mode 100644 index 000000000..babff4059 --- /dev/null +++ b/wai/http-conformance/playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004&h=1009","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381&h=1009","duration_ms":640,"ad_id":null}],"merkle_root":"aa4a88ea7452da29f8fed31b9ef9127adfc8f00a4ff7abe41ff1ae496c52141f","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"38ad4757b0dd56bf965b1704983d0c34fc6d8ea5a77bd208220143ea3da998cecf2104542fa6822266f6a640f3beb0b681f6c7c5ef7647a0492ec4a98afaa309","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true},"extras":[]} diff --git a/wai/http-conformance/playlists/o9/wai-180.m3u8 b/wai/http-conformance/playlists/o9/wai-180.m3u8 new file mode 100644 index 000000000..c4e44feb5 --- /dev/null +++ b/wai/http-conformance/playlists/o9/wai-180.m3u8 @@ -0,0 +1,11 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:1 +#EXT-X-MEDIA-SEQUENCE:0 +#EXTINF:0.640, +../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009 +#EXTINF:0.640, +../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004&h=1009 +#EXTINF:0.640, +../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381&h=1009 +#EXT-X-ENDLIST diff --git a/wai/http-conformance/playlists/o9/wai.m3u8 b/wai/http-conformance/playlists/o9/wai.m3u8 new file mode 100644 index 000000000..706574ba0 --- /dev/null +++ b/wai/http-conformance/playlists/o9/wai.m3u8 @@ -0,0 +1,3 @@ +#EXTM3U +#EXT-X-STREAM-INF:BANDWIDTH=262925,AVERAGE-BANDWIDTH=239246,CODECS="wai2.video.av1|video.int_hyper",X-WAI-FLOOR="wai2.video.av1",RESOLUTION=320x180,FRAME-RATE=25.000 +wai-180.m3u8 diff --git a/wai/http-conformance/playlists/o9/wai.mpd b/wai/http-conformance/playlists/o9/wai.mpd new file mode 100644 index 000000000..e1fbd3643 --- /dev/null +++ b/wai/http-conformance/playlists/o9/wai.mpd @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/wai/http-conformance/src/SOURCES.json b/wai/http-conformance/src/SOURCES.json index 609bdc66a..dc7e1d2a5 100644 --- a/wai/http-conformance/src/SOURCES.json +++ b/wai/http-conformance/src/SOURCES.json @@ -1,6 +1,31 @@ { "note": "Frozen sources of http-conformance. Each was encoded once, outside this repository, by an encoder of its format; the AV1 streams were checked to decode. Only their bytes matter to the corpus, which checks transport, not decoding.", "files": { + "../players/classical/init.mp4": { + "sha256": "a88bdf15ce097fa0d1fc8d8b3364a928d5e035ea5470dd1c1118c2c7ea84ddec", + "bytes": 755, + "what": "its initialization segment (fragmented MP4, H.264 constrained baseline, 64x36); the compressor-name field is blank, encoded once outside this repository by an encoder of its format, with no tool name left in the file; plays in a classical player" + }, + "../players/classical/seg0.m4s": { + "sha256": "16834afaf9da6c8ac5492ae49bd2c8b0e4ae81406b4f230169a9fa8725dc67ab", + "bytes": 1194, + "what": "its first one-second media segment, the same clip as players/wai0.wiv1, encoded once outside this repository by an encoder of its format, with no tool name left in the file; plays in a classical player" + }, + "../players/classical/seg1.m4s": { + "sha256": "38ed3b4754b302353555accca1f6b31fce9d7799dc328108576339fc21f01861", + "bytes": 1319, + "what": "its second, encoded once outside this repository by an encoder of its format, with no tool name left in the file; plays in a classical player" + }, + "../players/classical/seg2.m4s": { + "sha256": "d45f3179dce741b1a07b623abe4b38e4b4f86362f477e5d5a28ef090276d368c", + "bytes": 1355, + "what": "its third, encoded once outside this repository by an encoder of its format, with no tool name left in the file; plays in a classical player" + }, + "../players/classical/v0.m3u8": { + "sha256": "128a7d14cc27730d20c86f92f718147c8053b4170e55dfa9bbf5cd1aa20fb934", + "bytes": 220, + "what": "the classical media playlist of the player fixture, as its packager wrote it, encoded once outside this repository by an encoder of its format, with no tool name left in the file; plays in a classical player" + }, "image64.avif": { "sha256": "c56f9ab9a3b1df0bd650c3177bd85427f4a4be8fde82547219bcea4ec254162c", "bytes": 2619, @@ -16,6 +41,156 @@ "bytes": 11848, "what": "PNG of the 64x64 picture the pinned wai.neural.int_mlicpp vector (wai-rs/tests/vectors/int_mlicpp/stream.wai) decodes to" }, + "live/classical-r0.m3u8": { + "sha256": "fd00d14d3f64ea285d6ba2429f7f48463924e8041cc7cfefeee513e85f7f25d3", + "bytes": 590, + "what": "revision 0 of a classical low-latency media playlist: media sequence 1200, parts of 0.32 s, segments of 3.84 s; its media is not part of the corpus" + }, + "live/classical-r1.m3u8": { + "sha256": "00c81e0b64304d44a97e28cef4ec49ebccbb5c3df79fff9d14eca4b922a3ab41", + "bytes": 1205, + "what": "revision 1 of a classical low-latency media playlist: media sequence 1200, parts of 0.32 s, segments of 3.84 s; its media is not part of the corpus" + }, + "live/classical-r2.m3u8": { + "sha256": "1eda1a3295315cdd335d5e3504b7adcb0d6f3a4be94ccf686aee040bbe443bd7", + "bytes": 1761, + "what": "revision 2 of a classical low-latency media playlist: media sequence 1200, parts of 0.32 s, segments of 3.84 s; its media is not part of the corpus" + }, + "live/classical-r3.m3u8": { + "sha256": "fcce267265b5cfc94733640a7108eaba0048b89515aea41cd852494bffea170d", + "bytes": 309, + "what": "revision 3 of a classical low-latency media playlist: media sequence 1200, parts of 0.32 s, segments of 3.84 s; its media is not part of the corpus" + }, + "live/s0.wiv1": { + "sha256": "987cd6308c93cc8400e52e1c502495d0f0270a030acb24aa512254d12ba09e54", + "bytes": 16424, + "what": "WIV1 (wai.video.int_motion) of live segment 0: frames 0-95 of a 32x18, 25 fps synthetic clip, encoded once by this tree's integer video encoder (block 8, search 2, q 12); decodes to the encoder's reconstruction" + }, + "live/s0p00.wiv1": { + "sha256": "f917853d39e09cf2d7009ccea9f048902df032cef0515f28540dbc05cc1974c6", + "bytes": 2844, + "what": "WIV1 of part 0 of live segment 0 (frames 0-7 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s0p01.wiv1": { + "sha256": "0acedea6290c2fd084d3840e2053153d24aea99c035ef6cd1b7bfac6d6c6d798", + "bytes": 2780, + "what": "WIV1 of part 1 of live segment 0 (frames 8-15 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s0p02.wiv1": { + "sha256": "00b25faf764c18293702a17ed4921f5de92a7c024720354c086a7311edf1da0b", + "bytes": 2812, + "what": "WIV1 of part 2 of live segment 0 (frames 16-23 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s0p03.wiv1": { + "sha256": "83a954a7a02b23d2d1e724db6840a2655d249aae0b32fabb813196a62f3d4d10", + "bytes": 2836, + "what": "WIV1 of part 3 of live segment 0 (frames 24-31 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s0p04.wiv1": { + "sha256": "d3699e07a06780bc840f8049907200e135e9fb87942a8541d41ea8e003a72c99", + "bytes": 2844, + "what": "WIV1 of part 4 of live segment 0 (frames 32-39 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s0p05.wiv1": { + "sha256": "9a14e3c23ef087a4fb3e527c0b2c58eb384e28ba9ed528dcf90c4f26df752092", + "bytes": 2780, + "what": "WIV1 of part 5 of live segment 0 (frames 40-47 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s0p06.wiv1": { + "sha256": "25434f277adcee35046c5695ea76151b2fcf19ef47ce65df5cc538c0117697ef", + "bytes": 2812, + "what": "WIV1 of part 6 of live segment 0 (frames 48-55 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s0p07.wiv1": { + "sha256": "5e9319849a6ef9fb1b0071d6910700da04e07b4cfbc6fe990fef3947453ceb7a", + "bytes": 2836, + "what": "WIV1 of part 7 of live segment 0 (frames 56-63 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s0p08.wiv1": { + "sha256": "a8accb20ff173423e341ccf8d8de8c919cc34ef04d6105547d92b20240f264b3", + "bytes": 2844, + "what": "WIV1 of part 8 of live segment 0 (frames 64-71 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s0p09.wiv1": { + "sha256": "dff20a218b4c95873d0c7aff191b709213ff5fbe871676a0a66f97377f4413b2", + "bytes": 2780, + "what": "WIV1 of part 9 of live segment 0 (frames 72-79 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s0p10.wiv1": { + "sha256": "8fc419565f50f2c375673fbd41fcef4f5eeb2f8d82a6f0467477a7eb352e50d3", + "bytes": 2812, + "what": "WIV1 of part 10 of live segment 0 (frames 80-87 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s0p11.wiv1": { + "sha256": "b0df0401eff7110773c77c0b50156dab74c28007155ecefacaff0ecba135edd6", + "bytes": 2832, + "what": "WIV1 of part 11 of live segment 0 (frames 88-95 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1.wiv1": { + "sha256": "242c32c61997535a2ef98748278ff2a3b0c5b45b356efd6c495a055e6c39de39", + "bytes": 18936, + "what": "WIV1 (wai.video.int_motion) of live segment 1: frames 96-191 of a 32x18, 25 fps synthetic clip, encoded once by this tree's integer video encoder (block 8, search 2, q 12); decodes to the encoder's reconstruction" + }, + "live/s1p00.wiv1": { + "sha256": "af1d2d9466ec8e381f969b9100411e246540e4f93110f5bfe47e858cbcbe9bd3", + "bytes": 3008, + "what": "WIV1 of part 0 of live segment 1 (frames 96-103 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1p01.wiv1": { + "sha256": "6f024df144d0f8c4802b10a0b37acce204252c45ffb5dc774c1c34fd9f205b85", + "bytes": 2860, + "what": "WIV1 of part 1 of live segment 1 (frames 104-111 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1p02.wiv1": { + "sha256": "8e89733aafbb50283a35410b2d87dda25bc69d12a949cf346d65d564051a1b5f", + "bytes": 2940, + "what": "WIV1 of part 2 of live segment 1 (frames 112-119 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1p03.wiv1": { + "sha256": "99b9ee17ef0b3d14ba9628197b622672fc95228c0de7ddd123f5773f10694467", + "bytes": 3008, + "what": "WIV1 of part 3 of live segment 1 (frames 120-127 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1p04.wiv1": { + "sha256": "2d548f423d73b9edf1be59cafb90ad32bb712ac3ddf207555b6ad5c29069d729", + "bytes": 3056, + "what": "WIV1 of part 4 of live segment 1 (frames 128-135 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1p05.wiv1": { + "sha256": "b45a0afea1781538bb25439c1e7c046049ef0b81c7b6d1cb808cb477a433bb7a", + "bytes": 2964, + "what": "WIV1 of part 5 of live segment 1 (frames 136-143 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1p06.wiv1": { + "sha256": "b95e2294a2d85da0a5742c99f4a3f19f66777b776fa6edb376a54ecf186218f0", + "bytes": 3040, + "what": "WIV1 of part 6 of live segment 1 (frames 144-151 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1p07.wiv1": { + "sha256": "94004bb83c1cfcf3a4ff5abea33140661337129596dcaa28e8f174a17f42f3a4", + "bytes": 3040, + "what": "WIV1 of part 7 of live segment 1 (frames 152-159 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1p08.wiv1": { + "sha256": "b564d781d7dbc00615a64fc141e4c878b283522fb507e98ce14a2b1ee3fbbded", + "bytes": 3056, + "what": "WIV1 of part 8 of live segment 1 (frames 160-167 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1p09.wiv1": { + "sha256": "fa32a1a08c7263b1427b990255af2b1d1ee4653d57710f2b3d283c124d4b1ff6", + "bytes": 2988, + "what": "WIV1 of part 9 of live segment 1 (frames 168-175 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1p10.wiv1": { + "sha256": "12b461c9fb57d45e6aa1e2433af7be7ebabcbb280a918274dd582878fc14dbbb", + "bytes": 3040, + "what": "WIV1 of part 10 of live segment 1 (frames 176-183 of the same clip, keyframe first), encoded the same way; decodes alone" + }, + "live/s1p11.wiv1": { + "sha256": "2ca1ae2ab066d351ae845302f717843ebcb568facd73fe743b01cc55bdea4e21", + "bytes": 3040, + "what": "WIV1 of part 11 of live segment 1 (frames 184-191 of the same clip, keyframe first), encoded the same way; decodes alone" + }, "pattern_high.av1": { "sha256": "68a51be787c439226eb7bb1cf434e913fc1f0e221870da87f70615d667a7ab12", "bytes": 77040, @@ -26,6 +201,41 @@ "bytes": 20766, "what": "AV1 of a 2 s, 320x180, 25 fps test pattern at a low rate, in the reference implementation's AV1 payload framing (n_frames, h, w, fps_num, fps_den, then each temporal unit length-prefixed)" }, + "players/master.m3u8": { + "sha256": "9ea05f9bd91a83d8fc09cbb6793a39701e59af11fe2e1704b7088d56bbfbe380", + "bytes": 185, + "what": "the classical multivariant playlist of the player fixture, as authored: one H.264 variant, before any session-data line" + }, + "players/wai0.wiv1": { + "sha256": "69496cb4657aeb9a48c41fa33d9eec1234d81ed8d7e8eb2be1838f8bcf7524bb", + "bytes": 21576, + "what": "WIV1 of second 0 of a 64x36, 25 fps synthetic clip, encoded once by this tree's integer video encoder; decodes alone" + }, + "players/wai1.wiv1": { + "sha256": "464b64f9d651aa4d0945deb11aff9d847ea840767c420babff30ca21923d37b9", + "bytes": 20804, + "what": "WIV1 of second 1 of a 64x36, 25 fps synthetic clip, encoded once by this tree's integer video encoder; decodes alone" + }, + "players/wai2.wiv1": { + "sha256": "5675b65aac9dd591de37280dd5944536183755496a594d9d044161307545a4de", + "bytes": 21156, + "what": "WIV1 of second 2 of a 64x36, 25 fps synthetic clip, encoded once by this tree's integer video encoder; decodes alone" + }, + "playlists/o10-classical.m3u8": { + "sha256": "359ca270cee2576572d3a543b387ba91666f67d18e6ad9238525dc354ace90fa", + "bytes": 111, + "what": "a classical media playlist of one 0.05 s segment, the interval O10 and its two layers cover; its media is not part of the corpus" + }, + "playlists/o11-classical.m3u8": { + "sha256": "c08e5bbae88b94dea9d4eb7b15343b65a17981d85e8435c005c8f5c94aaf410f", + "bytes": 162, + "what": "a classical media playlist of two 0.2 s segments, the timeline O11a and O11b cover; its media is not part of the corpus" + }, + "playlists/o9-classical.m3u8": { + "sha256": "a85f7f5231df278b4518385c2b67c91bc8fa2ad1169a23237626a80f78adf0e7", + "bytes": 227, + "what": "a classical media playlist of three 0.64 s segments, the timeline O9a-O9c cover; its media is not part of the corpus" + }, "seg0.av1": { "sha256": "0c03a22bebcdb29e26fc1e6dbf040bd86751e8e0be66a2b2ba6709f786a260e8", "bytes": 18293, diff --git a/wai/http-conformance/src/live/classical-r0.m3u8 b/wai/http-conformance/src/live/classical-r0.m3u8 new file mode 100644 index 000000000..c1306e814 --- /dev/null +++ b/wai/http-conformance/src/live/classical-r0.m3u8 @@ -0,0 +1,14 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.32 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.96 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-MAP:URI="init.mp4" +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.0.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.1.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.2.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.3.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.4.m4s" +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="1200.5.m4s" diff --git a/wai/http-conformance/src/live/classical-r1.m3u8 b/wai/http-conformance/src/live/classical-r1.m3u8 new file mode 100644 index 000000000..c329380ac --- /dev/null +++ b/wai/http-conformance/src/live/classical-r1.m3u8 @@ -0,0 +1,26 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.32 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.96 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-MAP:URI="init.mp4" +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.0.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.1.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.2.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.3.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.4.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.5.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.6.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.7.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.8.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.9.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.10.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.11.m4s" +#EXTINF:3.84, +1200.m4s +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.0.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.1.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.2.m4s" +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="1201.3.m4s" diff --git a/wai/http-conformance/src/live/classical-r2.m3u8 b/wai/http-conformance/src/live/classical-r2.m3u8 new file mode 100644 index 000000000..d2531af0b --- /dev/null +++ b/wai/http-conformance/src/live/classical-r2.m3u8 @@ -0,0 +1,37 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.32 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.96 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-MAP:URI="init.mp4" +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.0.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.1.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.2.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.3.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.4.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.5.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.6.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.7.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.8.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.9.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.10.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1200.11.m4s" +#EXTINF:3.84, +1200.m4s +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.0.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.1.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.2.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.3.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.4.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.5.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.6.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.7.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.8.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.9.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.10.m4s" +#EXT-X-PART:DURATION=0.32,INDEPENDENT=YES,URI="1201.11.m4s" +#EXTINF:3.84, +1201.m4s +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="1202.0.m4s" diff --git a/wai/http-conformance/src/live/classical-r3.m3u8 b/wai/http-conformance/src/live/classical-r3.m3u8 new file mode 100644 index 000000000..c4049dc28 --- /dev/null +++ b/wai/http-conformance/src/live/classical-r3.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.32 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.96 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-MAP:URI="init.mp4" +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXTINF:3.84, +1200.m4s +#EXTINF:3.84, +1201.m4s +#EXT-X-ENDLIST diff --git a/wai/http-conformance/src/live/s0.wiv1 b/wai/http-conformance/src/live/s0.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..5d2652434224a3101a3e783a81cf676260969a80 GIT binary patch literal 16424 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s0p00.wiv1 b/wai/http-conformance/src/live/s0p00.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..288e4d6bd8305af48405fa92ddb2d9c6941dda55 GIT binary patch literal 2844 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s0p02.wiv1 b/wai/http-conformance/src/live/s0p02.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..ea4a6ec3e6de46d59e5f699a6d8cd130e557693e GIT binary patch literal 2812 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s0p03.wiv1 b/wai/http-conformance/src/live/s0p03.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..bed4182f8585370682ab2283458274acf34fb82d GIT binary patch literal 2836 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s0p04.wiv1 b/wai/http-conformance/src/live/s0p04.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..dba1add5724a34e65d223445519453896f4e6426 GIT binary patch literal 2844 zcmdVY`8yQa9|!O=`;3_}i>^IO)>K4Flp&OqC1joJS`ybzNwU|KCD#@rl`N6lwG3B6 z!cDH-m1(jjNfL>|EM~jY^Zot-_xt1bJkRSnpYuGQ&-?svj*FGEE(8Jq8UR2RQ4;xH z<3v`3LZOOi|GsvLD2OPEsEO<0JyA(GPeNmW&#kz0-%l!0GtDW)4_|66@O z{k!#_$$!`Tz3Y4b|1$Ic-7k#+;1nP%Df+XDlWX%dy8Nbr&FH4|0|Ji=c8&m+(E~8#o&? zEm(A`I{C-4=O~4-HE4(QC_d*-2(-|4mBqC99c?^&1csHqk7Br)L;W_RV83_2M2+}v z@~Tbdp{3Lu?k}BUuvZ0^i0Pk)#W%DXV3RA?NSYPLFelIFq8}IQ;mil@;Z05x$cORb z@TbbVIIfo22sX-vTZ=ZsADr6_Zz@j|mnmuEVK}i+ss(}Ha4`>dQsXtee}5TX{%AYg zr^1|cx_lK`#a|%&)}j#jk=+(#bKLSUtYD?37oWX(mRNo#l$b^V1f#rJqME@HL(k$Z@$92CD&IVt)AGG$C34uIR&5>dynjyMQm20xZ-L%H1e z%j_exJ8N+}R@eE)#TgjpVm|1gB_b3&Ee#T^Ug5Zc17S2UR1+m_ zvNdg}hFpvPAiP`+#Rcjc2Q?R2#w_~`g~|89x> zQB7>v^uTc$$%RzRpqt^|;p?u7tal~PqM?T*-RAWVv&cy?+0$dR?8cvufOJ9BnB2bZ zrT8UN&_l1CR{8@p>>Y>Myj^ti@-x%CJ)c9U6d%&rWum?MQARVW2Nqh~r$ETaPQQMm zfU3&3uC6`NZu6?-wuEn0dd1gK?K8~vcVyE^&SDCZhh&fyao={VQ=aV&H4l)%EYOf|Dz z)x4lTu#sbq)jDTaQgfv5W|&VP752JkbVc@{ylYA9qk~T_nRyLo$xrLXhoU0xdwLKS{-C-h&>0ijZ+8~!nfUKLNNZ!;Y3uBCzZD6; zr|%$kRr>2^WvYDy>?A`mYX{R`c%aMG@j|?)tz!1mjLx{FJ{1@>4ia9gk`vys?DrlC2>9eSdZqJVy`9jvM{=v-R$CT zXDiIy!>F+W61MjVOF5VG%NYD*++uoApA>iXBe`PJ)sdOZKQkEkzC zNI&g1Y4YbMIKiM=$)S~kM(my%=@CAsEXA#4#Fw5OHN~VhB$FivUl67*7BX@_QcHqo zIvHWaj%IZYdPZH0Q>($#%%oqZ9H`YJD@c3N@taqQHWXg?3@ZmvxH76xO6Z9k_?ec` z{xqMIoOr{g2TeMOffK6WCGNJZnO4Xb5L16G7%|w(u`eonS`nMF9B%gJTbXlbZ&rfo zlg6dt%d_Ebu4yCNwqOd@VJb(Tvq+1!%rII@klJH76;S9gqZ$%_-KgrfkB;-so)lB! mdi3mqzg|j>?#yXs!=DzHor&{q0F^tNxR6#N6!m4W2| literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s0p05.wiv1 b/wai/http-conformance/src/live/s0p05.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..683d7f80c8514703a1b977d5488c81054529aeda GIT binary patch literal 2780 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s0p06.wiv1 b/wai/http-conformance/src/live/s0p06.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..422e4259b8d6f948561106ea45662e0419338444 GIT binary patch literal 2812 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 XbvOCkTlU0MCUC60^{MPcU(mk*X~2Xy literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s0p07.wiv1 b/wai/http-conformance/src/live/s0p07.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..bec83220ee5fc6408e30f93c79758d2abf69ef3c GIT binary patch literal 2836 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s0p11.wiv1 b/wai/http-conformance/src/live/s0p11.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..19c3cf0a7397eb17637c2669a848def71848eba7 GIT binary patch literal 2832 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s1.wiv1 b/wai/http-conformance/src/live/s1.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..0d7b899ab0712283eaaffde762c4e8c221b85fda GIT binary patch literal 18936 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s1p00.wiv1 b/wai/http-conformance/src/live/s1p00.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..aae4790fd29595c3cbc386d03bfe12fa33e1b74f GIT binary patch literal 3008 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 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s1p02.wiv1 b/wai/http-conformance/src/live/s1p02.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..e2b7cacd495ff5d1fba30f5ee812cd37c99a949c GIT binary patch literal 2940 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 L<=+Ri9wq$?2&gPn literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s1p05.wiv1 b/wai/http-conformance/src/live/s1p05.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..7e4895bc9473945016ead5e1037899ca6f352b41 GIT binary patch literal 2964 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 B{}uoM literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s1p06.wiv1 b/wai/http-conformance/src/live/s1p06.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..7c7ecf273de409afa59ee2ae670aa8f30d716b14 GIT binary patch literal 3040 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 O=DwWz$LSN%lm7r_ln~zl literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s1p07.wiv1 b/wai/http-conformance/src/live/s1p07.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..b60291e1ef3bd39d0dffd13d2cf27b2ba41e0cb7 GIT binary patch literal 3040 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 JP=(-`e*hz>`e6V7 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/live/s1p11.wiv1 b/wai/http-conformance/src/live/s1p11.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..c2c5b91ea604e7c8edb2a521aa48c6842e5f204e GIT binary patch literal 3040 zcmc)GX*kqt8vyYCjM>e?j8L{TvKB&ybgWrYNGT~T93^DUQg%tkR(6swS&}6kOHLC< zGKmp6*=md>PDxj5u!#a0RYma0Kk450F2K9 zz@-fUxXl897`CW~0|3&SqS;&k*bsTm1Aqhp05C<~2mxS3WL$(PG9fZ4G9@xC@}AJYlO*G z^8yEdse%vfY6DEO;xHqFsyOmBRbWkh4jwo%2bk{e$agv42y6$PV|ltQN~m(w0i9kl zY9)OXh#K#LZ%?)bG6$Xto_{X~Y9i%9ZRO>lOJ2qhe*LI;L_i@pMY0-1Eg!`X&W~d; z?am;^?sT{#HywPNP{O*_(Sd!t*8`l@*oj%eKLaVMLHO^RC`j5%A{hT`IJ#Hb9b)w4 z41^)=3U4MkKs;mKWA7c;A{dTrL!(kjVm+Fzn5-rp$o&=#w#rxo&hhG52;rj~H?&g| zqBw2|iF|fN0+AUdHj)blDMvIyPfx2tbpog0FA6Q8hDUBfw08#+swIli^~Ud@NBJY%I~&Wa`Jz6oU0xH!$J#^!8%~C9vx5qz`kqO+A3;Ok`Ja}sW;(zIk^+(NS^A@eX(GC zH0B%bPYpf1jea4*JL(`mAk7fcKd{JKkC_z%NFS|*0`}iW>BTbM4N82SI3zP^dxd&v?x@rri#%rHxKE-pMw3v0?4i2R6 za1j5sU#IAwlKA&GULfINl9Is+)}Wo4o2aqiB$j+l6f7~V2kq#>*?0xYCg48IAaW^S z6u->}>s&3(nhfHz-{SgFQ-WGprphvS6?{p|+t`Hv=F~$$k@@9~LXtX2`ZJ0B4Xr4a z1?VA?eU;gr%{Nf(2Ud~u8_g_hx;?7ftQG6=^#-uwv}^_JQrQ~uiRnlV{5pBl&!UkXlJk&H-#&|7KLh7XW|5F%s{l8V?m?%WXP!@8p08HWGr3q)qT)HzcLVcGE03 zR`j;LoAP=Ih(ZvFt5K?qexdR^_vEZU^wtx5tPe<;+gja)|59v#(!1%gRpH&1{<_+O z+|QTS#JenM+z&0Kh%7m4Zr~wpv{I}OxA<@eLKmjY4UZlM{QF<>Oz79a>JqKu0Pq!3 zaK{^&@yB^AMAwSIP8i2NQvi6;`f>2HVzxZlJq@^o>S*4KfXVT&|Aoh61aSj`B6t#! zT%N9FH)iNrIk2?ihjyn4cpg0ykU-5E#HFh+K?);}kNTB@Jo`$MUm$rwU@>wY&UEbL z%M5>F*Uos0_bN^D3pm!u8wc5Z^|)|BhI|gnUHv=Wm=hRnLd4ZYIq@y z^m6$|%IS&p2h;~lUU*4(;nm|RoI|UO977e<2r-u^{;!c z=|*Pda0gVjgAAIpXYa2ozI+;`lLVN5ffDOhGEBVH}1IyV&*nHI%2Re)vVebcROT}TBc#- zw_FPw`g;Gi<2V1rwEEY1mFyB|=$b>M>-Tgdd7uisO`9muu;%er-qiTcV2Ziz_<_)M z%GTuW*z!{ul#9=Y--{g{y*t)SnS2)@d%iln^fvfJbF0<*V}(d_m_usNx2Gx84tGi8 z(qyN>jdw+h<)9bcO=%yiuCxcc$WFE7v0APxMh7vpcji(1X;klPPRliF)wPS~DibT> zOCoelYc92YpnoorQOhEIVJNWAnkh2xyuS&(Zi;h>@rK>9)KJ(r(bIXz%)ka?JoIbf zg|QRW*Ea^k*7G0LnO+N+c+~M3v&U^g^3v#(W$-{=d>q=qccMJYMyEA&xG=L$zu)Qn z{@`bsR<0q_3Yq*+@f_(81+qF2NhBT3JAK*0>dDxvg6-5+Vq_ihPx3t_9_hE85tw`QgrgdU(w%*Dm&y?k1T}t)PKSMZfF9}ZhQgLZRs_{ z-Onvj@6n`af4utD%2`{mn4@hslB|3{f;q=*$jKRd6|7?-(j(Vfs}8_6dy-p z-ytzE*gBRcVg|L vu?4%RIsB$ph*z0@5|w37F5q1A4@(x`$NPB2w39kXWOP&$D?^o%wp#xSrNb81 literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/players/master.m3u8 b/wai/http-conformance/src/players/master.m3u8 new file mode 100644 index 000000000..b39c07a4a --- /dev/null +++ b/wai/http-conformance/src/players/master.m3u8 @@ -0,0 +1,5 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-STREAM-INF:BANDWIDTH=10840,AVERAGE-BANDWIDTH=10315,CODECS="avc1.42c01e",RESOLUTION=64x36,FRAME-RATE=25.000 +classical/v0.m3u8 diff --git a/wai/http-conformance/src/players/wai0.wiv1 b/wai/http-conformance/src/players/wai0.wiv1 new file mode 100644 index 0000000000000000000000000000000000000000..48409a57e71198ce6ac5b4f834754de46b08086c GIT binary patch literal 21576 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 VFr2Y@5ClbDclrhx`9IE!tvfy6@&^C_ literal 0 HcmV?d00001 diff --git a/wai/http-conformance/src/playlists/o10-classical.m3u8 b/wai/http-conformance/src/playlists/o10-classical.m3u8 new file mode 100644 index 000000000..fe3501e09 --- /dev/null +++ b/wai/http-conformance/src/playlists/o10-classical.m3u8 @@ -0,0 +1,7 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:1 +#EXT-X-MEDIA-SEQUENCE:0 +#EXTINF:0.05, +seg0.m4s +#EXT-X-ENDLIST diff --git a/wai/http-conformance/src/playlists/o11-classical.m3u8 b/wai/http-conformance/src/playlists/o11-classical.m3u8 new file mode 100644 index 000000000..9319cf23b --- /dev/null +++ b/wai/http-conformance/src/playlists/o11-classical.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:1 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-MAP:URI="init.mp4" +#EXTINF:0.200, +seg0.m4s +#EXTINF:0.200, +seg1.m4s +#EXT-X-ENDLIST diff --git a/wai/http-conformance/src/playlists/o9-classical.m3u8 b/wai/http-conformance/src/playlists/o9-classical.m3u8 new file mode 100644 index 000000000..2f37de91e --- /dev/null +++ b/wai/http-conformance/src/playlists/o9-classical.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:1 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="init-180.mp4" +#EXTINF:0.64, +seg-180-0.m4s +#EXTINF:0.64, +seg-180-1.m4s +#EXTINF:0.64, +seg-180-2.m4s +#EXT-X-ENDLIST diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors.rs index fd6a85097..ce5f62bac 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors.rs @@ -35,6 +35,9 @@ use wai::http_binding::session::{check_response, drive, FetchSession, Fetched, R use wai::http_binding::{content_hash, hex, parse_fragment, ByteRange, RangeRefusal}; use wai::pin::HeldBytes; +#[path = "http_vectors_playlists.rs"] +mod playlists; + /// The committed vectors the corpus carries objects of, relative to the corpus. const STREAM_WAI: &str = "../wai-rs/tests/vectors/int_mlicpp/stream.wai"; const MLICPP_SET: &str = "../wai-rs/tests/vectors/int_mlicpp/model.wmb"; @@ -395,6 +398,11 @@ fn selector(c: &Corpus, input: &Value) -> Result { let order: Vec = order.iter().filter_map(|v| v.as_u64().map(|x| x as usize)).collect(); s.order = Some(Box::new(move |_, _| order.clone())); } + // A deployer policy's order (Appendix F): the policy over the state, on what the sink + // advertises. + if let Some(o) = playlists::abr_order(input, strs(&input["advertises"]))? { + s.order = Some(o); + } if let Some(d) = input["declared"].as_str() { s.declared = Some(codecs::parse_element(d).map_err(|e| Stop::Refused(json!({ "error": e.code() })))?); } @@ -757,7 +765,10 @@ fn outcome(c: &Corpus, op: &str, input: &Value) -> Result { Ok(t) => json!({ "n": t.n, "uri": t.uri }), Err(e) => err(&e), }, - other => return Err(format!("unknown op {other}").into()), + other => match playlists::outcome(c, other, input) { + Some(r) => r?, + None => return Err(format!("unknown op {other}").into()), + }, }) } @@ -1153,7 +1164,8 @@ fn generate(dir: &Path) -> Result>, String> { let mut files = BTreeMap::new(); let mut objs = BTreeMap::new(); let mut listing = serde_json::Map::new(); - for (id, bytes, what) in objects(dir)? { + let all = objects(dir)?.into_iter().map(|(a, b, c)| (a.to_string(), b, c.to_string())).chain(playlists::objects(dir)?); + for (id, bytes, what) in all { let cid = content_hash(&bytes); let h = head(&bytes)?; let union = codecs::union_of(&[SeqObject::from_envelope(&bytes).map_err(|e| e.to_string())?]).map(|e| codecs::render(&e)).ok(); @@ -1189,8 +1201,11 @@ fn generate(dir: &Path) -> Result>, String> { for bad in ["bad/O6.wai", "bad/OK.wai", "bad/OKN.wai"] { extra.insert(bad.to_string(), files[bad].clone()); } - let list = cases(&objs, &mut extra)?; - let corpus = Corpus { dir: dir.to_path_buf(), objects: objs, extra: extra.clone() }; + let mut list = cases(&objs, &mut extra)?; + let mut corpus = Corpus { dir: dir.to_path_buf(), objects: objs, extra: extra.clone() }; + // The playlists, their receipts and their cases (§8.6, §10.4, §11, Appendix F). + let playlist_files = playlists::generate(&mut corpus)?; + list.extend(playlists::cases(&corpus)?); let mut out = Vec::new(); for c in &list { let expect = eval(&corpus, c.op, &c.input).map_err(|e| format!("{}: {e}", c.name))?; @@ -1264,6 +1279,7 @@ fn generate(dir: &Path) -> Result>, String> { files.insert(k, b); } } + files.extend(playlist_files); files.insert("cases.json".into(), json_lines(&out)); Ok(files) } diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs new file mode 100644 index 000000000..adec4bd33 --- /dev/null +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -0,0 +1,823 @@ +//! The playlist and adaptation side of the HTTP delivery corpus (http-delivery §8.6, +//! §10.4, §11, Appendix F): the layered, low-latency and player-fixture objects, the WAI +//! playlists rendered beside their classical playlists, their receipts, and the cases +//! that hold all of it — playlists rendered from their inputs, read, held adjacent, +//! the classical master left unchanged, receipts checked and chained, a live edge's +//! parts verified against the cids later revisions list, and the reference adaptation +//! policies' choices, orders and layer depths. + +use std::collections::BTreeMap; +use std::path::Path; + +use serde_json::{json, Value}; + +use wai::container::{Conditioning, Manifest, ModelRequirement, Wai}; +use wai::http_binding::abr::{self, AbrState, BolaLike, BufferBased, Mpc, Policy, Sample, Throughput}; +use wai::http_binding::codecs::{self, SeqObject}; +use wai::http_binding::dash::{wai_mpd, DashTimeline, WaiRepresentation}; +use wai::http_binding::head::{parse_head, HeadParse}; +use wai::http_binding::hls::{ + bandwidths, check_adjacent, classical_unchanged, listed_cid, parse_media_playlist, session_data_line, wai_media_playlist, wai_multivariant, + MediaPlaylist, PlaylistError, VariantLine, WaiLayer, WaiOpen, WaiPart, WaiSegment, +}; +use wai::http_binding::{content_hash, hex, parse_fragment, unhex32}; + +use super::{case, err, patch, read, refused, strs, Case, Corpus, Stop}; + +/// The staged-delivery stage set of a clip split into two bases, the second with a +/// layer: the corpus's layered sequence (O11). +const STAGED_PAIR: &str = "../staged-conformance/cases/live_multi_base"; + +/// The seed of the corpus's playlist key: a published test key, attesting nothing. +pub(super) const PLAYLIST_SEED: [u8; 32] = [0x57; 32]; +/// The signer id the corpus's playlist receipts carry. +pub(super) const PLAYLIST_SIGNER: &str = "did:example:http-vectors"; + +fn int_motion(payload: Vec, w: u64, h: u64) -> Result, String> { + Wai::new( + Manifest::new("video", "replicate", ModelRequirement::new("wai.video.int_motion"), Conditioning::new("int_motion")) + .with_target(json!({ "w": w, "h": h })), + payload, + ).to_bytes().map_err(|e| e.to_string()) +} + +/// The id of live part `p` of segment `s`. +fn part_id(s: usize, p: usize) -> String { + format!("O12p{s}_{p:02}") +} + +/// The playlist side's objects, by id: `(id, bytes, what it covers)`. +pub(super) fn objects(dir: &Path) -> Result, String)>, String> { + let src = |name: &str| read(dir, &format!("src/{name}")); + let mut v: Vec<(String, Vec, String)> = vec![ + ("O11a".into(), read(dir, &format!("{STAGED_PAIR}/base_0.wai"))?, "a staged-delivery base: the first two frames of a clip (WAI1 int_motion), segment 1 of 2".into()), + ("O11b".into(), read(dir, &format!("{STAGED_PAIR}/base_1.wai"))?, "the next two frames, segment 2 of 2".into()), + ("O11bL1".into(), read(dir, &format!("{STAGED_PAIR}/L1_second.wai"))?, "layer 1 over O11b: listed after segment 2 only".into()), + ]; + for s in 0..2 { + v.push((format!("O12s{s}"), int_motion(src(&format!("live/s{s}.wiv1"))?, 32, 18)?, format!("live segment {} (WAI1 int_motion, 3.84 s): the object behind the live edge", 1200 + s))); + for p in 0..12 { + v.push((part_id(s, p), int_motion(src(&format!("live/s{s}p{p:02}.wiv1"))?, 32, 18)?, + format!("part {p} of live segment {} (0.32 s), served under its alias and decoded alone", 1200 + s))); + } + } + for (i, id) in ["O13a", "O13b", "O13c"].into_iter().enumerate() { + v.push((id.into(), int_motion(src(&format!("players/wai{i}.wiv1"))?, 64, 36)?, format!("player fixture: the WAI object for classical segment {i} (1 s)"))); + } + Ok(v) +} + +// ------------------------------------------------------------------- the inputs + +fn seg(object: &str) -> Value { + json!({ "object": object }) +} + +fn live_parts(s: usize, n: usize) -> Value { + Value::Array((0..n).map(|p| json!({ "object": part_id(s, p), "alias": format!("720/{}.{p}.wai", 1200 + s), "independent": true })).collect()) +} + +/// Each WAI media playlist the corpus renders: where it is written, the classical +/// playlist it sits beside, and the objects that cover it. +fn media_specs() -> Vec { + let live_seg = |s: usize, with_parts: bool| { + let mut v = seg(&format!("O12s{s}")); + if with_parts { + v["parts"] = live_parts(s, 12); + } + v + }; + vec![ + json!({ "file": "playlists/o9/wai-180.m3u8", "classical": "src/playlists/o9-classical.m3u8", "uri_prefix": "../../objects/", + "segments": [seg("O9a"), seg("O9b"), seg("O9c")] }), + json!({ "file": "playlists/o10/wai-a.m3u8", "classical": "src/playlists/o10-classical.m3u8", "uri_prefix": "../../objects/", + "segments": [{ "object": "O10", "layers": ["O10L1", "O10L2"] }] }), + json!({ "file": "playlists/o11/wai-v.m3u8", "classical": "src/playlists/o11-classical.m3u8", "uri_prefix": "../../objects/", + "segments": [seg("O11a"), { "object": "O11b", "layers": ["O11bL1"] }] }), + json!({ "file": "live/wai-r0.m3u8", "classical": "src/live/classical-r0.m3u8", "uri_prefix": "../objects/", + "segments": [], "open": { "parts": live_parts(0, 5), "preload_hint": "720/1200.5.wai" } }), + json!({ "file": "live/wai-r1.m3u8", "classical": "src/live/classical-r1.m3u8", "uri_prefix": "../objects/", + "segments": [live_seg(0, true)], "open": { "parts": live_parts(1, 3), "preload_hint": "720/1201.3.wai" } }), + json!({ "file": "live/wai-r2.m3u8", "classical": "src/live/classical-r2.m3u8", "uri_prefix": "../objects/", + "segments": [live_seg(0, true), live_seg(1, true)], "open": { "parts": [], "preload_hint": "720/1202.0.wai" } }), + json!({ "file": "live/wai-r3.m3u8", "classical": "src/live/classical-r3.m3u8", "uri_prefix": "../objects/", + "segments": [live_seg(0, false), live_seg(1, false)], "end_list": true }), + json!({ "file": "players/wai-v0.m3u8", "classical": "players/classical/v0.m3u8", "uri_prefix": "../objects/", + "segments": [seg("O13a"), seg("O13b"), seg("O13c")] }), + ] +} + +/// The WAI multivariant playlists: each variant's URI and the media playlist it names. +fn multivariant_specs() -> Vec { + vec![ + json!({ "file": "playlists/o9/wai.m3u8", "variants": [{ "uri": "wai-180.m3u8", "playlist": "playlists/o9/wai-180.m3u8", "resolution": [320, 180], "frame_rate_milli": 25000 }] }), + json!({ "file": "playlists/o10/wai.m3u8", "variants": [{ "uri": "wai-a.m3u8", "playlist": "playlists/o10/wai-a.m3u8" }] }), + json!({ "file": "playlists/o11/wai.m3u8", "variants": [{ "uri": "wai-v.m3u8", "playlist": "playlists/o11/wai-v.m3u8" }] }), + json!({ "file": "live/wai.m3u8", "variants": [{ "uri": "wai-720.m3u8", "playlist": "live/wai-r3.m3u8", "resolution": [32, 18], "frame_rate_milli": 25000 }] }), + json!({ "file": "players/wai.m3u8", "variants": [{ "uri": "wai-v0.m3u8", "playlist": "players/wai-v0.m3u8", "resolution": [64, 36], "frame_rate_milli": 25000 }] }), + ] +} + +fn mpd_specs() -> Vec { + vec![ + json!({ "file": "playlists/o9/wai.mpd", "playlist": "playlists/o9/wai-180.m3u8", "classical": "src/playlists/o9-classical.m3u8", + "period_id": "p0", "set_id": 1, "rep_id": "wai-180" }), + json!({ "file": "players/wai.mpd", "playlist": "players/wai-v0.m3u8", "classical": "players/classical/v0.m3u8", + "period_id": "p0", "set_id": 1, "rep_id": "wai-v0" }), + ] +} + +/// The receipts: each WAI media playlist, its content id, and (live) the revision whose +/// receipt is its parent. +fn receipt_specs() -> Vec<(&'static str, &'static str, Option)> { + vec![ + ("playlists/o9/wai-180.m3u8", "o9", None), + ("playlists/o10/wai-a.m3u8", "o10", None), + ("playlists/o11/wai-v.m3u8", "o11", None), + ("live/wai-r0.m3u8", "live/720", None), + ("live/wai-r1.m3u8", "live/720", Some(3)), + ("live/wai-r2.m3u8", "live/720", Some(4)), + ("live/wai-r3.m3u8", "live/720", Some(5)), + ("players/wai-v0.m3u8", "players/v0", None), + ] +} + +/// The published classical master of the player fixture: the original with the +/// session-data line before its first variant. +fn published_master(original: &str) -> Result { + let line = session_data_line("wai.m3u8").map_err(|e| e.to_string())?; + let at = original.find("#EXT-X-STREAM-INF").ok_or("the master has no variant")?; + Ok(format!("{}{line}\n{}", &original[..at], &original[at..])) +} + +// -------------------------------------------------------------- evaluation helpers + +fn text(c: &Corpus, path: &str) -> Result { + String::from_utf8(c.file(path)?).map_err(|_| Stop::Fault(format!("{path}: not UTF-8"))) +} + +fn pl_err(e: &PlaylistError) -> Stop { + Stop::Refused(json!({ "error": e.code() })) +} + +/// Apply `[[from, to], …]` to a text, each once, in order; a `from` the text does not +/// hold is a fault in the case. +fn edit(mut t: String, edits: &Value) -> Result { + for e in edits.as_array().into_iter().flatten() { + let (from, to) = (e[0].as_str().ok_or("an edit's from")?, e[1].as_str().ok_or("an edit's to")?); + if !t.contains(from) { + return Err(Stop::Fault(format!("the edit's {from:?} is not in the text"))); + } + t = t.replacen(from, to, 1); + } + Ok(t) +} + +/// The corpus object whose content hash is `cid`. +fn by_cid<'a>(c: &'a Corpus, cid: &[u8; 32]) -> Option<&'a Vec> { + c.objects.values().find(|b| content_hash(b) == *cid) +} + +fn object_bytes(c: &Corpus, id: &str) -> Result, Stop> { + c.objects.get(id).cloned().ok_or_else(|| Stop::Fault(format!("no object {id}"))) +} + +fn wai_parts(c: &Corpus, v: &Value, classical_ms: &[u64]) -> Result, Stop> { + v.as_array().into_iter().flatten().enumerate().map(|(j, p)| { + let env = object_bytes(c, p["object"].as_str().ok_or("a part's object")?)?; + let ms = classical_ms.get(j).copied().or(p["duration_ms"].as_u64()).ok_or("a part's duration")?; + let mut part = WaiPart::of(&env, p["alias"].as_str().ok_or("a part's alias")?, ms, p["independent"].as_bool() == Some(true)).map_err(|e| err_stop(&e))?; + part.layers = wai_layers(c, &p["layers"])?; + Ok(part) + }).collect() +} + +fn err_stop(e: &wai::http_binding::RangeRefusal) -> Stop { + Stop::Refused(err(e)) +} + +fn wai_layers(c: &Corpus, v: &Value) -> Result, Stop> { + strs(v).iter().map(|id| WaiLayer::of(&object_bytes(c, id)?).map_err(|e| err_stop(&e))).collect() +} + +/// A media playlist spec rendered: its text, or the refusal. +fn render_media(c: &Corpus, input: &Value) -> Result { + let classical = parse_media_playlist(&text(c, input["classical"].as_str().ok_or("classical")?)?).map_err(|e| pl_err(&e))?; + let mut segs = Vec::new(); + for (i, s) in input["segments"].as_array().ok_or("segments")?.iter().enumerate() { + let ms = classical.segments.get(i).map_or(0, |x| x.duration_ms); + let env = object_bytes(c, s["object"].as_str().ok_or("a segment's object")?)?; + let mut w = WaiSegment::of(&env, s["duration_ms"].as_u64().unwrap_or(ms)).map_err(|e| err_stop(&e))?; + w.layers = wai_layers(c, &s["layers"])?; + let cms: Vec = classical.segments.get(i).map(|x| x.extra.parts.iter().map(|p| p.duration_ms).collect()).unwrap_or_default(); + w.parts = wai_parts(c, &s["parts"], &cms)?; + segs.push(w); + } + let open = match &input["open"] { + Value::Null => None, + o => { + let cms: Vec = classical.open.as_ref().map(|x| x.parts.iter().map(|p| p.duration_ms).collect()).unwrap_or_default(); + Some(WaiOpen { parts: wai_parts(c, &o["parts"], &cms)?, preload_hint: o["preload_hint"].as_str().map(String::from) }) + } + }; + let end = input["end_list"].as_bool().unwrap_or(classical.end_list); + let (_, _, _, t) = wai_media_playlist(&classical, &segs, open.as_ref(), input["uri_prefix"].as_str().unwrap_or(""), end).map_err(|e| pl_err(&e))?; + Ok(t) +} + +/// The objects a WAI media playlist lists, resolved among the corpus's by cid: its +/// segments' (with their durations), and every object it lists (segments, parts, +/// layers), in the order listed. +fn listed_objects(c: &Corpus, p: &MediaPlaylist) -> Result<(Vec<(u64, u64)>, Vec<(u64, u64)>, Vec>), Stop> { + let resolve = |uri: &str| -> Result, Stop> { + let (base, frag) = parse_fragment(uri); + let cid = frag.cid.or_else(|| base.rsplit('/').next().and_then(|n| n.strip_suffix(".wai")).and_then(unhex32)) + .ok_or_else(|| Stop::Fault(format!("{uri}: no cid")))?; + by_cid(c, &cid).cloned().ok_or_else(|| Stop::Fault(format!("{uri}: no corpus object"))) + }; + let (mut segs, mut parts, mut all) = (Vec::new(), Vec::new(), Vec::new()); + let extra = |e: &wai::hls_render::SegmentExtra, parts: &mut Vec<(u64, u64)>, all: &mut Vec>| -> Result<(), Stop> { + for q in &e.parts { + let b = resolve(&q.uri)?; + parts.push((b.len() as u64, q.duration_ms)); + all.push(b); + for l in &q.layers { + all.push(resolve(&l.uri)?); + } + } + for l in &e.layers { + all.push(resolve(&l.uri)?); + } + Ok(()) + }; + for s in &p.segments { + let b = resolve(&s.uri)?; + segs.push((b.len() as u64, s.duration_ms)); + all.push(b); + extra(&s.extra, &mut parts, &mut all)?; + } + if let Some(o) = &p.open { + extra(o, &mut parts, &mut all)?; + } + Ok((segs, parts, all)) +} + +fn union_floor(all: &[Vec]) -> Result<(codecs::Element, codecs::Element), Stop> { + let objs: Vec = all.iter().map(|e| SeqObject::from_envelope(e).map_err(|e| err_stop(&e))).collect::>()?; + let u = codecs::union_of(&objs).map_err(|e| Stop::Refused(json!({ "error": e.code() })))?; + let f = codecs::floor_of(&objs).map_err(|e| Stop::Refused(json!({ "error": e.code() })))?; + Ok((u, f)) +} + +fn render_multivariant(c: &Corpus, input: &Value) -> Result { + let mut lines = Vec::new(); + for v in input["variants"].as_array().ok_or("variants")? { + let p = parse_media_playlist(&text(c, v["playlist"].as_str().ok_or("playlist")?)?).map_err(|e| pl_err(&e))?; + let (segs, parts, all) = listed_objects(c, &p)?; + let (union, floor) = union_floor(&all)?; + let (peak, avg) = bandwidths(if segs.is_empty() { &parts } else { &segs }).ok_or("no duration to compute a bandwidth from")?; + let resolution = v["resolution"].as_array().map(|r| (r[0].as_u64().unwrap_or(0) as u32, r[1].as_u64().unwrap_or(0) as u32)); + lines.push(VariantLine { uri: v["uri"].as_str().ok_or("uri")?.into(), union, floor, peak_bps: peak, avg_bps: avg, resolution, + frame_rate_milli: v["frame_rate_milli"].as_u64().map(|f| f as u32) }); + } + wai_multivariant(&lines).map_err(|e| pl_err(&e)) +} + +fn render_mpd(c: &Corpus, input: &Value) -> Result { + let classical = parse_media_playlist(&text(c, input["classical"].as_str().ok_or("classical")?)?).map_err(|e| pl_err(&e))?; + let p = parse_media_playlist(&text(c, input["playlist"].as_str().ok_or("playlist")?)?).map_err(|e| pl_err(&e))?; + let (segs, _, all) = listed_objects(c, &p)?; + let (union, floor) = union_floor(&all)?; + let (peak, _) = bandwidths(&segs).ok_or("no segment")?; + let t = DashTimeline::from_hls(&classical, input["period_id"].as_str().unwrap_or("p0"), 0); + let rep = WaiRepresentation { id: input["rep_id"].as_str().ok_or("rep_id")?.into(), bandwidth: peak, media: p.segments.iter().map(|s| s.uri.clone()).collect() }; + wai_mpd(&t, input["set_id"].as_u64().unwrap_or(1) as u32, &union, &floor, &[rep]).map_err(|e| pl_err(&e)) +} + +/// A rendered file's outcome: its BLAKE3 and length, and whether the committed file +/// (when the case names one) holds exactly these bytes. +fn rendered_outcome(c: &Corpus, input: &Value, t: &str) -> Value { + let mut o = json!({ "blake3": blake3::hash(t.as_bytes()).to_hex().to_string(), "bytes": t.len() }); + if let Some(f) = input["file"].as_str() { + o["same_as_file"] = json!(c.file(f).is_ok_and(|b| b == t.as_bytes())); + } + o +} + +fn parsed(p: &MediaPlaylist, t: &str) -> Value { + let layers = |ls: &[wai::hls_render::LayerLine]| ls.iter().map(|l| l.n).collect::>(); + let parts = |ps: &[wai::hls_render::PartLine]| ps.iter().map(|q| json!([q.duration_ms, q.independent, layers(&q.layers)])).collect::>(); + json!({ + "version": p.version, "target_duration_s": p.target_duration_s, "media_sequence": p.media_sequence, + "discontinuity_sequence": p.discontinuity_sequence, "part_target_ms": p.part_target_ms, "part_hold_back_ms": p.part_hold_back_ms, + "can_block_reload": p.can_block_reload, "end_list": p.end_list, + "segments": p.segments.iter().map(|s| json!([s.duration_ms, s.extra.discontinuity, s.extra.program_date_time, parts(&s.extra.parts), layers(&s.extra.layers), s.uri])).collect::>(), + "open": p.open.as_ref().map(|o| json!([o.discontinuity, o.program_date_time, parts(&o.parts)])), + "preload_hint": p.preload_hint, + "rendered_form": p.is_rendered_form(t), + }) +} + +// ---------------------------------------------------------------- adaptation + +/// A policy from its JSON form: `{"id": "throughput", "last_n": …, "safety_permille": …}` +/// and the like. +pub(super) fn policy(v: &Value) -> Result, String> { + let u = |k: &str| v[k].as_u64().ok_or(format!("policy.{k}")); + Ok(match v["id"].as_str() { + Some("throughput") => Box::new(Throughput { last_n: u("last_n")? as u8, safety_permille: u("safety_permille")? as u16 }), + Some("buffer_based") => Box::new(BufferBased { reservoir_ms: u("reservoir_ms")? as u32, cushion_ms: u("cushion_ms")? as u32 }), + Some("bola_like") => Box::new(BolaLike { v_milli: u("v_milli")? as u32, max_buffer_ms: u("max_buffer_ms")? as u32 }), + Some("mpc") => Box::new(Mpc { horizon: u("horizon")? as u8, safety_permille: u("safety_permille")? as u16 }), + _ => return Err("an unknown policy".into()), + }) +} + +/// A state from its JSON form. +pub(super) fn state(v: &Value) -> Result { + let nums = |k: &str| v[k].as_array().into_iter().flatten().map(|x| x.as_u64().ok_or(format!("state.{k}"))).collect::, String>>(); + Ok(AbrState { + buffer_ms: v["buffer_ms"].as_u64().ok_or("state.buffer_ms")? as u32, + samples: v["samples"].as_array().into_iter().flatten() + .map(|s| Some(Sample { at_ms: s[0].as_u64()?, bytes: s[1].as_u64()?, ms: s[2].as_u64()? })) + .collect::>>().ok_or("state.samples")?, + last: v["last"].as_u64().map(|l| l as usize), + option_bytes: nums("option_bytes")?, + option_joules_micro: v["option_joules_micro"].as_array().into_iter().flatten().map(Value::as_u64).collect(), + joule_budget_micro: v["joule_budget_micro"].as_u64(), + segment_ms: v["segment_ms"].as_u64().ok_or("state.segment_ms")? as u32, + }) +} + +// ------------------------------------------------------------------ the outcomes + +/// The outcome of a playlist or adaptation op; `None` for an op this side does not hold. +pub(super) fn outcome(c: &Corpus, op: &str, input: &Value) -> Option> { + Some(match op { + "media_playlist" => render_media(c, input).map(|t| rendered_outcome(c, input, &t)), + "multivariant" => render_multivariant(c, input).map(|t| rendered_outcome(c, input, &t)), + "mpd" => render_mpd(c, input).map(|t| rendered_outcome(c, input, &t)), + "playlist_parse" => (|| { + let t = match input["text"].as_str() { Some(t) => t.to_string(), None => text(c, input["file"].as_str().ok_or("file")?)? }; + let p = parse_media_playlist(&t).map_err(|e| pl_err(&e))?; + Ok(parsed(&p, &t)) + })(), + "adjacency" => (|| { + let w = edit(text(c, input["wai"].as_str().ok_or("wai")?)?, &input["edit_wai"])?; + let k = edit(text(c, input["classical"].as_str().ok_or("classical")?)?, &input["edit_classical"])?; + check_adjacent(&w, &k).map_err(|e| pl_err(&e))?; + Ok(json!({ "adjacent": true })) + })(), + "unchanged" => (|| { + let o = text(c, input["original"].as_str().ok_or("original")?)?; + let p = edit(text(c, input["published"].as_str().ok_or("published")?)?, &input["edit_published"])?; + classical_unchanged(&o, &p).map_err(|e| pl_err(&e))?; + Ok(json!({ "unchanged": true })) + })(), + "playlist_receipt" => receipt_outcome(c, input), + "live" => live_outcome(c, input), + "abr" => (|| { + let p = policy(&input["policy"])?; + let (k, b) = abr::decide(p.as_ref(), &state(&input["state"])?); + Ok(json!({ "choice": k, "budget": b.label() })) + })(), + "abr_order" => (|| { + let env = c.object(input)?; + let h = match parse_head(&env, env.len() as u64) { + Ok(HeadParse::Head(h)) => h, + _ => return Err(Stop::Fault("abr_order needs a whole object".into())), + }; + let adv = strs(&input["advertises"]); + let order = abr::order_entries(&h, &|cap: &str| adv.iter().any(|a| a == cap), policy(&input["policy"])?.as_ref(), &state(&input["state"])?); + Ok(json!({ "order": order })) + })(), + "depth" => (|| { + let layers: Vec = match input["objects"].as_array() { + Some(ids) => ids.iter().map(|id| Ok(object_bytes(c, id.as_str().ok_or("an object id")?)?.len() as u64)).collect::>()?, + None => input["layers_n"].as_array().ok_or("layers_n")?.iter().map(|x| x.as_u64().ok_or("layers_n")).collect::>()?, + }; + let (d, b) = abr::depth(&layers, policy(&input["policy"])?.as_ref(), &state(&input["state"])?); + Ok(json!({ "depth": d, "budget": b.label() })) + })(), + _ => return None, + }) +} + +#[cfg(feature = "video_manifest")] +fn receipt_outcome(c: &Corpus, input: &Value) -> Result { + use wai::http_binding::playlist_receipt::check_playlist; + let pl = edit(text(c, input["playlist"].as_str().ok_or("playlist")?)?, &input["edit_playlist"])?; + let rpath = input["receipt"].as_str().ok_or("receipt")?; + let rc = edit(text(c, rpath)?, &input["edit_receipt"])?; + let name = match &input["name"] { + Value::String(n) => Some(n.clone()), + Value::Null => rpath.rsplit('/').next().map(String::from), + _ => None, + }; + let r = check_playlist(pl.as_bytes(), rc.as_bytes(), name.as_deref()).map_err(|e| Stop::Refused(json!({ "error": e.code() })))?; + Ok(json!({ "signed": hex(&r.receipt_hash()), "parent": r.parent_receipt_hash.map(|h| hex(&h)) })) +} + +#[cfg(not(feature = "video_manifest"))] +fn receipt_outcome(_: &Corpus, _: &Value) -> Result { + Err(Stop::Fault("built without video_manifest: playlist receipts are not checked".into())) +} + +/// §11.4, §11.6 over a live playlist's revisions: each revision's adjacency to its +/// classical revision, its receipt and its chain; and each part fetched by alias, held +/// against the cid the first revision that lists it lists, at `signed` when that +/// revision's receipt verified and chains to the first, else at `listed`. +#[cfg(feature = "video_manifest")] +fn live_outcome(c: &Corpus, input: &Value) -> Result { + use wai::http_binding::playlist_receipt::{check_chain, check_playlist}; + let mut revs = Vec::new(); + let mut listed: Vec = Vec::new(); + let mut held: Vec> = Vec::new(); + for r in input["revisions"].as_array().ok_or("revisions")? { + let w = edit(text(c, r["wai"].as_str().ok_or("wai")?)?, &r["edit_wai"])?; + let k = text(c, r["classical"].as_str().ok_or("classical")?)?; + let adjacent = match check_adjacent(&w, &k) { Ok(()) => json!(true), Err(e) => json!(e.code()) }; + let rpath = r["receipt"].as_str().ok_or("receipt")?; + let name = rpath.rsplit('/').next(); + let (receipt, rr) = match check_playlist(w.as_bytes(), &c.file(rpath)?, if rpath.starts_with("cases/") { None } else { name }) { + Ok(x) => (json!("signed"), Some(x)), + Err(e) => (json!(e.code()), None), + }; + let chained = match (held.last(), &rr) { + (_, None) => Value::Null, + (None, Some(_)) => json!(true), + (Some(None), Some(_)) => Value::Null, + (Some(Some(prev)), Some(cur)) => match check_chain(&[prev.clone(), cur.clone()]) { Ok(()) => json!(true), Err((_, e)) => json!(e.code()) }, + }; + revs.push(json!({ "adjacent": adjacent, "receipt": receipt, "chained": chained })); + listed.push(parse_media_playlist(&w).map_err(|e| pl_err(&e))?); + held.push(rr); + } + // A revision is signed when its receipt verified and every link back to the first holds. + let mut signed = Vec::new(); + for (k, r) in revs.iter().enumerate() { + let prior = k == 0 || signed.get(k - 1).copied().unwrap_or(false); + signed.push(r["receipt"] == "signed" && (k == 0 || r["chained"] == true) && prior); + } + let mut fetches = Vec::new(); + for f in input["fetches"].as_array().into_iter().flatten() { + let alias = f["alias"].as_str().ok_or("alias")?; + let mut served = object_bytes(c, f["object"].as_str().ok_or("object")?)?; + patch(&mut served, &f["patch_object"])?; + let first = listed.iter().enumerate().find_map(|(k, p)| listed_cid(p, alias).map(|cid| (k, cid))); + fetches.push(match first { + None => json!({ "alias": alias, "listed_in": null, "outcome": "not_listed", "held": null }), + Some((k, cid)) if content_hash(&served) == cid => json!({ "alias": alias, "listed_in": k, "outcome": "accepted", "held": if signed[k] { "signed" } else { "listed" } }), + Some((k, _)) => json!({ "alias": alias, "listed_in": k, "outcome": "object_digest_mismatch", "held": null }), + }); + } + Ok(json!({ "revisions": revs, "fetches": fetches })) +} + +#[cfg(not(feature = "video_manifest"))] +fn live_outcome(_: &Corpus, _: &Value) -> Result { + Err(Stop::Fault("built without video_manifest: playlist receipts are not checked".into())) +} + +// ------------------------------------------------------------------ generation + +/// Every playlist-side file, by path: the WAI media playlists, multivariant playlists +/// and MPDs, the published classical master, and the receipts (each live revision's +/// chained to the one before), with the extra receipt a case needs. `files` holds the +/// files generated so far, and receives these. +pub(super) fn generate(c: &mut Corpus) -> Result>, String> { + let mut out = BTreeMap::new(); + let put = |c: &mut Corpus, out: &mut BTreeMap>, path: &str, b: Vec| { + c.extra.insert(path.to_string(), b.clone()); + out.insert(path.to_string(), b); + }; + let fault = |what: &str, s: Stop| match s { + Stop::Refused(v) => format!("{what}: refused {v}"), + Stop::Fault(e) => format!("{what}: {e}"), + }; + for spec in media_specs() { + let f = spec["file"].as_str().expect("file").to_string(); + let t = render_media(c, &spec).map_err(|s| fault(&f, s))?; + put(c, &mut out, &f, t.into_bytes()); + } + for spec in multivariant_specs() { + let f = spec["file"].as_str().expect("file").to_string(); + let t = render_multivariant(c, &spec).map_err(|s| fault(&f, s))?; + put(c, &mut out, &f, t.into_bytes()); + } + for spec in mpd_specs() { + let f = spec["file"].as_str().expect("file").to_string(); + let t = render_mpd(c, &spec).map_err(|s| fault(&f, s))?; + put(c, &mut out, &f, t.into_bytes()); + } + let original = String::from_utf8(c.file("src/players/master.m3u8")?).map_err(|_| "the master is not UTF-8")?; + put(c, &mut out, "players/master.m3u8", published_master(&original)?.into_bytes()); + seal_all(c, &mut out)?; + Ok(out) +} + +#[cfg(feature = "video_manifest")] +fn seal_all(c: &mut Corpus, out: &mut BTreeMap>) -> Result<(), String> { + use wai::http_binding::playlist_receipt::seal_playlist; + use wai::quantum_ops::GrantRef; + let key = ed25519_dalek::SigningKey::from_bytes(&PLAYLIST_SEED); + let mut sealed: Vec = Vec::new(); + for (path, content, parent) in receipt_specs() { + let t = String::from_utf8(c.file(path)?).map_err(|_| "not UTF-8")?; + let r = seal_playlist(&t, &key, PLAYLIST_SIGNER, content, GrantRef::unbounded("video.manifest"), parent.map(|k| &sealed[k])) + .map_err(|e| format!("{path}: {e}"))?; + let dir = path.rsplit_once('/').map_or("", |(d, _)| d); + let f = format!("{dir}/mr/{}", r.receipt_file_name()); + c.extra.insert(f.clone(), r.to_json().into_bytes()); + out.insert(f, r.to_json().into_bytes()); + sealed.push(r); + } + // Revision 2 sealed with no parent: a receipt that verifies and does not chain. + let t = String::from_utf8(c.file("live/wai-r2.m3u8")?).map_err(|_| "not UTF-8")?; + let orphan = seal_playlist(&t, &key, PLAYLIST_SIGNER, "live/720", GrantRef::unbounded("video.manifest"), None).map_err(|e| e.to_string())?; + c.extra.insert("cases/live-r2-orphan.json".into(), orphan.to_json().into_bytes()); + out.insert("cases/live-r2-orphan.json".into(), orphan.to_json().into_bytes()); + Ok(()) +} + +#[cfg(not(feature = "video_manifest"))] +fn seal_all(_: &mut Corpus, _: &mut BTreeMap>) -> Result<(), String> { + Err("built without video_manifest: the corpus's playlist receipts need it (build with --features http,video_manifest)".into()) +} + +/// The receipt file of a playlist, as generated. +fn receipt_of(c: &Corpus, playlist: &str) -> Result { + let b = c.file(playlist)?; + let dir = playlist.rsplit_once('/').map_or("", |(d, _)| d); + Ok(format!("{dir}/mr/{}.json", blake3::hash(&b).to_hex())) +} + +// ------------------------------------------------------------------ the cases + +/// splitmix64, as the differential uses. +fn next(s: &mut u64) -> u64 { + super::next(s) +} + +fn abr_state(s: &mut u64, n_opts: usize, with_budget: bool) -> Value { + let mut sizes: Vec = (0..n_opts).map(|_| 2_000 + next(s) % 400_000).collect(); + sizes.sort_unstable(); + let samples: Vec = (0..next(s) % 6).map(|i| json!([1000 * (i + 1), next(s) % 2_000_000, next(s) % 3000])).collect(); + let figures: Vec = (0..n_opts).map(|_| if next(s) % 3 == 0 { Value::Null } else { json!(1 + next(s) % 5000) }).collect(); + let segment_ms = [200u64, 1000, 2000, 3840, 4000][(next(s) % 5) as usize]; + json!({ + "buffer_ms": next(s) % 40_000, + "samples": samples, + "last": if next(s) % 2 == 0 { Value::Null } else { json!(next(s) % n_opts as u64) }, + "option_bytes": sizes, + "option_joules_micro": figures, + "joule_budget_micro": if with_budget && next(s) % 2 == 0 { json!(next(s) % 6000) } else { Value::Null }, + "segment_ms": segment_ms, + }) +} + +fn policies() -> Vec { + vec![ + json!({ "id": "throughput", "last_n": 3, "safety_permille": 900 }), + json!({ "id": "buffer_based", "reservoir_ms": 5000, "cushion_ms": 10_000 }), + json!({ "id": "bola_like", "v_milli": 930, "max_buffer_ms": 20_000 }), + json!({ "id": "mpc", "horizon": 3, "safety_permille": 850 }), + ] +} + +pub(super) fn cases(c: &Corpus) -> Result, String> { + let mut v = Vec::new(); + // Every playlist renders from its inputs to its committed bytes. + for spec in media_specs() { + let f = spec["file"].as_str().expect("file"); + v.push(case(format!("media_playlist_{}", f.replace(['/', '.', '-'], "_")), "media_playlist", spec.clone())); + } + for spec in multivariant_specs() { + let f = spec["file"].as_str().expect("file"); + v.push(case(format!("multivariant_{}", f.replace(['/', '.', '-'], "_")), "multivariant", spec.clone())); + } + for spec in mpd_specs() { + let f = spec["file"].as_str().expect("file"); + v.push(case(format!("mpd_{}", f.replace(['/', '.', '-'], "_")), "mpd", spec.clone())); + } + // Objects that do not cover a timeline. + let o9 = |segs: Value| json!({ "classical": "src/playlists/o9-classical.m3u8", "uri_prefix": "", "segments": segs }); + v.push(refused("media_playlist_short_O9", "media_playlist", o9(json!([seg("O9a"), seg("O9b")])), "adjacency_misaligned")); + v.push(refused("media_playlist_parts_beside_no_target", "media_playlist", + o9(json!([{ "object": "O9a", "parts": [{ "object": part_id(0, 0), "alias": "p0.wai", "independent": true, "duration_ms": 320 }, + { "object": part_id(0, 1), "alias": "p1.wai", "independent": true, "duration_ms": 320 }] }, + seg("O9b"), seg("O9c")])), "adjacency_misaligned")); + v.push(refused("media_playlist_layer_not_a_layer", "media_playlist", + o9(json!([{ "object": "O9a", "layers": ["O9b"] }, seg("O9b"), seg("O9c")])), "not_a_layer")); + v.push(refused("media_playlist_open_without_classical", "media_playlist", json!({ + "classical": "src/playlists/o9-classical.m3u8", "uri_prefix": "", "segments": [seg("O9a"), seg("O9b"), seg("O9c")], + "open": { "parts": [{ "object": part_id(1, 0), "alias": "p.wai", "independent": true, "duration_ms": 320 }] } }), "adjacency_misaligned")); + + // Reading: every WAI and classical playlist, and malformed ones. + for f in ["playlists/o9/wai-180.m3u8", "playlists/o10/wai-a.m3u8", "playlists/o11/wai-v.m3u8", "live/wai-r0.m3u8", "live/wai-r1.m3u8", + "live/wai-r2.m3u8", "live/wai-r3.m3u8", "players/wai-v0.m3u8", "src/live/classical-r1.m3u8", "players/classical/v0.m3u8", + "src/playlists/o9-classical.m3u8"] { + v.push(case(format!("playlist_parse_{}", f.replace(['/', '.', '-'], "_")), "playlist_parse", json!({ "file": f }))); + } + for (name, t) in [ + ("playlist_parse_no_header", "#EXT-X-VERSION:3\n#EXTINF:1,\na\n"), + ("playlist_parse_extinf_no_uri", "#EXTM3U\n#EXTINF:1,\n"), + ("playlist_parse_uri_no_extinf", "#EXTM3U\na.wai\n"), + ("playlist_parse_skip", "#EXTM3U\n#EXT-X-SKIP:SKIPPED-SEGMENTS=3\n"), + ("playlist_parse_variant", "#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1\na.m3u8\n"), + ("playlist_parse_sequence_late", "#EXTM3U\n#EXTINF:1,\na\n#EXT-X-MEDIA-SEQUENCE:3\n"), + ("playlist_parse_layer_after_uri", "#EXTM3U\n#EXTINF:1,\na\n#WAI-LAYER:N=1,URI=\"l.wai\"\n"), + ("playlist_parse_layer_order", "#EXTM3U\n#EXTINF:1,\n#WAI-LAYER:N=2,URI=\"l.wai\"\n#WAI-LAYER:N=1,URI=\"m.wai\"\na\n"), + ("playlist_parse_layer_comment_between", "#EXTM3U\n#EXTINF:1,\n# a note\n#WAI-LAYER:N=1,URI=\"l.wai\"\na\n"), + ("playlist_parse_part_no_uri", "#EXTM3U\n#EXT-X-PART:DURATION=0.5\n"), + ("playlist_parse_two_hints", "#EXTM3U\n#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"a\"\n#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"b\"\n"), + ("playlist_parse_negative", "#EXTM3U\n#EXTINF:-1,\na\n"), + ("playlist_parse_exponent", "#EXTM3U\n#EXTINF:1e3,\na\n"), + ("playlist_parse_block_maybe", "#EXTM3U\n#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=MAYBE\n"), + ] { + v.push(refused(name, "playlist_parse", json!({ "text": t }), "playlist_malformed")); + } + v.push(refused("playlist_parse_layer_zero", "playlist_parse", json!({ "text": "#EXTM3U\n#EXTINF:1,\n#WAI-LAYER:N=0,URI=\"l.wai\"\na\n" }), "layer_tag_malformed")); + for (name, t) in [ + ("playlist_parse_rounding", "#EXTM3U\n#EXTINF:3.9995,\na\n#EXTINF:3.99949,\nb\n#EXTINF:4.00008,\nc\n#EXTINF:4,\nd\n"), + ("playlist_parse_crlf_title", "#EXTM3U\r\n#EXTINF:0.64,a title\r\nseg.m4s\r\n#EXT-X-DISCONTINUITY\r\n#EXTINF:1,\r\nseg2.m4s\r\n"), + ("playlist_parse_hint_only", "#EXTM3U\n#EXT-X-PART-INF:PART-TARGET=1\n#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"p.0\"\n#EXT-X-PRELOAD-HINT:TYPE=MAP,URI=\"i\"\n"), + ] { + v.push(case(name, "playlist_parse", json!({ "text": t }))); + } + + // Adjacency (§11.2): each WAI playlist beside its classical one, and every way it + // can be broken. + for (w, k) in [("playlists/o9/wai-180.m3u8", "src/playlists/o9-classical.m3u8"), ("playlists/o10/wai-a.m3u8", "src/playlists/o10-classical.m3u8"), + ("playlists/o11/wai-v.m3u8", "src/playlists/o11-classical.m3u8"), ("live/wai-r0.m3u8", "src/live/classical-r0.m3u8"), + ("live/wai-r1.m3u8", "src/live/classical-r1.m3u8"), ("live/wai-r2.m3u8", "src/live/classical-r2.m3u8"), + ("live/wai-r3.m3u8", "src/live/classical-r3.m3u8"), ("players/wai-v0.m3u8", "players/classical/v0.m3u8")] { + v.push(case(format!("adjacency_{}", w.replace(['/', '.', '-'], "_")), "adjacency", json!({ "wai": w, "classical": k }))); + } + let o9a = |name: &str, edits: Value| refused(name, "adjacency", json!({ "wai": "playlists/o9/wai-180.m3u8", "classical": "src/playlists/o9-classical.m3u8", "edit_wai": edits }), "adjacency_misaligned"); + v.push(o9a("adjacency_extinf_shifted", json!([["#EXTINF:0.640,", "#EXTINF:0.641,"]]))); + v.push(o9a("adjacency_discontinuity_one_side", json!([["#EXTINF:0.640,", "#EXT-X-DISCONTINUITY\n#EXTINF:0.640,"]]))); + v.push(o9a("adjacency_media_sequence", json!([["#EXT-X-MEDIA-SEQUENCE:0", "#EXT-X-MEDIA-SEQUENCE:1"]]))); + v.push(o9a("adjacency_discontinuity_sequence", json!([["#EXT-X-MEDIA-SEQUENCE:0\n", "#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-DISCONTINUITY-SEQUENCE:1\n"]]))); + v.push(o9a("adjacency_segment_missing", json!([["#EXTINF:0.640,\n../../objects/", "#EXT-X-BYTERANGE:1\n#EXT-X-GAP\n#EXTINF:0.640,\n../../objects/"], ["#EXT-X-ENDLIST", "#EXTINF:0.640,\nextra.wai\n#EXT-X-ENDLIST"]]))); + v.push(refused("adjacency_classical_discontinuity", "adjacency", json!({ "wai": "playlists/o9/wai-180.m3u8", "classical": "src/playlists/o9-classical.m3u8", + "edit_classical": [["#EXTINF:0.64,\nseg-180-2", "#EXT-X-DISCONTINUITY\n#EXTINF:0.64,\nseg-180-2"]] }), "adjacency_misaligned")); + let r1 = |name: &str, edits: Value| refused(name, "adjacency", json!({ "wai": "live/wai-r1.m3u8", "classical": "src/live/classical-r1.m3u8", "edit_wai": edits }), "adjacency_misaligned"); + v.push(r1("adjacency_part_duration", json!([["#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI=\"720/1200.4.wai", "#EXT-X-PART:DURATION=0.319,INDEPENDENT=YES,URI=\"720/1200.4.wai"]]))); + v.push(r1("adjacency_open_part_duration", json!([["#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI=\"720/1201.2.wai", "#EXT-X-PART:DURATION=0.330,INDEPENDENT=YES,URI=\"720/1201.2.wai"]]))); + v.push(r1("adjacency_part_target", json!([["PART-TARGET=0.320", "PART-TARGET=0.330"]]))); + v.push(r1("adjacency_program_date_time", json!([["2026-10-01T12:00:00.000Z", "2026-10-01T12:00:00.000+00:00"]]))); + let layer_line = "#EXTINF:3.840,\n#WAI-LAYER:N=1,URI=\"../objects/l.wai#n=9\"\n"; + v.push(case("adjacency_layer_line_ignored", "adjacency", json!({ "wai": "live/wai-r1.m3u8", "classical": "src/live/classical-r1.m3u8", "edit_wai": [["#EXTINF:3.840,\n", layer_line]] }))); + v.push(case("adjacency_live_s_beside_live_p", "adjacency", json!({ "wai": "live/wai-r3.m3u8", "classical": "src/live/classical-r3.m3u8", + "edit_wai": [["#EXT-X-ENDLIST\n", ""]], "edit_classical": [["#EXT-X-ENDLIST\n", ""]] }))); + v.push(refused("adjacency_malformed_wai", "adjacency", json!({ "wai": "live/wai-r1.m3u8", "classical": "src/live/classical-r1.m3u8", "edit_wai": [["#EXTM3U", "#EXTM3X"]] }), "playlist_malformed")); + + // The classical master, published with at most the session-data line (§11.1). + let un = |edits: Value| json!({ "original": "src/players/master.m3u8", "published": "players/master.m3u8", "edit_published": edits }); + v.push(case("unchanged_players", "unchanged", un(json!([])))); + v.push(case("unchanged_original_itself", "unchanged", json!({ "original": "src/players/master.m3u8", "published": "src/players/master.m3u8" }))); + let sd = "#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n"; + for (name, edits) in [ + ("unchanged_two_lines", json!([[sd, format!("{sd}{sd}")]])), + ("unchanged_bandwidth_changed", json!([["BANDWIDTH=10840", "BANDWIDTH=10841"]])), + ("unchanged_other_data_id", json!([["science.transaction.wai.multivariant", "science.transaction.wai.other"]])), + ("unchanged_trailing_space", json!([["VALUE=\"wai.m3u8\"\n", "VALUE=\"wai.m3u8\" \n"]])), + ("unchanged_first_line", json!([[sd, ""], ["#EXTM3U\n", format!("{sd}#EXTM3U\n")]])), + ] { + v.push(refused(name, "unchanged", un(edits), "classical_changed")); + } + + // Playlist receipts (§8.6): each, and each way one is refused. + for (p, _, _) in receipt_specs() { + let r = receipt_of(c, p)?; + v.push(case(format!("playlist_receipt_{}", p.replace(['/', '.', '-'], "_")), "playlist_receipt", json!({ "playlist": p, "receipt": r }))); + } + let o9r = receipt_of(c, "playlists/o9/wai-180.m3u8")?; + let base = |extra: Value| { + let mut i = json!({ "playlist": "playlists/o9/wai-180.m3u8", "receipt": o9r }); + for (k, x) in extra.as_object().expect("object") { + i[k] = x.clone(); + } + i + }; + v.push(refused("playlist_receipt_name", "playlist_receipt", base(json!({ "name": "0000.json" })), "receipt_name_mismatch")); + v.push(refused("playlist_receipt_playlist_changed", "playlist_receipt", base(json!({ "edit_playlist": [["#EXTINF:0.640,", "#EXTINF:0.641,"]] })), "playlist_receipt_mismatch")); + v.push(refused("playlist_receipt_viewers", "playlist_receipt", base(json!({ "edit_receipt": [["\"viewers_served\":1", "\"viewers_served\":2"]] })), "playlist_receipt_invalid")); + v.push(refused("playlist_receipt_class", "playlist_receipt", base(json!({ "edit_receipt": [["\"entitlement\":\"public\"", "\"entitlement\":\"svod\""]] })), "playlist_receipt_invalid")); + let default_extra = "{\"discontinuity\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null}"; + v.push(refused("playlist_receipt_extras_equivalent", "playlist_receipt", base(json!({ "edit_receipt": [["\"extras\":[]", format!("\"extras\":[{default_extra},{default_extra},{default_extra}]")]] })), "playlist_receipt_invalid")); + v.push(refused("playlist_receipt_kind", "playlist_receipt", base(json!({ "edit_receipt": [["\"kind\":\"wai.video.manifest\"", "\"kind\":\"wai.video.package\""]] })), "playlist_receipt_malformed")); + v.push(refused("playlist_receipt_unknown_member", "playlist_receipt", base(json!({ "edit_receipt": [["{\"kind\"", "{\"note\":1,\"kind\""]] })), "playlist_receipt_malformed")); + let r1r = receipt_of(c, "live/wai-r1.m3u8")?; + v.push(refused("playlist_receipt_part_independent", "playlist_receipt", json!({ "playlist": "live/wai-r1.m3u8", "receipt": r1r, + "edit_playlist": [["DURATION=0.320,INDEPENDENT=YES,URI=\"720/1200.3", "DURATION=0.320,URI=\"720/1200.3"]] }), "playlist_receipt_mismatch")); + v.push(refused("playlist_receipt_parts_reordered", "playlist_receipt", json!({ "playlist": "live/wai-r1.m3u8", "receipt": r1r, + "edit_receipt": [["\"uri\":\"720/1200.3.wai", "\"uri\":\"720/1200.X.wai"], ["\"uri\":\"720/1200.4.wai", "\"uri\":\"720/1200.3.wai"], ["\"uri\":\"720/1200.X.wai", "\"uri\":\"720/1200.4.wai"]] }), "playlist_receipt_mismatch")); + + // A live edge (§11.4, §11.6): four revisions, each adjacent, signed and chained; parts + // fetched by alias before and after the revision that lists them. + let revs = |edit_r2: Value, r2_receipt: Option<&str>| -> Result { + Ok(Value::Array((0..4).map(|k| { + let w = format!("live/wai-r{k}.m3u8"); + let rc = if k == 2 { r2_receipt.map(String::from).unwrap_or(receipt_of(c, &w)?) } else { receipt_of(c, &w)? }; + let mut r = json!({ "wai": w, "receipt": rc, "classical": format!("src/live/classical-r{k}.m3u8") }); + if k == 2 { + r["edit_wai"] = edit_r2.clone(); + } + Ok(r) + }).collect::, String>>()?)) + }; + let fetches = json!([ + { "alias": "720/1200.0.wai", "object": part_id(0, 0) }, + { "alias": "720/1200.5.wai", "object": part_id(0, 5) }, + { "alias": "720/1201.3.wai", "object": part_id(1, 3) }, + { "alias": "720/1201.7.wai", "object": part_id(1, 8) }, + { "alias": "720/1200.2.wai", "object": part_id(0, 2), "patch_object": [[400, "00"]] }, + { "alias": "720/1202.0.wai", "object": part_id(1, 11) }, + { "alias": "1200.m4s", "object": "O12s0" }, + ]); + v.push(case("live_edge", "live", json!({ "revisions": revs(json!([]), None)?, "fetches": fetches }))); + let cid8 = hex(&content_hash(&c.objects[&part_id(1, 8)])); + let cid7 = hex(&content_hash(&c.objects[&part_id(1, 7)])); + v.push(case("live_edge_revision_changed", "live", json!({ + "revisions": revs(json!([[format!("720/1201.7.wai#cid={cid7}"), format!("720/1201.7.wai#cid={cid8}")]]), None)?, + "fetches": [{ "alias": "720/1201.7.wai", "object": part_id(1, 8) }, { "alias": "720/1201.3.wai", "object": part_id(1, 3) }] }))); + v.push(case("live_edge_unchained", "live", json!({ + "revisions": revs(json!([]), Some("cases/live-r2-orphan.json"))?, + "fetches": [{ "alias": "720/1201.3.wai", "object": part_id(1, 3) }, { "alias": "720/1200.0.wai", "object": part_id(0, 0) }] }))); + + // Adaptation (Appendix F): pinned states per policy, seeded; with and without a + // budget; and the budget's every outcome. + let mut s = 0x5741_495f_4142_5221u64; + for p in policies() { + let id = p["id"].as_str().expect("id").to_string(); + for i in 0..40 { + let n = 1 + (next(&mut s) % 6) as usize; + v.push(case(format!("abr_{id}_{i:02}"), "abr", json!({ "policy": p, "state": abr_state(&mut s, n, i % 2 == 1) }))); + } + } + let st = |buffer: u64, bps: u64, opts: Value, figs: Value, budget: Value| json!({ + "buffer_ms": buffer, "samples": [[1000, bps / 8, 1000]], "last": null, "option_bytes": opts, + "option_joules_micro": figs, "joule_budget_micro": budget, "segment_ms": 1000 }); + let opts = json!([10_000, 100_000, 1_000_000]); + let thr = json!({ "id": "throughput", "last_n": 1, "safety_permille": 900 }); + for (name, figs, budget) in [ + ("abr_budget_not_set", json!([10, 50, 500]), Value::Null), + ("abr_budget_not_applied", json!([10, 50, null]), json!(100)), + ("abr_budget_within", json!([10, 50, 100]), json!(100)), + ("abr_budget_lowered", json!([10, 50, 500]), json!(100)), + ("abr_budget_lowered_past_unlabelled", json!([10, null, 500]), json!(100)), + ("abr_budget_floor", json!([200, null, 500]), json!(100)), + ] { + v.push(case(name, "abr", json!({ "policy": thr, "state": st(0, 100_000_000, opts.clone(), figs, budget) }))); + } + // Entry orders over one WAI2 (SPEC §7.1 step 2) and layer depths (§10.4). + let av1 = json!(["wai.video.av1", "wai.meta.hdr10plus"]); + for (obj, adv) in [("O3", av1.clone()), ("OD", av1.clone()), ("O9a", json!(["wai.video.av1", "wai.video.int_hyper"])), + ("O2", json!(["wai.neural.int_mlicpp", "wai.image.avif", "wai.image.jxl", "wai.image.png"])), ("O8", json!(["wai.video.avc", "wai.video.av1"]))] { + for p in policies() { + for (k, (buffer, bps)) in [(0u64, 60_000u64), (8000, 1_500_000), (30_000, 40_000_000)].into_iter().enumerate() { + let state = json!({ "buffer_ms": buffer, "samples": [[1000, bps / 8, 1000], [2000, bps / 8, 1000]], "last": null, + "option_bytes": [], "option_joules_micro": [], "joule_budget_micro": null, "segment_ms": 2000 }); + v.push(case(format!("abr_order_{obj}_{}_{k}", p["id"].as_str().expect("id")), "abr_order", + json!({ "object": obj, "advertises": adv, "policy": p, "state": state }))); + } + } + } + for (name, objects) in [("O10", json!(["O10", "O10L1", "O10L2"])), ("O11b", json!(["O11b", "O11bL1"]))] { + for p in policies() { + for (k, (buffer, bps)) in [(0u64, 20_000u64), (6000, 300_000), (30_000, 5_000_000)].into_iter().enumerate() { + let state = json!({ "buffer_ms": buffer, "samples": [[1000, bps / 8, 1000]], "last": null, "option_bytes": [], + "option_joules_micro": [], "joule_budget_micro": null, "segment_ms": 1000 }); + v.push(case(format!("depth_{name}_{}_{k}", p["id"].as_str().expect("id")), "depth", json!({ "objects": objects, "policy": p, "state": state }))); + } + } + } + let rich = json!({ "buffer_ms": 0, "samples": [[1000, 10_000_000, 1000]], "last": null, "option_bytes": [], "option_joules_micro": [5, null, 20], + "joule_budget_micro": 30, "segment_ms": 1000 }); + v.push(case("depth_O10_budget_unlabelled_layer", "depth", json!({ "objects": ["O10", "O10L1", "O10L2"], "policy": thr, "state": rich }))); + let mut labelled = rich.clone(); + labelled["option_joules_micro"] = json!([5, 10, 20]); + v.push(case("depth_O10_budget_lowered", "depth", json!({ "objects": ["O10", "O10L1", "O10L2"], "policy": thr, "state": labelled }))); + // A fetch whose order is a policy's: the same pick as whole-envelope selection. + for (name, bps) in [("low", 100_000u64), ("high", 10_000_000)] { + let state = json!({ "buffer_ms": 0, "samples": [[1000, bps / 8, 1000]], "last": null, "option_bytes": [], "option_joules_micro": [], + "joule_budget_micro": null, "segment_ms": 2000 }); + let abr = json!({ "policy": thr, "state": state }); + v.push(case(format!("fetch_abr_O3_{name}"), "fetch", json!({ "object": "O3", "advertises": av1, "applies": ["wai.meta.hdr10plus"], "abr": abr, "fragment": "#h=1009" }))); + v.push(case(format!("same_pick_abr_O3_{name}"), "same_pick", json!({ "object": "O3", "advertises": av1, "applies": ["wai.meta.hdr10plus"], "abr": abr, "fragment": "#h=1009" }))); + v.push(case(format!("same_pick_abr_O7_{name}"), "same_pick", json!({ "object": "O7", "advertises": av1, "abr": abr }))); + } + Ok(v) +} + +/// The selector's order from a case's `abr` member: the policy over the state, on the +/// sink's advertised capabilities. +pub(super) fn abr_order(input: &Value, advertises: Vec) -> Result Vec>>, String> { + let a = &input["abr"]; + if a.is_null() { + return Ok(None); + } + Ok(Some(abr::selector_order(policy(&a["policy"])?, state(&a["state"])?, move |c| advertises.iter().any(|x| x == c)))) +} diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs index 5f08d6980..a8046b346 100644 --- a/wai/wai-rs/src/http_binding/abr.rs +++ b/wai/wai-rs/src/http_binding/abr.rs @@ -128,6 +128,8 @@ impl Budget { } /// `round(65536 · log2(1 + i/16))` for `i` in `0..=16`. +/// +/// cbindgen:ignore pub const LOG2_TABLE: [u64; 17] = [0, 5732, 11136, 16248, 21098, 25711, 30109, 34312, 38336, 42196, 45904, 49472, 52911, 56229, 59434, 62534, 65536]; /// `log2(a / b)` in Q16 (×65536), 0 when `a ≤ b` (`b` taken as at least 1): the ratio diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs index c4d9766bb..2623644b6 100644 --- a/wai/wai-rs/src/http_binding/hls.rs +++ b/wai/wai-rs/src/http_binding/hls.rs @@ -549,22 +549,34 @@ pub fn wai_media_playlist( preload_hint: None, }); } + let mut hint = None; if let Some(o) = open { - let c = classical.open.as_ref().ok_or_else(|| misaligned("an open segment the classical playlist does not have"))?; - if !o.parts.is_empty() && !c.parts.is_empty() && o.parts.iter().map(|p| p.duration_ms).collect::>() != parts_ms(&c.parts) { - return Err(misaligned("the open segment: other part durations")); + if o.parts.is_empty() { + // Only a hint: it goes on the playlist's last entry, as a reader finds it. + hint = o.preload_hint.clone(); + } else { + let c = classical.open.as_ref().ok_or_else(|| misaligned("an open segment the classical playlist does not have"))?; + if !c.parts.is_empty() && o.parts.iter().map(|p| p.duration_ms).collect::>() != parts_ms(&c.parts) { + return Err(misaligned("the open segment: other part durations")); + } + extras.push(SegmentExtra { + discontinuity: c.discontinuity, + program_date_time: c.program_date_time.clone(), + parts: part_lines(&o.parts, uri_prefix), + layers: Vec::new(), + preload_hint: o.preload_hint.clone(), + }); } - extras.push(SegmentExtra { - discontinuity: c.discontinuity, - program_date_time: c.program_date_time.clone(), - parts: part_lines(&o.parts, uri_prefix), - layers: Vec::new(), - preload_hint: o.preload_hint.clone(), - }); } if extras.iter().all(|e| *e == SegmentExtra::default()) { extras.clear(); } + if let Some(h) = hint { + if extras.is_empty() { + extras = vec![SegmentExtra::default(); segs.len().max(1)]; + } + extras.last_mut().expect("an extra").preload_hint = Some(h); + } let r = HlsRender { version: classical.version.unwrap_or(6), media_sequence: classical.media_sequence, -- 2.54.0 (Apple Git-157) From bbfecc24ccb1899c396da24f430be91f25d8b7a3 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 06:28:11 -0400 Subject: [PATCH 07/55] wai: the playlist side of the second verifier and the seeded differential (W3-4, W3-5) http_verify.py now implements, from the extension text, the media playlist reader and rendering, adjacency, the WAI media and multivariant playlists and wai.mpd, the session-data line and the unchanged classical master, playlist receipts (their file read strictly, the segment leaves, Merkle root, class hash, render digest over JCS, and Ed25519 verification written from RFC 8032, so the receipt cases still need only the standard library), the live edge, and the four adaptation policies with their own integer log; a sink's order may be a policy over the verified head. New mutations: pdt-ignored, dseq-ignored, parts-after-extinf, average-floor, render-digest-off, name-unchecked, chain-unchecked, ilog2-no-interp, bola-waits, unlabelled-as-zero, round-half-down. Its layer-tag reader now takes Rust's ASCII white space (no vertical tab), which it had not. wai_http_vectors fuzz gains seven families (playlist_parse, adjacency, unchanged, render, abr, ilog2, receipt) and the ops render and ilog2; the corpus gains ilog2 vectors. At seeds 1 and 2 the two implementations agree on every case; the differential first found the Python reader taking -0 as an unsigned integer, now refused as the Rust reader refuses it. Co-Authored-By: Claude Opus 5.5 --- wai/http-conformance/cases.json | 24 + wai/http-conformance/http_verify.py | 1259 ++++++++++++++++- wai/wai-rs/src/bin/wai_cli/http_vectors.rs | 6 + .../src/bin/wai_cli/http_vectors_playlists.rs | 360 ++++- 4 files changed, 1640 insertions(+), 9 deletions(-) diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index 234f96bf3..3948caa4e 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -613,6 +613,30 @@ {"name":"abr_budget_lowered","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":1,"budget":"lowered"}}, {"name":"abr_budget_lowered_past_unlabelled","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,null,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":0,"budget":"lowered"}}, {"name":"abr_budget_floor","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[200,null,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"ilog2_1_1","op":"ilog2","input":{"a":1,"b":1},"expect":{"log2_q16":0}}, +{"name":"ilog2_2_1","op":"ilog2","input":{"a":2,"b":1},"expect":{"log2_q16":65536}}, +{"name":"ilog2_3_2","op":"ilog2","input":{"a":3,"b":2},"expect":{"log2_q16":38336}}, +{"name":"ilog2_17_16","op":"ilog2","input":{"a":17,"b":16},"expect":{"log2_q16":5732}}, +{"name":"ilog2_31_16","op":"ilog2","input":{"a":31,"b":16},"expect":{"log2_q16":62534}}, +{"name":"ilog2_33_32","op":"ilog2","input":{"a":33,"b":32},"expect":{"log2_q16":2866}}, +{"name":"ilog2_1000_1","op":"ilog2","input":{"a":1000,"b":1},"expect":{"log2_q16":653108}}, +{"name":"ilog2_1001_1000","op":"ilog2","input":{"a":1001,"b":1000},"expect":{"log2_q16":90}}, +{"name":"ilog2_65535_65536","op":"ilog2","input":{"a":65535,"b":65536},"expect":{"log2_q16":0}}, +{"name":"ilog2_65537_65536","op":"ilog2","input":{"a":65537,"b":65536},"expect":{"log2_q16":1}}, +{"name":"ilog2_18446744073709551615_1","op":"ilog2","input":{"a":18446744073709551615,"b":1},"expect":{"log2_q16":4194303}}, +{"name":"ilog2_18446744073709551615_18446744073709551614","op":"ilog2","input":{"a":18446744073709551615,"b":18446744073709551614},"expect":{"log2_q16":0}}, +{"name":"ilog2_5_0","op":"ilog2","input":{"a":5,"b":0},"expect":{"log2_q16":152170}}, +{"name":"ilog2_0_0","op":"ilog2","input":{"a":0,"b":0},"expect":{"log2_q16":0}}, +{"name":"ilog2_1099511627776_3","op":"ilog2","input":{"a":1099511627776,"b":3},"expect":{"log2_q16":2517544}}, +{"name":"ilog2_123456789_1000","op":"ilog2","input":{"a":123456789,"b":1000},"expect":{"log2_q16":1108445}}, +{"name":"ilog2_7_3","op":"ilog2","input":{"a":7,"b":3},"expect":{"log2_q16":80079}}, +{"name":"ilog2_400000_2000","op":"ilog2","input":{"a":400000,"b":2000},"expect":{"log2_q16":500948}}, +{"name":"ilog2_999999_2","op":"ilog2","input":{"a":999999,"b":2},"expect":{"log2_q16":1240685}}, +{"name":"ilog2_4097_4096","op":"ilog2","input":{"a":4097,"b":4096},"expect":{"log2_q16":22}}, +{"name":"ilog2_6143_4096","op":"ilog2","input":{"a":6143,"b":4096},"expect":{"log2_q16":38320}}, +{"name":"ilog2_12000_7000","op":"ilog2","input":{"a":12000,"b":7000},"expect":{"log2_q16":50945}}, +{"name":"ilog2_9223372036854775808_1","op":"ilog2","input":{"a":9223372036854775808,"b":1},"expect":{"log2_q16":4128768}}, +{"name":"ilog2_9223372036854775809_4611686018427387904","op":"ilog2","input":{"a":9223372036854775809,"b":4611686018427387904},"expect":{"log2_q16":65536}}, {"name":"abr_order_O3_throughput_0","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,7500,1000],[2000,7500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[0,1,2]}}, {"name":"abr_order_O3_throughput_1","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":8000,"samples":[[1000,187500,1000],[2000,187500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, {"name":"abr_order_O3_throughput_2","op":"abr_order","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":30000,"samples":[[1000,5000000,1000],[2000,5000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"order":[1,0,2]}}, diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index 5c9736f53..6913a99fb 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -697,7 +697,7 @@ def parse_layer_tag(line): end = rest.find(b",") end = len(rest) if end < 0 else end value, quoted, after = rest[:end], False, rest[end:] - if not value or b'"' in value or re.search(rb"[ \t\n\r\x0b\x0c]", value): + if not value or b'"' in value or re.search(rb"[ \t\n\r\x0c]", value): raise bad if name == b"N": if quoted or len(value) > 3 or not re.fullmatch(rb"[0-9]+", value) or not 1 <= int(value) <= 255: @@ -942,7 +942,7 @@ class Session: self.to_rest("single_component", "base") else: entries = head["manifest"]["renditions"] - ordr = order if order is not None else list(range(len(entries))) + ordr = order(head) if callable(order) else (order if order is not None else list(range(len(entries)))) pick, skipped = select(head["manifest"], ordr, adv, held_sets, lambda i, e: True) wanted = [(pick, "base")] + self.applied(entries, pick) pl = plan(head, wanted, [(a, b) for a, b, _ in self.held], self.permille) @@ -1037,7 +1037,7 @@ class Session: return entries = m["renditions"] pay = [envb[s:s + ln] for s, ln in head["components"]] - ordr = order if order is not None else list(range(len(entries))) + ordr = order(head) if callable(order) else (order if order is not None else list(range(len(entries)))) pick, skipped = select(m, ordr, adv, held_sets, lambda i, e: payload_ok(e, pay[i])) comps, refs, dropped = [], [], [] for j, cl in self.applied(entries, pick): @@ -1230,7 +1230,1253 @@ def sink_of(d, inp, objs): if inp.get("layer_base") is not None or inp.get("layer_base_hex") is not None: b = bytes.fromhex(inp["layer_base_hex"]) if isinstance(inp.get("layer_base_hex"), str) else objs[inp["layer_base"]] base = seq_object(b) - return (inp.get("advertises") or [], inp.get("applies") or [], held, inp.get("order"), declared, base) + order = inp.get("order") + if inp.get("abr") is not None: + adv, pol, st = inp.get("advertises") or [], inp["abr"]["policy"], abr_state(inp["abr"]["state"]) + order = lambda head: order_entries(head, adv, pol, st) + return (inp.get("advertises") or [], inp.get("applies") or [], held, order, declared, base) + + +# ------------------------------------------------- adaptation (Appendix F) + +LOG2_TABLE = [0, 5732, 11136, 16248, 21098, 25711, 30109, 34312, 38336, 42196, 45904, 49472, 52911, 56229, 59434, 62534, 65536] + + +def ilog2_q16(a, b): + """log2(a/b) in Q16: the ratio's exponent, and the table interpolated on the + mantissa's top four fractional bits with the next twelve as the weight.""" + b = max(b, 1) + if a <= b: + return 0 + x = (a << 16) // b + e = x.bit_length() - 1 - 16 + f = (x >> e) - 65536 + i, r = f >> 12, f & 4095 + if MUTATE == "ilog2-no-interp": + r = 0 + return e * 65536 + LOG2_TABLE[i] + (((LOG2_TABLE[i + 1] - LOG2_TABLE[i]) * r) >> 12) + + +def throughput_bps(samples, n): + k = min(max(n, 1), len(samples)) + tail = samples[len(samples) - k:] + t = sum(max(s[2], 1) for s in tail) + return sum(s[1] for s in tail) * 8000 // t if t else 0 + + +def option_bps(nbytes, seg): + return -(-(nbytes * 8000) // max(seg, 1)) + + +def highest_under(st, budget): + for k in range(len(st["option_bytes"]) - 1, -1, -1): + if option_bps(st["option_bytes"][k], st["segment_ms"]) <= budget: + return k + return 0 + + +def utilities(st): + s0 = max(st["option_bytes"][0] if st["option_bytes"] else 1, 1) + return [ilog2_q16(max(b, 1), s0) for b in st["option_bytes"]] + + +def choose(p, st): + opts, seg, k = st["option_bytes"], st["segment_ms"], len(st["option_bytes"]) + if k == 0: + return 0 + if p["id"] == "throughput": + return highest_under(st, throughput_bps(st["samples"], p["last_n"]) * p["safety_permille"] // 1000) + if p["id"] == "buffer_based": + b, r, c = st["buffer_ms"], p["reservoir_ms"], p["cushion_ms"] + if b <= r: + return 0 + if c == 0 or b >= r + c: + return k - 1 + lo, hi = option_bps(opts[0], seg), option_bps(opts[-1], seg) + return highest_under(st, lo + max(hi - lo, 0) * (b - r) // c) + if p["id"] == "bola_like": + sg = max(seg, 1) + q, qmax = st["buffer_ms"] * 65536 // sg, p["max_buffer_ms"] * 65536 // sg + u = utilities(st) + vn, vd = max(qmax - 65536, 0), max(u) + 65536 + score = [vn * p["v_milli"] * (x + 65536) // (vd * 1000) - q for x in u] + size = [max(b, 1) for b in opts] + best = None + if any(x > 0 for x in score): + for i in range(k): + if score[i] > 0 and (best is None or score[i] * size[best] > score[best] * size[i]): + best = i + elif MUTATE == "bola-waits": + return 0 + else: + for i in range(k): + if best is None or score[i] > score[best]: + best = i + return best + if p["id"] == "mpc": + pred = throughput_bps(st["samples"], 5) * p["safety_permille"] // 1000 + if pred == 0: + return 0 + h = min(max(p["horizon"], 1), 5) + while h > 1 and k ** h > 65536: + h -= 1 + u = utilities(st) + u_top = max(u) + best = None + for code in range(k ** h): + seq, c = [0] * h, code + for t in range(h - 1, -1, -1): + seq[t], c = c % k, c // k + b, rebuf, qoe = st["buffer_ms"], 0, 0 + last = st["last"] + prev = u[last] if last is not None and last < k else None + for o in seq: + dl = -(-(opts[o] * 8000) // pred) + if dl > b: + rebuf, b = rebuf + dl - b, 0 + else: + b -= dl + b += seg + qoe += 1000 * u[o] + if prev is not None: + qoe -= 1000 * abs(u[o] - prev) + prev = u[o] + qoe -= rebuf * (u_top + 65536) + if best is None or qoe > best[0]: + best = (qoe, seq[0]) + return best[1] + raise SystemExit("http_verify: an unknown policy " + str(p["id"])) + + +def clamp_to_budget(st, choice): + budget = st["joule_budget_micro"] + if budget is None: + return choice, "not_set" + figs = st["option_joules_micro"] + fig = lambda j: figs[j] if j < len(figs) else None + if MUTATE == "unlabelled-as-zero": + fig = lambda j: (figs[j] if j < len(figs) else None) or 0 + f = fig(choice) + if f is None: + return choice, "not_applied" + if f <= budget: + return choice, "within" + for j in range(choice - 1, -1, -1): + if fig(j) is not None and fig(j) <= budget: + return j, "lowered" + return 0, "floor" + + +def decide(p, st): + return clamp_to_budget(st, choose(p, st)) + + +def abr_state(v): + return {"buffer_ms": v["buffer_ms"], "samples": [tuple(s) for s in v.get("samples") or []], "last": v.get("last"), + "option_bytes": list(v.get("option_bytes") or []), "option_joules_micro": list(v.get("option_joules_micro") or []), + "joule_budget_micro": v.get("joule_budget_micro"), "segment_ms": v["segment_ms"]} + + +def depth(layers_n, p, st): + st = dict(st) + acc, cum, jcum = 0, [], [] + figs = st["option_joules_micro"] + for k, n in enumerate(layers_n): + acc += n + cum.append(acc) + fs = [figs[j] if j < len(figs) else None for j in range(k + 1)] + jcum.append(None if any(f is None for f in fs) else sum(fs)) + st["option_bytes"], st["option_joules_micro"] = cum, jcum + if not layers_n: + return 0, clamp_to_budget(st, 0)[1] + return decide(p, st) + + +def order_entries(head, adv, p, st): + """SPEC §7.1 step 2's order under a policy: each advertised selectable capability's + entries by component length, the choice first, then those below it nearest first, + then those above; every other entry after, in table order.""" + if head["version"] != "WAI2": + return [0] + entries = head["manifest"]["renditions"] + ln = lambda i: head["components"][i][1] + groups = [] + for i, e in enumerate(entries): + if not selectable(e) or e["capability"] not in adv: + continue + g = next((g for g in groups if g[0] == e["capability"]), None) + if g is None: + groups.append((e["capability"], [i])) + else: + g[1].append(i) + order = [] + for _, g in groups: + g = sorted(g, key=lambda i: (ln(i), i)) + s2 = dict(st) + figs = st["option_joules_micro"] + s2["option_bytes"] = [ln(i) for i in g] + s2["option_joules_micro"] = [figs[i] if i < len(figs) else None for i in g] + c = min(decide(p, s2)[0], len(g) - 1) + order += [g[c]] + list(reversed(g[:c])) + g[c + 1:] + return order + [i for i in range(len(entries)) if i not in order] + + +# ------------------------------------------------------ playlists (§10.4, §11) + +U64 = 2 ** 64 - 1 +DIGITS3 = re.compile(r"[0-9]+(\.[0-9]+)?", re.ASCII) + + +def decimal_ms(s): + """A decimal number of seconds, exactly, in milliseconds rounded half up.""" + if not DIGITS3.fullmatch(s): + return None + whole, _, frac = s.partition(".") + f = (frac + "0000")[:4] + ms = int(whole) * 1000 + int(f[:3]) + (1 if int(f[3]) >= (6 if MUTATE == "round-half-down" else 5) else 0) + return ms if ms <= U64 else None + + +def decimal_int(s): + if not re.fullmatch(r"[0-9]+", s, re.ASCII): + return None + v = int(s) + return v if v <= U64 else None + + +def attributes(s): + """An HLS attribute list (RFC 8216 §4.2) as [(name, quoted, value)].""" + bad = Refusal("playlist_malformed") + out, rest = [], s + while True: + if "=" not in rest: + raise bad + name, after = rest.split("=", 1) + if not re.fullmatch(r"[A-Z0-9-]+", name, re.ASCII) or any(n == name for n, _, _ in out): + raise bad + if after.startswith('"'): + close = after.find('"', 1) + if close < 0: + raise bad + v = after[1:close] + if "\r" in v or "\n" in v: + raise bad + out.append((name, True, v)) + after = after[close + 1:] + else: + end = after.find(",") + end = len(after) if end < 0 else end + v = after[:end] + if not v or '"' in v or any(ch in " \t\n\r\x0c" for ch in v): + raise bad + out.append((name, False, v)) + after = after[end:] + if not after: + return out + if not after.startswith(","): + raise bad + rest = after[1:] + + +def attr(a, name): + return next(((q, v) for n, q, v in a if n == name), None) + + +def attr_ms(a, name): + x = attr(a, name) + if x is None: + return None + if x[0] or decimal_ms(x[1]) is None: + raise Refusal("playlist_malformed") + return decimal_ms(x[1]) + + +def attr_quoted(a, name): + x = attr(a, name) + if x is None: + return None + if not x[0]: + raise Refusal("playlist_malformed") + return x[1] + + +def attr_yes(a, name): + x = attr(a, name) + if x is None: + return False + if x[0] or x[1] not in ("YES", "NO"): + raise Refusal("playlist_malformed") + return x[1] == "YES" + + +def new_extra(): + return {"discontinuity": False, "pdt": None, "parts": [], "layers": [], "hint": None} + + +def push_layer(layers, l): + if layers and layers[-1]["n"] >= l["n"]: + raise Refusal("playlist_malformed") + layers.append(l) + + +def parse_media_playlist(text): + """A media playlist, classical or WAI, read as §11 needs it.""" + bad = Refusal("playlist_malformed") + lines = [l[:-1] if l.endswith("\r") else l for l in text.split("\n")] + lines = [l for l in lines if l] + if not lines or lines[0] != "#EXTM3U": + raise bad + p = {"version": None, "target": None, "seq": 0, "dseq": 0, "part_target": None, "hold_back": None, "block": False, + "segments": [], "open": None, "hint": None, "end": False} + pending, extinf, anchor, started = new_extra(), None, None, False + for line in lines[1:]: + if line.startswith("#WAI-LAYER:"): + l = parse_layer_tag(line) + if anchor == "extinf": + push_layer(pending["layers"], l) + elif anchor == "part": + push_layer(pending["parts"][-1]["layers"], l) + else: + raise bad + continue + anchor = None + if not line.startswith("#"): + if extinf is None: + raise bad + p["segments"].append({"ms": extinf, "uri": line, "extra": pending}) + pending, extinf, started = new_extra(), None, True + continue + if not line.startswith("#EXT"): + continue + tag, _, value = line.partition(":") + if tag == "#EXTM3U": + raise bad + elif tag == "#EXT-X-VERSION": + v = decimal_int(value) + if v is None or v > 255: + raise bad + p["version"] = v + elif tag == "#EXT-X-TARGETDURATION": + v = decimal_int(value) + if v is None: + raise bad + p["target"] = v + elif tag in ("#EXT-X-MEDIA-SEQUENCE", "#EXT-X-DISCONTINUITY-SEQUENCE"): + if started or extinf is not None or pending["parts"]: + raise bad + v = decimal_int(value) + if v is None: + raise bad + p["seq" if tag == "#EXT-X-MEDIA-SEQUENCE" else "dseq"] = v + elif tag == "#EXT-X-PART-INF": + a = attributes(value) + pt = attr_ms(a, "PART-TARGET") + if pt is None: + raise bad + p["part_target"] = pt + elif tag == "#EXT-X-SERVER-CONTROL": + a = attributes(value) + p["block"] = attr_yes(a, "CAN-BLOCK-RELOAD") + p["hold_back"] = attr_ms(a, "PART-HOLD-BACK") + elif tag == "#EXT-X-DISCONTINUITY": + pending["discontinuity"] = True + elif tag == "#EXT-X-PROGRAM-DATE-TIME": + pending["pdt"] = value + elif tag == "#EXT-X-PART": + a = attributes(value) + d, uri = attr_ms(a, "DURATION"), attr_quoted(a, "URI") + if d is None or uri is None: + raise bad + pending["parts"].append({"ms": d, "independent": attr_yes(a, "INDEPENDENT"), "uri": uri, "layers": []}) + anchor = "part" + elif tag == "#EXTINF": + if extinf is not None: + raise bad + extinf = decimal_ms(value.split(",", 1)[0]) + if extinf is None: + raise bad + anchor = "extinf" + elif tag == "#EXT-X-PRELOAD-HINT": + a = attributes(value) + t = attr(a, "TYPE") + if t is None or t[0]: + raise bad + if t[1] == "PART": + if p["hint"] is not None: + raise bad + h = attr_quoted(a, "URI") + if h is None: + raise bad + p["hint"] = h + elif tag == "#EXT-X-ENDLIST": + p["end"] = True + elif tag in ("#EXT-X-SKIP", "#EXT-X-STREAM-INF", "#EXT-X-I-FRAME-STREAM-INF", "#EXT-X-MEDIA"): + raise bad + if extinf is not None: + raise bad + if pending != new_extra(): + p["open"] = pending + return p + + +def secs(ms): + return "%d.%03d" % (ms // 1000, ms % 1000) + + +def render_hls(segs, r, extras): + """The rendering of §8.6: header, then each segment (discontinuity, date-time, parts + with their layer lines, EXTINF, its layer lines, URI), the open segment, the hint, + the end.""" + target = r["target"] if r["target"] is not None else max([-(-s["ms"] // 1000) for s in segs], default=0) + out = ["#EXTM3U", "#EXT-X-VERSION:%d" % r["version"], "#EXT-X-TARGETDURATION:%d" % target] + if r["part_target"] is not None: + out.append("#EXT-X-PART-INF:PART-TARGET=" + secs(r["part_target"])) + ctl = (["CAN-BLOCK-RELOAD=YES"] if r["block"] else []) + (["PART-HOLD-BACK=" + secs(r["hold_back"])] if r["hold_back"] is not None else []) + if ctl: + out.append("#EXT-X-SERVER-CONTROL:" + ",".join(ctl)) + out.append("#EXT-X-MEDIA-SEQUENCE:%d" % r["seq"]) + if r["dseq"]: + out.append("#EXT-X-DISCONTINUITY-SEQUENCE:%d" % r["dseq"]) + + def parts(ps): + for q in ps: + out.append("#EXT-X-PART:DURATION=%s%s,URI=\"%s\"" % (secs(q["ms"]), ",INDEPENDENT=YES" if q["independent"] else "", q["uri"])) + out.extend("#WAI-LAYER:N=%d,URI=\"%s\"" % (l["n"], l["uri"]) for l in q["layers"]) + + for i, s in enumerate(segs): + e = extras[i] if i < len(extras) else new_extra() + if s.get("ad") or e["discontinuity"]: + out.append("#EXT-X-DISCONTINUITY") + if e["pdt"] is not None: + out.append("#EXT-X-PROGRAM-DATE-TIME:" + e["pdt"]) + if MUTATE == "parts-after-extinf": + out.append("#EXTINF:%s," % secs(s["ms"])) + parts(e["parts"]) + else: + parts(e["parts"]) + out.append("#EXTINF:%s," % secs(s["ms"])) + out.extend("#WAI-LAYER:N=%d,URI=\"%s\"" % (l["n"], l["uri"]) for l in e["layers"]) + out.append(s["uri"]) + if len(extras) > len(segs): + o = extras[len(segs)] + if o["discontinuity"]: + out.append("#EXT-X-DISCONTINUITY") + if o["pdt"] is not None: + out.append("#EXT-X-PROGRAM-DATE-TIME:" + o["pdt"]) + parts(o["parts"]) + if extras and extras[-1]["hint"] is not None: + out.append("#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"%s\"" % extras[-1]["hint"]) + if r["end"]: + out.append("#EXT-X-ENDLIST") + return "\n".join(out) + "\n" + + +def uri_ok(u, quoted): + return bool(u) and not u.startswith("#") and "\r" not in u and "\n" not in u and not (quoted and '"' in u) + + +def check_render(segs, r, extras): + bad = Refusal("playlist_malformed") + n = len(segs) + if len(extras) not in (0, n, n + 1) or r["version"] == 0: + raise bad + if r["part_target"] is not None and (r["hold_back"] is None or r["part_target"] == 0 or r["hold_back"] < 2 * r["part_target"]): + raise bad + if any(not uri_ok(s["uri"], False) for s in segs): + raise bad + + def layers_ok(ls): + last = 0 + for l in ls: + if l["n"] == 0 or l["n"] <= last or not uri_ok(l["uri"], True): + raise bad + last = l["n"] + + for i, e in enumerate(extras): + if e["pdt"] is not None and (not e["pdt"] or not re.fullmatch(r"[A-Za-z0-9:.+-]+", e["pdt"], re.ASCII)): + raise bad + if e["parts"] and r["part_target"] is None: + raise bad + for q in e["parts"]: + if not uri_ok(q["uri"], True): + raise bad + layers_ok(q["layers"]) + layers_ok(e["layers"]) + if e["hint"] is not None and (i + 1 != len(extras) or r["end"] or not uri_ok(e["hint"], True)): + raise bad + if i == n: + if r["end"] or e["layers"] or (not e["parts"] and (e["discontinuity"] or e["pdt"] is not None)) or (not e["parts"] and e["hint"] is None): + raise bad + + +def render_inputs(p): + segs = [{"ms": s["ms"], "uri": s["uri"]} for s in p["segments"]] + r = {"version": p["version"] if p["version"] is not None else 1, "seq": p["seq"], "dseq": p["dseq"], "target": p["target"], + "part_target": p["part_target"], "hold_back": p["hold_back"], "block": p["block"], "end": p["end"]} + extras = [json.loads(json.dumps(s["extra"])) for s in p["segments"]] + if p["open"] is not None: + extras.append(json.loads(json.dumps(p["open"]))) + if p["hint"] is not None: + if not extras: + extras.append(new_extra()) + extras[-1]["hint"] = p["hint"] + if all(e == new_extra() for e in extras): + extras = [] + return segs, r, extras + + +def is_rendered_form(p, text): + segs, r, extras = render_inputs(p) + try: + check_render(segs, r, extras) + except Refusal: + return False + return render_hls(segs, r, extras) == text + + +def misaligned(): + return Refusal("adjacency_misaligned") + + +def timelines_adjacent(w, c): + if w["seq"] != c["seq"] or (w["dseq"] != c["dseq"] and MUTATE != "dseq-ignored") or len(w["segments"]) != len(c["segments"]): + raise misaligned() + both = w["part_target"] is not None and c["part_target"] is not None + if both and w["part_target"] != c["part_target"]: + raise misaligned() + for a, b in zip(w["segments"], c["segments"]): + if a["ms"] != b["ms"] or a["extra"]["discontinuity"] != b["extra"]["discontinuity"]: + raise misaligned() + if a["extra"]["pdt"] != b["extra"]["pdt"] and MUTATE != "pdt-ignored": + raise misaligned() + pa, pb = [q["ms"] for q in a["extra"]["parts"]], [q["ms"] for q in b["extra"]["parts"]] + if both and pa and pb and pa != pb: + raise misaligned() + if w["open"] is not None and c["open"] is not None: + a, b = w["open"], c["open"] + if a["discontinuity"] != b["discontinuity"] or a["pdt"] != b["pdt"]: + raise misaligned() + if both and [q["ms"] for q in a["parts"]] != [q["ms"] for q in b["parts"]]: + raise misaligned() + + +def check_adjacent(wai, classical): + w = parse_media_playlist(wai) + c = parse_media_playlist(classical) + timelines_adjacent(w, c) + + +def wai_media_playlist(c, segs, open_, prefix, end): + """The WAI media playlist on classical timeline `c` (§11.1-§11.4).""" + if len(segs) != len(c["segments"]): + raise misaligned() + has_parts = any(s["parts"] for s in segs) or (open_ is not None and open_["parts"]) + if has_parts and c["part_target"] is None: + raise misaligned() + lay = lambda ls: [{"n": l["n"], "uri": "%s%s.wai#n=%d" % (prefix, l["cid"].hex(), l["len"])} for l in ls] + prt = lambda ps: [{"ms": q["ms"], "independent": q["independent"], "uri": "%s#cid=%s&n=%d" % (q["alias"], q["cid"].hex(), q["n"]), "layers": lay(q["layers"])} for q in ps] + out_segs, extras = [], [] + for i, (w, k) in enumerate(zip(segs, c["segments"])): + if w["ms"] != k["ms"]: + raise misaligned() + if w["parts"] and k["extra"]["parts"] and [q["ms"] for q in w["parts"]] != [q["ms"] for q in k["extra"]["parts"]]: + raise misaligned() + h = "&h=%d" % w["h"] if w["h"] is not None else "" + out_segs.append({"ms": w["ms"], "uri": "%s%s.wai#n=%d%s" % (prefix, w["cid"].hex(), w["n"], h)}) + e = new_extra() + e.update(discontinuity=k["extra"]["discontinuity"], pdt=k["extra"]["pdt"], parts=prt(w["parts"]), layers=lay(w["layers"])) + extras.append(e) + hint = None + if open_ is not None: + if not open_["parts"]: + hint = open_["hint"] + else: + ko = c["open"] + if ko is None: + raise misaligned() + if ko["parts"] and [q["ms"] for q in open_["parts"]] != [q["ms"] for q in ko["parts"]]: + raise misaligned() + e = new_extra() + e.update(discontinuity=ko["discontinuity"], pdt=ko["pdt"], parts=prt(open_["parts"]), hint=open_["hint"]) + extras.append(e) + if all(e == new_extra() for e in extras): + extras = [] + if hint is not None: + if not extras: + extras = [new_extra() for _ in range(max(len(segs), 1))] + extras[-1]["hint"] = hint + pt = c["part_target"] + r = {"version": c["version"] if c["version"] is not None else 6, "seq": c["seq"], "dseq": c["dseq"], "target": c["target"], + "part_target": pt, "hold_back": c["hold_back"] if c["hold_back"] is not None else (pt * 3 if pt is not None else None), + "block": c["block"], "end": end} + check_render(out_segs, r, extras) + text = render_hls(out_segs, r, extras) + timelines_adjacent(parse_media_playlist(text), c) + return text + + +def bandwidths(objs): + if not objs or any(d == 0 for _, d in objs): + return None + rate = lambda n, d: min(-(-(8000 * n) // d), U64) + peak = max(rate(n, d) for n, d in objs) + sn, sd = sum(n for n, _ in objs), sum(d for _, d in objs) + return peak, (rate(sn, sd) if MUTATE != "average-floor" else 8000 * sn // sd) + + +SESSION_DATA = '#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="' + + +def classical_unchanged(original, published): + if original == published: + return + lines = published.split("\n") + lines = [l + "\n" for l in lines[:-1]] + ([lines[-1]] if lines[-1] else []) + for i in range(1, len(lines)): + l = lines[i] + if not l.endswith("\n"): + continue + bare = l[:-1] + if bare.endswith("\r"): + bare = bare[:-1] + if not (bare.startswith(SESSION_DATA) and bare.endswith('"')): + continue + uri = bare[len(SESSION_DATA):-1] + if not uri or any(ch in uri for ch in '"\r\n'): + continue + if "".join(lines[:i] + lines[i + 1:]) == original: + return + raise Refusal("classical_changed") + + +def listed_cid(p, uri): + lines = [] + for s in p["segments"]: + for q in s["extra"]["parts"]: + lines.append(q["uri"]) + lines += [l["uri"] for l in q["layers"]] + lines += [l["uri"] for l in s["extra"]["layers"]] + lines.append(s["uri"]) + if p["open"] is not None: + for q in p["open"]["parts"]: + lines.append(q["uri"]) + lines += [l["uri"] for l in q["layers"]] + line = next((l for l in lines if l.split("#", 1)[0] == uri), None) + if line is None: + return None + base, _, frag = line.partition("#") + for kv in frag.split("&") if frag else []: + k, eq, v = kv.partition("=") + if eq and k == "cid" and re.fullmatch(r"[0-9a-f]{64}", v): + return bytes.fromhex(v) + name = base.rsplit("/", 1)[-1] + if name.endswith(".wai") and re.fullmatch(r"[0-9a-f]{64}", name[:-4]): + return bytes.fromhex(name[:-4]) + return None + + +def esc_xml(s): + return s.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """) + + +def wai_mpd(c, period_id, set_id, union, floor, rep_id, bandwidth, media): + """wai.mpd (§11.1) on the classical HLS timeline at timescale 1000.""" + t, segs = 0, [] + for s in c["segments"]: + segs.append((t, s["ms"])) + t += s["ms"] + if not segs or len(media) != len(segs) or union[0] != floor[0]: + raise Refusal("playlist_malformed") + pt = lambda ms: "PT%sS" % secs(ms) + out = ['', + '' % (pt(sum(d for _, d in segs)), pt(max(d for _, d in segs))), + ' ' % (esc_xml(period_id), pt(0)), + ' ' % (set_id, esc_xml(render(union))), + ' ', + ' ' % esc_xml(render(floor)), + ' ' % (esc_xml(rep_id), bandwidth), + ' ', ' '] + out += [' ' % s for s in segs] + out += [' '] + [' ' % esc_xml(m) for m in media] + out += [' ', ' ', ' ', ' ', ''] + return "\n".join(out) + "\n" + + +# ------------------------------------------------ playlist receipts (§8.6) + +def jcs(v): + """RFC 8785 over the subset the render digest uses.""" + if v is None: + return "null" + if v is True or v is False: + return "true" if v else "false" + if isinstance(v, int): + if not 0 <= v <= 2 ** 53 - 1: + raise ValueError("outside the canonical subset") + return str(v) + if isinstance(v, str): + return json.dumps(v, ensure_ascii=False) + if isinstance(v, list): + return "[" + ",".join(jcs(x) for x in v) + "]" + keys = sorted(v, key=lambda k: k.encode("utf-16-be")) + return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + jcs(v[k]) for k in keys) + "}" + + +# Ed25519 (RFC 8032 §5.1), verification only, from the RFC's definitions: keys and R +# decoded with y < p, S < L, and [S]B = R + [k]A checked by encoding [S]B − [k]A. +_P = 2 ** 255 - 19 +_L = 2 ** 252 + 27742317777372353535851937790883648493 +_D = -121665 * pow(121666, _P - 2, _P) % _P +_I = pow(2, (_P - 1) // 4, _P) + + +def _recover_x(y, sign): + if y >= _P: + return None + x2 = (y * y - 1) * pow(_D * y * y + 1, _P - 2, _P) % _P + if x2 == 0: + return None if sign else 0 + x = pow(x2, (_P + 3) // 8, _P) + if (x * x - x2) % _P: + x = x * _I % _P + if (x * x - x2) % _P: + return None + return _P - x if (x & 1) != sign else x + + +def _decode(b): + y = int.from_bytes(b, "little") + sign, y = y >> 255, y & ((1 << 255) - 1) + x = _recover_x(y, sign) + return None if x is None else (x, y, 1, x * y % _P) + + +def _add(p, q): + a = (p[1] - p[0]) * (q[1] - q[0]) % _P + b = (p[1] + p[0]) * (q[1] + q[0]) % _P + c = 2 * p[3] * q[3] * _D % _P + d = 2 * p[2] * q[2] % _P + e, f, g, h = b - a, d - c, d + c, b + a + return (e * f % _P, g * h % _P, f * g % _P, e * h % _P) + + +def _mul(s, p): + q = (0, 1, 1, 0) + while s: + if s & 1: + q = _add(q, p) + p = _add(p, p) + s >>= 1 + return q + + +def _encode(p): + zi = pow(p[2], _P - 2, _P) + x, y = p[0] * zi % _P, p[1] * zi % _P + return (y | ((x & 1) << 255)).to_bytes(32, "little") + + +_BY = 4 * pow(5, _P - 2, _P) % _P +_B = (_recover_x(_BY, 0), _BY, 1, _recover_x(_BY, 0) * _BY % _P) + + +def ed25519_verify(pk, msg, sig): + a = _decode(pk) + if a is None or len(sig) != 64: + return False + s = int.from_bytes(sig[32:], "little") + if s >= _L: + return False + k = int.from_bytes(hashlib.sha512(sig[:32] + pk + msg).digest(), "little") % _L + neg_a = (_P - a[0], a[1], a[2], _P - a[3]) + return _encode(_add(_mul(s, _B), _mul(k, neg_a))) == sig[:32] + + +RECEIPT_KNOWN = {"kind", "manifest_hash", "session_class_hash", "class", "segments", "merkle_root", "work_compose_ops", "viewers_served", + "joules_micro", "grant", "parent_receipt_hash", "signer_pubkey", "signer_id", "sig", "render", "extras", + "energy_provenance", "energy_uncertainty", "energy_counter_filtering", "energy_calibration_ref"} +LEGACY_CLASSES = {"HwShunt": 1, "ModelBased": 2, "Estimator": 3} + + +class ReceiptMalformed(Exception): + pass + + +class NotUnsigned: + """A JSON number with a sign, a fraction or an exponent: never an unsigned integer of + the receipt file, whatever its value (so "-0" and "1.0" are not 0 and 1).""" + + +def read_receipt(text): + """The receipt file (§8.6), read strictly; ReceiptMalformed otherwise. A number is an + unsigned integer written as digits alone.""" + try: + o = json.loads(text, parse_int=lambda t: NotUnsigned() if t.startswith("-") else int(t), parse_float=lambda t: NotUnsigned()) + except ValueError: + raise ReceiptMalformed() + bad = ReceiptMalformed + + def obj(v, n): + if not isinstance(v, dict) or len(v) != n: + raise bad() + return v + + def u64(v): + if isinstance(v, bool) or not isinstance(v, int) or not 0 <= v <= U64: + raise bad() + return v + + def s(v): + if not isinstance(v, str): + raise bad() + return v + + def hx(v, n): + if not isinstance(v, str) or not re.fullmatch("[0-9a-f]{%d}" % (2 * n), v): + raise bad() + return bytes.fromhex(v) + + def opt(v, f): + return None if v is None else f(v) + + if not isinstance(o, dict) or not set(o) <= RECEIPT_KNOWN or o.get("kind") != "wai.video.manifest": + raise bad() + for k in ("manifest_hash", "session_class_hash", "class", "segments", "merkle_root", "work_compose_ops", "viewers_served", + "joules_micro", "grant", "parent_receipt_hash", "signer_pubkey", "signer_id", "sig"): + if k not in o: + raise bad() + cl = obj(o["class"], 6) + if not isinstance(cl.get("blackouts"), list): + raise bad() + klass = {"content_id": s(cl.get("content_id")), "entitlement": s(cl.get("entitlement")), "geo": s(cl.get("geo")), + "blackouts": [s(b) for b in cl["blackouts"]], "ad_pod_id": s(cl.get("ad_pod_id")), "window": u64(cl.get("window"))} + segs = [] + if not isinstance(o["segments"], list): + raise bad() + for x in o["segments"]: + x = obj(x, 5) + if x.get("kind") not in ("content", "ad") or not (x.get("ad_id") is None or isinstance(x.get("ad_id"), str)) or "ad_id" not in x: + raise bad() + segs.append({"index": u64(x.get("index")), "ad": x["kind"] == "ad", "uri": s(x.get("uri")), "ms": u64(x.get("duration_ms")), "ad_id": x["ad_id"]}) + g = obj(o["grant"], 4) + if "funds_ceiling" not in g: + raise bad() + grant = {"hash": hx(g.get("grant_hash"), 32), "capability": s(g.get("capability")), "ceiling": u64(g.get("joule_ceiling_micro")), "funds": opt(g["funds_ceiling"], u64)} + label = o.get("energy_provenance") + if label is None: + if any(o.get(k) is not None for k in ("energy_uncertainty", "energy_counter_filtering", "energy_calibration_ref")): + raise bad() + prov = None + elif label in LEGACY_CLASSES and all(o.get(k) is None for k in ("energy_uncertainty", "energy_counter_filtering", "energy_calibration_ref")): + prov = label + else: + raise SystemExit("http_verify: an energy class this verifier does not read") + render_, extras = None, [] + if ("render" in o) != ("extras" in o): + raise bad() + if "render" in o: + r = obj(o["render"], 8) + if not isinstance(r.get("can_block_reload"), bool) or not isinstance(r.get("end_list"), bool): + raise bad() + render_ = {"version": u64(r.get("version")), "seq": u64(r.get("media_sequence")), "dseq": u64(r.get("discontinuity_sequence")), + "target": opt(r.get("target_duration_s"), u64), "part_target": opt(r.get("part_target_ms"), u64), + "hold_back": opt(r.get("part_hold_back_ms"), u64), "block": r["can_block_reload"], "end": r["end_list"]} + for k in ("target_duration_s", "part_target_ms", "part_hold_back_ms"): + if k not in r: + raise bad() + if render_["version"] > 255 or not isinstance(o["extras"], list): + raise bad() + + def layers(v): + if not isinstance(v, list): + raise bad() + out = [] + for l in v: + l = obj(l, 2) + n = u64(l.get("n")) + if n > 255: + raise bad() + out.append({"n": n, "uri": s(l.get("uri"))}) + return out + + for e in o["extras"]: + e = obj(e, 5) + if not isinstance(e.get("discontinuity"), bool) or not isinstance(e.get("parts"), list) or "program_date_time" not in e or "preload_hint" not in e: + raise bad() + parts = [] + for q in e["parts"]: + q = obj(q, 4) + if not isinstance(q.get("independent"), bool): + raise bad() + parts.append({"ms": u64(q.get("duration_ms")), "independent": q["independent"], "uri": s(q.get("uri")), "layers": layers(q.get("layers"))}) + extras.append({"discontinuity": e["discontinuity"], "pdt": opt(e["program_date_time"], s), "parts": parts, + "layers": layers(e.get("layers")), "hint": opt(e["preload_hint"], s)}) + return {"manifest_hash": hx(o["manifest_hash"], 32), "class_hash": hx(o["session_class_hash"], 32), "class": klass, "segments": segs, + "merkle_root": hx(o["merkle_root"], 32), "work": u64(o["work_compose_ops"]), "viewers": u64(o["viewers_served"]), + "joules": u64(o["joules_micro"]), "prov": prov, "grant": grant, "parent": opt(o["parent_receipt_hash"], lambda v: hx(v, 32)), + "pubkey": hx(o["signer_pubkey"], 32), "signer_id": s(o["signer_id"]), "sig": hx(o["sig"], 64), "render": render_, "extras": extras, + "render_json": (o.get("render"), o.get("extras"))} + + +def b3(*parts): + return blake3(b"".join(parts)) + + +def be(n, width): + return n.to_bytes(width, "big") + + +def lp(s): + b = s.encode("utf-8") + return be(len(b), 4) + b + + +def segment_leaf(sg): + ad = (b"\x01" + lp(sg["ad_id"])) if sg["ad_id"] is not None else b"\x00" + return b3(b"wai:video-manifest-seg\x01", be(sg["index"], 8), bytes([2 if sg["ad"] else 1]), be(sg["ms"], 8), lp(sg["uri"]), ad) + + +def merkle_root(hashes): + if not hashes: + return bytes(32) + level = [b3(b"moq-jwp:merkle-leaf\x01", h) for h in hashes] + while len(level) > 1: + if len(level) % 2: + level.append(level[-1]) + level = [b3(b"moq-jwp:merkle-node\x01", level[i], level[i + 1]) for i in range(0, len(level), 2)] + return level[0] + + +def class_hash(c): + bs = sorted(set(c["blackouts"]), key=lambda x: x.encode("utf-8")) + out = [b"wai:video-manifest-class\x01"] + [lp(c[k]) for k in ("content_id", "entitlement", "geo", "ad_pod_id")] + out.append(be(len(bs), 4)) + out += [lp(b) for b in bs] + out.append(be(c["window"], 8)) + return b3(*out) + + +def render_digest(r): + """BLAKE3 of the render domain and the JCS of {"render", "extras"}, as the receipt + file carries them; None without a rendering or past the canonical subset.""" + if r["render"] is None: + return None + try: + c = jcs({"render": r["render_json"][0], "extras": r["render_json"][1]}) + except ValueError: + return None + return b3(b"wai:video-manifest-render\x01", c.encode("utf-8")) + + +def signing_payload(r): + o = [b"wai:video-manifest-receipt\x01", r["manifest_hash"], r["class_hash"], r["merkle_root"], be(r["work"], 8), be(r["viewers"], 8), be(r["joules"], 8), + bytes([LEGACY_CLASSES[r["prov"]] if r["prov"] else 0]), r["grant"]["hash"], lp(r["grant"]["capability"]), be(r["grant"]["ceiling"], 8), + (b"\x01" + be(r["grant"]["funds"], 8)) if r["grant"]["funds"] is not None else b"\x00", + (b"\x01" + r["parent"]) if r["parent"] is not None else b"\x00", be(len(r["segments"]), 8)] + o += [segment_leaf(sg) for sg in r["segments"]] + o += [r["pubkey"], lp(r["signer_id"])] + if r["render"] is not None and MUTATE != "render-digest-off": + d = render_digest(r) + o.append(b"\x01" + d if d is not None else b"\x02") + return b"".join(o) + + +def receipt_hash(r): + return b3(b"wai:video-manifest-receipt-id\x01", signing_payload(r), r["sig"]) + + +def rendered(r): + rr = r["render"] or {"version": 6, "seq": 0, "dseq": 0, "target": None, "part_target": None, "hold_back": None, "block": False, "end": True} + return render_hls(r["segments"], rr, r["extras"]) + + +def receipt_verifies(r): + if r["render"] is None and r["extras"]: + return False + if r["render"] is not None: + try: + check_render(r["segments"], r["render"], r["extras"]) + except Refusal: + return False + if render_digest(r) is None: + return False + if blake3(rendered(r).encode("utf-8")) != r["manifest_hash"]: + return False + if merkle_root([segment_leaf(sg) for sg in r["segments"]]) != r["merkle_root"] or class_hash(r["class"]) != r["class_hash"]: + return False + if r["work"] != len(r["segments"]) or r["viewers"] == 0: + return False + if any(sg["index"] != i or sg["ad"] != (sg["ad_id"] is not None) for i, sg in enumerate(r["segments"])): + return False + if (r["prov"] is None) != (r["joules"] == 0): + return False + return ed25519_verify(r["pubkey"], signing_payload(r), r["sig"]) + + +def check_playlist(playlist, receipt_bytes, name): + """§8.6, §11.6, in order: the file reads; its name; the bytes; the receipt.""" + try: + r = read_receipt(receipt_bytes.decode("utf-8")) + except (ReceiptMalformed, UnicodeDecodeError): + raise Refusal("playlist_receipt_malformed") + if name is not None and name != r["manifest_hash"].hex() + ".json" and MUTATE != "name-unchecked": + raise Refusal("receipt_name_mismatch") + if blake3(playlist) != r["manifest_hash"] or rendered(r).encode("utf-8") != playlist: + raise Refusal("playlist_receipt_mismatch") + if not receipt_verifies(r): + raise Refusal("playlist_receipt_invalid") + return r + + +def chained(prev, cur): + return (cur["parent"] == receipt_hash(prev) or MUTATE == "chain-unchecked") and cur["pubkey"] == prev["pubkey"] + + +# ---------------------------------------------------- the playlist-side cases + +def edit_text(t, edits): + for e in edits or []: + if e[0] not in t: + raise SystemExit("http_verify: a case edits text its file does not hold") + t = t.replace(e[0], e[1], 1) + return t + + +def read_text(d, path): + return open(os.path.join(d, path), "rb").read().decode("utf-8") + + +def layer_of(env): + h = whole_head(env) + if not is_layer(h): + raise Refusal("not_a_layer") + try: + r = SV.parse_refines(h["manifest"].get("refines")) + except SV.Malformed: + raise Refusal("not_a_layer") + return {"n": r["layer"], "cid": content_hash(env), "len": len(env)} + + +def parts_of(objs, ps, cms): + out = [] + for j, p in enumerate(ps or []): + env = objs[p["object"]] + ms = cms[j] if j < len(cms) else p.get("duration_ms") + if not isinstance(ms, int): + raise SystemExit("http_verify: a part with no duration") + whole_head(env) + out.append({"alias": p["alias"], "cid": content_hash(env), "n": len(env), "ms": ms, "independent": p.get("independent") is True, + "layers": [layer_of(objs[x]) for x in p.get("layers") or []]}) + return out + + +def render_media_case(d, objs, inp): + c = parse_media_playlist(read_text(d, inp["classical"])) + segs = [] + for i, s in enumerate(inp["segments"]): + k = c["segments"][i] if i < len(c["segments"]) else None + env = objs[s["object"]] + h = whole_head(env) + w = {"cid": content_hash(env), "n": len(env), "h": covered_len(h["head_len"], len(env)), + "ms": s["duration_ms"] if isinstance(s.get("duration_ms"), int) else (k["ms"] if k else 0)} + w["layers"] = [layer_of(objs[x]) for x in s.get("layers") or []] + w["parts"] = parts_of(objs, s.get("parts"), [q["ms"] for q in k["extra"]["parts"]] if k else []) + segs.append(w) + open_ = None + if inp.get("open") is not None: + o = inp["open"] + cms = [q["ms"] for q in c["open"]["parts"]] if c["open"] else [] + open_ = {"parts": parts_of(objs, o.get("parts"), cms), "hint": o.get("preload_hint")} + end = inp["end_list"] if isinstance(inp.get("end_list"), bool) else c["end"] + return wai_media_playlist(c, segs, open_, inp.get("uri_prefix") or "", end) + + +def resolve_uri(objs, uri): + base, _, frag = uri.partition("#") + cid = None + for kv in frag.split("&") if frag else []: + k, eq, v = kv.partition("=") + if eq and k == "cid" and re.fullmatch(r"[0-9a-f]{64}", v): + cid = v + if cid is None: + name = base.rsplit("/", 1)[-1] + if name.endswith(".wai") and re.fullmatch(r"[0-9a-f]{64}", name[:-4]): + cid = name[:-4] + b = next((b for b in objs.values() if cid is not None and content_hash(b).hex() == cid), None) + if b is None: + raise SystemExit("http_verify: %s names no corpus object" % uri) + return b + + +def listed_objects(objs, p): + segs, parts, every = [], [], [] + + def extra(e): + for q in e["parts"]: + b = resolve_uri(objs, q["uri"]) + parts.append((len(b), q["ms"])) + every.append(b) + every.extend(resolve_uri(objs, l["uri"]) for l in q["layers"]) + every.extend(resolve_uri(objs, l["uri"]) for l in e["layers"]) + + for s in p["segments"]: + b = resolve_uri(objs, s["uri"]) + segs.append((len(b), s["ms"])) + every.append(b) + extra(s["extra"]) + if p["open"] is not None: + extra(p["open"]) + return segs, parts, every + + +def union_floor_of(every): + so = [seq_object(e) for e in every] + return union_of(so), floor_of(so) + + +def multivariant_case(d, objs, inp): + out = ["#EXTM3U"] + for v in inp["variants"]: + p = parse_media_playlist(read_text(d, v["playlist"])) + segs, parts, every = listed_objects(objs, p) + union, floor = union_floor_of(every) + bw = bandwidths(segs if segs else parts) + if bw is None: + raise SystemExit("http_verify: a variant with no duration") + uri = v["uri"] + if not uri_ok(uri, False) or union[0] != floor[0]: + raise Refusal("playlist_malformed") + line = '#EXT-X-STREAM-INF:BANDWIDTH=%d,AVERAGE-BANDWIDTH=%d,CODECS="%s",X-WAI-FLOOR="%s"' % (bw[0], bw[1], render(union), render(floor)) + if v.get("resolution"): + line += ",RESOLUTION=%dx%d" % tuple(v["resolution"]) + if v.get("frame_rate_milli") is not None: + if v["frame_rate_milli"] == 0: + raise Refusal("playlist_malformed") + line += ",FRAME-RATE=" + secs(v["frame_rate_milli"]) + out += [line, uri] + return "\n".join(out) + "\n" + + +def mpd_case(d, objs, inp): + c = parse_media_playlist(read_text(d, inp["classical"])) + p = parse_media_playlist(read_text(d, inp["playlist"])) + segs, _, every = listed_objects(objs, p) + union, floor = union_floor_of(every) + peak = bandwidths(segs)[0] + return wai_mpd(c, inp.get("period_id") or "p0", inp.get("set_id") or 1, union, floor, inp["rep_id"], peak, [s["uri"] for s in p["segments"]]) + + +def rendered_case(d, inp, t): + b = t.encode("utf-8") + o = {"blake3": blake3(b).hex(), "bytes": len(b)} + if "file" in inp: + o["same_as_file"] = os.path.exists(os.path.join(d, inp["file"])) and open(os.path.join(d, inp["file"]), "rb").read() == b + return o + + +def parsed_case(p, t): + parts = lambda ps: [[q["ms"], q["independent"], [l["n"] for l in q["layers"]]] for q in ps] + return {"version": p["version"], "target_duration_s": p["target"], "media_sequence": p["seq"], "discontinuity_sequence": p["dseq"], + "part_target_ms": p["part_target"], "part_hold_back_ms": p["hold_back"], "can_block_reload": p["block"], "end_list": p["end"], + "segments": [[s["ms"], s["extra"]["discontinuity"], s["extra"]["pdt"], parts(s["extra"]["parts"]), [l["n"] for l in s["extra"]["layers"]], s["uri"]] for s in p["segments"]], + "open": None if p["open"] is None else [p["open"]["discontinuity"], p["open"]["pdt"], parts(p["open"]["parts"])], + "preload_hint": p["hint"], "rendered_form": is_rendered_form(p, t)} + + +def live_case(d, objs, inp): + """§11.4, §11.6 over a live playlist's revisions, and parts fetched by alias.""" + revs, listed, held = [], [], [] + for r in inp["revisions"]: + w = edit_text(read_text(d, r["wai"]), r.get("edit_wai")) + k = read_text(d, r["classical"]) + try: + check_adjacent(w, k) + adjacent = True + except Refusal as e: + adjacent = e.code + rpath = r["receipt"] + name = None if rpath.startswith("cases/") else rpath.rsplit("/", 1)[-1] + try: + rr = check_playlist(w.encode("utf-8"), open(os.path.join(d, rpath), "rb").read(), name) + receipt = "signed" + except Refusal as e: + rr, receipt = None, e.code + if rr is None or (held and held[-1] is None): + ch = None + elif not held: + ch = True + else: + ch = True if chained(held[-1], rr) else "playlist_receipt_unchained" + revs.append({"adjacent": adjacent, "receipt": receipt, "chained": ch}) + listed.append(parse_media_playlist(w)) + held.append(rr) + signed = [] + for k, r in enumerate(revs): + signed.append(r["receipt"] == "signed" and (k == 0 or r["chained"] is True) and (k == 0 or signed[k - 1])) + fetches = [] + for f in inp.get("fetches") or []: + served = apply_patch(objs[f["object"]], f.get("patch_object")) + first = next(((k, c) for k, p in enumerate(listed) for c in [listed_cid(p, f["alias"])] if c is not None), None) + if first is None: + fetches.append({"alias": f["alias"], "listed_in": None, "outcome": "not_listed", "held": None}) + elif content_hash(served) == first[1]: + fetches.append({"alias": f["alias"], "listed_in": first[0], "outcome": "accepted", "held": "signed" if signed[first[0]] else "listed"}) + else: + fetches.append({"alias": f["alias"], "listed_in": first[0], "outcome": "object_digest_mismatch", "held": None}) + return {"revisions": revs, "fetches": fetches} + + +def playlist_evaluate(d, objs, op, inp, obj): + """The playlist and adaptation ops; None for any other op.""" + if op == "media_playlist": + return rendered_case(d, inp, render_media_case(d, objs, inp)) + if op == "multivariant": + return rendered_case(d, inp, multivariant_case(d, objs, inp)) + if op == "mpd": + return rendered_case(d, inp, mpd_case(d, objs, inp)) + if op == "playlist_parse": + t = inp["text"] if "text" in inp else read_text(d, inp["file"]) + return parsed_case(parse_media_playlist(t), t) + inline = lambda k: inp[k + "_text"] if isinstance(inp.get(k + "_text"), str) else read_text(d, inp[k]) + if op == "adjacency": + check_adjacent(edit_text(inline("wai"), inp.get("edit_wai")), edit_text(inline("classical"), inp.get("edit_classical"))) + return {"adjacent": True} + if op == "unchanged": + classical_unchanged(inline("original"), edit_text(inline("published"), inp.get("edit_published"))) + return {"unchanged": True} + if op == "render": + segs = [{"ms": x[0], "uri": x[1], "ad": x[2] is True} for x in inp["segments"]] + r = inp["render"] + rr = {"version": r["version"] % 256, "seq": r["media_sequence"], "dseq": r["discontinuity_sequence"], "target": r["target_duration_s"], + "part_target": r["part_target_ms"], "hold_back": r["part_hold_back_ms"], "block": r["can_block_reload"] is True, "end": r["end_list"] is True} + txt = lambda v: v if isinstance(v, str) else None + lays = lambda ls: [{"n": l["n"] % 256, "uri": l["uri"]} for l in ls or []] + extras = [{"discontinuity": e.get("discontinuity") is True, "pdt": txt(e.get("program_date_time")), + "parts": [{"ms": q["duration_ms"], "independent": q.get("independent") is True, "uri": q["uri"], "layers": lays(q.get("layers"))} for q in e.get("parts") or []], + "layers": lays(e.get("layers")), "hint": txt(e.get("preload_hint"))} for e in inp.get("extras") or []] + check_render(segs, rr, extras) + t = render_hls(segs, rr, extras).encode("utf-8") + return {"blake3": blake3(t).hex(), "bytes": len(t)} + if op == "ilog2": + return {"log2_q16": ilog2_q16(inp["a"], inp["b"])} + if op == "playlist_receipt": + pl = edit_text(read_text(d, inp["playlist"]), inp.get("edit_playlist")).encode("utf-8") + rc = edit_text(read_text(d, inp["receipt"]), inp.get("edit_receipt")).encode("utf-8") + name = inp["name"] if isinstance(inp.get("name"), str) else (inp["receipt"].rsplit("/", 1)[-1] if inp.get("name") is None else None) + r = check_playlist(pl, rc, name) + return {"signed": receipt_hash(r).hex(), "parent": r["parent"].hex() if r["parent"] is not None else None} + if op == "live": + return live_case(d, objs, inp) + if op == "abr": + k, b = decide(inp["policy"], abr_state(inp["state"])) + return {"choice": k, "budget": b} + if op == "abr_order": + return {"order": order_entries(whole_head(obj(inp)), inp.get("advertises") or [], inp["policy"], abr_state(inp["state"]))} + if op == "depth": + layers = [len(objs[i]) for i in inp["objects"]] if "objects" in inp else inp["layers_n"] + k, b = depth(layers, inp["policy"], abr_state(inp["state"])) + return {"depth": k, "budget": b} + return None def refusal(e): @@ -1283,7 +2529,7 @@ def evaluate(d, objs, op, inp): h = whole_head(b) pay = [b[s:s + ln] for s, ln in h["components"]] adv, _, held, order, _, _ = sink_of(d, inp, objs) - order = order if order is not None else list(range(len(pay))) + order = order(h) if callable(order) else (order if order is not None else list(range(len(pay)))) try: i, sk = select(h["manifest"], order, adv, held, lambda i, e: payload_ok(e, pay[i])) return {"pick": i, "skipped": sk} @@ -1316,6 +2562,9 @@ def evaluate(d, objs, op, inp): return {"supports": supports(parse_element(inp["floor"]), inp["containers"], inp["caps"])} if op == "layer_tag": return parse_layer_tag(inp["line"]) + r = playlist_evaluate(d, objs, op, inp, obj) + if r is not None: + return r except Refusal as e: if op == "same_pick" and e.code == "inert": try: diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors.rs index ce5f62bac..ad5b200b3 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors.rs @@ -1453,6 +1453,7 @@ fn security(s: &mut u64) -> Option<&'static str> { fn fuzz(dir: &Path, seed: u64, count: usize) -> Result<(Vec, usize, BTreeMap), String> { let mut s = seed; let corpus = Corpus { dir: dir.to_path_buf(), objects: BTreeMap::new(), extra: BTreeMap::new() }; + let fuzz_corpus = Corpus { dir: dir.to_path_buf(), objects: BTreeMap::new(), extra: BTreeMap::new() }; let mut out: Vec<(String, &str, Value, Value)> = Vec::new(); let mut stats: BTreeMap = BTreeMap::new(); let mut push = |name: String, op: &'static str, input: Value| -> Result<(), String> { @@ -1693,6 +1694,11 @@ fn fuzz(dir: &Path, seed: u64, count: usize) -> Result<(Vec, usize, BTreeMap push(format!("fuzz_whole_refine_{i}"), "fetch", json!({ "envelope_hex": hex(&raw_envelope(true, &m4, &[nb, nr])), "advertises": ["wai.video.av1"], "applies": ["wai.video.int_refine"], "threshold_permille": permille }))?; } + // The playlist side: playlists read, held adjacent, the classical master left + // unchanged, renderings, adaptation choices, the integer log, receipt files. + for (name, op, input) in playlists::fuzz_cases(&fuzz_corpus, &mut s, i)? { + push(name, op, input)?; + } } let n = out.len(); Ok((json_lines(&out), n, stats)) diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index adec4bd33..aaa52d385 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -152,6 +152,51 @@ fn published_master(original: &str) -> Result { // -------------------------------------------------------------- evaluation helpers +/// `_text` written inline, or the file `` names. +fn inline_or_file(c: &Corpus, input: &Value, key: &str) -> Result { + match input[format!("{key}_text").as_str()].as_str() { + Some(t) => Ok(t.to_string()), + None => text(c, input[key].as_str().ok_or_else(|| Stop::Fault(format!("{key}")))?), + } +} + +/// A rendering's inputs written inline: `segments` (`[duration_ms, uri, ad]`), `render` +/// and `extras` in the receipt file's JSON forms. +fn render_inputs(input: &Value) -> Result<(Vec, wai::hls_render::HlsRender, Vec), Stop> { + use wai::hls_render::{HlsRender, LayerLine, PartLine, Segment, SegmentExtra, SegmentKind}; + let u = |v: &Value, what: &str| v.as_u64().ok_or_else(|| Stop::Fault(format!("{what} is an unsigned integer"))); + let opt = |v: &Value, what: &str| if v.is_null() { Ok(None) } else { u(v, what).map(Some) }; + let st = |v: &Value| v.as_str().map(String::from); + let segs = input["segments"].as_array().ok_or("segments")?.iter().enumerate().map(|(i, x)| { + let ad = x[2].as_bool() == Some(true); + Ok(Segment { index: i as u64, kind: if ad { SegmentKind::Ad } else { SegmentKind::Content }, uri: st(&x[1]).ok_or("uri")?, duration_ms: u(&x[0], "duration")?, + ad_id: ad.then(|| "ad".to_string()) }) + }).collect::, Stop>>()?; + let r = &input["render"]; + let render = HlsRender { + version: u(&r["version"], "version")? as u8, media_sequence: u(&r["media_sequence"], "media_sequence")?, + discontinuity_sequence: u(&r["discontinuity_sequence"], "discontinuity_sequence")?, target_duration_s: opt(&r["target_duration_s"], "target")?, + part_target_ms: opt(&r["part_target_ms"], "part target")?, part_hold_back_ms: opt(&r["part_hold_back_ms"], "hold back")?, + can_block_reload: r["can_block_reload"].as_bool() == Some(true), end_list: r["end_list"].as_bool() == Some(true), + }; + let layers = |v: &Value| -> Result, Stop> { + v.as_array().into_iter().flatten().map(|l| Ok(LayerLine { n: u(&l["n"], "n")? as u8, uri: st(&l["uri"]).ok_or("layer uri")? })).collect() + }; + let extras = input["extras"].as_array().into_iter().flatten().map(|e| { + Ok(SegmentExtra { + discontinuity: e["discontinuity"].as_bool() == Some(true), + program_date_time: st(&e["program_date_time"]), + parts: e["parts"].as_array().into_iter().flatten().map(|q| Ok(PartLine { + duration_ms: u(&q["duration_ms"], "part duration")?, independent: q["independent"].as_bool() == Some(true), + uri: st(&q["uri"]).ok_or("part uri")?, layers: layers(&q["layers"])?, + })).collect::>()?, + layers: layers(&e["layers"])?, + preload_hint: st(&e["preload_hint"]), + }) + }).collect::, Stop>>()?; + Ok((segs, render, extras)) +} + fn text(c: &Corpus, path: &str) -> Result { String::from_utf8(c.file(path)?).map_err(|_| Stop::Fault(format!("{path}: not UTF-8"))) } @@ -363,19 +408,29 @@ pub(super) fn outcome(c: &Corpus, op: &str, input: &Value) -> Option (|| { - let w = edit(text(c, input["wai"].as_str().ok_or("wai")?)?, &input["edit_wai"])?; - let k = edit(text(c, input["classical"].as_str().ok_or("classical")?)?, &input["edit_classical"])?; + let w = edit(inline_or_file(c, input, "wai")?, &input["edit_wai"])?; + let k = edit(inline_or_file(c, input, "classical")?, &input["edit_classical"])?; check_adjacent(&w, &k).map_err(|e| pl_err(&e))?; Ok(json!({ "adjacent": true })) })(), "unchanged" => (|| { - let o = text(c, input["original"].as_str().ok_or("original")?)?; - let p = edit(text(c, input["published"].as_str().ok_or("published")?)?, &input["edit_published"])?; + let o = inline_or_file(c, input, "original")?; + let p = edit(inline_or_file(c, input, "published")?, &input["edit_published"])?; classical_unchanged(&o, &p).map_err(|e| pl_err(&e))?; Ok(json!({ "unchanged": true })) })(), "playlist_receipt" => receipt_outcome(c, input), "live" => live_outcome(c, input), + "render" => (|| { + let (segs, r, extras) = render_inputs(input)?; + wai::hls_render::check_render(&segs, &r, &extras).map_err(|e| Stop::Refused(json!({ "error": e.code() })))?; + let t = wai::hls_render::render_hls_with(&segs, &r, &extras); + Ok(json!({ "blake3": blake3::hash(t.as_bytes()).to_hex().to_string(), "bytes": t.len() })) + })(), + "ilog2" => (|| { + let (a, b) = (input["a"].as_u64().ok_or("a")?, input["b"].as_u64().ok_or("b")?); + Ok(json!({ "log2_q16": abr::ilog2_q16(a, b) })) + })(), "abr" => (|| { let p = policy(&input["policy"])?; let (k, b) = abr::decide(p.as_ref(), &state(&input["state"])?); @@ -772,6 +827,13 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { ] { v.push(case(name, "abr", json!({ "policy": thr, "state": st(0, 100_000_000, opts.clone(), figs, budget) }))); } + // The integer log every utility rests on, at the table's knots, between them, and at + // the ends of u64. + for (a, b) in [(1u64, 1u64), (2, 1), (3, 2), (17, 16), (31, 16), (33, 32), (1000, 1), (1001, 1000), (65_535, 65_536), (65_537, 65_536), + (u64::MAX, 1), (u64::MAX, u64::MAX - 1), (5, 0), (0, 0), (1 << 40, 3), (123_456_789, 1_000), (7, 3), (400_000, 2_000), + (999_999, 2), (4_097, 4_096), (6_143, 4_096), (12_000, 7_000), (1 << 63, 1), ((1 << 63) + 1, 1 << 62)] { + v.push(case(format!("ilog2_{a}_{b}"), "ilog2", json!({ "a": a, "b": b }))); + } // Entry orders over one WAI2 (SPEC §7.1 step 2) and layer depths (§10.4). let av1 = json!(["wai.video.av1", "wai.meta.hdr10plus"]); for (obj, adv) in [("O3", av1.clone()), ("OD", av1.clone()), ("O9a", json!(["wai.video.av1", "wai.video.int_hyper"])), @@ -821,3 +883,293 @@ pub(super) fn abr_order(input: &Value, advertises: Vec) -> Result diff --git a/wai/http-conformance/playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json b/wai/http-conformance/playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json new file mode 100644 index 000000000..4def4364b --- /dev/null +++ b/wai/http-conformance/playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77","session_class_hash":"28bd23d88c20332da1bf73dc84748f431ca1459b0f6edf73c2a083da2c48c77d","class":{"content_id":"o10","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825","duration_ms":50,"ad_id":null}],"merkle_root":"30497ebce24b685a2c900556166c46c9954bc45600b2a9ab2832343efad972b7","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"ae378cd7bbaed8c6cf074f89701c138672faca5d570e0b9c05d632bfb98b465a760eaa504572640a037acacd91052100bff94b6cccd8cfe23a8f672af0624a0e","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab.wai#n=12711"},{"n":2,"uri":"../../objects/753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c.wai#n=18367"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o10/mr/89f7c647fb786e14ced2c1c8250f30fc105274bfae7e065ecbfa41446b78d296.json b/wai/http-conformance/playlists/o10/mr/89f7c647fb786e14ced2c1c8250f30fc105274bfae7e065ecbfa41446b78d296.json deleted file mode 100644 index c3d64bd3b..000000000 --- a/wai/http-conformance/playlists/o10/mr/89f7c647fb786e14ced2c1c8250f30fc105274bfae7e065ecbfa41446b78d296.json +++ /dev/null @@ -1 +0,0 @@ -{"kind":"wai.video.manifest","manifest_hash":"89f7c647fb786e14ced2c1c8250f30fc105274bfae7e065ecbfa41446b78d296","session_class_hash":"28bd23d88c20332da1bf73dc84748f431ca1459b0f6edf73c2a083da2c48c77d","class":{"content_id":"o10","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825&h=1009","duration_ms":50,"ad_id":null}],"merkle_root":"7e79bdbd00dca71e6672e23d5bf604d75d48151cb8a0279dbba5ce023e27d067","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"0d17331fe52f1cda1f5a8564b346011e1050642258db19e8c7066e2e83b566b236719182ce608bf4665ab9fd5ef9b5c80ec0efc8bebc91ed8fbf4b07a21dc70f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true},"extras":[{"discontinuity":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab.wai#n=12711"},{"n":2,"uri":"../../objects/753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c.wai#n=18367"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o10/wai-a.m3u8 b/wai/http-conformance/playlists/o10/wai-a.m3u8 index df7d021ae..85645fcaa 100644 --- a/wai/http-conformance/playlists/o10/wai-a.m3u8 +++ b/wai/http-conformance/playlists/o10/wai-a.m3u8 @@ -5,5 +5,5 @@ #EXTINF:0.050, #WAI-LAYER:N=1,URI="../../objects/53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab.wai#n=12711" #WAI-LAYER:N=2,URI="../../objects/753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c.wai#n=18367" -../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825&h=1009 +../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825 #EXT-X-ENDLIST diff --git a/wai/http-conformance/playlists/o10/wai.m3u8 b/wai/http-conformance/playlists/o10/wai.m3u8 index 4637cbe24..cecfd842c 100644 --- a/wai/http-conformance/playlists/o10/wai.m3u8 +++ b/wai/http-conformance/playlists/o10/wai.m3u8 @@ -1,3 +1,3 @@ #EXTM3U -#EXT-X-STREAM-INF:BANDWIDTH=292000,AVERAGE-BANDWIDTH=292000,CODECS="wai1.audio.flac+audio.int_refine",X-WAI-FLOOR="wai1.audio.flac+audio.int_refine" +#EXT-X-STREAM-INF:BANDWIDTH=5264480,AVERAGE-BANDWIDTH=5264480,CODECS="wai1.audio.flac+audio.int_refine",X-WAI-FLOOR="wai1.audio.flac+audio.int_refine" wai-a.m3u8 diff --git a/wai/http-conformance/playlists/o11/mr/044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79.json b/wai/http-conformance/playlists/o11/mr/044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79.json new file mode 100644 index 000000000..bea3f71ef --- /dev/null +++ b/wai/http-conformance/playlists/o11/mr/044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79","session_class_hash":"7ecee2c182c9807140e70aa35a6abc8888ea610c8497def214626e2cdc30102b","class":{"content_id":"o11","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551","duration_ms":200,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523","duration_ms":200,"ad_id":null}],"merkle_root":"3dda1c1caa7d0ab22afbd28e76e6eacbd57aa35c8cbcda8b7427ad12467a9560","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"3a35c0bcb9a44f27eab67be767d2ed7effa70dc4d7b14b1d56b65c4137dbc0432428207c9996a1c35834e9309398301c93ad7339a1116785cfebd2659589c20d","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai#n=535"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o11/mr/bc3327099dc8f1121c168b9b9872233eca247c07d0ccf4fd30f6eb633d87acb6.json b/wai/http-conformance/playlists/o11/mr/bc3327099dc8f1121c168b9b9872233eca247c07d0ccf4fd30f6eb633d87acb6.json deleted file mode 100644 index b10c7cb81..000000000 --- a/wai/http-conformance/playlists/o11/mr/bc3327099dc8f1121c168b9b9872233eca247c07d0ccf4fd30f6eb633d87acb6.json +++ /dev/null @@ -1 +0,0 @@ -{"kind":"wai.video.manifest","manifest_hash":"bc3327099dc8f1121c168b9b9872233eca247c07d0ccf4fd30f6eb633d87acb6","session_class_hash":"7ecee2c182c9807140e70aa35a6abc8888ea610c8497def214626e2cdc30102b","class":{"content_id":"o11","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551&h=551","duration_ms":200,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523&h=523","duration_ms":200,"ad_id":null}],"merkle_root":"e8ad08a2388f7a1c781ee512230eeefb5b0e7f2d3adfe861101b15edcd01b43a","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"915116ea2e9e9f317cd26b716c7cd112f68dde83adce981327d15c3d500630100806c9233093353ae71aba95ecce760fb4c1183c3463d9f607d6135a8f2ade06","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true},"extras":[{"discontinuity":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai#n=535"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o11/wai-v.m3u8 b/wai/http-conformance/playlists/o11/wai-v.m3u8 index 4f112f798..b03faa3e2 100644 --- a/wai/http-conformance/playlists/o11/wai-v.m3u8 +++ b/wai/http-conformance/playlists/o11/wai-v.m3u8 @@ -3,8 +3,8 @@ #EXT-X-TARGETDURATION:1 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:0.200, -../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551&h=551 +../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551 #EXTINF:0.200, #WAI-LAYER:N=1,URI="../../objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai#n=535" -../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523&h=523 +../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523 #EXT-X-ENDLIST diff --git a/wai/http-conformance/playlists/o11/wai.m3u8 b/wai/http-conformance/playlists/o11/wai.m3u8 index 427218d58..c6c9abf2f 100644 --- a/wai/http-conformance/playlists/o11/wai.m3u8 +++ b/wai/http-conformance/playlists/o11/wai.m3u8 @@ -1,3 +1,3 @@ #EXTM3U -#EXT-X-STREAM-INF:BANDWIDTH=22040,AVERAGE-BANDWIDTH=21480,CODECS="wai1.video.int_motion+video.int_refine",X-WAI-FLOOR="wai1.video.int_motion" +#EXT-X-STREAM-INF:BANDWIDTH=42320,AVERAGE-BANDWIDTH=32180,CODECS="wai1.video.int_motion+video.int_refine",X-WAI-FLOOR="wai1.video.int_motion" wai-v.m3u8 diff --git a/wai/http-conformance/playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json b/wai/http-conformance/playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json deleted file mode 100644 index babff4059..000000000 --- a/wai/http-conformance/playlists/o9/mr/01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f.json +++ /dev/null @@ -1 +0,0 @@ -{"kind":"wai.video.manifest","manifest_hash":"01997f6163b95bdcc9d3edb37d5885627ec17670de4fc42d88f2513d0d5bb85f","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004&h=1009","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381&h=1009","duration_ms":640,"ad_id":null}],"merkle_root":"aa4a88ea7452da29f8fed31b9ef9127adfc8f00a4ff7abe41ff1ae496c52141f","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"38ad4757b0dd56bf965b1704983d0c34fc6d8ea5a77bd208220143ea3da998cecf2104542fa6822266f6a640f3beb0b681f6c7c5ef7647a0492ec4a98afaa309","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true},"extras":[]} diff --git a/wai/http-conformance/playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json b/wai/http-conformance/playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json new file mode 100644 index 000000000..ca7df89fc --- /dev/null +++ b/wai/http-conformance/playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"319206c1fef8bab9685bfda23964e51d515afd41f5b893cdd635a959de4a434e400f074ea8605aca48293f6b6b8b1fe7fb4582477f191078cb24c393e22d840f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/playlists/o9/wai-180-gap.m3u8 b/wai/http-conformance/playlists/o9/wai-180-gap.m3u8 new file mode 100644 index 000000000..5ab9a8b09 --- /dev/null +++ b/wai/http-conformance/playlists/o9/wai-180-gap.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:1 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:0.640, +../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009 +#EXT-X-GAP +#EXTINF:0.640, +../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004 +#EXTINF:0.640, +../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381 +#EXT-X-ENDLIST diff --git a/wai/http-conformance/playlists/o9/wai-180.m3u8 b/wai/http-conformance/playlists/o9/wai-180.m3u8 index c4e44feb5..e2c19b3da 100644 --- a/wai/http-conformance/playlists/o9/wai-180.m3u8 +++ b/wai/http-conformance/playlists/o9/wai-180.m3u8 @@ -2,10 +2,11 @@ #EXT-X-VERSION:7 #EXT-X-TARGETDURATION:1 #EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD #EXTINF:0.640, ../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009 #EXTINF:0.640, -../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004&h=1009 +../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004 #EXTINF:0.640, -../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381&h=1009 +../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381 #EXT-X-ENDLIST diff --git a/wai/http-conformance/playlists/o9/wai-gap.m3u8 b/wai/http-conformance/playlists/o9/wai-gap.m3u8 new file mode 100644 index 000000000..880013347 --- /dev/null +++ b/wai/http-conformance/playlists/o9/wai-gap.m3u8 @@ -0,0 +1,3 @@ +#EXTM3U +#EXT-X-STREAM-INF:BANDWIDTH=262925,AVERAGE-BANDWIDTH=246344,CODECS="wai2.video.av1|video.int_hyper",X-WAI-FLOOR="wai2.video.av1",RESOLUTION=320x180 +wai-180-gap.m3u8 diff --git a/wai/http-conformance/playlists/o9/wai.mpd b/wai/http-conformance/playlists/o9/wai.mpd index e1fbd3643..2e24f569a 100644 --- a/wai/http-conformance/playlists/o9/wai.mpd +++ b/wai/http-conformance/playlists/o9/wai.mpd @@ -1,6 +1,6 @@ - - + + @@ -12,8 +12,8 @@ - - + + diff --git a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs index 29e80eb5c..d07f0832e 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs @@ -21,7 +21,7 @@ //! { "uri_prefix": "objects/", "end_list": true, //! "variants": [ { "uri": "wai-720.m3u8", "classical": "classical-720.m3u8", //! "resolution": [1280, 720], "frame_rate_milli": 25000, -//! "segments": [ { "object": "a.wai", "layers": ["l1.wai"], +//! "segments": [ { "object": "a.wai", "layers": ["l1.wai"], "gap": false, //! "parts": [ { "object": "p.wai", "alias": "live/720/1200.0.wai", //! "independent": true, "layers": [] } ] } ], //! "open": { "parts": [ … ], "preload_hint": "live/720/1201.2.wai" } } ] } @@ -117,7 +117,10 @@ fn load(path: &Path) -> Result { let p = s["object"].as_str().ok_or("a segment names its object")?; let env = read(&dir.join(p))?; let mut seg = WaiSegment::of(&env, c.duration_ms).map_err(|e| format!("{p}: {e}"))?; - envelopes.push(env); + seg.gap = s["gap"].as_bool() == Some(true); + if !seg.gap { + envelopes.push(env); + } seg.layers = layers(dir, &s["layers"], &mut envelopes)?; let cms: Vec = c.extra.parts.iter().map(|q| q.duration_ms).collect(); seg.parts = parts(dir, &s["parts"], &cms, &mut envelopes)?; @@ -150,10 +153,11 @@ fn union_floor(envs: &[Vec]) -> Result<(codecs::Element, codecs::Element), S Ok((codecs::union_of(&objs).map_err(|e| e.to_string())?, codecs::floor_of(&objs).map_err(|e| e.to_string())?)) } -/// The peak and average of a variant: from its segment objects, or its parts when it -/// has no segment yet. +/// The peak and average of a variant (§11.1): each segment that is not a gap with its +/// layer objects, or its parts when it has no segment yet. fn rates(v: &Variant) -> Result<(u64, u64), String> { - let mut objs: Vec<(u64, u64)> = v.segments.iter().map(|s| (s.n, s.duration_ms)).collect(); + let mut objs: Vec<(u64, u64)> = v.segments.iter().filter(|s| !s.gap) + .map(|s| (s.n + s.layers.iter().map(|l| l.len).sum::(), s.duration_ms)).collect(); if objs.is_empty() { objs = v.open.iter().flat_map(|o| o.parts.iter().map(|p| (p.n, p.duration_ms))).collect(); } diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index 6934d37da..29d4d12c5 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -90,6 +90,8 @@ fn media_specs() -> Vec { vec![ json!({ "file": "playlists/o9/wai-180.m3u8", "classical": "src/playlists/o9-classical.m3u8", "uri_prefix": "../../objects/", "segments": [seg("O9a"), seg("O9b"), seg("O9c")] }), + json!({ "file": "playlists/o9/wai-180-gap.m3u8", "classical": "src/playlists/o9-classical.m3u8", "uri_prefix": "../../objects/", + "segments": [seg("O9a"), { "object": "O9b", "gap": true }, seg("O9c")] }), json!({ "file": "playlists/o10/wai-a.m3u8", "classical": "src/playlists/o10-classical.m3u8", "uri_prefix": "../../objects/", "segments": [{ "object": "O10", "layers": ["O10L1", "O10L2"] }] }), json!({ "file": "playlists/o11/wai-v.m3u8", "classical": "src/playlists/o11-classical.m3u8", "uri_prefix": "../../objects/", @@ -111,6 +113,7 @@ fn media_specs() -> Vec { fn multivariant_specs() -> Vec { vec![ json!({ "file": "playlists/o9/wai.m3u8", "variants": [{ "uri": "wai-180.m3u8", "playlist": "playlists/o9/wai-180.m3u8", "resolution": [320, 180], "frame_rate_milli": 25000 }] }), + json!({ "file": "playlists/o9/wai-gap.m3u8", "variants": [{ "uri": "wai-180-gap.m3u8", "playlist": "playlists/o9/wai-180-gap.m3u8", "resolution": [320, 180] }] }), json!({ "file": "playlists/o10/wai.m3u8", "variants": [{ "uri": "wai-a.m3u8", "playlist": "playlists/o10/wai-a.m3u8" }] }), json!({ "file": "playlists/o11/wai.m3u8", "variants": [{ "uri": "wai-v.m3u8", "playlist": "playlists/o11/wai-v.m3u8" }] }), json!({ "file": "live/wai.m3u8", "variants": [{ "uri": "wai-720.m3u8", "playlist": "live/wai-r3.m3u8", "resolution": [32, 18], "frame_rate_milli": 25000 }] }), @@ -178,6 +181,13 @@ fn render_inputs(input: &Value) -> Result<(Vec, wai::h discontinuity_sequence: u(&r["discontinuity_sequence"], "discontinuity_sequence")?, target_duration_s: opt(&r["target_duration_s"], "target")?, part_target_ms: opt(&r["part_target_ms"], "part target")?, part_hold_back_ms: opt(&r["part_hold_back_ms"], "hold back")?, can_block_reload: r["can_block_reload"].as_bool() == Some(true), end_list: r["end_list"].as_bool() == Some(true), + playlist_type: match r["playlist_type"].as_str() { + Some("VOD") => Some(wai::hls_render::PlaylistType::Vod), + Some("EVENT") => Some(wai::hls_render::PlaylistType::Event), + _ => None, + }, + independent_segments: r["independent_segments"].as_bool() == Some(true), + map_uri: st(&r["map_uri"]), }; let layers = |v: &Value| -> Result, Stop> { v.as_array().into_iter().flatten().map(|l| Ok(LayerLine { n: u(&l["n"], "n")? as u8, uri: st(&l["uri"]).ok_or("layer uri")? })).collect() @@ -185,6 +195,7 @@ fn render_inputs(input: &Value) -> Result<(Vec, wai::h let extras = input["extras"].as_array().into_iter().flatten().map(|e| { Ok(SegmentExtra { discontinuity: e["discontinuity"].as_bool() == Some(true), + gap: e["gap"].as_bool() == Some(true), program_date_time: st(&e["program_date_time"]), parts: e["parts"].as_array().into_iter().flatten().map(|q| Ok(PartLine { duration_ms: u(&q["duration_ms"], "part duration")?, independent: q["independent"].as_bool() == Some(true), @@ -253,6 +264,7 @@ fn render_media(c: &Corpus, input: &Value) -> Result { let ms = classical.segments.get(i).map_or(0, |x| x.duration_ms); let env = object_bytes(c, s["object"].as_str().ok_or("a segment's object")?)?; let mut w = WaiSegment::of(&env, s["duration_ms"].as_u64().unwrap_or(ms)).map_err(|e| err_stop(&e))?; + w.gap = s["gap"].as_bool() == Some(true); w.layers = wai_layers(c, &s["layers"])?; let cms: Vec = classical.segments.get(i).map(|x| x.extra.parts.iter().map(|p| p.duration_ms).collect()).unwrap_or_default(); w.parts = wai_parts(c, &s["parts"], &cms)?; @@ -270,9 +282,10 @@ fn render_media(c: &Corpus, input: &Value) -> Result { Ok(t) } -/// The objects a WAI media playlist lists, resolved among the corpus's by cid: its -/// segments' (with their durations), and every object it lists (segments, parts, -/// layers), in the order listed. +/// The objects a WAI media playlist lists, resolved among the corpus's by cid: per +/// segment that is not a gap, the bytes of its object and its layers with its duration; +/// per part, its bytes with its duration; and every object a client may fetch (segments +/// that are not gaps, parts, layers), segment by segment. fn listed_objects(c: &Corpus, p: &MediaPlaylist) -> Result<(Vec<(u64, u64)>, Vec<(u64, u64)>, Vec>), Stop> { let resolve = |uri: &str| -> Result, Stop> { let (base, frag) = parse_fragment(uri); @@ -296,9 +309,16 @@ fn listed_objects(c: &Corpus, p: &MediaPlaylist) -> Result<(Vec<(u64, u64)>, Vec Ok(()) }; for s in &p.segments { + if s.extra.gap { + continue; + } let b = resolve(&s.uri)?; - segs.push((b.len() as u64, s.duration_ms)); + let mut n = b.len() as u64; all.push(b); + for l in &s.extra.layers { + n += resolve(&l.uri)?.len() as u64; + } + segs.push((n, s.duration_ms)); extra(&s.extra, &mut parts, &mut all)?; } if let Some(o) = &p.open { @@ -356,7 +376,8 @@ fn parsed(p: &MediaPlaylist, t: &str) -> Value { "version": p.version, "target_duration_s": p.target_duration_s, "media_sequence": p.media_sequence, "discontinuity_sequence": p.discontinuity_sequence, "part_target_ms": p.part_target_ms, "part_hold_back_ms": p.part_hold_back_ms, "can_block_reload": p.can_block_reload, "end_list": p.end_list, - "segments": p.segments.iter().map(|s| json!([s.duration_ms, s.extra.discontinuity, s.extra.program_date_time, parts(&s.extra.parts), layers(&s.extra.layers), s.uri])).collect::>(), + "playlist_type": p.playlist_type.map(|t| t.label()), "independent_segments": p.independent_segments, "map_uri": p.map_uri, + "segments": p.segments.iter().map(|s| json!([s.duration_ms, s.extra.discontinuity, s.extra.program_date_time, parts(&s.extra.parts), layers(&s.extra.layers), s.uri, s.extra.gap])).collect::>(), "open": p.open.as_ref().map(|o| json!([o.discontinuity, o.program_date_time, parts(&o.parts)])), "preload_hint": p.preload_hint, "rendered_form": p.is_rendered_form(t), @@ -669,7 +690,7 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { "open": { "parts": [{ "object": part_id(1, 0), "alias": "p.wai", "independent": true, "duration_ms": 320 }] } }), "adjacency_misaligned")); // Reading: every WAI and classical playlist, and malformed ones. - for f in ["playlists/o9/wai-180.m3u8", "playlists/o10/wai-a.m3u8", "playlists/o11/wai-v.m3u8", "live/wai-r0.m3u8", "live/wai-r1.m3u8", + for f in ["playlists/o9/wai-180.m3u8", "playlists/o9/wai-180-gap.m3u8", "playlists/o10/wai-a.m3u8", "playlists/o11/wai-v.m3u8", "live/wai-r0.m3u8", "live/wai-r1.m3u8", "live/wai-r2.m3u8", "live/wai-r3.m3u8", "players/wai-v0.m3u8", "src/live/classical-r1.m3u8", "players/classical/v0.m3u8", "src/playlists/o9-classical.m3u8"] { v.push(case(format!("playlist_parse_{}", f.replace(['/', '.', '-'], "_")), "playlist_parse", json!({ "file": f }))); @@ -689,6 +710,8 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { ("playlist_parse_negative", "#EXTM3U\n#EXTINF:-1,\na\n"), ("playlist_parse_exponent", "#EXTM3U\n#EXTINF:1e3,\na\n"), ("playlist_parse_block_maybe", "#EXTM3U\n#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=MAYBE\n"), + ("playlist_parse_type_live", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:LIVE\n"), + ("playlist_parse_map_no_uri", "#EXTM3U\n#EXT-X-MAP:BYTERANGE=\"1@0\"\n"), ] { v.push(refused(name, "playlist_parse", json!({ "text": t }), "playlist_malformed")); } @@ -697,13 +720,14 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { ("playlist_parse_rounding", "#EXTM3U\n#EXTINF:3.9995,\na\n#EXTINF:3.99949,\nb\n#EXTINF:4.00008,\nc\n#EXTINF:4,\nd\n"), ("playlist_parse_crlf_title", "#EXTM3U\r\n#EXTINF:0.64,a title\r\nseg.m4s\r\n#EXT-X-DISCONTINUITY\r\n#EXTINF:1,\r\nseg2.m4s\r\n"), ("playlist_parse_hint_only", "#EXTM3U\n#EXT-X-PART-INF:PART-TARGET=1\n#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"p.0\"\n#EXT-X-PRELOAD-HINT:TYPE=MAP,URI=\"i\"\n"), + ("playlist_parse_packager_lines", "#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-TARGETDURATION:3\n#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-PLAYLIST-TYPE:EVENT\n#EXT-X-INDEPENDENT-SEGMENTS\n#EXT-X-MAP:URI=\"i.mp4\"\n#EXT-X-GAP\n#EXTINF:2.400,\na.m4s\n#EXT-X-MAP:URI=\"j.mp4\"\n#EXTINF:1.000,\nb.m4s\n"), ] { v.push(case(name, "playlist_parse", json!({ "text": t }))); } // Adjacency (§11.2): each WAI playlist beside its classical one, and every way it // can be broken. - for (w, k) in [("playlists/o9/wai-180.m3u8", "src/playlists/o9-classical.m3u8"), ("playlists/o10/wai-a.m3u8", "src/playlists/o10-classical.m3u8"), + for (w, k) in [("playlists/o9/wai-180.m3u8", "src/playlists/o9-classical.m3u8"), ("playlists/o9/wai-180-gap.m3u8", "src/playlists/o9-classical.m3u8"), ("playlists/o10/wai-a.m3u8", "src/playlists/o10-classical.m3u8"), ("playlists/o11/wai-v.m3u8", "src/playlists/o11-classical.m3u8"), ("live/wai-r0.m3u8", "src/live/classical-r0.m3u8"), ("live/wai-r1.m3u8", "src/live/classical-r1.m3u8"), ("live/wai-r2.m3u8", "src/live/classical-r2.m3u8"), ("live/wai-r3.m3u8", "src/live/classical-r3.m3u8"), ("players/wai-v0.m3u8", "players/classical/v0.m3u8")] { @@ -760,7 +784,7 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { v.push(refused("playlist_receipt_playlist_changed", "playlist_receipt", base(json!({ "edit_playlist": [["#EXTINF:0.640,", "#EXTINF:0.641,"]] })), "playlist_receipt_mismatch")); v.push(refused("playlist_receipt_viewers", "playlist_receipt", base(json!({ "edit_receipt": [["\"viewers_served\":1", "\"viewers_served\":2"]] })), "playlist_receipt_invalid")); v.push(refused("playlist_receipt_class", "playlist_receipt", base(json!({ "edit_receipt": [["\"entitlement\":\"public\"", "\"entitlement\":\"svod\""]] })), "playlist_receipt_invalid")); - let default_extra = "{\"discontinuity\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null}"; + let default_extra = "{\"discontinuity\":false,\"gap\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null}"; v.push(refused("playlist_receipt_extras_equivalent", "playlist_receipt", base(json!({ "edit_receipt": [["\"extras\":[]", format!("\"extras\":[{default_extra},{default_extra},{default_extra}]")]] })), "playlist_receipt_invalid")); v.push(refused("playlist_receipt_kind", "playlist_receipt", base(json!({ "edit_receipt": [["\"kind\":\"wai.video.manifest\"", "\"kind\":\"wai.video.package\""]] })), "playlist_receipt_malformed")); v.push(refused("playlist_receipt_unknown_member", "playlist_receipt", base(json!({ "edit_receipt": [["{\"kind\"", "{\"note\":1,\"kind\""]] })), "playlist_receipt_malformed")); @@ -926,7 +950,8 @@ fn playlist_text(s: &mut u64) -> String { 18 => pick(s, &["#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"h.wai\"", "#EXT-X-PRELOAD-HINT:TYPE=MAP,URI=\"m\"", "#EXT-X-PRELOAD-HINT:URI=\"h\""]).into(), 19 => "#EXT-X-ENDLIST".into(), 20 => pick(s, &["#EXT-X-SKIP:SKIPPED-SEGMENTS=1", "#EXT-X-STREAM-INF:BANDWIDTH=1", "#EXTM3U", "#EXT-X-MEDIA:TYPE=AUDIO"]).into(), - 21 => pick(s, &["# a comment", "#WAI-LAYER", "#EXT-X-GAP", "#EXT-X-BYTERANGE:100@0", "#EXT-X-MAP:URI=\"i.mp4\""]).into(), + 21 => pick(s, &["# a comment", "#WAI-LAYER", "#EXT-X-GAP", "#EXT-X-BYTERANGE:100@0", "#EXT-X-MAP:URI=\"i.mp4\"", "#EXT-X-PLAYLIST-TYPE:VOD", + "#EXT-X-PLAYLIST-TYPE:vod", "#EXT-X-INDEPENDENT-SEGMENTS", "#EXT-X-MAP:URI=i.mp4"]).into(), _ => String::new(), }); } @@ -948,6 +973,15 @@ fn structured_playlist(s: &mut u64) -> String { l.push(format!("#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD={},PART-HOLD-BACK=1.5", pick(s, &["YES", "NO"]))); } l.push(format!("#EXT-X-MEDIA-SEQUENCE:{}", next(s) % 3000)); + if next(s) % 2 == 0 { + l.push(pick(s, &["#EXT-X-PLAYLIST-TYPE:VOD", "#EXT-X-PLAYLIST-TYPE:EVENT"]).into()); + } + if next(s) % 2 == 0 { + l.push("#EXT-X-INDEPENDENT-SEGMENTS".into()); + } + if next(s) % 3 == 0 { + l.push("#EXT-X-MAP:URI=\"init.mp4\"".into()); + } for k in 0..next(s) % 4 { if next(s) % 3 == 0 { l.push("#EXT-X-DISCONTINUITY".into()); @@ -963,6 +997,9 @@ fn structured_playlist(s: &mut u64) -> String { } } } + if next(s) % 4 == 0 { + l.push("#EXT-X-GAP".into()); + } l.push(format!("#EXTINF:{},", pick(s, &["4", "3.84", "3.9995", "4.000"]))); for n in 1..=next(s) % 3 { l.push(format!("#WAI-LAYER:N={n},URI=\"l{k}.{n}.wai\"")); @@ -1090,6 +1127,7 @@ fn render_case(s: &mut u64) -> Value { let pdt = [Value::Null, json!("2026-10-01T12:00:00.000Z"), json!(""), json!("a b")][(next(s) % 4) as usize].clone(); json!({ "discontinuity": next(s) % 3 == 0, + "gap": next(s) % 4 == 0, "program_date_time": if next(s) % 2 == 0 { Value::Null } else { pdt }, "parts": parts, "layers": layers(s), "preload_hint": if next(s) % 3 == 0 { json!(pick(s, uris)) } else { Value::Null }, @@ -1100,11 +1138,14 @@ fn render_case(s: &mut u64) -> Value { let hold = pt.map(|t| t * [1u64, 2, 3, 3][(next(s) % 4) as usize] + next(s) % 2); let (target, part_target, hold_back) = (o(s, 4), json!(pt), json!(hold.or(if next(s) % 4 == 0 { Some(900) } else { None }))); let version = [0u64, 6, 7, 9, 9, 9][(next(s) % 6) as usize]; + let playlist_type = [Value::Null, json!("VOD"), json!("EVENT")][(next(s) % 3) as usize].clone(); + let map_uri = [Value::Null, Value::Null, json!("init.mp4"), json!("a\"b")][(next(s) % 4) as usize].clone(); json!({ "segments": segs, "render": { "version": version, "media_sequence": next(s) % 3000, "discontinuity_sequence": next(s) % 3, "target_duration_s": target, "part_target_ms": part_target, "part_hold_back_ms": hold_back, - "can_block_reload": next(s) % 2 == 0, "end_list": next(s) % 2 == 0 }, + "can_block_reload": next(s) % 2 == 0, "end_list": next(s) % 2 == 0, "playlist_type": playlist_type, + "independent_segments": next(s) % 2 == 0, "map_uri": map_uri }, "extras": extras, }) } diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs index dcea26bfc..7e29b8dd0 100644 --- a/wai/wai-rs/src/hls_render.rs +++ b/wai/wai-rs/src/hls_render.rs @@ -47,8 +47,25 @@ pub struct Segment { pub ad_id: Option, } -/// The playlist-wide rendering parameters (the header lines and the end). +/// `EXT-X-PLAYLIST-TYPE`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PlaylistType { + Vod, + Event, +} + +impl PlaylistType { + /// `VOD` or `EVENT`. + pub fn label(self) -> &'static str { + match self { + PlaylistType::Vod => "VOD", + PlaylistType::Event => "EVENT", + } + } +} + +/// The playlist-wide rendering parameters (the header lines and the end). +#[derive(Clone, Debug, PartialEq, Eq)] pub struct HlsRender { /// `EXT-X-VERSION`. pub version: u8, @@ -69,6 +86,13 @@ pub struct HlsRender { pub can_block_reload: bool, /// `EXT-X-ENDLIST`. pub end_list: bool, + /// `EXT-X-PLAYLIST-TYPE`. + pub playlist_type: Option, + /// `EXT-X-INDEPENDENT-SEGMENTS`: every segment decodes without the one before it. + pub independent_segments: bool, + /// `EXT-X-MAP:URI`, once, before the first segment: a classical playlist's + /// initialization section (a WAI playlist has none). + pub map_uri: Option, } impl HlsRender { @@ -84,6 +108,9 @@ impl HlsRender { part_hold_back_ms: None, can_block_reload: false, end_list: true, + playlist_type: None, + independent_segments: false, + map_uri: None, } } } @@ -107,8 +134,8 @@ pub struct PartLine { } /// What a rendering adds to one segment: a discontinuity, a program date-time, the -/// partial segments that precede its URI, the layer lines after its `EXTINF`, and a -/// preload hint (only on the playlist's last entry). +/// partial segments that precede its URI, a gap, the layer lines after its `EXTINF`, and +/// a preload hint (only on the playlist's last entry). /// /// [`render_hls_with`] takes no extras, one per segment, or one per segment and one /// more: the **open** segment at a live edge, whose parts are listed and whose @@ -116,6 +143,9 @@ pub struct PartLine { #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct SegmentExtra { pub discontinuity: bool, + /// `EXT-X-GAP`: the segment is absent here, and a sink takes the adjacent classical + /// segment (a class that takes the piece as floor only). + pub gap: bool, pub program_date_time: Option, pub parts: Vec, pub layers: Vec, @@ -178,12 +208,13 @@ fn part_lines(out: &mut String, parts: &[PartLine]) { /// The playlist `segments` render to under `r`, with `extras` (none, one per segment, /// or one per segment and the open segment's). Each segment is written as: its /// discontinuity (an ad's, or its extra's; one line), its program date-time, its -/// parts (each `EXT-X-PART` followed by its layer lines), its `EXTINF`, its layer -/// lines, and its URI. The open segment follows with its discontinuity, program -/// date-time and parts; then the preload hint and `EXT-X-ENDLIST`. The header is -/// `EXTM3U`, `EXT-X-VERSION`, `EXT-X-TARGETDURATION`, `EXT-X-PART-INF`, -/// `EXT-X-SERVER-CONTROL`, `EXT-X-MEDIA-SEQUENCE` and, when not 0, -/// `EXT-X-DISCONTINUITY-SEQUENCE`. Durations have three fractional digits. +/// parts (each `EXT-X-PART` followed by its layer lines), its `EXT-X-GAP`, its +/// `EXTINF`, its layer lines, and its URI. The open segment follows with its +/// discontinuity, program date-time and parts; then the preload hint and +/// `EXT-X-ENDLIST`. The header is `EXTM3U`, `EXT-X-VERSION`, `EXT-X-TARGETDURATION`, +/// `EXT-X-PART-INF`, `EXT-X-SERVER-CONTROL`, `EXT-X-MEDIA-SEQUENCE`, and, those that +/// apply, `EXT-X-DISCONTINUITY-SEQUENCE` (when not 0), `EXT-X-PLAYLIST-TYPE`, +/// `EXT-X-INDEPENDENT-SEGMENTS` and `EXT-X-MAP`. Durations have three fractional digits. /// /// Rendering never fails; [`check_render`] says whether the inputs read back as /// themselves, and a WAI playlist is published or sealed only when they do. @@ -209,6 +240,15 @@ pub fn render_hls_with(segments: &[Segment], r: &HlsRender, extras: &[SegmentExt if r.discontinuity_sequence != 0 { out.push_str(&format!("#EXT-X-DISCONTINUITY-SEQUENCE:{}\n", r.discontinuity_sequence)); } + if let Some(t) = r.playlist_type { + out.push_str(&format!("#EXT-X-PLAYLIST-TYPE:{}\n", t.label())); + } + if r.independent_segments { + out.push_str("#EXT-X-INDEPENDENT-SEGMENTS\n"); + } + if let Some(m) = &r.map_uri { + out.push_str(&format!("#EXT-X-MAP:URI=\"{m}\"\n")); + } let none = SegmentExtra::default(); for (i, s) in segments.iter().enumerate() { let e = extras.get(i).unwrap_or(&none); @@ -219,6 +259,9 @@ pub fn render_hls_with(segments: &[Segment], r: &HlsRender, extras: &[SegmentExt out.push_str(&format!("#EXT-X-PROGRAM-DATE-TIME:{pdt}\n")); } part_lines(&mut out, &e.parts); + if e.gap { + out.push_str("#EXT-X-GAP\n"); + } // Fixed 3-dp seconds so the byte output is stable across platforms. out.push_str(&format!("#EXTINF:{},\n", seconds(s.duration_ms))); layer_lines(&mut out, &e.layers); @@ -271,7 +314,8 @@ fn check_layers(layers: &[LayerLine], at: &str) -> Result<(), RenderError> { /// - parts need a part target, and a part target needs a part hold-back of at least /// twice it (the HLS second edition requires both); /// - every URI is non-empty, does not begin with `#`, and holds no CR or LF, and a -/// quoted one (a part's, a layer's, a hint's) no `"`; +/// quoted one (a part's, a layer's, a hint's, the map's) no `"`; +/// - the open segment has no gap; /// - a program date-time is ASCII letters, digits and `:.+-` only; /// - layer numbers are 1 to 255 and strictly increasing after each segment or part; /// - the version is at least 1. @@ -283,6 +327,9 @@ pub fn check_render(segments: &[Segment], r: &HlsRender, extras: &[SegmentExtra] if r.version == 0 { return Err(RenderError("version 0".into())); } + if r.map_uri.as_deref().is_some_and(|m| !uri_ok(m, true)) { + return Err(RenderError("the map URI is empty or holds a quote, CR or LF".into())); + } if let Some(pt) = r.part_target_ms { match r.part_hold_back_ms { Some(h) if pt > 0 && h >= pt.saturating_mul(2) => {} @@ -323,8 +370,8 @@ pub fn check_render(segments: &[Segment], r: &HlsRender, extras: &[SegmentExtra] if r.end_list { return Err(RenderError("an open segment in a playlist that ends".into())); } - if !e.layers.is_empty() { - return Err(RenderError("the open segment carries layer lines of its own".into())); + if !e.layers.is_empty() || e.gap { + return Err(RenderError("the open segment carries layer lines or a gap of its own".into())); } if e.parts.is_empty() && (e.discontinuity || e.program_date_time.is_some()) { return Err(RenderError("the open segment's discontinuity or date-time comes before a part".into())); diff --git a/wai/wai-rs/src/http_binding/dash.rs b/wai/wai-rs/src/http_binding/dash.rs index 685d25ba2..449d52bfa 100644 --- a/wai/wai-rs/src/http_binding/dash.rs +++ b/wai/wai-rs/src/http_binding/dash.rs @@ -6,8 +6,10 @@ //! timescale, `t` and `d`), given by the caller, or read from the classical HLS media //! playlist it shares a timeline with ([`DashTimeline::from_hls`]). //! -//! A DASH playlist is not authenticated in this revision: the playlist receipt (§8.6) -//! signs HLS renderings only. +//! [`render_mpd`] writes any static MPD of one period this binding's packagers need — +//! the classical one beside it too — and [`wai_mpd`] is `wai.mpd` on a classical +//! timeline. A DASH playlist is not authenticated in this revision: the playlist +//! receipt (§8.6) signs HLS renderings only. use super::codecs::{self, Element}; use super::hls::{MediaPlaylist, PlaylistError}; @@ -19,11 +21,11 @@ pub const SCHEME_DELIVERY: &str = "tag:transaction.science,2026:wai/http-deliver pub const SCHEME_FLOOR: &str = "tag:transaction.science,2026:wai/floor"; /// The classical timeline a WAI `AdaptationSet` is aligned with: one period's `@id` -/// and `@start`, and its segments' `t` and `d` at `timescale`. +/// and `@start` (whole seconds), and its segments' `t` and `d` at `timescale`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct DashTimeline { pub period_id: String, - pub period_start_ms: u64, + pub period_start_s: u64, pub timescale: u64, pub segments: Vec<(u64, u64)>, } @@ -31,14 +33,20 @@ pub struct DashTimeline { impl DashTimeline { /// The timeline of a classical HLS media playlist at timescale 1000: `t` from 0, /// each `d` a segment's duration in milliseconds. - pub fn from_hls(p: &MediaPlaylist, period_id: &str, period_start_ms: u64) -> DashTimeline { + pub fn from_hls(p: &MediaPlaylist, period_id: &str, period_start_s: u64) -> DashTimeline { let mut t = 0u64; let segments = p.segments.iter().map(|s| { let at = t; t = t.saturating_add(s.duration_ms); (at, s.duration_ms) }).collect(); - DashTimeline { period_id: period_id.to_string(), period_start_ms, timescale: 1000, segments } + DashTimeline { period_id: period_id.to_string(), period_start_s, timescale: 1000, segments } + } + + /// The timeline's length in milliseconds, and its longest segment in milliseconds. + fn lengths_ms(&self) -> (u64, u64) { + let to_ms = |ticks: u64| u64::try_from(u128::from(ticks) * 1000 / u128::from(self.timescale.max(1))).unwrap_or(u64::MAX); + (self.segments.iter().map(|&(_, d)| to_ms(d)).fold(0, u64::saturating_add), self.segments.iter().map(|&(_, d)| to_ms(d)).max().unwrap_or(0)) } } @@ -51,63 +59,174 @@ pub struct WaiRepresentation { pub media: Vec, } +/// A descriptor (`EssentialProperty`, `SupplementalProperty`): its scheme and value. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Descriptor { + pub scheme: String, + pub value: Option, +} + +/// A `Representation` with a `SegmentList`: its `@id`, `@dependencyId` and +/// `@bandwidth`, the list's timescale, an `Initialization`, the `S` elements (`t`, +/// `d`) and one `SegmentURL` per segment. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Representation { + pub id: String, + pub dependency_id: Option, + pub bandwidth: u64, + pub timescale: u64, + pub initialization: Option, + pub timeline: Vec<(u64, u64)>, + pub media: Vec, +} + +/// An `AdaptationSet`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AdaptationSet { + pub id: u32, + pub content_type: Option, + pub mime_type: String, + pub codecs: String, + pub size: Option<(u32, u32)>, + pub segment_alignment: bool, + pub start_with_sap: Option, + pub essential: Vec, + pub supplemental: Vec, + pub representations: Vec, +} + +/// A static MPD of one period. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Mpd { + /// `mediaPresentationDuration`, in milliseconds. + pub duration_ms: u64, + /// `minBufferTime`, in whole seconds. + pub min_buffer_s: u64, + pub period_id: String, + /// `Period@start`, in whole seconds. + pub period_start_s: u64, + pub adaptation_sets: Vec, +} + /// An XML attribute value, escaped. fn esc(s: &str) -> String { s.replace('&', "&").replace('<', "<").replace('>', ">").replace('"', """) } -/// `PTS` with three fractional digits. -fn duration(ms: u64) -> String { - format!("PT{}S", crate::hls_render::seconds(ms)) +fn id_ok(s: &str) -> bool { + !s.is_empty() && !s.chars().any(char::is_whitespace) } -/// `wai.mpd`: a static MPD with one period and one WAI `AdaptationSet` (`@id` -/// `set_id`), its representations each listing one `SegmentURL` per segment of the -/// timeline under a `SegmentList` whose `SegmentTimeline` has one `S` per segment with -/// its `t` and `d`. `mediaPresentationDuration` is the timeline's length and -/// `minBufferTime` its longest segment. Attribute order is fixed and lines end in LF. -/// Refused (`playlist_malformed`) for no segment or representation, a timescale of 0, -/// a representation with another number of segments, an `@id` that is empty or holds -/// white space, or a floor whose container is not the union's. -pub fn wai_mpd(t: &DashTimeline, set_id: u32, union: &Element, floor: &Element, reps: &[WaiRepresentation]) -> Result { +/// An MPD's text: ``, `` (the DASH namespace, the full profile, `static`, +/// `mediaPresentationDuration` in seconds with three decimals, `minBufferTime` in whole +/// seconds), one `` (`@id`, `@start` in whole seconds), each `AdaptationSet` +/// (`@id`, `@contentType`, `@mimeType`, `@codecs`, `@width`, `@height`, +/// `@segmentAlignment`, `@startWithSAP`, those given, in that order) with its essential +/// then supplemental descriptors and its representations (`@id`, `@dependencyId`, +/// `@bandwidth`), each a `SegmentList` (`@timescale`) of an `Initialization`, a +/// `SegmentTimeline` of one `S` per segment with its `t` and `d`, and one `SegmentURL` +/// per segment. Attribute values are escaped, elements are indented by two spaces a +/// level, and lines end in LF. Refused (`playlist_malformed`) for no adaptation set or +/// representation, an `@id` that is empty or holds white space, a timescale of 0, or a +/// representation whose timeline and segment URLs differ in number. +pub fn render_mpd(m: &Mpd) -> Result { let bad = |s: &str| PlaylistError::Malformed(s.to_string()); - if t.segments.is_empty() || reps.is_empty() || t.timescale == 0 { - return Err(bad("an MPD needs a segment, a representation and a timescale")); + if m.adaptation_sets.is_empty() || m.adaptation_sets.iter().any(|a| a.representations.is_empty()) || !id_ok(&m.period_id) { + return Err(bad("an MPD needs a period id, an adaptation set and a representation")); } - if floor.prefix != union.prefix { - return Err(bad("the floor is written with the union's prefix")); - } - let id_ok = |s: &str| !s.is_empty() && !s.chars().any(char::is_whitespace); - if !id_ok(&t.period_id) || reps.iter().any(|r| !id_ok(&r.id) || r.media.len() != t.segments.len()) { - return Err(bad("an @id that is empty or holds white space, or a representation of another length")); - } - let to_ms = |ticks: u64| u64::try_from(u128::from(ticks) * 1000 / u128::from(t.timescale)).unwrap_or(u64::MAX); - let total: u64 = t.segments.iter().map(|&(_, d)| to_ms(d)).fold(0, u64::saturating_add); - let longest = t.segments.iter().map(|&(_, d)| to_ms(d)).max().unwrap_or(0); let mut out = String::from("\n"); out.push_str(&format!( - "\n", - duration(total), duration(longest) + "\n", + crate::hls_render::seconds(m.duration_ms), m.min_buffer_s )); - out.push_str(&format!(" \n", esc(&t.period_id), duration(t.period_start_ms))); - out.push_str(&format!( - " \n", - esc(&codecs::render(union)) - )); - out.push_str(&format!(" \n")); - out.push_str(&format!(" \n", esc(&codecs::render(floor)))); - for r in reps { - out.push_str(&format!(" \n", esc(&r.id), r.bandwidth)); - out.push_str(&format!(" \n \n", t.timescale)); - for &(st, d) in &t.segments { - out.push_str(&format!(" \n")); + out.push_str(&format!(" \n", esc(&m.period_id), m.period_start_s)); + for a in &m.adaptation_sets { + out.push_str(&format!(" \n"); - for m in &r.media { - out.push_str(&format!(" \n", esc(m))); + out.push_str(&format!(" mimeType=\"{}\" codecs=\"{}\"", esc(&a.mime_type), esc(&a.codecs))); + if let Some((w, h)) = a.size { + out.push_str(&format!(" width=\"{w}\" height=\"{h}\"")); } - out.push_str(" \n \n"); + if a.segment_alignment { + out.push_str(" segmentAlignment=\"true\""); + } + if let Some(sap) = a.start_with_sap { + out.push_str(&format!(" startWithSAP=\"{sap}\"")); + } + out.push_str(">\n"); + for (tag, ds) in [("EssentialProperty", &a.essential), ("SupplementalProperty", &a.supplemental)] { + for d in ds { + out.push_str(&format!(" <{tag} schemeIdUri=\"{}\"", esc(&d.scheme))); + if let Some(v) = &d.value { + out.push_str(&format!(" value=\"{}\"", esc(v))); + } + out.push_str("/>\n"); + } + } + for r in &a.representations { + if !id_ok(&r.id) || r.dependency_id.as_deref().is_some_and(|d| !id_ok(d)) || r.timescale == 0 || r.timeline.len() != r.media.len() { + return Err(bad("a representation's id, dependency id, timescale or segment count")); + } + out.push_str(&format!(" \n \n", r.bandwidth, r.timescale)); + if let Some(i) = &r.initialization { + out.push_str(&format!(" \n", esc(i))); + } + out.push_str(" \n"); + for &(t, d) in &r.timeline { + out.push_str(&format!(" \n")); + } + out.push_str(" \n"); + for u in &r.media { + out.push_str(&format!(" \n", esc(u))); + } + out.push_str(" \n \n"); + } + out.push_str(" \n"); } - out.push_str(" \n \n\n"); + out.push_str(" \n\n"); Ok(out) } + +/// `wai.mpd` (§11.1) as [`render_mpd`] writes it: one period on timeline `t`, its +/// length the timeline's and `minBufferTime` its longest segment rounded up to whole +/// seconds, and one WAI `AdaptationSet` (`@id` `set_id`, `application/wai`, the union as +/// `@codecs`, segment-aligned, the delivery `EssentialProperty` and the floor +/// `SupplementalProperty`), each representation listing every segment of the timeline. +/// Refused (`playlist_malformed`) as [`render_mpd`] refuses, for no segment, or a floor +/// whose container is not the union's. +pub fn wai_mpd(t: &DashTimeline, set_id: u32, union: &Element, floor: &Element, reps: &[WaiRepresentation]) -> Result { + if t.segments.is_empty() || t.timescale == 0 { + return Err(PlaylistError::Malformed("an MPD needs a segment and a timescale".into())); + } + if floor.prefix != union.prefix { + return Err(PlaylistError::Malformed("the floor is written with the union's prefix".into())); + } + let (total, longest) = t.lengths_ms(); + render_mpd(&Mpd { + duration_ms: total, + min_buffer_s: longest.div_ceil(1000), + period_id: t.period_id.clone(), + period_start_s: t.period_start_s, + adaptation_sets: vec![AdaptationSet { + id: set_id, + content_type: None, + mime_type: super::MEDIA_TYPE.into(), + codecs: codecs::render(union), + size: None, + segment_alignment: true, + start_with_sap: None, + essential: vec![Descriptor { scheme: SCHEME_DELIVERY.into(), value: None }], + supplemental: vec![Descriptor { scheme: SCHEME_FLOOR.into(), value: Some(codecs::render(floor)) }], + representations: reps.iter().map(|r| Representation { + id: r.id.clone(), dependency_id: None, bandwidth: r.bandwidth, timescale: t.timescale, initialization: None, + timeline: t.segments.clone(), media: r.media.clone(), + }).collect(), + }], + }) +} diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs index 2623644b6..9e5087bf1 100644 --- a/wai/wai-rs/src/http_binding/hls.rs +++ b/wai/wai-rs/src/http_binding/hls.rs @@ -20,7 +20,7 @@ use super::codecs::{self, Element}; use super::playlist::{parse_layer_tag, LayerTag, LAYER_TAG}; use super::{hex, RangeRefusal}; -use crate::hls_render::{check_render, render_hls_with, seconds, HlsRender, PartLine, Segment, SegmentExtra, SegmentKind}; +use crate::hls_render::{check_render, render_hls_with, seconds, HlsRender, PartLine, PlaylistType, Segment, SegmentExtra, SegmentKind}; /// The `DATA-ID` of the session-data line that points a WAI client from the classical /// multivariant playlist to `wai.m3u8` (§11.1). @@ -203,6 +203,11 @@ pub struct MediaPlaylist { /// The `EXT-X-PRELOAD-HINT` of `TYPE=PART`. pub preload_hint: Option, pub end_list: bool, + pub playlist_type: Option, + pub independent_segments: bool, + /// The `EXT-X-MAP` URI before the first segment (one later in the playlist is not + /// read). + pub map_uri: Option, } /// Where a `#WAI-LAYER` line attaches: the `EXTINF` line just before it (its segment) @@ -230,8 +235,9 @@ fn push_layer(layers: &mut Vec, l: LayerTag) -> Result<(), PlaylistErr /// - `EXT-X-VERSION`, `EXT-X-TARGETDURATION`, `EXT-X-MEDIA-SEQUENCE` and /// `EXT-X-DISCONTINUITY-SEQUENCE` (the last two before any segment), `EXT-X-PART-INF`, /// `EXT-X-SERVER-CONTROL`, `EXT-X-DISCONTINUITY`, `EXT-X-PROGRAM-DATE-TIME`, -/// `EXT-X-PART`, `EXTINF`, `EXT-X-PRELOAD-HINT` (`TYPE=PART`, once) and -/// `EXT-X-ENDLIST` are read; a delta update (`EXT-X-SKIP`) and a multivariant +/// `EXT-X-PART`, `EXTINF`, `EXT-X-PRELOAD-HINT` (`TYPE=PART`, once), `EXT-X-ENDLIST`, +/// `EXT-X-PLAYLIST-TYPE` (`VOD` or `EVENT`), `EXT-X-INDEPENDENT-SEGMENTS`, `EXT-X-GAP`, +/// and `EXT-X-MAP` before the first segment are read; a delta update (`EXT-X-SKIP`) and a multivariant /// playlist's variant tags are refused; any other tag and any comment is skipped; /// - a `#WAI-LAYER` line (§10.4) attaches to the `EXTINF` or `EXT-X-PART` line just /// before it, with only other layer lines between, in increasing `N`; @@ -291,6 +297,22 @@ pub fn parse_media_playlist(text: &str) -> Result p.part_hold_back_ms = attr_ms(&a, "PART-HOLD-BACK", tag)?; } "#EXT-X-DISCONTINUITY" => pending.discontinuity = true, + "#EXT-X-GAP" => pending.gap = true, + "#EXT-X-INDEPENDENT-SEGMENTS" => p.independent_segments = true, + "#EXT-X-PLAYLIST-TYPE" => { + p.playlist_type = Some(match value { + "VOD" => PlaylistType::Vod, + "EVENT" => PlaylistType::Event, + _ => return Err(malformed("EXT-X-PLAYLIST-TYPE is neither VOD nor EVENT")), + }) + } + "#EXT-X-MAP" => { + let a = attributes(value, tag)?; + let uri = attr_quoted(&a, "URI", tag)?.ok_or_else(|| malformed("EXT-X-MAP without URI"))?; + if !started && extinf.is_none() && pending == SegmentExtra::default() && p.map_uri.is_none() { + p.map_uri = Some(uri); + } + } "#EXT-X-PROGRAM-DATE-TIME" => pending.program_date_time = Some(value.to_string()), "#EXT-X-PART" => { let a = attributes(value, tag)?; @@ -356,6 +378,9 @@ impl MediaPlaylist { part_hold_back_ms: self.part_hold_back_ms, can_block_reload: self.can_block_reload, end_list: self.end_list, + playlist_type: self.playlist_type, + independent_segments: self.independent_segments, + map_uri: self.map_uri.clone(), }; let mut extras: Vec = self.segments.iter().map(|s| s.extra.clone()).collect(); if let Some(o) = &self.open { @@ -469,13 +494,15 @@ pub struct WaiPart { } /// A segment's object, listed as `.wai#n=[&h=]` (§11.3), with its -/// layers and, in Live-P, its parts. +/// layers and, in Live-P, its parts; `gap` marks it `EXT-X-GAP`, absent where a sink +/// takes the adjacent classical segment instead. #[derive(Clone, Debug, PartialEq, Eq)] pub struct WaiSegment { pub cid: [u8; 32], pub n: u64, pub h: Option, pub duration_ms: u64, + pub gap: bool, pub layers: Vec, pub parts: Vec, } @@ -504,10 +531,12 @@ fn part_lines(parts: &[WaiPart], prefix: &str) -> Vec { /// The WAI media playlist beside the classical media playlist `classical` (§11.1 to /// §11.4): segment for segment the classical timeline (media and discontinuity /// sequences, durations, discontinuities, program date-times, part target), each -/// segment listing its object, its layers and its parts, and the open segment's parts -/// and preload hint at a live edge. The target duration, part target, part hold-back -/// (three part targets when the classical states none), blocking reload and version -/// are the classical playlist's (version 6 when it states none). `uri_prefix` is put +/// segment listing its object (or a gap), its layers and its parts, and the open +/// segment's parts and preload hint at a live edge. The target duration, part target, +/// part hold-back (three part targets when the classical states none), blocking reload, +/// playlist type, independent segments and version are the classical playlist's +/// (version 6 when it states none); its `EXT-X-MAP` is not (a WAI object has no +/// initialization section). `uri_prefix` is put /// before every `.wai`. Returns the renderer's inputs (what a playlist receipt /// seals) and the text. /// @@ -543,6 +572,7 @@ pub fn wai_media_playlist( segments.push(Segment { index: i as u64, kind: SegmentKind::Content, uri: format!("{uri_prefix}{}.wai#n={}{h}", hex(&w.cid), w.n), duration_ms: w.duration_ms, ad_id: None }); extras.push(SegmentExtra { discontinuity: c.extra.discontinuity, + gap: w.gap, program_date_time: c.extra.program_date_time.clone(), parts: part_lines(&w.parts, uri_prefix), layers: layer_lines(&w.layers, uri_prefix), @@ -561,6 +591,7 @@ pub fn wai_media_playlist( } extras.push(SegmentExtra { discontinuity: c.discontinuity, + gap: false, program_date_time: c.program_date_time.clone(), parts: part_lines(&o.parts, uri_prefix), layers: Vec::new(), @@ -586,6 +617,9 @@ pub fn wai_media_playlist( part_hold_back_ms: classical.part_hold_back_ms.or(classical.part_target_ms.map(|t| t.saturating_mul(3))), can_block_reload: classical.can_block_reload, end_list, + playlist_type: classical.playlist_type, + independent_segments: classical.independent_segments, + map_uri: None, }; check_render(&segments, &r, &extras).map_err(|e| malformed(e.0))?; let text = render_hls_with(&segments, &r, &extras); @@ -623,29 +657,85 @@ pub struct VariantLine { pub frame_rate_milli: Option, } -/// `wai.m3u8` (§11.1): `#EXTM3U`, then per variant an `EXT-X-STREAM-INF` with -/// `BANDWIDTH` and `AVERAGE-BANDWIDTH` (computed by [`bandwidths`], never stated by -/// hand), `CODECS` (the union), `X-WAI-FLOOR` (the floor), and `RESOLUTION` and -/// `FRAME-RATE` (three fractional digits) when given, followed by its URI. Nothing in -/// it needs a version. Refused (`playlist_malformed`) for no variant, a URI that is -/// empty, begins with `#` or holds CR or LF, a floor whose container is not the -/// union's, or a frame rate of 0. -pub fn wai_multivariant(variants: &[VariantLine]) -> Result { - if variants.is_empty() { +/// An `EXT-X-SESSION-DATA` line: its `DATA-ID` and its `VALUE` or `URI`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SessionData { + Value { data_id: String, value: String }, + Uri { data_id: String, uri: String }, +} + +/// One `EXT-X-STREAM-INF` and its URI: a WAI variant (`wai_floor` its floor) or a +/// classical one (`codecs` a classical value, no floor). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StreamInf { + pub uri: String, + pub bandwidth: u64, + pub average_bandwidth: Option, + pub codecs: String, + pub wai_floor: Option, + pub resolution: Option<(u32, u32)>, + pub frame_rate_milli: Option, +} + +/// A multivariant playlist, classical or WAI. +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct Multivariant { + pub version: Option, + pub independent_segments: bool, + pub session_data: Vec, + pub variants: Vec, +} + +fn attr_text_ok(s: &str) -> bool { + !s.contains(['"', '\r', '\n']) +} + +/// A multivariant playlist's text: `#EXTM3U`; `#EXT-X-VERSION` and +/// `#EXT-X-INDEPENDENT-SEGMENTS` when given; each `EXT-X-SESSION-DATA` line +/// (`DATA-ID`, then `VALUE` or `URI`); then per variant an `EXT-X-STREAM-INF` with +/// `BANDWIDTH`, `AVERAGE-BANDWIDTH`, `CODECS`, `X-WAI-FLOOR`, `RESOLUTION` and +/// `FRAME-RATE` (three fractional digits), those given, in that order, followed by its +/// URI. Lines end in LF. Refused (`playlist_malformed`) for no variant, a URI that is +/// empty, begins with `#` or holds CR or LF, a quoted value holding a quote, CR or LF, +/// a version of 0 or a frame rate of 0. +pub fn render_multivariant(m: &Multivariant) -> Result { + if m.variants.is_empty() { return Err(malformed("no variant")); } let mut out = String::from("#EXTM3U\n"); - for v in variants { + match m.version { + Some(0) => return Err(malformed("version 0")), + Some(v) => out.push_str(&format!("#EXT-X-VERSION:{v}\n")), + None => {} + } + if m.independent_segments { + out.push_str("#EXT-X-INDEPENDENT-SEGMENTS\n"); + } + for d in &m.session_data { + let (id, key, v) = match d { + SessionData::Value { data_id, value } => (data_id, "VALUE", value), + SessionData::Uri { data_id, uri } => (data_id, "URI", uri), + }; + if id.is_empty() || v.is_empty() || !attr_text_ok(id) || !attr_text_ok(v) { + return Err(malformed("a session-data id or value is empty or holds a quote, CR or LF")); + } + out.push_str(&format!("#EXT-X-SESSION-DATA:DATA-ID=\"{id}\",{key}=\"{v}\"\n")); + } + for v in &m.variants { if v.uri.is_empty() || v.uri.starts_with('#') || v.uri.contains(['\r', '\n']) { return Err(malformed("a variant URI is empty, begins with # or holds CR or LF")); } - if v.floor.prefix != v.union.prefix { - return Err(malformed("the floor is written with the union's prefix")); + if !attr_text_ok(&v.codecs) || v.wai_floor.as_deref().is_some_and(|f| !attr_text_ok(f)) { + return Err(malformed("a codecs value holds a quote, CR or LF")); + } + out.push_str(&format!("#EXT-X-STREAM-INF:BANDWIDTH={}", v.bandwidth)); + if let Some(a) = v.average_bandwidth { + out.push_str(&format!(",AVERAGE-BANDWIDTH={a}")); + } + out.push_str(&format!(",CODECS=\"{}\"", v.codecs)); + if let Some(f) = &v.wai_floor { + out.push_str(&format!(",X-WAI-FLOOR=\"{f}\"")); } - out.push_str(&format!( - "#EXT-X-STREAM-INF:BANDWIDTH={},AVERAGE-BANDWIDTH={},CODECS=\"{}\",X-WAI-FLOOR=\"{}\"", - v.peak_bps, v.avg_bps, codecs::render(&v.union), codecs::render(&v.floor) - )); if let Some((w, h)) = v.resolution { out.push_str(&format!(",RESOLUTION={w}x{h}")); } @@ -662,6 +752,35 @@ pub fn wai_multivariant(variants: &[VariantLine]) -> Result Result { + if self.floor.prefix != self.union.prefix { + return Err(malformed("the floor is written with the union's prefix")); + } + Ok(StreamInf { + uri: self.uri.clone(), + bandwidth: self.peak_bps, + average_bandwidth: Some(self.avg_bps), + codecs: codecs::render(&self.union), + wai_floor: Some(codecs::render(&self.floor)), + resolution: self.resolution, + frame_rate_milli: self.frame_rate_milli, + }) + } +} + +/// `wai.m3u8` (§11.1) as [`render_multivariant`] writes it with no version line and no +/// session data: per variant an `EXT-X-STREAM-INF` with `BANDWIDTH` and +/// `AVERAGE-BANDWIDTH` (computed from the objects, never stated by hand), `CODECS` (the +/// union), `X-WAI-FLOOR` (the floor), and `RESOLUTION` and `FRAME-RATE` when given. +pub fn wai_multivariant(variants: &[VariantLine]) -> Result { + let variants = variants.iter().map(VariantLine::stream_inf).collect::, _>>()?; + render_multivariant(&Multivariant { variants, ..Default::default() }) +} + /// The line a publisher MAY add to the classical multivariant playlist (§11.1): /// `#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE=""`. /// Refused for a URI that is empty or holds a quote, CR or LF. @@ -776,10 +895,12 @@ impl WaiLayer { } impl WaiSegment { - /// A segment's object (§11.3): its cid, `n` and, as `h`, its `covered_len`. + /// A segment's object (§11.3): its cid, `n` and, for a `WAI2`, its `covered_len` as + /// `h` (§5.4). pub fn of(env: &[u8], duration_ms: u64) -> Result { let h = whole_head(env)?; - Ok(WaiSegment { cid: super::content_hash(env), n: env.len() as u64, h: Some(h.covered_len()), duration_ms, layers: Vec::new(), parts: Vec::new() }) + let h_hint = (h.version == super::head::Version::Wai2).then(|| h.covered_len()); + Ok(WaiSegment { cid: super::content_hash(env), n: env.len() as u64, h: h_hint, duration_ms, gap: false, layers: Vec::new(), parts: Vec::new() }) } } diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index 5fefb41c0..320e1023e 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -18,7 +18,7 @@ fn cid(b: u8) -> [u8; 32] { } fn seg(b: u8, ms: u64) -> WaiSegment { - WaiSegment { cid: cid(b), n: 2000 + u64::from(b), h: Some(1009), duration_ms: ms, layers: Vec::new(), parts: Vec::new() } + WaiSegment { cid: cid(b), n: 2000 + u64::from(b), h: Some(1009), duration_ms: ms, gap: false, layers: Vec::new(), parts: Vec::new() } } fn wpart(seq: u64, i: u64, ms: u64) -> WaiPart { @@ -262,7 +262,7 @@ fn wai_mpd_shape() { let floor = parse_element("wai2.video.av1").unwrap(); let rep = WaiRepresentation { id: "wai-180".into(), bandwidth: 262_925, media: (1..=3).map(|b| format!("../objects/{}.wai#n=9&h=1009", super::hex(&cid(b)))).collect() }; let m = wai_mpd(&t, 1, &union, &floor, &[rep.clone()]).unwrap(); - assert!(m.starts_with("\n\n \n")); + assert!(m.starts_with("\n\n \n")); assert!(m.contains("\n \n \n")); assert!(m.contains("\n")); assert!(m.contains("#n=9&h=1009\"/>")); @@ -360,3 +360,112 @@ mod receipts { let _ = cid(0); } } + + +/// The lines a packager's playlists carry beside the timeline: `EXT-X-PLAYLIST-TYPE`, +/// `EXT-X-INDEPENDENT-SEGMENTS` and `EXT-X-MAP` in the header, `EXT-X-GAP` before a +/// segment's `EXTINF`. Each renders and reads back; the WAI playlist takes the +/// classical one's type and independence and never its map; a gap does not move +/// alignment; the open segment has no gap. +#[test] +fn packager_lines_render_and_read_back() { + use crate::hls_render::{check_render, PlaylistType, Segment, SegmentKind}; + let classical = "#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-TARGETDURATION:3\n#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-PLAYLIST-TYPE:VOD\n#EXT-X-INDEPENDENT-SEGMENTS\n#EXT-X-MAP:URI=\"init.mp4\"\n#EXTINF:2.400,\na.m4s\n#EXTINF:1.000,\nb.m4s\n#EXT-X-ENDLIST\n"; + let c = parse_media_playlist(classical).unwrap(); + assert_eq!((c.playlist_type, c.independent_segments, c.map_uri.as_deref()), (Some(PlaylistType::Vod), true, Some("init.mp4"))); + assert!(c.is_rendered_form(classical), "the classical playlist is in the rendered form"); + let mut b = seg(2, 1000); + b.gap = true; + let (_, r, _, w) = wai_media_playlist(&c, &[seg(1, 2400), b], None, "../../objects/", true).unwrap(); + assert_eq!(r.map_uri, None); + assert!(w.starts_with("#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-TARGETDURATION:3\n#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-PLAYLIST-TYPE:VOD\n#EXT-X-INDEPENDENT-SEGMENTS\n#EXTINF:2.400,\n"), "{w}"); + assert!(w.contains("#EXT-X-GAP\n#EXTINF:1.000,\n../../objects/0202"), "{w}"); + check_adjacent(&w, classical).unwrap(); + let back = parse_media_playlist(&w).unwrap(); + assert!(back.segments[1].extra.gap && back.is_rendered_form(&w)); + for bad in ["#EXTM3U\n#EXT-X-PLAYLIST-TYPE:LIVE\n", "#EXTM3U\n#EXT-X-MAP:BYTERANGE=\"1@0\"\n"] { + assert_eq!(parse_media_playlist(bad).unwrap_err().code(), "playlist_malformed", "{bad:?}"); + } + // A map after the first segment is not the header's. + let late = parse_media_playlist("#EXTM3U\n#EXTINF:1,\na\n#EXT-X-MAP:URI=\"i.mp4\"\n#EXTINF:1,\nb\n").unwrap(); + assert_eq!(late.map_uri, None); + let segs = vec![Segment { index: 0, kind: SegmentKind::Content, uri: "a".into(), duration_ms: 1000, ad_id: None }]; + let mut r = crate::hls_render::HlsRender { end_list: false, part_target_ms: Some(500), part_hold_back_ms: Some(1000), ..crate::hls_render::HlsRender::legacy() }; + let gap_open = vec![SegmentExtra::default(), SegmentExtra { gap: true, parts: vec![crate::hls_render::PartLine { duration_ms: 500, independent: true, uri: "p".into(), layers: vec![] }], ..Default::default() }]; + assert!(check_render(&segs, &r, &gap_open).is_err(), "a gap on the open segment"); + r.map_uri = Some("i\"nit".into()); + assert!(check_render(&segs, &r, &[]).is_err(), "a quote in the map URI"); +} + +/// A multivariant playlist of either kind: a classical one with a version, independent +/// segments and the session-data line; a WAI one naming its class and stage view. +#[test] +fn multivariant_of_either_kind() { + let floor = StreamInf { uri: "../classic/floor-v.m3u8".into(), bandwidth: 10_295, average_bandwidth: Some(9_026), codecs: "av01.0.00M.08".into(), + wai_floor: None, resolution: Some((64, 36)), frame_rate_milli: None }; + let classic = Multivariant { version: Some(7), independent_segments: true, session_data: vec![], variants: vec![floor.clone()] }; + let c = render_multivariant(&classic).unwrap(); + assert_eq!(c, "#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-INDEPENDENT-SEGMENTS\n#EXT-X-STREAM-INF:BANDWIDTH=10295,AVERAGE-BANDWIDTH=9026,CODECS=\"av01.0.00M.08\",RESOLUTION=64x36\n../classic/floor-v.m3u8\n"); + let class = Multivariant { session_data: vec![SessionData::Value { data_id: SESSION_DATA_ID.into(), value: "wai.m3u8".into() }], ..classic.clone() }; + let k = render_multivariant(&class).unwrap(); + classical_unchanged(&c, &k).unwrap(); + let wai = Multivariant { + version: Some(7), independent_segments: true, + session_data: vec![SessionData::Value { data_id: "science.transaction.wai.class".into(), value: "tv".into() }, + SessionData::Uri { data_id: "science.transaction.wai.stages".into(), uri: "../../stages/tv.json".into() }], + variants: vec![StreamInf { uri: "wai-v.m3u8".into(), bandwidth: 143_528, average_bandwidth: Some(121_122), codecs: "wai1.video.int_motion".into(), + wai_floor: Some("wai1.video.int_motion".into()), resolution: Some((64, 36)), frame_rate_milli: None }], + }; + assert!(render_multivariant(&wai).unwrap().contains("#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.stages\",URI=\"../../stages/tv.json\"\n#EXT-X-STREAM-INF:BANDWIDTH=143528,AVERAGE-BANDWIDTH=121122,CODECS=\"wai1.video.int_motion\",X-WAI-FLOOR=\"wai1.video.int_motion\",RESOLUTION=64x36\nwai-v.m3u8\n")); + let mut bad = class.clone(); + bad.session_data = vec![SessionData::Value { data_id: "a\"b".into(), value: "x".into() }]; + assert_eq!(render_multivariant(&bad).unwrap_err().code(), "playlist_malformed"); + let mut bad = classic.clone(); + bad.version = Some(0); + assert!(render_multivariant(&bad).is_err()); + let mut bad = classic; + bad.variants[0].codecs = "a\nb".into(); + assert!(render_multivariant(&bad).is_err()); +} + +/// The one DASH renderer writes a classical MPD (an initialization, `startWithSAP`) and a +/// layered WAI one (a dependency per layer, a timeline with a gap), with durations in +/// seconds with three decimals and the buffer and start in whole seconds. +#[test] +fn render_mpd_of_either_kind() { + use super::dash::*; + let rep = |id: &str, dep: Option<&str>, init: Option<&str>, tl: Vec<(u64, u64)>| Representation { + id: id.into(), dependency_id: dep.map(String::from), bandwidth: 100, timescale: 25, initialization: init.map(String::from), + media: tl.iter().map(|(t, _)| format!("s{t}.wai")).collect(), timeline: tl, + }; + let set = |mime: &str, align: bool, sap: Option, reps: Vec| AdaptationSet { + id: 1, content_type: Some("video".into()), mime_type: mime.into(), codecs: "c".into(), size: Some((64, 36)), segment_alignment: align, + start_with_sap: sap, essential: vec![], supplemental: vec![Descriptor { scheme: SCHEME_FLOOR.into(), value: Some("f&g".into()) }], representations: reps, + }; + let m = Mpd { duration_ms: 8000, min_buffer_s: 3, period_id: "p0".into(), period_start_s: 0, adaptation_sets: vec![ + set("video/mp4", true, Some(1), vec![rep("v-floor", None, Some("../floor/v/init.mp4"), vec![(0, 60), (60, 25)])])] }; + let t = render_mpd(&m).unwrap(); + assert!(t.contains("mediaPresentationDuration=\"PT8.000S\" minBufferTime=\"PT3S\">\n \n")); + assert!(t.contains("")); + assert!(t.contains("value=\"f&g\"") && t.contains("\n \n \n")); + let w = Mpd { adaptation_sets: vec![set("application/wai", false, None, vec![ + rep("v-l0", None, None, vec![(0, 60), (60, 25), (85, 50)]), rep("v-l1", Some("v-l0"), None, vec![(0, 60), (85, 50)])])], ..m.clone() }; + let t = render_mpd(&w).unwrap(); + assert!(t.contains("") && !t.contains("segmentAlignment")); + let mut bad = w.clone(); + bad.adaptation_sets[0].representations[1].media.pop(); + assert_eq!(render_mpd(&bad).unwrap_err().code(), "playlist_malformed"); + let mut bad = w; + bad.adaptation_sets[0].representations[0].dependency_id = Some("a b".into()); + assert!(render_mpd(&bad).is_err()); +} + +/// §5.4: a segment's object is listed with its `n`, and with its `h` only for a `WAI2`. +#[test] +fn segment_hints_follow_the_container() { + use crate::container::{Rendition, WaiMulti}; + let w2 = WaiMulti::new("video", "replicate", serde_json::Value::Null, vec![Rendition::media("wai.video.av1", "av1", vec![7u8; 3000]).with_id("a").with_payload_digest()]).to_bytes().unwrap(); + let w1 = crate::container::Wai::new(crate::container::Manifest::new("video", "replicate", crate::container::ModelRequirement::new("wai.video.av1"), crate::container::Conditioning::new("av1")), vec![7u8; 3000]).to_bytes().unwrap(); + assert_eq!(WaiSegment::of(&w2, 1000).unwrap().h, Some(1009)); + assert_eq!(WaiSegment::of(&w1, 1000).unwrap().h, None); +} diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs index fd25c6287..6d1c762ff 100644 --- a/wai/wai-rs/src/wai_video_manifest.rs +++ b/wai/wai-rs/src/wai_video_manifest.rs @@ -190,6 +190,9 @@ pub fn render_json(r: &HlsRender) -> serde_json::Value { "part_hold_back_ms": r.part_hold_back_ms, "can_block_reload": r.can_block_reload, "end_list": r.end_list, + "playlist_type": r.playlist_type.map(|t| t.label()), + "independent_segments": r.independent_segments, + "map_uri": r.map_uri, }) } @@ -201,6 +204,7 @@ fn layers_json(ls: &[LayerLine]) -> serde_json::Value { pub fn extra_json(e: &SegmentExtra) -> serde_json::Value { serde_json::json!({ "discontinuity": e.discontinuity, + "gap": e.gap, "program_date_time": e.program_date_time, "parts": e.parts.iter().map(|p| serde_json::json!({ "duration_ms": p.duration_ms, "independent": p.independent, "uri": p.uri, "layers": layers_json(&p.layers), @@ -214,7 +218,7 @@ impl ManifestReceipt { /// The playlist bytes this receipt signs: [`render_hls_with`] over the segments /// with `render` (or [`HlsRender::legacy`]) and `extras`. pub fn rendered(&self) -> String { - render_hls_with(&self.segments, &self.render.unwrap_or_else(HlsRender::legacy), &self.extras) + render_hls_with(&self.segments, &self.render.clone().unwrap_or_else(HlsRender::legacy), &self.extras) } /// BLAKE3 over the render domain and the canonical JSON (RFC 8785) of @@ -342,7 +346,7 @@ impl ManifestReceipt { render: Option, extras: Vec, ) -> ManifestReceipt { - let manifest_hash = *blake3::hash(render_hls_with(&segments, &render.unwrap_or_else(HlsRender::legacy), &extras).as_bytes()).as_bytes(); + let manifest_hash = *blake3::hash(render_hls_with(&segments, &render.clone().unwrap_or_else(HlsRender::legacy), &extras).as_bytes()).as_bytes(); let session_class_hash = class.class_hash(); let leaves: Vec<[u8; 32]> = segments.iter().map(Segment::leaf).collect(); let mut r = ManifestReceipt { @@ -671,7 +675,13 @@ fn j_members(v: &serde_json::Value, n: usize, what: &str) -> Result Result { - let o = j_members(v, 8, "render")?; + let o = j_members(v, 11, "render")?; + let playlist_type = match o.get("playlist_type") { + Some(serde_json::Value::Null) => None, + Some(serde_json::Value::String(t)) if t == "VOD" => Some(crate::hls_render::PlaylistType::Vod), + Some(serde_json::Value::String(t)) if t == "EVENT" => Some(crate::hls_render::PlaylistType::Event), + _ => return Err("render.playlist_type is VOD, EVENT or null".into()), + }; Ok(HlsRender { version: u8::try_from(j_u64(&o, "version")?).map_err(|_| "render.version is at most 255")?, media_sequence: j_u64(&o, "media_sequence")?, @@ -681,6 +691,9 @@ pub fn render_from_json(v: &serde_json::Value) -> Result { part_hold_back_ms: j_opt_u64(&o, "part_hold_back_ms")?, can_block_reload: j_bool(&o, "can_block_reload")?, end_list: j_bool(&o, "end_list")?, + playlist_type, + independent_segments: j_bool(&o, "independent_segments")?, + map_uri: j_opt_str(&o, "map_uri")?, }) } @@ -693,13 +706,14 @@ fn layers_from_json(v: Option<&serde_json::Value>) -> Result, Str /// One extra a receipt file carries ([`extra_json`]). pub fn extra_from_json(v: &serde_json::Value) -> Result { - let o = j_members(v, 5, "an extra")?; + let o = j_members(v, 6, "an extra")?; let parts = o.get("parts").and_then(serde_json::Value::as_array).ok_or("parts is an array")?.iter().map(|p| { let p = j_members(p, 4, "a part")?; Ok(PartLine { duration_ms: j_u64(&p, "duration_ms")?, independent: j_bool(&p, "independent")?, uri: j_str(&p, "uri")?, layers: layers_from_json(p.get("layers"))? }) }).collect::>()?; Ok(SegmentExtra { discontinuity: j_bool(&o, "discontinuity")?, + gap: j_bool(&o, "gap")?, program_date_time: j_opt_str(&o, "program_date_time")?, parts, layers: layers_from_json(o.get("layers"))?, @@ -972,7 +986,7 @@ mod tests { fn live_render(media_sequence: u64) -> HlsRender { HlsRender { version: 9, media_sequence, discontinuity_sequence: 0, target_duration_s: Some(4), part_target_ms: Some(320), - part_hold_back_ms: Some(960), can_block_reload: true, end_list: false, + part_hold_back_ms: Some(960), can_block_reload: true, end_list: false, ..HlsRender::legacy() } } @@ -1046,7 +1060,7 @@ mod tests { HlsRender { version: 7, ..HlsRender::legacy() }, Vec::new()).unwrap(); assert!(r.verify()); let mut t = r.clone(); - t.render = Some(HlsRender { end_list: false, ..t.render.unwrap() }); + t.render = Some(HlsRender { end_list: false, ..t.render.clone().unwrap() }); assert!(!t.verify()); let mut t = r.clone(); t.extras = vec![SegmentExtra::default(); 3]; diff --git a/wai/wai-rs/tests/cli_http.rs b/wai/wai-rs/tests/cli_http.rs index fd4a021fc..86f0e8c6d 100644 --- a/wai/wai-rs/tests/cli_http.rs +++ b/wai/wai-rs/tests/cli_http.rs @@ -446,7 +446,9 @@ fn hls_and_mpd_write_aligned_playlists() { let classical = std::fs::read_to_string(d.join("classical.m3u8")).unwrap(); wai::http_binding::hls::check_adjacent(&media, &classical).unwrap(); let cid = hex(&content_hash(&std::fs::read(object("O9b")).unwrap())); - assert!(media.contains(&format!("#EXTINF:0.640,\nobjects/{cid}.wai#n=18004&h=1009\n")), "{media}"); + // A WAI1 is listed with its n alone; a WAI2 with its h too (§5.4). + assert!(media.contains(&format!("#EXTINF:0.640,\nobjects/{cid}.wai#n=18004\n")), "{media}"); + assert!(media.contains("wai#n=21034&h=1009\n"), "{media}"); let master = std::fs::read_to_string(d.join("wai.m3u8")).unwrap(); assert_eq!(master, "#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=262925,AVERAGE-BANDWIDTH=239246,CODECS=\"wai2.video.av1|video.int_hyper\",X-WAI-FLOOR=\"wai2.video.av1\",RESOLUTION=320x180,FRAME-RATE=25.000\nwai-180.m3u8\n"); let run = wai(&["http", "mpd", "--objects", d.join("list.json").to_str().unwrap(), "--out", d.join("wai.mpd").to_str().unwrap()]); -- 2.54.0 (Apple Git-157) From fd1464b65020b2e0d916a71dc5e7a3ae12905261 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 07:30:39 -0400 Subject: [PATCH 14/55] wai: falsification follows the renderer: two patches re-cut, a gap and layer bytes added Co-Authored-By: Claude Opus 5.5 --- .../falsify/gap-not-rendered.patch | 16 ++++++++++++++++ .../falsify/layer-after-uri.patch | 6 +++--- .../falsify/layers-not-counted.patch | 16 ++++++++++++++++ .../falsify/render-parts-after-extinf.patch | 7 +++++-- 4 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 wai/http-conformance/falsify/gap-not-rendered.patch create mode 100644 wai/http-conformance/falsify/layers-not-counted.patch diff --git a/wai/http-conformance/falsify/gap-not-rendered.patch b/wai/http-conformance/falsify/gap-not-rendered.patch new file mode 100644 index 000000000..e1c789cf4 --- /dev/null +++ b/wai/http-conformance/falsify/gap-not-rendered.patch @@ -0,0 +1,16 @@ +# must fail: media_playlist_playlists_o9_wai_180_gap_m3u8 +# a gap segment rendered as a segment (hls_render::render_hls_with) +diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs +index 7e29b8dd..52cd436d 100644 +--- a/wai/wai-rs/src/hls_render.rs ++++ b/wai/wai-rs/src/hls_render.rs +@@ -259,9 +259,6 @@ pub fn render_hls_with(segments: &[Segment], r: &HlsRender, extras: &[SegmentExt + out.push_str(&format!("#EXT-X-PROGRAM-DATE-TIME:{pdt}\n")); + } + part_lines(&mut out, &e.parts); +- if e.gap { +- out.push_str("#EXT-X-GAP\n"); +- } + // Fixed 3-dp seconds so the byte output is stable across platforms. + out.push_str(&format!("#EXTINF:{},\n", seconds(s.duration_ms))); + layer_lines(&mut out, &e.layers); diff --git a/wai/http-conformance/falsify/layer-after-uri.patch b/wai/http-conformance/falsify/layer-after-uri.patch index 29ae1b42e..7a4d919e4 100644 --- a/wai/http-conformance/falsify/layer-after-uri.patch +++ b/wai/http-conformance/falsify/layer-after-uri.patch @@ -1,11 +1,11 @@ # must fail: media_playlist_playlists_o10_wai_a_m3u8 media_playlist_playlists_o11_wai_v_m3u8 # layer lines rendered after the segment's URI (hls_render::render_hls_with) diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs -index dcea26bf..dba2fdb1 100644 +index 7e29b8dd..3c910dc0 100644 --- a/wai/wai-rs/src/hls_render.rs +++ b/wai/wai-rs/src/hls_render.rs -@@ -221,9 +221,9 @@ pub fn render_hls_with(segments: &[Segment], r: &HlsRender, extras: &[SegmentExt - part_lines(&mut out, &e.parts); +@@ -264,9 +264,9 @@ pub fn render_hls_with(segments: &[Segment], r: &HlsRender, extras: &[SegmentExt + } // Fixed 3-dp seconds so the byte output is stable across platforms. out.push_str(&format!("#EXTINF:{},\n", seconds(s.duration_ms))); - layer_lines(&mut out, &e.layers); diff --git a/wai/http-conformance/falsify/layers-not-counted.patch b/wai/http-conformance/falsify/layers-not-counted.patch new file mode 100644 index 000000000..f89667c6b --- /dev/null +++ b/wai/http-conformance/falsify/layers-not-counted.patch @@ -0,0 +1,16 @@ +# must fail: multivariant_playlists_o10_wai_m3u8 +# a variant's rates without the layer objects listed with its segments (the corpus tool's listed_objects) +diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +index 29d4d12c..bebf0a80 100644 +--- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs ++++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +@@ -315,9 +315,6 @@ fn listed_objects(c: &Corpus, p: &MediaPlaylist) -> Result<(Vec<(u64, u64)>, Vec + let b = resolve(&s.uri)?; + let mut n = b.len() as u64; + all.push(b); +- for l in &s.extra.layers { +- n += resolve(&l.uri)?.len() as u64; +- } + segs.push((n, s.duration_ms)); + extra(&s.extra, &mut parts, &mut all)?; + } diff --git a/wai/http-conformance/falsify/render-parts-after-extinf.patch b/wai/http-conformance/falsify/render-parts-after-extinf.patch index fa98e795a..f0eaf9fdb 100644 --- a/wai/http-conformance/falsify/render-parts-after-extinf.patch +++ b/wai/http-conformance/falsify/render-parts-after-extinf.patch @@ -1,14 +1,17 @@ # must fail: media_playlist_live_wai_r1_m3u8 playlist_receipt_live_wai_r1_m3u8 # a segment's parts rendered after its EXTINF, not before it (hls_render::render_hls_with) diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs -index dcea26bf..7fee6cd4 100644 +index 7e29b8dd..0d62afe4 100644 --- a/wai/wai-rs/src/hls_render.rs +++ b/wai/wai-rs/src/hls_render.rs -@@ -218,9 +218,9 @@ pub fn render_hls_with(segments: &[Segment], r: &HlsRender, extras: &[SegmentExt +@@ -258,12 +258,12 @@ pub fn render_hls_with(segments: &[Segment], r: &HlsRender, extras: &[SegmentExt if let Some(pdt) = &e.program_date_time { out.push_str(&format!("#EXT-X-PROGRAM-DATE-TIME:{pdt}\n")); } - part_lines(&mut out, &e.parts); + if e.gap { + out.push_str("#EXT-X-GAP\n"); + } // Fixed 3-dp seconds so the byte output is stable across platforms. out.push_str(&format!("#EXTINF:{},\n", seconds(s.duration_ms))); + part_lines(&mut out, &e.parts); -- 2.54.0 (Apple Git-157) From dafaaa051cd5e8dd451e16187224f1fa6b5bd2e9 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 07:38:30 -0400 Subject: [PATCH 15/55] wai-pack: its HLS and DASH catalogs render through http-delivery's renderers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One HLS and one DASH renderer for WAI: wai-pack's floor playlist, masters, WAI multivariant and media playlists and both MPDs are now written by wai::hls_render::render_hls_with, http_binding::hls::{wai_media_playlist, render_multivariant} and http_binding::dash::render_mpd; the WAI media playlist is wai_media_playlist over the floor playlist, so its adjacency is checked as it is written. Every golden byte is unchanged but one line, by the generator (wai-pack conformance regen), for http-delivery: - the classical master's variant is ../classic/floor-.m3u8, which resolves to the same playlist from every class's directory, so each class's master is the classical one with only the session-data line added (http-delivery §11.1, §11.8: no other line changes); before, each class's master rewrote the variant URI; - a #WAI-LAYER line follows its segment's EXTINF, before the URI (http-delivery §10.4, where its reader finds it); before, it followed the URI, where a reader takes it as the next segment's. The golden lists no layer, so no golden byte moves for this. packaging §8.3 and pack_verify.py say and derive the same; pack_verify.py holds 540/540, wai-pack conformance check holds every vector, clippy is clean under -D warnings, and the catalog test reads the layered playlist with http-delivery's reader, holds it adjacent to the floor's, and holds a class's master to the classical one. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/packaging.md | 22 +- wai/pack-conformance/pack_verify.py | 10 +- .../packs/video-base-01/package/LISTING | 2 +- .../package/hls/classic/master.m3u8 | 2 +- wai/wai-pack/src/catalog.rs | 262 +++++++++++------- 5 files changed, 186 insertions(+), 112 deletions(-) diff --git a/wai/extensions/packaging.md b/wai/extensions/packaging.md index a8dc7b7a8..f92725ca0 100644 --- a/wai/extensions/packaging.md +++ b/wai/extensions/packaging.md @@ -647,14 +647,16 @@ codecs string *F* and maximum frame size `FW×FH`: - `hls/classic/master.m3u8`: `#EXTM3U`, `#EXT-X-VERSION:7`, `#EXT-X-INDEPENDENT-SEGMENTS`, `#EXT-X-STREAM-INF:BANDWIDTH=,AVERAGE-BANDWIDTH=,CODECS="F",RESOLUTION=FWxFH`, - `floor-.m3u8`. + `../classic/floor-.m3u8`: a URI that names the same playlist from every class's + directory, so each class's master differs from this one only by the session-data + line, as [http-delivery](http-delivery.md) §11.1 and §11.8 require. - `hls/classic/floor-.m3u8`: `#EXTM3U`, `#EXT-X-VERSION:7`, `#EXT-X-TARGETDURATION:`, `#EXT-X-MEDIA-SEQUENCE:0`, `#EXT-X-PLAYLIST-TYPE:VOD`, `#EXT-X-INDEPENDENT-SEGMENTS`, `#EXT-X-MAP:URI="../../floor//init.mp4"`, then per piece `#EXTINF:,` and `../../floor//.m4s`, then `#EXT-X-ENDLIST`. -- `hls//master.m3u8`: the classical master's lines, with, when the class lists a - WAI object, `#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"` - after `#EXT-X-INDEPENDENT-SEGMENTS`, and the variant's URI `../classic/floor-.m3u8`. +- `hls//master.m3u8`: the classical master, byte for byte, with, when the class + lists a WAI object, `#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"` + after `#EXT-X-INDEPENDENT-SEGMENTS`, and no other change. - `hls//wai.m3u8`, when the class lists a WAI object: `#EXTM3U`, `#EXT-X-VERSION:7`, `#EXT-X-INDEPENDENT-SEGMENTS`, `#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.class",VALUE=""`, @@ -664,10 +666,14 @@ codecs string *F* and maximum frame size `FW×FH`: over the objects the class lists. - `hls//wai-.m3u8`: the floor playlist's lines without `#EXT-X-MAP`, adjacent to it ([http-delivery](http-delivery.md) §11.2); per piece, `#EXT-X-GAP` when the - class takes it as floor only, `#EXTINF:,`, its base as - `../../objects/.wai#n=`, then each refinement the class lists, in - entry order, as `#WAI-LAYER:N=,URI="../../objects/.wai#n="` - ([http-delivery](http-delivery.md) §10.4). + class takes it as floor only, `#EXTINF:,`, each refinement the class lists, + in entry order, as `#WAI-LAYER:N=,URI="../../objects/.wai#n="`, + then its base as `../../objects/.wai#n=`: a layer line follows its + segment's `EXTINF` ([http-delivery](http-delivery.md) §10.4). + +Every HLS file above and every MPD below is a rendering of +[http-delivery](http-delivery.md)'s (Appendix G for a media playlist): the reference +packager writes them with the binding's one HLS and one DASH renderer. DASH, the same as two MPDs ([http-delivery](http-delivery.md) §11.1), each ``, then diff --git a/wai/pack-conformance/pack_verify.py b/wai/pack-conformance/pack_verify.py index a677458f0..b8d2141e7 100644 --- a/wai/pack-conformance/pack_verify.py +++ b/wai/pack-conformance/pack_verify.py @@ -2114,7 +2114,9 @@ def render_catalogs(P, ix, c, floor, size, policy_hex): fb, fa = rates([(len(fr), pc["duration"]) for (_, fr), pc in zip(frags, pieces)]) head = ["#EXTM3U", "#EXT-X-VERSION:7", "#EXT-X-INDEPENDENT-SEGMENTS"] inf = f'#EXT-X-STREAM-INF:BANDWIDTH={fb},AVERAGE-BANDWIDTH={fa},CODECS="{F}",RESOLUTION={FW}x{FH}' - out["hls/classic/master.m3u8"] = text(head + [inf, f"floor-{c}.m3u8"]) + # The floor variant's URI resolves from hls/classic/ and from every class's directory + # alike, so a class's master is the classical one with at most one line added. + out["hls/classic/master.m3u8"] = text(head + [inf, f"../classic/floor-{c}.m3u8"]) media = ["#EXTM3U", "#EXT-X-VERSION:7", f"#EXT-X-TARGETDURATION:{tgt}", "#EXT-X-MEDIA-SEQUENCE:0", "#EXT-X-PLAYLIST-TYPE:VOD", "#EXT-X-INDEPENDENT-SEGMENTS"] lines = media + [f'#EXT-X-MAP:URI="../../floor/{c}/init.mp4"'] @@ -2188,10 +2190,12 @@ def render_catalogs(P, ix, c, floor, size, policy_hex): base = next(e for e in E[p0:p0 + pc["n_entries"]] if e["role"] == BASE) if row[k] == 0xFF: lines.append("#EXT-X-GAP") - lines += [f"#EXTINF:{ms_text(pc['duration'], num, den)},", - f"../../objects/{base['hash'].hex()}.wai#n={base['byte_len']}"] + # A layer line follows its segment's EXTINF, before the segment's URI + # (http-delivery §10.4). + lines.append(f"#EXTINF:{ms_text(pc['duration'], num, den)},") lines += [f'#WAI-LAYER:N={e["layer"]},URI="../../objects/{e["hash"].hex()}.wai#n={e["byte_len"]}"' for e in lists[k] if e["role"] == REFINEMENT] + lines.append(f"../../objects/{base['hash'].hex()}.wai#n={base['byte_len']}") out[f"hls/{name}/wai-{c}.m3u8"] = text(lines + ["#EXT-X-ENDLIST"]) lines = mpd_head + [ f' .m3u8`: the classical -//! presentation, the floor alone; it names nothing of WAI. -//! - `hls//master.m3u8`: the classical presentation with, for a class offered WAI +//! presentation, the floor alone; it names nothing of WAI. Its variant is +//! `../classic/floor-.m3u8`, which resolves to the same playlist from every +//! class's directory, so a class's master is this one byte for byte. +//! - `hls//master.m3u8`: the classical master with, for a class offered WAI //! objects, the one line http-delivery §11.1 lets a publisher add, naming -//! `wai.m3u8`. +//! `wai.m3u8`, and no other change (http-delivery §11.1, §11.8). //! - `hls//wai.m3u8`: the class's WAI playlist (http-delivery §11.1): its class and //! stage view as `EXT-X-SESSION-DATA`, and one variant whose `CODECS` is the union and //! `X-WAI-FLOOR` the floor (http-delivery §4.2) of the objects it lists, with //! `BANDWIDTH` the peak and `AVERAGE-BANDWIDTH` the mean of `8 · bytes / duration`. //! - `hls//wai-.m3u8`: the WAI media playlist, adjacent to the floor's -//! (http-delivery §11.2): a piece's base as its segment, each layer the class lists as a -//! `#WAI-LAYER` line (http-delivery §10.4), and a piece the class takes as floor only -//! marked `#EXT-X-GAP`, so the sink takes the floor's segment there. +//! (http-delivery §11.2), rendered from the floor playlist and the objects by +//! `wai::http_binding::hls::wai_media_playlist`: a piece's base as its segment, each +//! layer the class lists as a `#WAI-LAYER` line after the segment's `EXTINF` +//! (http-delivery §10.4), and a piece the class takes as floor only marked +//! `#EXT-X-GAP`, so the sink takes the floor's segment there. //! - `dash/classic.mpd` and `dash/.mpd`: the same as two MPDs (http-delivery //! §11.1): the classical one, and the class's WAI MPD with `EssentialProperty` and the //! floor in a `SupplementalProperty`, one Representation per layer, each layer's @@ -30,14 +34,21 @@ //! milliseconds, written `S.mmm`; a target duration is the longest piece in whole //! seconds, rounded up. Rates: `ceil(8 · bytes · num / (d · den))` bits per second. //! +//! Every HLS and DASH file is written by the one HLS and the one DASH renderer of +//! `wai-rs` (`wai::hls_render::render_hls_with`, `wai::http_binding::hls::{ +//! wai_media_playlist, render_multivariant}`, `wai::http_binding::dash::render_mpd`); +//! this module says what each holds. +//! //! [packaging]: ../../../extensions/packaging.md //! [http-delivery]: ../../../extensions/http-delivery.md use crate::policy::Timebase; use serde_json::{json, Value}; use std::collections::BTreeMap; -use std::fmt::Write as _; +use wai::hls_render::{render_hls_with, HlsRender, PlaylistType, Segment, SegmentKind}; use wai::http_binding::codecs::{self, CodecsError, SeqObject}; +use wai::http_binding::dash::{self, render_mpd, AdaptationSet, Descriptor, Mpd, Representation}; +use wai::http_binding::hls::{self, parse_media_playlist, render_multivariant, Multivariant, SessionData, StreamInf, WaiLayer, WaiSegment}; use wai::stage_index::{EntryRec, Role, StageIndex, FLOOR_ONLY, TIER_DECODE, TIER_ENTROPY}; /// The SESSION-DATA identifier of a class's id. @@ -45,10 +56,10 @@ pub const DATA_ID_CLASS: &str = "science.transaction.wai.class"; /// The SESSION-DATA identifier of a class's stage view. pub const DATA_ID_STAGES: &str = "science.transaction.wai.stages"; /// http-delivery §11.1's SESSION-DATA identifier of the WAI playlist. -pub const DATA_ID_MULTIVARIANT: &str = "science.transaction.wai.multivariant"; +pub const DATA_ID_MULTIVARIANT: &str = hls::SESSION_DATA_ID; /// http-delivery §11.1's DASH schemes. -pub const SCHEME_HTTP: &str = "tag:transaction.science,2026:wai/http-delivery"; -pub const SCHEME_FLOOR: &str = "tag:transaction.science,2026:wai/floor"; +pub const SCHEME_HTTP: &str = dash::SCHEME_DELIVERY; +pub const SCHEME_FLOOR: &str = dash::SCHEME_FLOOR; /// The DASH schemes naming a class and its stage view. pub const SCHEME_CLASS: &str = "tag:transaction.science,2026:wai/class"; pub const SCHEME_STAGES: &str = "tag:transaction.science,2026:wai/stages"; @@ -98,6 +109,12 @@ impl From for CatalogError { } } +impl From for CatalogError { + fn from(e: hls::PlaylistError) -> Self { + CatalogError(e.to_string()) + } +} + /// `floor(units · den · 1000 / num)` milliseconds as `S.mmm`. pub fn seconds(units: u64, tb: Timebase) -> String { let ms = crate::plan::units_to_ms(units, tb); @@ -182,44 +199,66 @@ impl CompView<'_> { } } -fn media_head(ix: &StageIndex, tb: Timebase) -> String { - format!("#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-TARGETDURATION:{}\n#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-PLAYLIST-TYPE:VOD\n#EXT-X-INDEPENDENT-SEGMENTS\n", target_duration(ix, tb)) +/// A piece's duration in milliseconds. +fn piece_ms(v: &CompView, p: usize) -> u64 { + crate::plan::units_to_ms(v.index.pieces[p].duration as u64, v.tb) +} + +/// The header every media playlist of a package carries: version 7, the target +/// duration, media sequence 0, on demand, independent segments. +fn media_render(v: &CompView, map_uri: Option) -> HlsRender { + HlsRender { + version: 7, + target_duration_s: Some(target_duration(v.index, v.tb)), + playlist_type: Some(PlaylistType::Vod), + independent_segments: true, + map_uri, + ..HlsRender::legacy() + } } /// `hls/classic/floor-.m3u8`. pub fn render_floor_media(v: &CompView) -> String { let f = v.floor.as_ref().expect("a floor"); - let mut o = media_head(v.index, v.tb); - let _ = writeln!(o, "#EXT-X-MAP:URI=\"../../{}\"", f.init); - for (p, (path, _)) in v.index.pieces.iter().zip(&f.fragments) { - let _ = writeln!(o, "#EXTINF:{},\n../../{path}", seconds(p.duration as u64, v.tb)); - } - o.push_str("#EXT-X-ENDLIST\n"); - o + let segments: Vec = f.fragments.iter().enumerate() + .map(|(p, (path, _))| Segment { index: p as u64, kind: SegmentKind::Content, uri: format!("../../{path}"), duration_ms: piece_ms(v, p), ad_id: None }) + .collect(); + render_hls_with(&segments, &media_render(v, Some(format!("../../{}", f.init))), &[]) } -fn floor_variant(v: &CompView, uri: &str) -> String { +/// The floor's variant, as every master names it: `../classic/floor-.m3u8`, the +/// same URI from `hls/classic/` and from a class's directory. +fn floor_variant(v: &CompView) -> StreamInf { let f = v.floor.as_ref().expect("a floor"); let (peak, mean) = v.floor_rates(); - format!("#EXT-X-STREAM-INF:BANDWIDTH={peak},AVERAGE-BANDWIDTH={mean},CODECS=\"{}\",RESOLUTION={}x{}\n{uri}\n", f.codec, f.w, f.h) + StreamInf { + uri: format!("../classic/floor-{}.m3u8", v.id()), + bandwidth: peak, + average_bandwidth: Some(mean), + codecs: f.codec.clone(), + wai_floor: None, + resolution: Some((f.w, f.h)), + frame_rate_milli: None, + } } -const MASTER_HEAD: &str = "#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-INDEPENDENT-SEGMENTS\n"; +fn master(session_data: Vec, variants: Vec) -> Result { + Ok(render_multivariant(&Multivariant { version: Some(7), independent_segments: true, session_data, variants })?) +} /// `hls/classic/master.m3u8`. pub fn render_classic_master(v: &CompView) -> String { - format!("{MASTER_HEAD}{}", floor_variant(v, &format!("floor-{}.m3u8", v.id()))) + master(Vec::new(), vec![floor_variant(v)]).expect("a floor variant renders") } -/// `hls//master.m3u8`: the classical presentation, and the line naming the class's +/// `hls//master.m3u8`: the classical master, and the line naming the class's /// WAI playlist when it has one. pub fn render_class_master(v: &CompView, c: usize) -> Result { - let mut o = MASTER_HEAD.to_string(); - if v.wai_codecs(c)?.is_some() { - let _ = writeln!(o, "#EXT-X-SESSION-DATA:DATA-ID=\"{DATA_ID_MULTIVARIANT}\",VALUE=\"wai.m3u8\""); - } - o.push_str(&floor_variant(v, &format!("../classic/floor-{}.m3u8", v.id()))); - Ok(o) + let data = match v.wai_codecs(c)? { + Some(_) => vec![SessionData::Value { data_id: DATA_ID_MULTIVARIANT.into(), value: "wai.m3u8".into() }], + None => Vec::new(), + }; + master(data, vec![floor_variant(v)]) } /// `hls//wai.m3u8`, or `None` when the class lists no WAI object. @@ -227,106 +266,122 @@ pub fn render_wai_multivariant(v: &CompView, c: usize) -> Result, let Some((union, floor)) = v.wai_codecs(c)? else { return Ok(None) }; let (peak, mean) = v.wai_rates(c); let class = v.class_id(c); - let mut o = MASTER_HEAD.to_string(); - let _ = writeln!(o, "#EXT-X-SESSION-DATA:DATA-ID=\"{DATA_ID_CLASS}\",VALUE=\"{class}\""); - let _ = writeln!(o, "#EXT-X-SESSION-DATA:DATA-ID=\"{DATA_ID_STAGES}\",URI=\"../../stages/{class}.json\""); - let _ = writeln!(o, "#EXT-X-STREAM-INF:BANDWIDTH={peak},AVERAGE-BANDWIDTH={mean},CODECS=\"{union}\",X-WAI-FLOOR=\"{floor}\",RESOLUTION={}x{}", v.w, v.h); - let _ = writeln!(o, "wai-{}.m3u8", v.id()); - Ok(Some(o)) + let data = vec![ + SessionData::Value { data_id: DATA_ID_CLASS.into(), value: class.into() }, + SessionData::Uri { data_id: DATA_ID_STAGES.into(), uri: format!("../../stages/{class}.json") }, + ]; + let variant = StreamInf { + uri: format!("wai-{}.m3u8", v.id()), + bandwidth: peak, + average_bandwidth: Some(mean), + codecs: union, + wai_floor: Some(floor), + resolution: Some((v.w, v.h)), + frame_rate_milli: None, + }; + Ok(Some(master(data, vec![variant])?)) } -fn object_uri(prefix: &str, e: &EntryRec) -> String { - format!("{prefix}objects/{}.wai#n={}", crate::hex(&e.content_hash), e.byte_len) +fn object(v: &CompView, e: &EntryRec) -> Result, CatalogError> { + v.objects.get(&e.content_hash).cloned().ok_or_else(|| CatalogError(format!("object {} is not held", crate::hex(&e.content_hash)))) } -/// `hls//wai-.m3u8`. -pub fn render_wai_media(v: &CompView, c: usize) -> String { - let mut o = media_head(v.index, v.tb); - for (p, piece) in v.index.pieces.iter().enumerate() { - let entries = v.index.piece_entries(p); - let base = entries.iter().find(|e| e.role == Role::Base).expect("a piece has a base"); - if v.index.plan[c][p] == FLOOR_ONLY { - o.push_str("#EXT-X-GAP\n"); +/// `hls//wai-.m3u8`: `wai_media_playlist` over the floor playlist, each piece's +/// base as its segment (a gap where the class takes the floor), with the layers it +/// lists. +pub fn render_wai_media(v: &CompView, c: usize) -> Result { + let floor = parse_media_playlist(&render_floor_media(v))?; + let mut segs = Vec::with_capacity(v.index.pieces.len()); + for p in 0..v.index.pieces.len() { + let base = v.index.piece_entries(p).iter().find(|e| e.role == Role::Base).expect("a piece has a base"); + let mut seg = WaiSegment::of(&object(v, base)?, piece_ms(v, p)).map_err(|e| CatalogError(e.to_string()))?; + if seg.n != base.byte_len { + return Err(CatalogError(format!("object {} is not {} bytes", crate::hex(&base.content_hash), base.byte_len))); } - let _ = writeln!(o, "#EXTINF:{},\n{}", seconds(piece.duration as u64, v.tb), object_uri("../../", base)); + seg.gap = v.index.plan[c][p] == FLOOR_ONLY; for e in v.listed(c, p).into_iter().filter(|e| e.role == Role::Refinement) { - let _ = writeln!(o, "#WAI-LAYER:N={},URI=\"{}\"", e.layer, object_uri("../../", e)); + seg.layers.push(WaiLayer::of(&object(v, e)?).map_err(|r| CatalogError(r.to_string()))?); } + segs.push(seg); } - o.push_str("#EXT-X-ENDLIST\n"); - o + let (_, _, _, text) = hls::wai_media_playlist(&floor, &segs, None, "../../objects/", true)?; + Ok(text) } -fn xml_head(duration: &str, min_buffer: u64) -> String { - format!( - "\n\n \n" - ) -} - -const XML_TAIL: &str = " \n\n"; - fn total_units(ix: &StageIndex) -> u64 { ix.pieces.iter().map(|p| p.duration as u64).sum() } -/// A `SegmentTimeline` over the pieces `ps`, each with its `t`. -fn timeline(ix: &StageIndex, ps: &[usize], tb: Timebase, indent: &str) -> String { - let mut o = format!("{indent}\n"); - for &p in ps { - let piece = &ix.pieces[p]; - let _ = writeln!(o, "{indent} ", (piece.start - ix.pieces[0].start) * tb.den as u64, piece.duration as u64 * tb.den as u64); - } - let _ = writeln!(o, "{indent}"); - o +/// The timeline over the pieces `ps`: each `t` from the first piece's start and its `d`, +/// in units of the timebase's numerator. +fn timeline(ix: &StageIndex, ps: &[usize], tb: Timebase) -> Vec<(u64, u64)> { + ps.iter().map(|&p| ((ix.pieces[p].start - ix.pieces[0].start) * tb.den as u64, ix.pieces[p].duration as u64 * tb.den as u64)).collect() +} + +fn mpd(v: &CompView, set: AdaptationSet) -> Result { + Ok(render_mpd(&Mpd { + duration_ms: crate::plan::units_to_ms(total_units(v.index), v.tb), + min_buffer_s: target_duration(v.index, v.tb), + period_id: "p0".into(), + period_start_s: 0, + adaptation_sets: vec![set], + })?) } /// `dash/classic.mpd`. pub fn render_dash_classic(v: &CompView) -> String { let f = v.floor.as_ref().expect("a floor"); let (peak, _) = v.floor_rates(); - let mut o = xml_head(&seconds(total_units(v.index), v.tb), target_duration(v.index, v.tb)); - let _ = writeln!(o, " ", f.codec, f.w, f.h); - let _ = writeln!(o, " ", v.id()); - let _ = writeln!(o, " ", v.tb.num); - let _ = writeln!(o, " ", f.init); let all: Vec = (0..v.index.pieces.len()).collect(); - o.push_str(&timeline(v.index, &all, v.tb, " ")); - for (path, _) in &f.fragments { - let _ = writeln!(o, " "); - } - o.push_str(" \n \n \n"); - o.push_str(XML_TAIL); - o + let rep = Representation { + id: format!("{}-floor", v.id()), + dependency_id: None, + bandwidth: peak, + timescale: v.tb.num as u64, + initialization: Some(format!("../{}", f.init)), + timeline: timeline(v.index, &all, v.tb), + media: f.fragments.iter().map(|(path, _)| format!("../{path}")).collect(), + }; + let set = AdaptationSet { + id: 1, content_type: Some("video".into()), mime_type: "video/mp4".into(), codecs: f.codec.clone(), size: Some((f.w, f.h)), + segment_alignment: true, start_with_sap: Some(1), essential: Vec::new(), supplemental: Vec::new(), representations: vec![rep], + }; + mpd(v, set).expect("the classical MPD renders") } /// `dash/.mpd`, or `None` when the class lists no WAI object. pub fn render_dash_class(v: &CompView, c: usize) -> Result, CatalogError> { let Some((union, floor)) = v.wai_codecs(c)? else { return Ok(None) }; let class = v.class_id(c); - let mut o = xml_head(&seconds(total_units(v.index), v.tb), target_duration(v.index, v.tb)); - let _ = writeln!(o, " ", v.w, v.h); - let _ = writeln!(o, " "); - let _ = writeln!(o, " "); - let _ = writeln!(o, " "); - let _ = writeln!(o, " "); let top = (0..v.index.pieces.len()).filter(|&p| v.index.plan[c][p] != FLOOR_ONLY).map(|p| v.index.plan[c][p]).max().unwrap_or(0); + let mut reps = Vec::new(); for k in 0..=top { let ps: Vec<(usize, &EntryRec)> = (0..v.index.pieces.len()) .filter_map(|p| v.listed(c, p).into_iter().find(|e| e.layer == k && (k > 0 || e.role == Role::Base)).map(|e| (p, e))) .collect(); let (peak, _) = rates(&ps.iter().map(|(p, e)| (e.byte_len, v.index.pieces[*p].duration as u64)).collect::>(), v.tb); - let dep = if k > 0 { format!(" dependencyId=\"{}-l{}\"", v.id(), k - 1) } else { String::new() }; - let _ = writeln!(o, " ", v.id()); - let _ = writeln!(o, " ", v.tb.num); - o.push_str(&timeline(v.index, &ps.iter().map(|(p, _)| *p).collect::>(), v.tb, " ")); - for (_, e) in &ps { - let _ = writeln!(o, " ", crate::hex(&e.content_hash)); - } - o.push_str(" \n \n"); + reps.push(Representation { + id: format!("{}-l{k}", v.id()), + dependency_id: (k > 0).then(|| format!("{}-l{}", v.id(), k - 1)), + bandwidth: peak, + timescale: v.tb.num as u64, + initialization: None, + timeline: timeline(v.index, &ps.iter().map(|(p, _)| *p).collect::>(), v.tb), + media: ps.iter().map(|(_, e)| format!("../objects/{}.wai", crate::hex(&e.content_hash))).collect(), + }); } - o.push_str(" \n"); - o.push_str(XML_TAIL); - Ok(Some(o)) + let set = AdaptationSet { + id: 1, content_type: Some("video".into()), mime_type: wai::http_binding::MEDIA_TYPE.into(), codecs: union, size: Some((v.w, v.h)), + segment_alignment: false, start_with_sap: None, + essential: vec![Descriptor { scheme: SCHEME_HTTP.into(), value: None }], + supplemental: vec![ + Descriptor { scheme: SCHEME_FLOOR.into(), value: Some(floor) }, + Descriptor { scheme: SCHEME_CLASS.into(), value: Some(class.into()) }, + Descriptor { scheme: SCHEME_STAGES.into(), value: Some(format!("../stages/{class}.json")) }, + ], + representations: reps, + }; + Ok(Some(mpd(v, set)?)) } fn tier_label(t: u8) -> Option<&'static str> { @@ -437,7 +492,7 @@ pub fn render_all(v: &CompView, layer_lag_ms: &[u64], default_deadline: u64, pol out.insert(format!("hls/{class}/master.m3u8"), render_class_master(v, c)?.into_bytes()); if let Some(m) = render_wai_multivariant(v, c)? { out.insert(format!("hls/{class}/wai.m3u8"), m.into_bytes()); - out.insert(format!("hls/{class}/wai-{id}.m3u8"), render_wai_media(v, c).into_bytes()); + out.insert(format!("hls/{class}/wai-{id}.m3u8"), render_wai_media(v, c)?.into_bytes()); } if let Some(d) = render_dash_class(v, c)? { out.insert(format!("dash/{class}.mpd"), d.into_bytes()); @@ -475,7 +530,7 @@ mod tests { let objects: BTreeMap<[u8; 32], Vec> = objs.iter().map(|o| (crate::objects::object_hash(o), o.clone())).collect(); let v = view(&ix, &objects); assert_eq!(v.wai_codecs(0).unwrap().unwrap(), ("wai1.video.int_motion+video.int_refine".to_string(), "wai1.video.int_motion+video.int_refine".to_string())); - let media = render_wai_media(&v, 0); + let media = render_wai_media(&v, 0).unwrap(); let layers: Vec<&str> = media.lines().filter(|l| l.starts_with("#WAI-LAYER")).collect(); assert_eq!(layers.len(), 2); for (i, l) in layers.iter().enumerate() { @@ -483,11 +538,20 @@ mod tests { assert_eq!(t.n as usize, i + 1); assert!(t.uri.starts_with("../../objects/") && t.uri.contains(".wai#n=")); } - let m1 = render_wai_media(&v, 1); + let m1 = render_wai_media(&v, 1).unwrap(); assert_eq!(m1.lines().filter(|l| l.starts_with("#WAI-LAYER")).count(), 1, "class 1 lists layer 1 of piece 0 only"); assert_eq!(m1.lines().filter(|l| *l == "#EXT-X-GAP").count(), 1, "and piece 1 as floor only"); let extinf = |s: &str| s.lines().filter(|l| l.starts_with("#EXTINF") || l.starts_with("#EXT-X-TARGETDURATION") || l.starts_with("#EXT-X-MEDIA-SEQUENCE")).map(str::to_string).collect::>(); assert_eq!(extinf(&media), extinf(&render_floor_media(&v)), "adjacent: the same sequence and durations to the millisecond"); + // The layers sit after their segment's EXTINF, where http-delivery's reader finds + // them; the WAI playlist is adjacent to the floor's; a class's master is the + // classical master with the one line http-delivery §11.1 allows. + let read = wai::http_binding::hls::parse_media_playlist(&media).unwrap(); + assert_eq!(read.segments[0].extra.layers.len(), 2); + assert!(read.is_rendered_form(&media)); + wai::http_binding::hls::check_adjacent(&media, &render_floor_media(&v)).unwrap(); + wai::http_binding::hls::check_adjacent(&m1, &render_floor_media(&v)).unwrap(); + wai::http_binding::hls::classical_unchanged(&render_classic_master(&v), &render_class_master(&v, 0).unwrap()).unwrap(); let dash = render_dash_class(&v, 0).unwrap().unwrap(); assert!(dash.contains(" Date: Tue, 6 Oct 2026 09:06:19 -0400 Subject: [PATCH 16/55] wai: one strict Ed25519 rule (wai::sig::verify_strict), held by the playlist receipt and http_verify.py A canonical key of large order, S < L, R of large order compared byte for byte. Corpus receipts at the edges: the identity key in both spellings, R the identity canonical and respelled under the key B, and a signature under B the rule accepts. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/http-delivery.md | 8 +- wai/http-conformance/README.md | 1 + wai/http-conformance/cases.json | 5 + .../cases/sig-key-base-point.json | 1 + .../cases/sig-key-identity-long.json | 1 + .../cases/sig-key-identity.json | 1 + .../cases/sig-r-non-canonical.json | 1 + .../cases/sig-r-small-order.json | 1 + wai/http-conformance/falsify/run.sh | 2 +- wai/http-conformance/http_verify.py | 20 +- .../src/bin/wai_cli/http_vectors_playlists.rs | 109 ++++++++++ wai/wai-rs/src/lib.rs | 6 + wai/wai-rs/src/sig.rs | 196 ++++++++++++++++++ wai/wai-rs/src/wai_video_manifest.rs | 11 +- 14 files changed, 353 insertions(+), 10 deletions(-) create mode 100644 wai/http-conformance/cases/sig-key-base-point.json create mode 100644 wai/http-conformance/cases/sig-key-identity-long.json create mode 100644 wai/http-conformance/cases/sig-key-identity.json create mode 100644 wai/http-conformance/cases/sig-r-non-canonical.json create mode 100644 wai/http-conformance/cases/sig-r-small-order.json create mode 100644 wai/wai-rs/src/sig.rs diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index f970499b6..8ccdbb364 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -1115,4 +1115,10 @@ reads back (Appendix G) and has an RFC 8785 form; the BLAKE3 of the rendering is `manifest_hash`; `work_compose_ops` is the number of segments and `viewers_served` is at least 1; the segment at position `i` has `index` `i`, and `ad_id` exactly when it is an ad; `joules_micro` and the class are both present or the figure is the unmetered 0; -and `sig` is an Ed25519 signature (RFC 8032) by `signer_pubkey` over the signed bytes. +and `sig` is an Ed25519 signature (RFC 8032) by `signer_pubkey` over the signed bytes, +verified strictly: `signer_pubkey` decodes (§5.1.3) and is the one encoding of its point +(`y` < p, and no sign bit when `x` is 0), and neither it nor `R` is of small order +(`[8]P` is not the identity); `S` < L; and the encoding of `[S]B − [k]A` equals `R` +byte for byte, so an `R` that is not its point's one encoding never verifies. A verifier +that accepts a key or an `R` this refuses accepts a signature any party can make: under +a key of small order, `(R, S) = (B, 1)` verifies over every message. diff --git a/wai/http-conformance/README.md b/wai/http-conformance/README.md index 8435a3f1b..be9c503d9 100644 --- a/wai/http-conformance/README.md +++ b/wai/http-conformance/README.md @@ -37,6 +37,7 @@ reference adaptation policies. Two implementations check every case and must agr | `players/` | the player fixture: `classical/` (frozen) is a playable classical presentation, `master.m3u8` its master as published with the session-data line, and the WAI multivariant and media playlists and MPD over O13 | | `*/mr/.json` | each WAI media playlist's receipt (§8.6, Appendix H), sealed with the corpus's test key (seed 32 bytes of `0x57`, signer `did:example:http-vectors`; it attests nothing); the live revisions are chained | | `cases/live-r2-orphan.json` | the third live revision sealed with no parent: a receipt that verifies and does not chain | +| `cases/sig-*.json` | the O9 playlist's receipt re-signed at the edges of Ed25519 (Appendix H): the identity key, canonical and respelled `y` = p + 1, under `(R, S) = (B, 1)`; under the key B (scalar 1), `R` the identity, canonical and respelled, with `S` = k, so the cofactorless equation holds point for point; and under the key B a signature the strict rule accepts (`sig-key-base-point.json`) | ## Objects diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index 8aac9807d..176d7c0ae 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -449,6 +449,11 @@ {"name":"playlist_receipt_extras_equivalent","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"extras\":[]","\"extras\":[{\"discontinuity\":false,\"gap\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null},{\"discontinuity\":false,\"gap\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null},{\"discontinuity\":false,\"gap\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null}]"]]},"expect":{"error":"playlist_receipt_invalid"}}, {"name":"playlist_receipt_kind","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"kind\":\"wai.video.manifest\"","\"kind\":\"wai.video.package\""]]},"expect":{"error":"playlist_receipt_malformed"}}, {"name":"playlist_receipt_unknown_member","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["{\"kind\"","{\"note\":1,\"kind\""]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_sig_key_identity","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-key-identity.json","name":false},"expect":{"error":"playlist_receipt_invalid"}}, +{"name":"playlist_receipt_sig_key_identity_long","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-key-identity-long.json","name":false},"expect":{"error":"playlist_receipt_invalid"}}, +{"name":"playlist_receipt_sig_r_small_order","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-r-small-order.json","name":false},"expect":{"error":"playlist_receipt_invalid"}}, +{"name":"playlist_receipt_sig_r_non_canonical","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-r-non-canonical.json","name":false},"expect":{"error":"playlist_receipt_invalid"}}, +{"name":"playlist_receipt_sig_key_base_point","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-key-base-point.json","name":false},"expect":{"signed":"9cec31c4957646fe2bb595b69590f102377d9a6754db72d613cbfad041d55d06","parent":null}}, {"name":"playlist_receipt_part_independent","op":"playlist_receipt","input":{"playlist":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","edit_playlist":[["DURATION=0.320,INDEPENDENT=YES,URI=\"720/1200.3","DURATION=0.320,URI=\"720/1200.3"]]},"expect":{"error":"playlist_receipt_mismatch"}}, {"name":"playlist_receipt_parts_reordered","op":"playlist_receipt","input":{"playlist":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","edit_receipt":[["\"uri\":\"720/1200.3.wai","\"uri\":\"720/1200.X.wai"],["\"uri\":\"720/1200.4.wai","\"uri\":\"720/1200.3.wai"],["\"uri\":\"720/1200.X.wai","\"uri\":\"720/1200.4.wai"]]},"expect":{"error":"playlist_receipt_mismatch"}}, {"name":"live_edge","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8","edit_wai":[]},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1200.5.wai","object":"O12p0_05"},{"alias":"720/1201.3.wai","object":"O12p1_03"},{"alias":"720/1201.7.wai","object":"O12p1_08"},{"alias":"720/1200.2.wai","object":"O12p0_02","patch_object":[[400,"00"]]},{"alias":"720/1202.0.wai","object":"O12p1_11"},{"alias":"1200.m4s","object":"O12s0"}]},"expect":{"revisions":[{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1200.5.wai","listed_in":1,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"},{"alias":"720/1201.7.wai","listed_in":2,"outcome":"object_digest_mismatch","held":null},{"alias":"720/1200.2.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1202.0.wai","listed_in":null,"outcome":"not_listed","held":null},{"alias":"1200.m4s","listed_in":null,"outcome":"not_listed","held":null}]}}, diff --git a/wai/http-conformance/cases/sig-key-base-point.json b/wai/http-conformance/cases/sig-key-base-point.json new file mode 100644 index 000000000..ea393b61f --- /dev/null +++ b/wai/http-conformance/cases/sig-key-base-point.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666662f03c04a2605fd41de75215b9a872c6a05423724a0ed9ee29c5c6854cc8a7d05","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/cases/sig-key-identity-long.json b/wai/http-conformance/cases/sig-key-identity-long.json new file mode 100644 index 000000000..784c688d7 --- /dev/null +++ b/wai/http-conformance/cases/sig-key-identity-long.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666660100000000000000000000000000000000000000000000000000000000000000","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/cases/sig-key-identity.json b/wai/http-conformance/cases/sig-key-identity.json new file mode 100644 index 000000000..0df50a92e --- /dev/null +++ b/wai/http-conformance/cases/sig-key-identity.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"0100000000000000000000000000000000000000000000000000000000000000","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666660100000000000000000000000000000000000000000000000000000000000000","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/cases/sig-r-non-canonical.json b/wai/http-conformance/cases/sig-r-non-canonical.json new file mode 100644 index 000000000..86887c419 --- /dev/null +++ b/wai/http-conformance/cases/sig-r-non-canonical.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f080a137c08411f3c035f03a58f1b5131bcb5887f5082212f2caa4d827d8aed04","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/cases/sig-r-small-order.json b/wai/http-conformance/cases/sig-r-small-order.json new file mode 100644 index 000000000..e4e3d9c2e --- /dev/null +++ b/wai/http-conformance/cases/sig-r-small-order.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"0100000000000000000000000000000000000000000000000000000000000000f57a2a3a17cc1400dba50adbc733921a1b095514a8d91a25909f588c437ac10f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh index b913b2a64..6072d13b3 100755 --- a/wai/http-conformance/falsify/run.sh +++ b/wai/http-conformance/falsify/run.sh @@ -47,7 +47,7 @@ done cd "$ROOT/wai/http-conformance" for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done tick-noop refinement-unclipped \ pdt-ignored dseq-ignored parts-after-extinf average-floor render-digest-off name-unchecked chain-unchecked \ - ilog2-no-interp bola-waits unlabelled-as-zero round-half-down; do + ilog2-no-interp bola-waits unlabelled-as-zero round-half-down small-order-key small-order-r; do if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then echo "::error::http_verify.py --mutate $m passed"; fail=1 else diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index e5572422b..f494d25f6 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -1947,8 +1947,10 @@ def jcs(v): return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + jcs(v[k]) for k in keys) + "}" -# Ed25519 (RFC 8032 §5.1), verification only, from the RFC's definitions: keys and R -# decoded with y < p, S < L, and [S]B = R + [k]A checked by encoding [S]B − [k]A. +# Ed25519 (RFC 8032 §5.1), verification only, from the RFC's definitions, strictly as +# WAI verifies it: the key and R decoded with y < p (and no sign bit on x = 0), neither of +# small order, S < L, and [S]B = R + [k]A checked by encoding [S]B − [k]A and comparing it +# with R byte for byte. _P = 2 ** 255 - 19 _L = 2 ** 252 + 27742317777372353535851937790883648493 _D = -121665 * pow(121666, _P - 2, _P) % _P @@ -2005,9 +2007,19 @@ _BY = 4 * pow(5, _P - 2, _P) % _P _B = (_recover_x(_BY, 0), _BY, 1, _recover_x(_BY, 0) * _BY % _P) +def _small_order(p): + """Whether [8]P is the identity: P's order divides 8.""" + q = _mul(8, p) + return q[0] % _P == 0 and (q[1] - q[2]) % _P == 0 + + def ed25519_verify(pk, msg, sig): - a = _decode(pk) - if a is None or len(sig) != 64: + if len(pk) != 32 or len(sig) != 64: + return False + a, r = _decode(pk), _decode(sig[:32]) + if a is None or (_small_order(a) and MUTATE != "small-order-key"): + return False + if r is None or (_small_order(r) and MUTATE != "small-order-r"): return False s = int.from_bytes(sig[32:], "little") if s >= _L: diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index 29d4d12c5..b7ef73a79 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -608,6 +608,21 @@ fn seal_all(c: &mut Corpus, out: &mut BTreeMap>) -> Result<(), S out.insert(f, r.to_json().into_bytes()); sealed.push(r); } + // Signatures a lenient verifier accepts and the strict rule refuses (crate::sig), each + // over the o9 playlist's receipt: the identity key in its canonical and its long + // spelling under (R, S) = (B, 1), which satisfies the equation for any message; and + // under the key B (scalar 1), R the identity, canonical and respelled, with S = k. + // The same key with R = B and S = 1 + k is a signature the rule accepts. + for (file, key_bytes, r, s_of_k) in crafted_signatures() { + let mut x = sealed[0].clone(); + x.signer_pubkey = key_bytes; + let k = k_of(&r, &key_bytes, &x.signing_payload()); + x.sig[..32].copy_from_slice(&r); + x.sig[32..].copy_from_slice(&s_of_k(&k)); + let f = format!("cases/{file}"); + c.extra.insert(f.clone(), x.to_json().into_bytes()); + out.insert(f, x.to_json().into_bytes()); + } // Revision 2 sealed with no parent: a receipt that verifies and does not chain. let t = String::from_utf8(c.file("live/wai-r2.m3u8")?).map_err(|_| "not UTF-8")?; let orphan = seal_playlist(&t, &key, PLAYLIST_SIGNER, "live/720", GrantRef::unbounded("video.manifest"), None).map_err(|e| e.to_string())?; @@ -616,6 +631,94 @@ fn seal_all(c: &mut Corpus, out: &mut BTreeMap>) -> Result<(), S Ok(()) } +// -------------------------------------------------- signatures at the edges + +/// L = 2^252 + 27742317777372353535851937790883648493, in little-endian 64-bit limbs. +const L64: [u64; 4] = [0x5812_631a_5cf5_d3ed, 0x14de_f9de_a2f7_9cd6, 0, 0x1000_0000_0000_0000]; + +/// A little-endian integer of any length, reduced mod L, by binary long division. +fn mod_l(bytes: &[u8]) -> [u8; 32] { + let mut r = [0u64; 4]; + for i in (0..bytes.len() * 8).rev() { + // r < L < 2^253, so 2r + 1 < 2^254 fits in four limbs. + let mut carry = u64::from((bytes[i / 8] >> (i % 8)) & 1); + for limb in r.iter_mut() { + let top = *limb >> 63; + *limb = (*limb << 1) | carry; + carry = top; + } + if (0..4).rev().find(|&j| r[j] != L64[j]).is_none_or(|j| r[j] > L64[j]) { + let mut borrow = 0u64; + for (j, limb) in r.iter_mut().enumerate() { + let (d, b1) = limb.overflowing_sub(L64[j]); + let (d, b2) = d.overflowing_sub(borrow); + *limb = d; + borrow = u64::from(b1 || b2); + } + } + } + let mut out = [0u8; 32]; + for (j, limb) in r.iter().enumerate() { + out[8 * j..8 * j + 8].copy_from_slice(&limb.to_le_bytes()); + } + out +} + +/// k = SHA-512(R ‖ A ‖ M) mod L (RFC 8032 §5.1.7). +fn k_of(r: &[u8; 32], a: &[u8; 32], msg: &[u8]) -> [u8; 32] { + use sha2::{Digest, Sha512}; + let mut h = Sha512::new(); + h.update(r); + h.update(a); + h.update(msg); + mod_l(&h.finalize()) +} + +/// `n` as 32 little-endian bytes. +fn le32(n: u8) -> [u8; 32] { + let mut b = [0u8; 32]; + b[0] = n; + b +} + +/// p + `n` = 2^255 − 19 + `n`, a second spelling of the field element `n` (n < 19). +fn long_spelling(n: u8) -> [u8; 32] { + let mut b = [0xffu8; 32]; + b[0] = 0xed + n; + b[31] = 0x7f; + b +} + +/// The base point B. +const B_ENC: [u8; 32] = [0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66]; + +type SOfK = fn(&[u8; 32]) -> [u8; 32]; + +/// Each crafted receipt: its file under `cases/`, the key, R, and S as a function of k. +fn crafted_signatures() -> Vec<(&'static str, [u8; 32], [u8; 32], SOfK)> { + let one: SOfK = |_| le32(1); + let k: SOfK = |k| *k; + let one_plus_k: SOfK = |k| { + let mut wide = [0u8; 33]; + wide[..32].copy_from_slice(k); + let mut carry = 1u16; + for b in wide.iter_mut() { + let v = u16::from(*b) + carry; + *b = v as u8; + carry = v >> 8; + } + mod_l(&wide) + }; + vec![ + ("sig-key-identity.json", le32(1), B_ENC, one), + ("sig-key-identity-long.json", long_spelling(1), B_ENC, one), + ("sig-r-small-order.json", B_ENC, le32(1), k), + ("sig-r-non-canonical.json", B_ENC, long_spelling(1), k), + ("sig-key-base-point.json", B_ENC, B_ENC, one_plus_k), + ] +} + #[cfg(not(feature = "video_manifest"))] fn seal_all(_: &mut Corpus, _: &mut BTreeMap>) -> Result<(), String> { Err("built without video_manifest: the corpus's playlist receipts need it (build with --features http,video_manifest)".into()) @@ -788,6 +891,12 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { v.push(refused("playlist_receipt_extras_equivalent", "playlist_receipt", base(json!({ "edit_receipt": [["\"extras\":[]", format!("\"extras\":[{default_extra},{default_extra},{default_extra}]")]] })), "playlist_receipt_invalid")); v.push(refused("playlist_receipt_kind", "playlist_receipt", base(json!({ "edit_receipt": [["\"kind\":\"wai.video.manifest\"", "\"kind\":\"wai.video.package\""]] })), "playlist_receipt_malformed")); v.push(refused("playlist_receipt_unknown_member", "playlist_receipt", base(json!({ "edit_receipt": [["{\"kind\"", "{\"note\":1,\"kind\""]] })), "playlist_receipt_malformed")); + // Ed25519 at its edges (§8.6 step 4, strictly): keys and R a lenient verifier accepts. + for (file, _, _, _) in crafted_signatures() { + let name = format!("playlist_receipt_{}", file.trim_end_matches(".json").replace('-', "_")); + let input = json!({ "playlist": "playlists/o9/wai-180.m3u8", "receipt": format!("cases/{file}"), "name": false }); + v.push(if file == "sig-key-base-point.json" { case(name, "playlist_receipt", input) } else { refused(&name, "playlist_receipt", input, "playlist_receipt_invalid") }); + } let r1r = receipt_of(c, "live/wai-r1.m3u8")?; v.push(refused("playlist_receipt_part_independent", "playlist_receipt", json!({ "playlist": "live/wai-r1.m3u8", "receipt": r1r, "edit_playlist": [["DURATION=0.320,INDEPENDENT=YES,URI=\"720/1200.3", "DURATION=0.320,URI=\"720/1200.3"]] }), "playlist_receipt_mismatch")); diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 78139350a..f4a7d69ed 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -311,6 +311,12 @@ pub mod energy_binding; #[cfg(feature = "c2pa_emit")] pub mod c2pa_emit; +// Ed25519 verified strictly (RFC 8032 §5.1.7 with a canonical key of large order, S < L, +// and R of large order compared byte for byte): the one routine every receipt family's +// signature check is to move onto. Needs ed25519-dalek, which `world` and `oer` bring. +#[cfg(any(feature = "world", feature = "oer"))] +pub mod sig; + // RFC 8785 canonical JSON over the subset WAI signs and digests (integers up to // 2^53 − 1, no floats). Needs serde_json alone, so it is ungated: claims, key logs, // energy figures and packaging policies canonicalise through this one function. diff --git a/wai/wai-rs/src/sig.rs b/wai/wai-rs/src/sig.rs new file mode 100644 index 000000000..429304a22 --- /dev/null +++ b/wai/wai-rs/src/sig.rs @@ -0,0 +1,196 @@ +//! Ed25519 verification as WAI verifies it (RFC 8032 §5.1.7, strictly): one routine, so +//! every receipt family that moves onto it accepts exactly the signatures every other +//! conforming verifier accepts. +//! +//! The rule, in order: +//! +//! 1. The key `A` decodes, and is the one canonical encoding of its point: `y` < p, and +//! `x` = 0 never carries the sign bit. `VerifyingKey::from_bytes` reduces a `y` ≥ p +//! and accepts the sign bit on `x` = 0; both are refused here by re-encoding the point +//! and comparing bytes. +//! 2. `A` is not of small order (its order divides 8). Under such a key the equation can +//! hold for a signature made without any private key: under the identity key, every +//! `R` = [s]B with `S` = s verifies. +//! 3. `S` < L. Checked here as well as inside the library, whose own test shrinks to the +//! top three bits when any crate in a build enables its `legacy_compatibility` +//! feature, and S + L passes that test. +//! 4. `R` decodes and is not of small order, and the cofactorless equation holds with +//! [S]B − [k]A encoded and compared with `R` byte for byte, so a non-canonical `R` +//! never verifies (ed25519-dalek's `verify_strict`). + +use ed25519_dalek::{Signature, VerifyingKey}; + +/// Why a signature is refused. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SigError { + /// The key does not decode, is not its point's one canonical encoding, or is of small + /// order (steps 1 and 2). + BadKey, + /// `S` ≥ L, `R` does not decode or is of small order, or the equation does not hold + /// with `R` compared byte for byte (steps 3 and 4). + BadSignature, +} + +impl std::fmt::Display for SigError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + SigError::BadKey => "the key is not a canonical point of large order", + SigError::BadSignature => "the signature does not verify strictly", + }) + } +} + +impl std::error::Error for SigError {} + +/// The group order L = 2^252 + 27742317777372353535851937790883648493, little-endian. +const L: [u8; 32] = [ + 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10, +]; + +/// Whether the little-endian integer `s` is below L. +fn below_l(s: &[u8; 32]) -> bool { + for i in (0..32).rev() { + if s[i] != L[i] { + return s[i] < L[i]; + } + } + false +} + +/// Steps 1 and 2: the key, refused unless it is the canonical encoding of a point of +/// large order. +pub fn strict_key(bytes: &[u8; 32]) -> Result { + let key = VerifyingKey::from_bytes(bytes).map_err(|_| SigError::BadKey)?; + if key.to_edwards().compress().to_bytes() != *bytes || key.is_weak() { + return Err(SigError::BadKey); + } + Ok(key) +} + +/// The whole rule over `msg`: `Ok` only when every step holds. +pub fn verify_strict(pubkey: &[u8; 32], msg: &[u8], sig: &[u8; 64]) -> Result<(), SigError> { + let key = strict_key(pubkey)?; + let s: &[u8; 32] = sig[32..].try_into().expect("a signature is 64 bytes"); + if !below_l(s) { + return Err(SigError::BadSignature); + } + key.verify_strict(msg, &Signature::from_bytes(sig)).map_err(|_| SigError::BadSignature) +} + +#[cfg(test)] +mod tests { + use super::*; + use ed25519_dalek::{Signer, SigningKey}; + + /// The base point B, encoded. + const B: [u8; 32] = [ + 0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + ]; + + fn le(mut n: [u8; 32], add: u8) -> [u8; 32] { + let mut carry = add as u16; + for b in n.iter_mut() { + let v = *b as u16 + carry; + *b = v as u8; + carry = v >> 8; + } + n + } + + /// p = 2^255 − 19, little-endian. + fn p() -> [u8; 32] { + let mut b = [0xffu8; 32]; + b[0] = 0xed; + b[31] = 0x7f; + b + } + + fn sig(r: [u8; 32], s: [u8; 32]) -> [u8; 64] { + let mut g = [0u8; 64]; + g[..32].copy_from_slice(&r); + g[32..].copy_from_slice(&s); + g + } + + #[test] + fn a_real_signature_verifies() { + let k = SigningKey::from_bytes(&[7u8; 32]); + let g = k.sign(b"m").to_bytes(); + verify_strict(&k.verifying_key().to_bytes(), b"m", &g).unwrap(); + assert_eq!(verify_strict(&k.verifying_key().to_bytes(), b"n", &g), Err(SigError::BadSignature)); + } + + #[test] + fn the_identity_key_is_refused_in_either_spelling() { + let one = le([0; 32], 1); + // (R, S) = (B, 1) satisfies the equation under the identity for any message. + let forged = sig(B, one); + assert_eq!(verify_strict(&one, b"any", &forged), Err(SigError::BadKey)); + // y = p + 1, a second spelling of the identity that from_bytes reduces. + let long = le(p(), 1); + assert!(VerifyingKey::from_bytes(&long).is_ok(), "the library reads the long spelling"); + assert_eq!(verify_strict(&long, b"any", &forged), Err(SigError::BadKey)); + // x = 0 with the sign bit set. + let mut signed = one; + signed[31] |= 0x80; + assert_eq!(verify_strict(&signed, b"any", &forged), Err(SigError::BadKey)); + } + + #[test] + fn a_non_canonical_key_of_large_order_is_refused() { + // A point of large order whose y is below 19 has the second spelling y + p, + // which the library reads as the same point; strictly, only y itself is a key. + let mut seen = 0; + for y in 2u8..19 { + let short = le([0; 32], y); + let Ok(k) = VerifyingKey::from_bytes(&short) else { continue }; + if k.is_weak() { + continue; + } + let long = le(p(), y); + assert!(VerifyingKey::from_bytes(&long).is_ok(), "the library reads y = p + {y}"); + assert_eq!(strict_key(&long).err(), Some(SigError::BadKey), "y = p + {y}"); + assert!(strict_key(&short).is_ok(), "y = {y}"); + seen += 1; + } + assert!(seen > 0, "some y below 19 is on the curve with large order"); + } + + #[test] + fn s_at_or_above_l_is_refused() { + let k = SigningKey::from_bytes(&[9u8; 32]); + let g = k.sign(b"m").to_bytes(); + let s: [u8; 32] = g[32..].try_into().unwrap(); + // S + L, as a 256-bit little-endian sum (it fits: S < L < 2^253). + let mut sum = [0u8; 32]; + let mut carry = 0u16; + for i in 0..32 { + let v = s[i] as u16 + L[i] as u16 + carry; + sum[i] = v as u8; + carry = v >> 8; + } + let r: [u8; 32] = g[..32].try_into().unwrap(); + assert_eq!(verify_strict(&k.verifying_key().to_bytes(), b"m", &sig(r, sum)), Err(SigError::BadSignature)); + assert_eq!(verify_strict(&k.verifying_key().to_bytes(), b"m", &sig(r, L)), Err(SigError::BadSignature)); + assert!(!below_l(&L) && below_l(&s)); + } + + #[test] + fn a_small_order_or_non_canonical_r_is_refused() { + let k = SigningKey::from_bytes(&[11u8; 32]); + let pk = k.verifying_key().to_bytes(); + // R = the identity (small order), S = k·a would be needed for the equation; any S + // fails the small-order test first. + let one = le([0; 32], 1); + assert_eq!(verify_strict(&pk, b"m", &sig(one, [0; 32])), Err(SigError::BadSignature)); + // A real signature with R respelled y + p, when R's y is below 19 (rare), is + // covered by the corpus; here: R with the sign bit flipped is another point. + let g = k.sign(b"m").to_bytes(); + let mut r: [u8; 32] = g[..32].try_into().unwrap(); + r[31] ^= 0x80; + let s: [u8; 32] = g[32..].try_into().unwrap(); + assert_eq!(verify_strict(&pk, b"m", &sig(r, s)), Err(SigError::BadSignature)); + } +} diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs index 6d1c762ff..6315ee926 100644 --- a/wai/wai-rs/src/wai_video_manifest.rs +++ b/wai/wai-rs/src/wai_video_manifest.rs @@ -34,7 +34,7 @@ //! theirs: [`GrantRef`] carries the capability, the joule ceiling and the //! `funds_ceiling` — here the ad budget. Reuse, not reinvention. -use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; +use ed25519_dalek::{Signer, SigningKey}; use crate::quantum_ops::GrantRef; use crate::world::receipt::merkle_root; @@ -235,7 +235,9 @@ impl ManifestReceipt { Some(*h.finalize().as_bytes()) } - fn signing_payload(&self) -> Vec { + /// The bytes the signature covers (Appendix H of http-delivery): everything but + /// `sig`. + pub fn signing_payload(&self) -> Vec { let mut o = DOMAIN_RECEIPT.to_vec(); o.extend_from_slice(&self.manifest_hash); o.extend_from_slice(&self.session_class_hash); @@ -421,8 +423,9 @@ impl ManifestReceipt { if !figure_and_class_cohere(self.joules_micro, self.energy_provenance.as_ref()) { return false; } - let Ok(k) = VerifyingKey::from_bytes(&self.signer_pubkey) else { return false }; - k.verify(&self.signing_payload(), &Signature::from_bytes(&self.sig)).is_ok() + // Strictly (crate::sig): a canonical key of large order, S < L, and R of large + // order compared byte for byte, as every playlist verifier checks it. + crate::sig::verify_strict(&self.signer_pubkey, &self.signing_payload(), &self.sig).is_ok() } /// The independent check: the bytes a player received hash to -- 2.54.0 (Apple Git-157) From 74fd4baf1e8c8922715839b1bb76acce01dd1444 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 09:07:27 -0400 Subject: [PATCH 17/55] wai::sig: the small-order R test holds a signature a lenient check accepts Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/sig.rs | 50 ++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/wai/wai-rs/src/sig.rs b/wai/wai-rs/src/sig.rs index 429304a22..461359334 100644 --- a/wai/wai-rs/src/sig.rs +++ b/wai/wai-rs/src/sig.rs @@ -177,20 +177,46 @@ mod tests { assert!(!below_l(&L) && below_l(&s)); } + /// `bytes` (little-endian, any length) mod L, by binary long division. + fn mod_l(bytes: &[u8]) -> [u8; 32] { + let mut r = [0u8; 33]; + for i in (0..bytes.len() * 8).rev() { + let mut carry = (bytes[i / 8] >> (i % 8)) & 1; + for b in r.iter_mut() { + let top = *b >> 7; + *b = (*b << 1) | carry; + carry = top; + } + let lx = |j: usize| if j < 32 { L[j] } else { 0 }; + if (0..33).rev().find(|&j| r[j] != lx(j)).is_none_or(|j| r[j] > lx(j)) { + let mut borrow = 0i16; + for j in 0..33 { + let v = r[j] as i16 - lx(j) as i16 - borrow; + r[j] = v.rem_euclid(256) as u8; + borrow = i16::from(v < 0); + } + } + } + r[..32].try_into().unwrap() + } + #[test] fn a_small_order_or_non_canonical_r_is_refused() { - let k = SigningKey::from_bytes(&[11u8; 32]); - let pk = k.verifying_key().to_bytes(); - // R = the identity (small order), S = k·a would be needed for the equation; any S - // fails the small-order test first. + use ed25519_dalek::Verifier; + use sha2::{Digest, Sha512}; + // Under the key B (scalar 1), R the identity and S = k satisfy the cofactorless + // equation point for point: [k]B = O + [k]B. A lenient check accepts it. + let msg = b"m"; + let k_of = |r: &[u8; 32]| mod_l(&Sha512::new().chain_update(r).chain_update(B).chain_update(msg).finalize()); let one = le([0; 32], 1); - assert_eq!(verify_strict(&pk, b"m", &sig(one, [0; 32])), Err(SigError::BadSignature)); - // A real signature with R respelled y + p, when R's y is below 19 (rare), is - // covered by the corpus; here: R with the sign bit flipped is another point. - let g = k.sign(b"m").to_bytes(); - let mut r: [u8; 32] = g[..32].try_into().unwrap(); - r[31] ^= 0x80; - let s: [u8; 32] = g[32..].try_into().unwrap(); - assert_eq!(verify_strict(&pk, b"m", &sig(r, s)), Err(SigError::BadSignature)); + let g = sig(one, k_of(&one)); + let key = VerifyingKey::from_bytes(&B).unwrap(); + assert!(key.verify(msg, &Signature::from_bytes(&g)).is_ok(), "the lenient check accepts R of small order"); + assert_eq!(verify_strict(&B, msg, &g), Err(SigError::BadSignature)); + // The identity respelled y = p + 1, with S over those bytes: never byte-equal. + let long = le(p(), 1); + assert_eq!(verify_strict(&B, msg, &sig(long, k_of(&long))), Err(SigError::BadSignature)); + // R = B, S = 1 + k: a signature by the scalar 1, which the rule accepts. + verify_strict(&B, msg, &sig(B, mod_l(&le(k_of(&B), 1)))).unwrap(); } } -- 2.54.0 (Apple Git-157) From 344530fb7ef4fe0ce2bdc9f0fcd9728133940de6 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 09:08:46 -0400 Subject: [PATCH 18/55] http-conformance: falsify the strict Ed25519 rule (a lenient equation; lenient keys) Co-Authored-By: Claude Opus 5.5 --- .../falsify/sig-key-lenient.patch | 25 +++++++++++++++++++ .../falsify/sig-verify-lenient.patch | 15 +++++++++++ 2 files changed, 40 insertions(+) create mode 100644 wai/http-conformance/falsify/sig-key-lenient.patch create mode 100644 wai/http-conformance/falsify/sig-verify-lenient.patch diff --git a/wai/http-conformance/falsify/sig-key-lenient.patch b/wai/http-conformance/falsify/sig-key-lenient.patch new file mode 100644 index 000000000..804c776ae --- /dev/null +++ b/wai/http-conformance/falsify/sig-key-lenient.patch @@ -0,0 +1,25 @@ +# must fail: playlist_receipt_sig_key_identity playlist_receipt_sig_key_identity_long playlist_receipt_sig_r_small_order +# keys read as the library reads them, and the equation checked leniently (wai::sig) +diff --git a/wai/wai-rs/src/sig.rs b/wai/wai-rs/src/sig.rs +index 46135933..01f85c39 100644 +--- a/wai/wai-rs/src/sig.rs ++++ b/wai/wai-rs/src/sig.rs +@@ -62,9 +62,6 @@ fn below_l(s: &[u8; 32]) -> bool { + /// large order. + pub fn strict_key(bytes: &[u8; 32]) -> Result { + let key = VerifyingKey::from_bytes(bytes).map_err(|_| SigError::BadKey)?; +- if key.to_edwards().compress().to_bytes() != *bytes || key.is_weak() { +- return Err(SigError::BadKey); +- } + Ok(key) + } + +@@ -75,7 +72,7 @@ pub fn verify_strict(pubkey: &[u8; 32], msg: &[u8], sig: &[u8; 64]) -> Result<() + if !below_l(s) { + return Err(SigError::BadSignature); + } +- key.verify_strict(msg, &Signature::from_bytes(sig)).map_err(|_| SigError::BadSignature) ++ ed25519_dalek::Verifier::verify(&key, msg, &Signature::from_bytes(sig)).map_err(|_| SigError::BadSignature) + } + + #[cfg(test)] diff --git a/wai/http-conformance/falsify/sig-verify-lenient.patch b/wai/http-conformance/falsify/sig-verify-lenient.patch new file mode 100644 index 000000000..2450c0fb0 --- /dev/null +++ b/wai/http-conformance/falsify/sig-verify-lenient.patch @@ -0,0 +1,15 @@ +# must fail: playlist_receipt_sig_r_small_order +# R of small order accepted (wai::sig::verify_strict checks the equation leniently) +diff --git a/wai/wai-rs/src/sig.rs b/wai/wai-rs/src/sig.rs +index 46135933..ebe58160 100644 +--- a/wai/wai-rs/src/sig.rs ++++ b/wai/wai-rs/src/sig.rs +@@ -75,7 +75,7 @@ pub fn verify_strict(pubkey: &[u8; 32], msg: &[u8], sig: &[u8; 64]) -> Result<() + if !below_l(s) { + return Err(SigError::BadSignature); + } +- key.verify_strict(msg, &Signature::from_bytes(sig)).map_err(|_| SigError::BadSignature) ++ ed25519_dalek::Verifier::verify(&key, msg, &Signature::from_bytes(sig)).map_err(|_| SigError::BadSignature) + } + + #[cfg(test)] -- 2.54.0 (Apple Git-157) From 9c6ce6fac9f7f32bc2a53f2dd10f4c0a53d487e0 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 09:22:34 -0400 Subject: [PATCH 19/55] wai: a live client walks back to its head across skipped revisions, and no revision rebinds an alias or a sequence number Every playlist receipt is served at mr/.json too. LiveChain holds the head and what its receipts bound: the head fetched again is unchanged; any other receipt chains when the walk by parent links reaches the head, each parent served, read, named by its link, verified and under the head's key, at most 64 back (playlist_receipt_parent_missing, playlist_receipt_walk_exceeded). Bindings keep each URI's and media sequence number's cid across revisions and receipts (playlist_listing_rebound). Corpus cases for the reload and skip an origin's replay gives, the bound, a missing parent, a receipt chained by hash under another key, and an alias and a sequence number rebound; http_verify.py holds each. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/http-delivery.md | 47 ++++- wai/http-conformance/README.md | 5 +- wai/http-conformance/cases.json | 17 +- ...0aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2.json | 1 + wai/http-conformance/cases/other-key/r2.json | 1 + wai/http-conformance/cases/other-key/r3.json | 1 + ...42529a2bf185da6fd9797c3bab7abcc3f4fd7.json | 1 + wai/http-conformance/cases/relist/r2.json | 1 + .../cases/relist/r3-after.json | 1 + wai/http-conformance/cases/relist/r3.json | 1 + wai/http-conformance/cases/relist/wai-r2.m3u8 | 36 ++++ wai/http-conformance/cases/relist/wai-r3.m3u8 | 12 ++ wai/http-conformance/falsify/run.sh | 3 +- wai/http-conformance/http_verify.py | 148 ++++++++++--- ...38e6ff46022b5a7ba48c994338876b6a7c364.json | 1 + ...b93b7c0b84ff123b99ee2c26505649c68bdf6.json | 1 + ...3fbd4230a4fd626ffccdba54047bf0f6573ae.json | 1 + ...eb5f2660f80ca6267a73a2ae9cd186df824ed.json | 1 + ...0860033667f9cea89b45ca1849f0e54fc654d.json | 1 + ...8aa7963ca1135536a7c7edd08d4dec08b674e.json | 1 + ...e1ca89f4501b3c546280a48b4de0e729398ea.json | 1 + ...7fd259910c5ddfd6c5314d6ed6c9894c80f13.json | 1 + wai/wai-rs/src/bin/wai_cli/http_playlists.rs | 35 +++- .../src/bin/wai_cli/http_vectors_playlists.rs | 198 +++++++++++++----- wai/wai-rs/src/http_binding/hls.rs | 90 +++++++- .../src/http_binding/playlist_receipt.rs | 165 ++++++++++++++- .../src/http_binding/tests_playlists.rs | 76 +++++++ wai/wai-rs/tests/cli_http.rs | 27 ++- 28 files changed, 757 insertions(+), 117 deletions(-) create mode 100644 wai/http-conformance/cases/other-key/mr/90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2.json create mode 100644 wai/http-conformance/cases/other-key/r2.json create mode 100644 wai/http-conformance/cases/other-key/r3.json create mode 100644 wai/http-conformance/cases/relist/mr/4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7.json create mode 100644 wai/http-conformance/cases/relist/r2.json create mode 100644 wai/http-conformance/cases/relist/r3-after.json create mode 100644 wai/http-conformance/cases/relist/r3.json create mode 100644 wai/http-conformance/cases/relist/wai-r2.m3u8 create mode 100644 wai/http-conformance/cases/relist/wai-r3.m3u8 create mode 100644 wai/http-conformance/live/mr/3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364.json create mode 100644 wai/http-conformance/live/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json create mode 100644 wai/http-conformance/live/mr/dbc00c5ff81292325d272cbd6b83fbd4230a4fd626ffccdba54047bf0f6573ae.json create mode 100644 wai/http-conformance/live/mr/eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed.json create mode 100644 wai/http-conformance/players/mr/493155241cd03b53886e9bd1a210860033667f9cea89b45ca1849f0e54fc654d.json create mode 100644 wai/http-conformance/playlists/o10/mr/73416370f12adade369abf7a0e48aa7963ca1135536a7c7edd08d4dec08b674e.json create mode 100644 wai/http-conformance/playlists/o11/mr/461a7871f56c0e69017be36a7dce1ca89f4501b3c546280a48b4de0e729398ea.json create mode 100644 wai/http-conformance/playlists/o9/mr/4b4657cea62905884efdfc59c357fd259910c5ddfd6c5314d6ed6c9894c80f13.json diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index 8ccdbb364..c18d00209 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -461,9 +461,10 @@ held, and dispatch continues as [SPEC](../SPEC.md) §4 and §7.1 state. A WAI media playlist (§11) is authenticated by a manifest receipt (`wai.video.manifest`, [SPEC](../SPEC.md) §5) whose `manifest_hash` is the BLAKE3 of the playlist bytes, served immutable at `mr/.json` resolved against the playlist URI, in the -form of Appendix H. Each revision of a live playlist has its own receipt, whose -`parent_receipt_hash` is the receipt hash of the previous revision's receipt, under the -same key. A playlist receipt MAY carry `render`, the rendering parameters of the +form of Appendix H, and with the same bytes at `mr/.json`, its receipt +hash (Appendix H), so a receipt can be fetched by the link a later one names. Each +revision of a live playlist has its own receipt, whose `parent_receipt_hash` is the +receipt hash of the previous revision's receipt, under the same key. A playlist receipt MAY carry `render`, the rendering parameters of the playlist it signs (version, media sequence, discontinuity sequence, target duration, part target, part hold-back, blocking reload, whether it ends), and `extras`, the per-segment lines that rendering adds (discontinuities, program date-times, parts, @@ -483,8 +484,24 @@ A client checks a playlist against a receipt file in this order: 4. the receipt verifies (`playlist_receipt_invalid`): its signature, and every value it recomputes (Appendix H). -A live revision whose `parent_receipt_hash` is not the previous revision's receipt hash, -or whose key is another, does not chain (`playlist_receipt_unchained`). +A client of a live playlist holds a chain: its head, the latest receipt that verified +and chained, and what the receipts in it bound. The first receipt it verifies is the +head. A later revision's receipt that is the head itself (a reload with no new +revision) leaves the chain as it is. Any other becomes the head only when it chains to +the head: it is under the head's key (`playlist_receipt_unchained`), and following +`parent_receipt_hash` from it reaches the head's receipt hash. Each parent on the way is +fetched from `mr/.json` and checked in order: it is served +(`playlist_receipt_parent_missing`); it reads as Appendix H's form +(`playlist_receipt_malformed`); its receipt hash is the link it was fetched by +(`receipt_name_mismatch`); it verifies (`playlist_receipt_invalid`); and it is under the +same key (`playlist_receipt_unchained`). A walk that reaches a receipt with no parent +before the head does not chain (`playlist_receipt_unchained`), so a revision older than +the head never does. A client fetches at most 64 parents for one revision, and MAY hold +a lower bound; a head further back is refused (`playlist_receipt_walk_exceeded`), and +the revision is held at **listed** (§11.6). Then the walked receipts, oldest first, and +the new head bind what they list (§11.5); one that lists another cid for a URI or a +media sequence number the chain bound is refused (`playlist_listing_rebound`). A +refused revision leaves the chain as it was. ## 9. Cache rules @@ -726,6 +743,14 @@ Each part is its own object, served under an alias, and listed with its cid: Every object is immutable from the moment it is listed, so a live playlist becomes on-demand by appending `EXT-X-ENDLIST`; no object is rewritten. +A revision MUST NOT list another cid for a URI an earlier revision listed (an alias, +or any part, segment or layer URI, without its fragment), nor another object at a media +sequence number an earlier revision listed an object at (a gap segment lists no object +at its number). A client keeps both bindings from every revision it accepts, a URI's +cid being the first one listed with it (§5.2), and refuses a revision that lists +another (`playlist_listing_rebound`), taking none of its listings. A chain of receipts +binds the same way over the receipts in it (§8.6). + ### 11.6 Playlist authentication A WAI playlist SHOULD be authenticated by its receipt (§8.6). A client that verifies it @@ -737,9 +762,9 @@ multivariant playlist names), **signed** (that playlist's receipt, or the object receipt, verified), and the confirmation level of [jwp-receipts](jwp-receipts.md) §7.4. In Live-P a client MAY present at **listed** and verify the playlist receipt after presenting; it MUST NOT report the part as signed until it has. A part is listed by the -first revision whose playlist names its alias with a `cid`; it is signed when that -revision's receipt and the receipt of every revision before it verified, each chained -to the one before it (§8.6). +first accepted revision whose playlist names its alias with a `cid`; it is signed when a +receipt in the chain the client holds (§8.6), the parents walked to it included, binds +the alias to that cid. A WAI segment whose payload is a classical segment's media relates to the classical package's segment map (`wai.video.package`) by sequence number: the package's @@ -825,7 +850,8 @@ is a share of sessions, not of devices or people. | `adjacency_misaligned` | §11.2 | | `playlist_malformed` | §10.4, §11.2, Appendix G | | `classical_changed` | §11.1 | -| `playlist_receipt_malformed`, `playlist_receipt_mismatch`, `playlist_receipt_invalid`, `playlist_receipt_unchained` | §8.6 | +| `playlist_receipt_malformed`, `playlist_receipt_mismatch`, `playlist_receipt_invalid`, `playlist_receipt_unchained`, `playlist_receipt_parent_missing`, `playlist_receipt_walk_exceeded` | §8.6 | +| `playlist_listing_rebound` | §8.6, §11.5 | Selection, pin, receipt, key and module reasons are those of [SPEC](../SPEC.md) §3.1 and §7.1 and [jwp-receipts](jwp-receipts.md) §3, §7.7 and §7.9, and the layer reasons @@ -843,7 +869,8 @@ step 5 states. `Content-Range`, `codecs` value, `#WAI-LAYER` line, manifest, member type, encryption, `refines` and layer sequence it generates. For §8.6, §10.4, §11 and Appendix F the corpus holds WAI playlists rendered from their inputs beside their classical ones, - their receipts (a live playlist's chained), a published classical master and a + their receipts (a live playlist's chained, and walked across revisions a client + skipped or fetched again), a published classical master and a playable classical presentation for players, and the policies' choices; the differential adds playlists, adjacent pairs, published masters, renderings, receipt files and adaptation states. diff --git a/wai/http-conformance/README.md b/wai/http-conformance/README.md index be9c503d9..0fccdad3b 100644 --- a/wai/http-conformance/README.md +++ b/wai/http-conformance/README.md @@ -37,6 +37,9 @@ reference adaptation policies. Two implementations check every case and must agr | `players/` | the player fixture: `classical/` (frozen) is a playable classical presentation, `master.m3u8` its master as published with the session-data line, and the WAI multivariant and media playlists and MPD over O13 | | `*/mr/.json` | each WAI media playlist's receipt (§8.6, Appendix H), sealed with the corpus's test key (seed 32 bytes of `0x57`, signer `did:example:http-vectors`; it attests nothing); the live revisions are chained | | `cases/live-r2-orphan.json` | the third live revision sealed with no parent: a receipt that verifies and does not chain | +| `*/mr/.json` | each receipt again under its receipt hash, where a client walking back from a later revision fetches it by its link (§8.6) | +| `cases/other-key/` | the third live revision's receipt chained to the second by hash and signed by another key (`r2.json`, and in `mr/` under its receipt hash), and the fourth revision's receipt chained to it (`r3.json`) | +| `cases/relist/` | the third live revision listing the alias of part 0 of segment 1201 with part 1's object (`wai-r2.m3u8`), sealed and chained to the second (`r2.json`, and in `mr/`); the fourth revision's receipt chained to it (`r3-after.json`); and the fourth revision listing segment 1201's object at sequence 1200 (`wai-r3.m3u8`, `r3.json`) | | `cases/sig-*.json` | the O9 playlist's receipt re-signed at the edges of Ed25519 (Appendix H): the identity key, canonical and respelled `y` = p + 1, under `(R, S) = (B, 1)`; under the key B (scalar 1), `R` the identity, canonical and respelled, with `S` = k, so the cofactorless equation holds point for point; and under the key B a signature the strict rule accepts (`sig-key-base-point.json`) | ## Objects @@ -103,7 +106,7 @@ outside this repository, and the AV1 streams were checked to decode. | `adjacency` | a `wai` and a `classical` playlist (files, or `*_text`), optionally edited (`edit_wai`, `edit_classical`: `[from, to]` pairs, each replaced once) | `adjacent`, or `error` | | `unchanged` | an `original` and a `published` classical master (files, or `*_text`), `edit_published` | `unchanged`, or `error` | | `playlist_receipt` | a `playlist`, its `receipt` file and the `name` it is fetched as (the file's own by default), each optionally edited | the receipt hash and its parent, or `error` | -| `live` | `revisions` (each a WAI playlist, its receipt, its classical playlist, `edit_wai`) and `fetches` (an `alias` and the `object` served under it, optionally patched) | per revision its adjacency, receipt and chain; per fetch the revision that first lists the alias, the outcome and what held (`listed` or `signed`) | +| `live` | `revisions` in the order a client fetched them (each a WAI playlist, its receipt, its classical playlist, `edit_wai`), `fetches` (an `alias` and the `object` served under it, optionally patched), and where parents are fetched from (`parents`, a directory holding `.json`; the first revision's `mr/` by default) and the walk's bound (`walk_bound`, 64 by default) | per revision its adjacency, its listing against what accepted revisions bound, its receipt, what it did to the chain (`true`, `unchanged`, or the reason) and the parents walked; per fetch the accepted revision that first lists the alias, the outcome and what held (`listed`, or `signed` when a receipt in the chain binds the alias to that cid) | | `abr` | a `policy` and a `state` (Appendix F) | the `choice` and what the `budget` did | | `abr_order` | an object, what the sink `advertises`, a `policy`, a `state` | the entry `order` | | `depth` | layer `objects` (or `layers_n`), a `policy`, a `state` | the `depth` and what the `budget` did | diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index 176d7c0ae..0dfbb5f31 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -456,9 +456,20 @@ {"name":"playlist_receipt_sig_key_base_point","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-key-base-point.json","name":false},"expect":{"signed":"9cec31c4957646fe2bb595b69590f102377d9a6754db72d613cbfad041d55d06","parent":null}}, {"name":"playlist_receipt_part_independent","op":"playlist_receipt","input":{"playlist":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","edit_playlist":[["DURATION=0.320,INDEPENDENT=YES,URI=\"720/1200.3","DURATION=0.320,URI=\"720/1200.3"]]},"expect":{"error":"playlist_receipt_mismatch"}}, {"name":"playlist_receipt_parts_reordered","op":"playlist_receipt","input":{"playlist":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","edit_receipt":[["\"uri\":\"720/1200.3.wai","\"uri\":\"720/1200.X.wai"],["\"uri\":\"720/1200.4.wai","\"uri\":\"720/1200.3.wai"],["\"uri\":\"720/1200.X.wai","\"uri\":\"720/1200.4.wai"]]},"expect":{"error":"playlist_receipt_mismatch"}}, -{"name":"live_edge","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8","edit_wai":[]},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1200.5.wai","object":"O12p0_05"},{"alias":"720/1201.3.wai","object":"O12p1_03"},{"alias":"720/1201.7.wai","object":"O12p1_08"},{"alias":"720/1200.2.wai","object":"O12p0_02","patch_object":[[400,"00"]]},{"alias":"720/1202.0.wai","object":"O12p1_11"},{"alias":"1200.m4s","object":"O12s0"}]},"expect":{"revisions":[{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1200.5.wai","listed_in":1,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"},{"alias":"720/1201.7.wai","listed_in":2,"outcome":"object_digest_mismatch","held":null},{"alias":"720/1200.2.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1202.0.wai","listed_in":null,"outcome":"not_listed","held":null},{"alias":"1200.m4s","listed_in":null,"outcome":"not_listed","held":null}]}}, -{"name":"live_edge_revision_changed","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8","edit_wai":[["720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f","720/1201.7.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed"]]},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1201.7.wai","object":"O12p1_08"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"playlist_receipt_mismatch","chained":null},{"adjacent":true,"receipt":"signed","chained":null}],"fetches":[{"alias":"720/1201.7.wai","listed_in":2,"outcome":"accepted","held":"listed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"listed"}]}}, -{"name":"live_edge_unchained","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"cases/live-r2-orphan.json","classical":"src/live/classical-r2.m3u8","edit_wai":[]},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1201.3.wai","object":"O12p1_03"},{"alias":"720/1200.0.wai","object":"O12p0_00"}]},"expect":{"revisions":[{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":true},{"adjacent":true,"receipt":"signed","chained":"playlist_receipt_unchained"},{"adjacent":true,"receipt":"signed","chained":"playlist_receipt_unchained"}],"fetches":[{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"listed"},{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"}]}}, +{"name":"live_edge","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1200.5.wai","object":"O12p0_05"},{"alias":"720/1201.3.wai","object":"O12p1_03"},{"alias":"720/1201.7.wai","object":"O12p1_08"},{"alias":"720/1200.2.wai","object":"O12p0_02","patch_object":[[400,"00"]]},{"alias":"720/1202.0.wai","object":"O12p1_11"},{"alias":"1200.m4s","object":"O12s0"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1200.5.wai","listed_in":1,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"},{"alias":"720/1201.7.wai","listed_in":2,"outcome":"object_digest_mismatch","held":null},{"alias":"720/1200.2.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1202.0.wai","listed_in":null,"outcome":"not_listed","held":null},{"alias":"1200.m4s","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_edge_revision_changed","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8","edit_wai":[["720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f","720/1201.7.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed"]]},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1201.7.wai","object":"O12p1_08"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"playlist_receipt_mismatch","chained":null,"walked":null},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":1}],"fetches":[{"alias":"720/1201.7.wai","listed_in":2,"outcome":"accepted","held":"listed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"}]}}, +{"name":"live_edge_unchained","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"cases/live-r2-orphan.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1201.3.wai","object":"O12p1_03"},{"alias":"720/1200.0.wai","object":"O12p0_00"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_receipt_unchained","walked":null},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":1}],"fetches":[{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"},{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"}]}}, +{"name":"live_edge_unchanged_reload","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"unchanged","walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"unchanged","walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":3,"outcome":"accepted","held":"signed"}]}}, +{"name":"live_edge_skip","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":1},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":1,"outcome":"accepted","held":"signed"}]}}, +{"name":"live_edge_skip_two","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":2}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_edge_walk_exceeded","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"walk_bound":1,"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_receipt_walk_exceeded","walked":null}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_edge_walk_at_bound","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"walk_bound":2,"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":2}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_edge_parent_missing","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"parents":"cases/no-receipts","fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_receipt_parent_missing","walked":null}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_edge_other_key","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"cases/other-key/r2.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_receipt_unchained","walked":null},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":1}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"}]}}, +{"name":"live_edge_other_key_walked","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"cases/other-key/r3.json","classical":"src/live/classical-r3.m3u8"}],"parents":"cases/other-key/mr","fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_receipt_unchained","walked":null}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_relisted_alias","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"cases/relist/wai-r2.m3u8","receipt":"cases/relist/r2.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1201.0.wai","object":"O12p1_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":"playlist_listing_rebound","receipt":null,"chained":null,"walked":null},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":1}],"fetches":[{"alias":"720/1201.0.wai","listed_in":1,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_relisted_alias_walked","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"cases/relist/r3-after.json","classical":"src/live/classical-r3.m3u8"}],"parents":"cases/relist/mr","fetches":[{"alias":"720/1201.0.wai","object":"O12p1_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_listing_rebound","walked":null}],"fetches":[{"alias":"720/1201.0.wai","listed_in":1,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_resequenced","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8"},{"wai":"cases/relist/wai-r3.m3u8","receipt":"cases/relist/r3.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":"playlist_listing_rebound","receipt":null,"chained":null,"walked":null}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"}]}}, {"name":"abr_throughput_00","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":28203,"samples":[[1000,1259305,570]],"last":2,"option_bytes":[229507,231557,344895],"option_joules_micro":[3913,null,3815],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_throughput_01","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":27557,"samples":[],"last":0,"option_bytes":[48543,69733,178222,189003,255156,340556],"option_joules_micro":[250,4083,4219,null,null,null],"joule_budget_micro":1274,"segment_ms":2000}},"expect":{"choice":0,"budget":"within"}}, {"name":"abr_throughput_02","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":27339,"samples":[],"last":null,"option_bytes":[132854,232413],"option_joules_micro":[null,315],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, diff --git a/wai/http-conformance/cases/other-key/mr/90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2.json b/wai/http-conformance/cases/other-key/mr/90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2.json new file mode 100644 index 000000000..6be03d27a --- /dev/null +++ b/wai/http-conformance/cases/other-key/mr/90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"dac31ebca159d283f8341fd90d7fa1cd95bf5eccab657f7952662e67e56afbe3","signer_id":"did:example:http-vectors","sig":"f60bd29bb57eea95c41ee5b5219f2ee034076f500a8c23fef53c5e8de36315bd33867a5d19697a241b07defbe8f88947d20f5eeb119720af8a0416c3cafa2400","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/other-key/r2.json b/wai/http-conformance/cases/other-key/r2.json new file mode 100644 index 000000000..6be03d27a --- /dev/null +++ b/wai/http-conformance/cases/other-key/r2.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"dac31ebca159d283f8341fd90d7fa1cd95bf5eccab657f7952662e67e56afbe3","signer_id":"did:example:http-vectors","sig":"f60bd29bb57eea95c41ee5b5219f2ee034076f500a8c23fef53c5e8de36315bd33867a5d19697a241b07defbe8f88947d20f5eeb119720af8a0416c3cafa2400","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/other-key/r3.json b/wai/http-conformance/cases/other-key/r3.json new file mode 100644 index 000000000..f28197f15 --- /dev/null +++ b/wai/http-conformance/cases/other-key/r3.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"eba7d6f7efd7d3b43d1b44eb82645a2736ad931fe99cc27af3de657bed2d6ee13f079ca53fb5ff8c08404ced7ced0769cbaf43c051ca9b3d217d26dbad3a7707","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/cases/relist/mr/4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7.json b/wai/http-conformance/cases/relist/mr/4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7.json new file mode 100644 index 000000000..31233213b --- /dev/null +++ b/wai/http-conformance/cases/relist/mr/4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"3d2caa315b4db835b377e56ccb5fd3f78bc039f618bfe766db613481a547a9d7","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"ff79ca7c3a2525d4ba7cb27bfbe5fc353baecd5c322e53a6afd5c9dd68d8d1a9eb9b189d5115e8d07eca7d3e9d0c750811e347f15c24899086e1d6ae4ef12e03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/relist/r2.json b/wai/http-conformance/cases/relist/r2.json new file mode 100644 index 000000000..31233213b --- /dev/null +++ b/wai/http-conformance/cases/relist/r2.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"3d2caa315b4db835b377e56ccb5fd3f78bc039f618bfe766db613481a547a9d7","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"ff79ca7c3a2525d4ba7cb27bfbe5fc353baecd5c322e53a6afd5c9dd68d8d1a9eb9b189d5115e8d07eca7d3e9d0c750811e347f15c24899086e1d6ae4ef12e03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/relist/r3-after.json b/wai/http-conformance/cases/relist/r3-after.json new file mode 100644 index 000000000..6a6783022 --- /dev/null +++ b/wai/http-conformance/cases/relist/r3-after.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"0f6f640981fe8a69c0631835056547d825d21155ac95abc410baf8a990cea12a70d1c3db6835fec26b8e63ba26f78c2febba35605333c5f915fceb4f0f070b03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/cases/relist/r3.json b/wai/http-conformance/cases/relist/r3.json new file mode 100644 index 000000000..f66cd652e --- /dev/null +++ b/wai/http-conformance/cases/relist/r3.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"198e04e4e3aa8c149d7adbcc6c1b5ed0a56956ceb3c8d1f34e42104f318dcbdb","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"c5e68c2958aa68df360e32a66700bdff0c36c01e63d0d6bd80b9f03b2bd66a87","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"b08962976e408f754a89d2cdc1a5ca5f1cae76a186e8a155ff97cbe3f327239113e9beec406d25a73013ec79f87b2ac5d53171b16e0203a4d5c06d29132a2e0c","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/cases/relist/wai-r2.m3u8 b/wai/http-conformance/cases/relist/wai-r2.m3u8 new file mode 100644 index 000000000..c66aa3ebe --- /dev/null +++ b/wai/http-conformance/cases/relist/wai-r2.m3u8 @@ -0,0 +1,36 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.320 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014" +#EXTINF:3.840, +../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606 +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.0.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222" +#EXTINF:3.840, +../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118 +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="720/1202.0.wai" diff --git a/wai/http-conformance/cases/relist/wai-r3.m3u8 b/wai/http-conformance/cases/relist/wai-r3.m3u8 new file mode 100644 index 000000000..0c9177660 --- /dev/null +++ b/wai/http-conformance/cases/relist/wai-r3.m3u8 @@ -0,0 +1,12 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.320 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXTINF:3.840, +../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118 +#EXTINF:3.840, +../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118 +#EXT-X-ENDLIST diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh index 6072d13b3..61f42fbb6 100755 --- a/wai/http-conformance/falsify/run.sh +++ b/wai/http-conformance/falsify/run.sh @@ -47,7 +47,8 @@ done cd "$ROOT/wai/http-conformance" for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done tick-noop refinement-unclipped \ pdt-ignored dseq-ignored parts-after-extinf average-floor render-digest-off name-unchecked chain-unchecked \ - ilog2-no-interp bola-waits unlabelled-as-zero round-half-down small-order-key small-order-r; do + ilog2-no-interp bola-waits unlabelled-as-zero round-half-down small-order-key small-order-r \ + rebind-uri rebind-sequence key-unchecked walk-unbounded reload-walked; do if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then echo "::error::http_verify.py --mutate $m passed"; fail=1 else diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index f494d25f6..e3a11bc86 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -1874,7 +1874,9 @@ def classical_unchanged(original, published): raise Refusal("classical_changed") -def listed_cid(p, uri): +def listing_lines(p): + """Every part, layer and segment line of a media playlist, in the order a client + reads them for a cid (§5.2, §11.4).""" lines = [] for s in p["segments"]: for q in s["extra"]["parts"]: @@ -1886,9 +1888,11 @@ def listed_cid(p, uri): for q in p["open"]["parts"]: lines.append(q["uri"]) lines += [l["uri"] for l in q["layers"]] - line = next((l for l in lines if l.split("#", 1)[0] == uri), None) - if line is None: - return None + return lines + + +def uri_cid(line): + """The cid a listed URI names: its cid fragment, or the .wai its path ends in.""" base, _, frag = line.partition("#") for kv in frag.split("&") if frag else []: k, eq, v = kv.partition("=") @@ -1900,6 +1904,31 @@ def listed_cid(p, uri): return None +def listed_cid(p, uri): + line = next((l for l in listing_lines(p) if l.split("#", 1)[0] == uri), None) + return None if line is None else uri_cid(line) + + +def bind(b, p): + """§5.2, §11.5: the bindings `b` (each URI and media sequence number to its cid) with + playlist p's added; Refusal when p lists another cid for one already bound.""" + uris, seqs = dict(b[0]), dict(b[1]) + for line in listing_lines(p): + cid = uri_cid(line) + if cid is not None and uris.setdefault(line.split("#", 1)[0], cid) != cid and MUTATE != "rebind-uri": + raise Refusal("playlist_listing_rebound") + for i, sg in enumerate(p["segments"]): + cid = uri_cid(sg["uri"]) + if cid is None or sg["extra"]["gap"]: + continue + n = p["seq"] + i + if n > U64: + raise Refusal("playlist_malformed") + if seqs.setdefault(n, cid) != cid and MUTATE != "rebind-sequence": + raise Refusal("playlist_listing_rebound") + return (uris, seqs) + + def esc_xml(s): return s.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """) @@ -2269,8 +2298,62 @@ def check_playlist(playlist, receipt_bytes, name): return r -def chained(prev, cur): - return (cur["parent"] == receipt_hash(prev) or MUTATE == "chain-unchecked") and cur["pubkey"] == prev["pubkey"] +WALK_BOUND = 64 + + +def advance(chain, cur, fetch): + """§8.6: take the receipt cur (checked against its playlist) into the chain a client + holds, {"head", "bindings", "bound"}; fetch(h) gives the bytes at mr/.json or None. + Returns ("first" | "unchanged" | "chained", walked) and updates chain, or raises + Refusal and leaves it as it was.""" + if chain["head"] is None: + b = bind_receipt(chain["bindings"], cur) + chain["head"], chain["bindings"] = cur, b + return "first", 0 + target = receipt_hash(chain["head"]) + if receipt_hash(cur) == target and MUTATE != "reload-walked": + return "unchanged", 0 + if cur["pubkey"] != chain["head"]["pubkey"] and MUTATE != "key-unchecked": + raise Refusal("playlist_receipt_unchained") + walked, link = [], cur["parent"] + while MUTATE != "chain-unchecked": + if link is None: + raise Refusal("playlist_receipt_unchained") + if link == target: + break + if len(walked) == chain["bound"] and MUTATE != "walk-unbounded": + raise Refusal("playlist_receipt_walk_exceeded") + raw = fetch(link) + if raw is None: + raise Refusal("playlist_receipt_parent_missing") + try: + r = read_receipt(raw.decode("utf-8")) + except (ReceiptMalformed, UnicodeDecodeError): + raise Refusal("playlist_receipt_malformed") + if receipt_hash(r) != link: + raise Refusal("receipt_name_mismatch") + if not receipt_verifies(r): + raise Refusal("playlist_receipt_invalid") + if r["pubkey"] != cur["pubkey"] and MUTATE != "key-unchecked": + raise Refusal("playlist_receipt_unchained") + link = r["parent"] + walked.append(r) + b = chain["bindings"] + for r in list(reversed(walked)) + [cur]: + b = bind_receipt(b, r) + chain["head"], chain["bindings"] = cur, b + return "chained", len(walked) + + +def bind_receipt(b, r): + try: + p = parse_media_playlist(rendered(r)) + except Refusal: + raise Refusal("playlist_receipt_malformed") + try: + return bind(b, p) + except Refusal as e: + raise Refusal("playlist_listing_rebound" if e.code == "playlist_listing_rebound" else "playlist_receipt_malformed") # ---------------------------------------------------- the playlist-side cases @@ -2428,9 +2511,21 @@ def parsed_case(p, t): def live_case(d, objs, inp): - """§11.4, §11.6 over a live playlist's revisions, and parts fetched by alias.""" - revs, listed, held = [], [], [] - for r in inp["revisions"]: + """§8.6, §11.4, §11.6 over a live playlist's revisions in the order a client fetched + them, and parts fetched by alias.""" + revisions = inp["revisions"] + parents = inp.get("parents") + if parents is None: + parents = (revisions[0]["wai"].rsplit("/", 1)[0] if "/" in revisions[0]["wai"] else "") + "/mr" if revisions else "/mr" + + def fetch(h): + f = os.path.join(d, parents, h.hex() + ".json") + return open(f, "rb").read() if os.path.isfile(f) else None + + chain = {"head": None, "bindings": ({}, {}), "bound": inp["walk_bound"] if isinstance(inp.get("walk_bound"), int) else WALK_BOUND} + listed_b = ({}, {}) + revs, listed = [], [] + for r in revisions: w = edit_text(read_text(d, r["wai"]), r.get("edit_wai")) k = read_text(d, r["classical"]) try: @@ -2438,33 +2533,36 @@ def live_case(d, objs, inp): adjacent = True except Refusal as e: adjacent = e.code + p = parse_media_playlist(w) + try: + listed_b = bind(listed_b, p) + except Refusal as e: + revs.append({"adjacent": adjacent, "listing": e.code, "receipt": None, "chained": None, "walked": None}) + listed.append(None) + continue + listed.append(p) rpath = r["receipt"] name = None if rpath.startswith("cases/") else rpath.rsplit("/", 1)[-1] try: rr = check_playlist(w.encode("utf-8"), open(os.path.join(d, rpath), "rb").read(), name) - receipt = "signed" except Refusal as e: - rr, receipt = None, e.code - if rr is None or (held and held[-1] is None): - ch = None - elif not held: - ch = True - else: - ch = True if chained(held[-1], rr) else "playlist_receipt_unchained" - revs.append({"adjacent": adjacent, "receipt": receipt, "chained": ch}) - listed.append(parse_media_playlist(w)) - held.append(rr) - signed = [] - for k, r in enumerate(revs): - signed.append(r["receipt"] == "signed" and (k == 0 or r["chained"] is True) and (k == 0 or signed[k - 1])) + revs.append({"adjacent": adjacent, "listing": True, "receipt": e.code, "chained": None, "walked": None}) + continue + try: + step, walked = advance(chain, rr, fetch) + ch = "unchanged" if step == "unchanged" else True + except Refusal as e: + ch, walked = e.code, None + revs.append({"adjacent": adjacent, "listing": True, "receipt": "signed", "chained": ch, "walked": walked}) fetches = [] for f in inp.get("fetches") or []: served = apply_patch(objs[f["object"]], f.get("patch_object")) - first = next(((k, c) for k, p in enumerate(listed) for c in [listed_cid(p, f["alias"])] if c is not None), None) + first = next(((k, c) for k, p in enumerate(listed) if p is not None for c in [listed_cid(p, f["alias"])] if c is not None), None) if first is None: fetches.append({"alias": f["alias"], "listed_in": None, "outcome": "not_listed", "held": None}) elif content_hash(served) == first[1]: - fetches.append({"alias": f["alias"], "listed_in": first[0], "outcome": "accepted", "held": "signed" if signed[first[0]] else "listed"}) + held = "signed" if chain["bindings"][0].get(f["alias"]) == first[1] else "listed" + fetches.append({"alias": f["alias"], "listed_in": first[0], "outcome": "accepted", "held": held}) else: fetches.append({"alias": f["alias"], "listed_in": first[0], "outcome": "object_digest_mismatch", "held": None}) return {"revisions": revs, "fetches": fetches} diff --git a/wai/http-conformance/live/mr/3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364.json b/wai/http-conformance/live/mr/3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364.json new file mode 100644 index 000000000..f9c7b9011 --- /dev/null +++ b/wai/http-conformance/live/mr/3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[],"merkle_root":"0000000000000000000000000000000000000000000000000000000000000000","work_compose_ops":0,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"5d213b6e5ce07b2c7391f0d73501c7ed8e91d6b81ab418e6b1eff4169d127f6f943306b5e1dc44844f69505c34156c040686a6e4151b37ddda98a617f2ac7609","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]}],"layers":[],"preload_hint":"720/1200.5.wai"}]} diff --git a/wai/http-conformance/live/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json b/wai/http-conformance/live/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json new file mode 100644 index 000000000..d57cdf573 --- /dev/null +++ b/wai/http-conformance/live/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"d02f849c0a2476cd732b53039fa56de97d4aedf4f36c49696a0bad2349f8530c31deea9635825a8fc33ef0a16a55e55b15d90a9633e821bcdfed0a5eba7be803","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/live/mr/dbc00c5ff81292325d272cbd6b83fbd4230a4fd626ffccdba54047bf0f6573ae.json b/wai/http-conformance/live/mr/dbc00c5ff81292325d272cbd6b83fbd4230a4fd626ffccdba54047bf0f6573ae.json new file mode 100644 index 000000000..b62535422 --- /dev/null +++ b/wai/http-conformance/live/mr/dbc00c5ff81292325d272cbd6b83fbd4230a4fd626ffccdba54047bf0f6573ae.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"96ae8d50d493052725debf607391a6a057a94053fdd065799a16a0adbad9dc89379df504d6e055e85588035a3d94153177ca81ae31f25713adbd5de453c7c103","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/live/mr/eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed.json b/wai/http-conformance/live/mr/eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed.json new file mode 100644 index 000000000..83d1b3bfe --- /dev/null +++ b/wai/http-conformance/live/mr/eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null}],"merkle_root":"00e2bc6cdc748c6ab760c8e867603fd4d9a72d3a58f985bd13810a8d77aedcd3","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"8186f3adb28113d8ad08c923d24df463eb898d7a95b04902d23ca208fc17fe677249dc30f280faef8ec5aeac81abdb6b6fcd577075d9e9346f445c3b8c13fa03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]}],"layers":[],"preload_hint":"720/1201.3.wai"}]} diff --git a/wai/http-conformance/players/mr/493155241cd03b53886e9bd1a210860033667f9cea89b45ca1849f0e54fc654d.json b/wai/http-conformance/players/mr/493155241cd03b53886e9bd1a210860033667f9cea89b45ca1849f0e54fc654d.json new file mode 100644 index 000000000..7731c6c80 --- /dev/null +++ b/wai/http-conformance/players/mr/493155241cd03b53886e9bd1a210860033667f9cea89b45ca1849f0e54fc654d.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"57bbf3d1af7c937c13ac72b729895532f68da3c5e55365cebdfa8d92dd73a9c5","session_class_hash":"2c244c78e6a1548a4563faa232e39f2731ceac2d0594269f393d83ad3e8b9dfc","class":{"content_id":"players/v0","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai#n=21758","duration_ms":1000,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai#n=20986","duration_ms":1000,"ad_id":null},{"index":2,"kind":"content","uri":"../objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai#n=21338","duration_ms":1000,"ad_id":null}],"merkle_root":"c8766cdcc12477f790f429db5da1053c2c016877b1e439dd922bf658723dfe1a","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"b3a180a6c86ce30e5b117efa88c5f56f3639165249b4922e068340920eac40d73f2d0034594ef3372c0b19cf473a20b0bb702b3be60fb85fdaf07cc659c3fb0f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/playlists/o10/mr/73416370f12adade369abf7a0e48aa7963ca1135536a7c7edd08d4dec08b674e.json b/wai/http-conformance/playlists/o10/mr/73416370f12adade369abf7a0e48aa7963ca1135536a7c7edd08d4dec08b674e.json new file mode 100644 index 000000000..4def4364b --- /dev/null +++ b/wai/http-conformance/playlists/o10/mr/73416370f12adade369abf7a0e48aa7963ca1135536a7c7edd08d4dec08b674e.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77","session_class_hash":"28bd23d88c20332da1bf73dc84748f431ca1459b0f6edf73c2a083da2c48c77d","class":{"content_id":"o10","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825","duration_ms":50,"ad_id":null}],"merkle_root":"30497ebce24b685a2c900556166c46c9954bc45600b2a9ab2832343efad972b7","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"ae378cd7bbaed8c6cf074f89701c138672faca5d570e0b9c05d632bfb98b465a760eaa504572640a037acacd91052100bff94b6cccd8cfe23a8f672af0624a0e","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab.wai#n=12711"},{"n":2,"uri":"../../objects/753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c.wai#n=18367"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o11/mr/461a7871f56c0e69017be36a7dce1ca89f4501b3c546280a48b4de0e729398ea.json b/wai/http-conformance/playlists/o11/mr/461a7871f56c0e69017be36a7dce1ca89f4501b3c546280a48b4de0e729398ea.json new file mode 100644 index 000000000..bea3f71ef --- /dev/null +++ b/wai/http-conformance/playlists/o11/mr/461a7871f56c0e69017be36a7dce1ca89f4501b3c546280a48b4de0e729398ea.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79","session_class_hash":"7ecee2c182c9807140e70aa35a6abc8888ea610c8497def214626e2cdc30102b","class":{"content_id":"o11","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551","duration_ms":200,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523","duration_ms":200,"ad_id":null}],"merkle_root":"3dda1c1caa7d0ab22afbd28e76e6eacbd57aa35c8cbcda8b7427ad12467a9560","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"3a35c0bcb9a44f27eab67be767d2ed7effa70dc4d7b14b1d56b65c4137dbc0432428207c9996a1c35834e9309398301c93ad7339a1116785cfebd2659589c20d","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai#n=535"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o9/mr/4b4657cea62905884efdfc59c357fd259910c5ddfd6c5314d6ed6c9894c80f13.json b/wai/http-conformance/playlists/o9/mr/4b4657cea62905884efdfc59c357fd259910c5ddfd6c5314d6ed6c9894c80f13.json new file mode 100644 index 000000000..ca7df89fc --- /dev/null +++ b/wai/http-conformance/playlists/o9/mr/4b4657cea62905884efdfc59c357fd259910c5ddfd6c5314d6ed6c9894c80f13.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"319206c1fef8bab9685bfda23964e51d515afd41f5b893cdd635a959de4a434e400f074ea8605aca48293f6b6b8b1fe7fb4582477f191078cb24c393e22d840f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs index d07f0832e..520b2d859 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs @@ -10,11 +10,14 @@ //! [--signer-id ID] [--content-id ID] [--parent ] //! [--out ] //! seal a WAI playlist in its rendered form; write /mr/.json -//! (dir: the playlist's own by default) and print its path and receipt hash +//! and /mr/.json (dir: the playlist's own by default) and +//! print its path and receipt hash //! wai http check-playlist --receipt //! [--parent ] [--classical ] -//! check the playlist against its receipt (and its parent, and adjacency to the -//! classical playlist); print what held, or the reason it did not +//! check the playlist against its receipt (and its chain to the receipt held as +//! --parent, walking any revisions between by their links from the receipt's +//! directory, and its adjacency to the classical playlist); print what held, or +//! the reason it did not //! //! The objects list, its paths relative to its own directory: //! @@ -227,8 +230,11 @@ pub fn seal(args: &[String]) -> Result<(), String> { let r = seal_playlist(&text, &key, opt(args, "--signer-id").unwrap_or("unconfirmed"), opt(args, "--content-id").unwrap_or(stem), wai::quantum_ops::GrantRef::unbounded("video.manifest"), parent.as_ref()).map_err(|e| format!("{path}: {e}"))?; let dir = opt(args, "--out").map(PathBuf::from).unwrap_or_else(|| Path::new(path).parent().unwrap_or(Path::new(".")).to_path_buf()); + // §8.6: at its manifest hash, where a client checks the playlist, and at its receipt + // hash, where a client that skipped this revision fetches it by a later one's link. let file = dir.join("mr").join(r.receipt_file_name()); write(&file, r.to_json().as_bytes())?; + write(&dir.join("mr").join(format!("{}.json", hex(&r.receipt_hash()))), r.to_json().as_bytes())?; println!("{}\treceipt {}", file.display(), hex(&r.receipt_hash())); Ok(()) } @@ -241,7 +247,7 @@ pub fn seal(_: &[String]) -> Result<(), String> { #[cfg(feature = "video_manifest")] pub fn check(args: &[String]) -> Result { use wai::http_binding::hls::check_adjacent; - use wai::http_binding::playlist_receipt::{check_chain, check_playlist}; + use wai::http_binding::playlist_receipt::{check_playlist, LiveChain, Step}; use wai::wai_video_manifest::ManifestReceipt; let path = args.first().filter(|a| !a.starts_with("--")).ok_or("check-playlist needs the playlist")?; let text = read(Path::new(path))?; @@ -254,11 +260,21 @@ pub fn check(args: &[String]) -> Result { return Ok(false); } }; + let mut walked = None; if let Some(p) = opt(args, "--parent") { - let parent = ManifestReceipt::from_json(&String::from_utf8_lossy(&read(Path::new(p))?)).map_err(|e| format!("{p}: {e}"))?; - if let Err((_, e)) = check_chain(&[parent, held.clone()]) { - println!("refused\t{}", e.code()); - return Ok(false); + // The head the client holds; parents between it and this revision are fetched + // by their links from the receipt's own directory (§8.6). + let head = ManifestReceipt::from_json(&String::from_utf8_lossy(&read(Path::new(p))?)).map_err(|e| format!("{p}: {e}"))?; + let dir = Path::new(rpath).parent().unwrap_or(Path::new(".")).to_path_buf(); + let mut chain = LiveChain::new(); + chain.advance(&head, |_| None).map_err(|e| format!("{p}: {e}"))?; + match chain.advance(&held, |h| std::fs::read(dir.join(format!("{}.json", hex(h)))).ok()) { + Ok(Step::Chained { walked: n }) => walked = Some(n), + Ok(_) => walked = Some(0), + Err(e) => { + println!("refused\t{}", e.code()); + return Ok(false); + } } } if let Some(c) = opt(args, "--classical") { @@ -268,7 +284,8 @@ pub fn check(args: &[String]) -> Result { return Ok(false); } } - println!("signed\treceipt {}\tsigner {}\t(signer unconfirmed until anchored)", hex(&held.receipt_hash()), hex(&held.signer_pubkey)); + let chain = walked.map(|n| format!("\tchained ({n} parents walked)")).unwrap_or_default(); + println!("signed\treceipt {}\tsigner {}{chain}\t(signer unconfirmed until anchored)", hex(&held.receipt_hash()), hex(&held.signer_pubkey)); Ok(true) } diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index b7ef73a79..ae3709576 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -499,51 +499,68 @@ fn receipt_outcome(_: &Corpus, _: &Value) -> Result { Err(Stop::Fault("built without video_manifest: playlist receipts are not checked".into())) } -/// §11.4, §11.6 over a live playlist's revisions: each revision's adjacency to its -/// classical revision, its receipt and its chain; and each part fetched by alias, held -/// against the cid the first revision that lists it lists, at `signed` when that -/// revision's receipt verified and chains to the first, else at `listed`. +/// §8.6, §11.4, §11.6 over a live playlist's revisions, in the order a client fetched +/// them: each revision's adjacency to its classical revision; its listing, held against +/// every URI and sequence number the revisions accepted before it bound; its receipt; +/// and the receipt taken into the chain the client holds, walking back by parent links +/// to the head through the receipts served under `parents` (`.json`) up to +/// `walk_bound`. Then each part fetched by alias, held against the cid the first +/// accepted revision that lists it lists, at `signed` when a receipt in the chain binds +/// the alias to that cid, else at `listed`. #[cfg(feature = "video_manifest")] fn live_outcome(c: &Corpus, input: &Value) -> Result { - use wai::http_binding::playlist_receipt::{check_chain, check_playlist}; + use wai::http_binding::hls::Bindings; + use wai::http_binding::playlist_receipt::{check_playlist, LiveChain, Step, WALK_BOUND}; + let revisions = input["revisions"].as_array().ok_or("revisions")?; + let parents = match input["parents"].as_str() { + Some(d) => d.to_string(), + None => format!("{}/mr", revisions.first().and_then(|r| r["wai"].as_str()).and_then(|w| w.rsplit_once('/')).map_or("", |(d, _)| d)), + }; + let bound = input["walk_bound"].as_u64().map_or(WALK_BOUND, |b| b as usize); + let mut chain = LiveChain::with_bound(bound); + let mut listed_bindings = Bindings::default(); let mut revs = Vec::new(); - let mut listed: Vec = Vec::new(); - let mut held: Vec> = Vec::new(); - for r in input["revisions"].as_array().ok_or("revisions")? { + let mut listed: Vec> = Vec::new(); + for r in revisions { let w = edit(text(c, r["wai"].as_str().ok_or("wai")?)?, &r["edit_wai"])?; let k = text(c, r["classical"].as_str().ok_or("classical")?)?; let adjacent = match check_adjacent(&w, &k) { Ok(()) => json!(true), Err(e) => json!(e.code()) }; + let p = parse_media_playlist(&w).map_err(|e| pl_err(&e))?; + if let Err(e) = listed_bindings.bind(&p) { + revs.push(json!({ "adjacent": adjacent, "listing": e.code(), "receipt": null, "chained": null, "walked": null })); + listed.push(None); + continue; + } + listed.push(Some(p)); let rpath = r["receipt"].as_str().ok_or("receipt")?; let name = rpath.rsplit('/').next(); - let (receipt, rr) = match check_playlist(w.as_bytes(), &c.file(rpath)?, if rpath.starts_with("cases/") { None } else { name }) { - Ok(x) => (json!("signed"), Some(x)), - Err(e) => (json!(e.code()), None), + let rr = match check_playlist(w.as_bytes(), &c.file(rpath)?, if rpath.starts_with("cases/") { None } else { name }) { + Ok(x) => x, + Err(e) => { + revs.push(json!({ "adjacent": adjacent, "listing": true, "receipt": e.code(), "chained": null, "walked": null })); + continue; + } }; - let chained = match (held.last(), &rr) { - (_, None) => Value::Null, - (None, Some(_)) => json!(true), - (Some(None), Some(_)) => Value::Null, - (Some(Some(prev)), Some(cur)) => match check_chain(&[prev.clone(), cur.clone()]) { Ok(()) => json!(true), Err((_, e)) => json!(e.code()) }, + let (chained, walked) = match chain.advance(&rr, |h| c.file(&format!("{parents}/{}.json", hex(h))).ok()) { + Ok(Step::First) => (json!(true), json!(0)), + Ok(Step::Chained { walked }) => (json!(true), json!(walked)), + Ok(Step::Unchanged) => (json!("unchanged"), json!(0)), + Err(e) => (json!(e.code()), Value::Null), }; - revs.push(json!({ "adjacent": adjacent, "receipt": receipt, "chained": chained })); - listed.push(parse_media_playlist(&w).map_err(|e| pl_err(&e))?); - held.push(rr); - } - // A revision is signed when its receipt verified and every link back to the first holds. - let mut signed = Vec::new(); - for (k, r) in revs.iter().enumerate() { - let prior = k == 0 || signed.get(k - 1).copied().unwrap_or(false); - signed.push(r["receipt"] == "signed" && (k == 0 || r["chained"] == true) && prior); + revs.push(json!({ "adjacent": adjacent, "listing": true, "receipt": "signed", "chained": chained, "walked": walked })); } let mut fetches = Vec::new(); for f in input["fetches"].as_array().into_iter().flatten() { let alias = f["alias"].as_str().ok_or("alias")?; let mut served = object_bytes(c, f["object"].as_str().ok_or("object")?)?; patch(&mut served, &f["patch_object"])?; - let first = listed.iter().enumerate().find_map(|(k, p)| listed_cid(p, alias).map(|cid| (k, cid))); + let first = listed.iter().enumerate().find_map(|(k, p)| p.as_ref().and_then(|p| listed_cid(p, alias)).map(|cid| (k, cid))); fetches.push(match first { None => json!({ "alias": alias, "listed_in": null, "outcome": "not_listed", "held": null }), - Some((k, cid)) if content_hash(&served) == cid => json!({ "alias": alias, "listed_in": k, "outcome": "accepted", "held": if signed[k] { "signed" } else { "listed" } }), + Some((k, cid)) if content_hash(&served) == cid => { + let held = if chain.bindings().cid_of(alias) == Some(cid) { "signed" } else { "listed" }; + json!({ "alias": alias, "listed_in": k, "outcome": "accepted", "held": held }) + } Some((k, _)) => json!({ "alias": alias, "listed_in": k, "outcome": "object_digest_mismatch", "held": null }), }); } @@ -603,9 +620,7 @@ fn seal_all(c: &mut Corpus, out: &mut BTreeMap>) -> Result<(), S let r = seal_playlist(&t, &key, PLAYLIST_SIGNER, content, GrantRef::unbounded("video.manifest"), parent.map(|k| &sealed[k])) .map_err(|e| format!("{path}: {e}"))?; let dir = path.rsplit_once('/').map_or("", |(d, _)| d); - let f = format!("{dir}/mr/{}", r.receipt_file_name()); - c.extra.insert(f.clone(), r.to_json().into_bytes()); - out.insert(f, r.to_json().into_bytes()); + publish(c, out, &format!("{dir}/mr"), &r); sealed.push(r); } // Signatures a lenient verifier accepts and the strict rule refuses (crate::sig), each @@ -623,14 +638,73 @@ fn seal_all(c: &mut Corpus, out: &mut BTreeMap>) -> Result<(), S c.extra.insert(f.clone(), x.to_json().into_bytes()); out.insert(f, x.to_json().into_bytes()); } + let live = |c: &Corpus, k: usize| String::from_utf8(c.file(&format!("live/wai-r{k}.m3u8"))?).map_err(|_| "not UTF-8".to_string()); + let seal = |t: &str, key: &ed25519_dalek::SigningKey, parent: Option<&wai::wai_video_manifest::ManifestReceipt>| { + seal_playlist(t, key, PLAYLIST_SIGNER, "live/720", GrantRef::unbounded("video.manifest"), parent).map_err(|e| e.to_string()) + }; + let put = |c: &mut Corpus, out: &mut BTreeMap>, f: &str, b: Vec| { + c.extra.insert(f.to_string(), b.clone()); + out.insert(f.to_string(), b); + }; // Revision 2 sealed with no parent: a receipt that verifies and does not chain. - let t = String::from_utf8(c.file("live/wai-r2.m3u8")?).map_err(|_| "not UTF-8")?; - let orphan = seal_playlist(&t, &key, PLAYLIST_SIGNER, "live/720", GrantRef::unbounded("video.manifest"), None).map_err(|e| e.to_string())?; - c.extra.insert("cases/live-r2-orphan.json".into(), orphan.to_json().into_bytes()); - out.insert("cases/live-r2-orphan.json".into(), orphan.to_json().into_bytes()); + let orphan = seal(&live(c, 2)?, &key, None)?; + put(c, out, "cases/live-r2-orphan.json", orphan.to_json().into_bytes()); + // Revision 2 chained to revision 1 by hash and signed by another key; and revision 3 + // chained to that one, with the other key's receipt served by its receipt hash. + let other = ed25519_dalek::SigningKey::from_bytes(&[0x58; 32]); + let r2_other = seal(&live(c, 2)?, &other, Some(&sealed[4]))?; + put(c, out, "cases/other-key/r2.json", r2_other.to_json().into_bytes()); + publish_by_hash(c, out, "cases/other-key/mr", &r2_other); + put(c, out, "cases/other-key/r3.json", seal(&live(c, 3)?, &key, Some(&r2_other))?.to_json().into_bytes()); + // Revision 2 relisting the alias of part 0 of segment 1201 (listed by revision 1) with + // part 1's object, sealed and chained to revision 1; revision 3 chained to it; and + // revision 3 rewriting media sequence 1200 to segment 1201's object, chained to + // revision 2. + let part = |s: usize, p: usize| -> Result { + let b = c.objects.get(&part_id(s, p)).ok_or("a live part")?; + Ok(format!("720/{}.{p}.wai#cid={}&n={}", 1200 + s, hex(&content_hash(b)), b.len())) + }; + let (p0, p1) = (part(1, 0)?, part(1, 1)?); + let relisted = live(c, 2)?.replacen(&p0, &p1.replacen("1201.1.wai", "1201.0.wai", 1), 1); + if relisted == live(c, 2)? { + return Err("revision 2 does not list part 0 of segment 1201 as expected".into()); + } + put(c, out, "cases/relist/wai-r2.m3u8", relisted.clone().into_bytes()); + let r2_relisted = seal(&relisted, &key, Some(&sealed[4]))?; + put(c, out, "cases/relist/r2.json", r2_relisted.to_json().into_bytes()); + publish_by_hash(c, out, "cases/relist/mr", &r2_relisted); + put(c, out, "cases/relist/r3-after.json", seal(&live(c, 3)?, &key, Some(&r2_relisted))?.to_json().into_bytes()); + let seg = |s: usize| -> Result { + let b = c.objects.get(&format!("O12s{s}")).ok_or("a live segment")?; + Ok(format!("../objects/{}.wai#n={}", hex(&content_hash(b)), b.len())) + }; + let (s0, s1) = (seg(0)?, seg(1)?); + let r3 = live(c, 3)?; + let at = r3.find(&s0).ok_or("revision 3 does not list segment 1200 as expected")?; + let resequenced = format!("{}{s1}{}", &r3[..at], &r3[at + s0.len()..]); + put(c, out, "cases/relist/wai-r3.m3u8", resequenced.clone().into_bytes()); + put(c, out, "cases/relist/r3.json", seal(&resequenced, &key, Some(&sealed[5]))?.to_json().into_bytes()); Ok(()) } +/// A receipt published in `dir` as §8.6 serves it: at `.json` and at +/// `.json`. +#[cfg(feature = "video_manifest")] +fn publish(c: &mut Corpus, out: &mut BTreeMap>, dir: &str, r: &wai::wai_video_manifest::ManifestReceipt) { + let f = format!("{dir}/{}", r.receipt_file_name()); + c.extra.insert(f.clone(), r.to_json().into_bytes()); + out.insert(f, r.to_json().into_bytes()); + publish_by_hash(c, out, dir, r); +} + +/// A receipt at `/.json`, where a walk fetches it by its link. +#[cfg(feature = "video_manifest")] +fn publish_by_hash(c: &mut Corpus, out: &mut BTreeMap>, dir: &str, r: &wai::wai_video_manifest::ManifestReceipt) { + let f = format!("{dir}/{}.json", hex(&r.receipt_hash())); + c.extra.insert(f.clone(), r.to_json().into_bytes()); + out.insert(f, r.to_json().into_bytes()); +} + // -------------------------------------------------- signatures at the edges /// L = 2^252 + 27742317777372353535851937790883648493, in little-endian 64-bit limbs. @@ -905,16 +979,16 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { // A live edge (§11.4, §11.6): four revisions, each adjacent, signed and chained; parts // fetched by alias before and after the revision that lists them. - let revs = |edit_r2: Value, r2_receipt: Option<&str>| -> Result { - Ok(Value::Array((0..4).map(|k| { - let w = format!("live/wai-r{k}.m3u8"); - let rc = if k == 2 { r2_receipt.map(String::from).unwrap_or(receipt_of(c, &w)?) } else { receipt_of(c, &w)? }; - let mut r = json!({ "wai": w, "receipt": rc, "classical": format!("src/live/classical-r{k}.m3u8") }); - if k == 2 { - r["edit_wai"] = edit_r2.clone(); - } - Ok(r) - }).collect::, String>>()?)) + let rev = |k: usize| -> Result { + let w = format!("live/wai-r{k}.m3u8"); + Ok(json!({ "wai": w, "receipt": receipt_of(c, &w)?, "classical": format!("src/live/classical-r{k}.m3u8") })) + }; + let revs_of = |ks: &[usize]| -> Result, String> { ks.iter().map(|&k| rev(k)).collect() }; + let with = |mut r: Value, extra: Value| { + for (k, x) in extra.as_object().expect("object") { + r[k] = x.clone(); + } + r }; let fetches = json!([ { "alias": "720/1200.0.wai", "object": part_id(0, 0) }, @@ -925,15 +999,41 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { { "alias": "720/1202.0.wai", "object": part_id(1, 11) }, { "alias": "1200.m4s", "object": "O12s0" }, ]); - v.push(case("live_edge", "live", json!({ "revisions": revs(json!([]), None)?, "fetches": fetches }))); + v.push(case("live_edge", "live", json!({ "revisions": revs_of(&[0, 1, 2, 3])?, "fetches": fetches }))); let cid8 = hex(&content_hash(&c.objects[&part_id(1, 8)])); let cid7 = hex(&content_hash(&c.objects[&part_id(1, 7)])); - v.push(case("live_edge_revision_changed", "live", json!({ - "revisions": revs(json!([[format!("720/1201.7.wai#cid={cid7}"), format!("720/1201.7.wai#cid={cid8}")]]), None)?, + let mut changed = revs_of(&[0, 1, 2, 3])?; + changed[2]["edit_wai"] = json!([[format!("720/1201.7.wai#cid={cid7}"), format!("720/1201.7.wai#cid={cid8}")]]); + v.push(case("live_edge_revision_changed", "live", json!({ "revisions": changed, "fetches": [{ "alias": "720/1201.7.wai", "object": part_id(1, 8) }, { "alias": "720/1201.3.wai", "object": part_id(1, 3) }] }))); - v.push(case("live_edge_unchained", "live", json!({ - "revisions": revs(json!([]), Some("cases/live-r2-orphan.json"))?, + let mut orphaned = revs_of(&[0, 1, 2, 3])?; + orphaned[2]["receipt"] = json!("cases/live-r2-orphan.json"); + v.push(case("live_edge_unchained", "live", json!({ "revisions": orphaned, "fetches": [{ "alias": "720/1201.3.wai", "object": part_id(1, 3) }, { "alias": "720/1200.0.wai", "object": part_id(0, 0) }] }))); + // What an origin's replay gives a polling client: a revision fetched again unchanged, + // and revisions skipped, whose receipts the client walks back by their links. + let few = json!([{ "alias": "720/1200.0.wai", "object": part_id(0, 0) }, { "alias": "720/1201.3.wai", "object": part_id(1, 3) }]); + v.push(case("live_edge_unchanged_reload", "live", json!({ "revisions": revs_of(&[0, 1, 1, 2, 2, 3])?, "fetches": few }))); + v.push(case("live_edge_skip", "live", json!({ "revisions": revs_of(&[0, 2, 3])?, "fetches": few }))); + v.push(case("live_edge_skip_two", "live", json!({ "revisions": revs_of(&[0, 3])?, "fetches": few }))); + v.push(case("live_edge_walk_exceeded", "live", json!({ "revisions": revs_of(&[0, 3])?, "walk_bound": 1, "fetches": few }))); + v.push(case("live_edge_walk_at_bound", "live", json!({ "revisions": revs_of(&[0, 3])?, "walk_bound": 2, "fetches": few }))); + v.push(case("live_edge_parent_missing", "live", json!({ "revisions": revs_of(&[0, 3])?, "parents": "cases/no-receipts", "fetches": few }))); + // Chained by hash, signed by another key: as the next revision, and as a parent walked. + let mut other = revs_of(&[0, 1, 2, 3])?; + other[2]["receipt"] = json!("cases/other-key/r2.json"); + v.push(case("live_edge_other_key", "live", json!({ "revisions": other, "fetches": few }))); + let walked_other = vec![rev(0)?, rev(1)?, with(rev(3)?, json!({ "receipt": "cases/other-key/r3.json" }))]; + v.push(case("live_edge_other_key_walked", "live", json!({ "revisions": walked_other, "parents": "cases/other-key/mr", "fetches": few }))); + // A revision that lists another cid for an alias or a media sequence number an earlier + // one bound (§5.2, §11.5): in its playlist, and in a receipt walked as a parent. + let relisted = vec![rev(0)?, rev(1)?, with(rev(2)?, json!({ "wai": "cases/relist/wai-r2.m3u8", "receipt": "cases/relist/r2.json" })), rev(3)?]; + let relist_fetches = json!([{ "alias": "720/1201.0.wai", "object": part_id(1, 0) }, { "alias": "720/1201.3.wai", "object": part_id(1, 3) }]); + v.push(case("live_relisted_alias", "live", json!({ "revisions": relisted, "fetches": relist_fetches }))); + let relisted_walked = vec![rev(0)?, rev(1)?, with(rev(3)?, json!({ "receipt": "cases/relist/r3-after.json" }))]; + v.push(case("live_relisted_alias_walked", "live", json!({ "revisions": relisted_walked, "parents": "cases/relist/mr", "fetches": relist_fetches }))); + let resequenced = vec![rev(0)?, rev(1)?, rev(2)?, with(rev(3)?, json!({ "wai": "cases/relist/wai-r3.m3u8", "receipt": "cases/relist/r3.json" }))]; + v.push(case("live_resequenced", "live", json!({ "revisions": resequenced, "fetches": few }))); // Adaptation (Appendix F): pinned states per policy, seeded; with and without a // budget; and the budget's every outcome. diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs index 9e5087bf1..265df4e97 100644 --- a/wai/wai-rs/src/http_binding/hls.rs +++ b/wai/wai-rs/src/http_binding/hls.rs @@ -39,17 +39,21 @@ pub enum PlaylistError { ClassicalChanged(String), /// §10.4: a `#WAI-LAYER` line that does not have its form. Layer(RangeRefusal), + /// §5.2, §11.5: a revision lists another cid for a URI or a media sequence number + /// that an earlier revision (or an earlier line) bound. + Rebound(String), } impl PlaylistError { - /// `playlist_malformed`, `adjacency_misaligned`, `classical_changed` or - /// `layer_tag_malformed`. + /// `playlist_malformed`, `adjacency_misaligned`, `classical_changed`, + /// `layer_tag_malformed` or `playlist_listing_rebound`. pub fn code(&self) -> &'static str { match self { PlaylistError::Malformed(_) => "playlist_malformed", PlaylistError::Misaligned(_) => "adjacency_misaligned", PlaylistError::ClassicalChanged(_) => "classical_changed", PlaylistError::Layer(e) => e.code(), + PlaylistError::Rebound(_) => "playlist_listing_rebound", } } } @@ -57,7 +61,9 @@ impl PlaylistError { impl std::fmt::Display for PlaylistError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - PlaylistError::Malformed(s) | PlaylistError::Misaligned(s) | PlaylistError::ClassicalChanged(s) => write!(f, "{}: {s}", self.code()), + PlaylistError::Malformed(s) | PlaylistError::Misaligned(s) | PlaylistError::ClassicalChanged(s) | PlaylistError::Rebound(s) => { + write!(f, "{}: {s}", self.code()) + } PlaylistError::Layer(e) => e.fmt(f), } } @@ -871,6 +877,84 @@ pub fn listed_cid(p: &MediaPlaylist, uri: &str) -> Option<[u8; 32]> { }) } +/// The cid a listed URI names: its `cid` fragment, or the `.wai` its path ends in. +fn uri_cid(line: &str) -> Option<(String, [u8; 32])> { + let (base, frag) = super::parse_fragment(line); + let cid = frag.cid.or_else(|| super::unhex32(base.rsplit('/').next()?.strip_suffix(".wai")?))?; + Some((base.to_string(), cid)) +} + +/// What the revisions of a live playlist bound so far (§5.2, §11.5): each URI (without +/// its fragment) to the cid listed with it, and each media sequence number to its +/// segment's cid. A URI or sequence number keeps its cid: an alias names the same bytes +/// for its lifetime, and every object is immutable from the moment it is listed. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Bindings { + uris: std::collections::BTreeMap, + sequences: std::collections::BTreeMap, +} + +impl Bindings { + /// Add `p`'s listings: every part, layer and segment line that names a cid, in the + /// order [`listed_cid`] reads them, and each segment that is not a gap by its media + /// sequence number (a gap offers no object there). Refused + /// (`playlist_listing_rebound`), with nothing added, when a URI or a sequence number + /// is already bound to another cid. + pub fn bind(&mut self, p: &MediaPlaylist) -> Result<(), PlaylistError> { + let mut next = self.clone(); + let mut uri = |line: &str| -> Result<(), PlaylistError> { + let Some((base, cid)) = uri_cid(line) else { return Ok(()) }; + match next.uris.get(&base) { + Some(held) if *held != cid => Err(PlaylistError::Rebound(format!("{base} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))), + Some(_) => Ok(()), + None => { + next.uris.insert(base, cid); + Ok(()) + } + } + }; + let extra = |e: &SegmentExtra, uri: &mut dyn FnMut(&str) -> Result<(), PlaylistError>| -> Result<(), PlaylistError> { + for part in &e.parts { + uri(&part.uri)?; + for l in &part.layers { + uri(&l.uri)?; + } + } + Ok(()) + }; + for s in &p.segments { + extra(&s.extra, &mut uri)?; + for l in &s.extra.layers { + uri(&l.uri)?; + } + uri(&s.uri)?; + } + if let Some(o) = &p.open { + extra(o, &mut uri)?; + } + for (i, s) in p.segments.iter().enumerate() { + let Some((_, cid)) = uri_cid(&s.uri).filter(|_| !s.extra.gap) else { continue }; + let n = p.media_sequence.checked_add(i as u64).ok_or_else(|| malformed("the media sequence number passes 2^64"))?; + match next.sequences.get(&n) { + Some(held) if *held != cid => { + return Err(PlaylistError::Rebound(format!("media sequence {n} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))); + } + Some(_) => {} + None => { + next.sequences.insert(n, cid); + } + } + } + *self = next; + Ok(()) + } + + /// The cid bound to `uri` (without its fragment), if any. + pub fn cid_of(&self, uri: &str) -> Option<[u8; 32]> { + self.uris.get(uri).copied() + } +} + fn whole_head(env: &[u8]) -> Result { match super::head::parse_head(env, env.len() as u64)? { super::head::HeadParse::Head(h) => Ok(h), diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs index a42d4379b..6666c22af 100644 --- a/wai/wai-rs/src/http_binding/playlist_receipt.rs +++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs @@ -6,11 +6,13 @@ //! A public WAI playlist is sealed under [`public_class`]: entitlement `public`, no //! geography, blackout or ad pod, and the playlist's media sequence as the window. The //! receipt carries the playlist's rendering (`render`, `extras`), so a verifier renders -//! the playlist from the receipt and compares bytes. +//! the playlist from the receipt and compares bytes. Every receipt is also served at +//! `mr/.json`, so a client that skipped revisions walks back to the head +//! it holds by the links the receipts name ([`LiveChain`]). use ed25519_dalek::SigningKey; -use super::hls::{parse_media_playlist, PlaylistError}; +use super::hls::{parse_media_playlist, Bindings, PlaylistError}; use crate::quantum_ops::GrantRef; use crate::wai_video_manifest::{ManifestReceipt, SessionClass}; @@ -27,9 +29,17 @@ pub enum ReceiptError { /// The receipt does not verify: its signature, or a value it recomputes /// (`playlist_receipt_invalid`). Invalid, - /// A live revision whose parent is not the receipt before it - /// (`playlist_receipt_unchained`). + /// A live revision that does not chain to the held head: another key, or a walk + /// that ends before it (`playlist_receipt_unchained`). Unchained, + /// A parent receipt the walk needs is not served (`playlist_receipt_parent_missing`). + ParentMissing, + /// The held head is more than the bound's parents back + /// (`playlist_receipt_walk_exceeded`). + WalkExceeded, + /// A receipt lists another cid for a URI or a media sequence number the chain bound + /// (`playlist_listing_rebound`). + Rebound, } impl ReceiptError { @@ -40,6 +50,9 @@ impl ReceiptError { ReceiptError::Mismatch => "playlist_receipt_mismatch", ReceiptError::Invalid => "playlist_receipt_invalid", ReceiptError::Unchained => "playlist_receipt_unchained", + ReceiptError::ParentMissing => "playlist_receipt_parent_missing", + ReceiptError::WalkExceeded => "playlist_receipt_walk_exceeded", + ReceiptError::Rebound => "playlist_listing_rebound", } } } @@ -113,14 +126,144 @@ pub fn check_playlist(playlist: &[u8], receipt: &[u8], name: Option<&str>) -> Re Ok(r) } -/// A live playlist's receipts, oldest first (§8.6): each after the first names the one -/// before it as its parent, and all have one signer. The first failing revision's index -/// and `playlist_receipt_unchained`. -pub fn check_chain(revisions: &[ManifestReceipt]) -> Result<(), (usize, ReceiptError)> { - for (k, w) in revisions.windows(2).enumerate() { - if w[1].parent_receipt_hash != Some(w[0].receipt_hash()) || w[1].signer_pubkey != w[0].signer_pubkey { - return Err((k + 1, ReceiptError::Unchained)); +/// How many parent receipts a client fetches for one revision, at most (§8.6): a +/// revision further from the held head than this is refused +/// (`playlist_receipt_walk_exceeded`). A client MAY hold a lower bound. +pub const WALK_BOUND: usize = 64; + +/// What a revision's receipt did to the chain a client holds (§8.6). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Step { + /// The first receipt held: it becomes the head. + First, + /// The receipt is the head itself, fetched again (a reload with no new revision). + Unchanged, + /// The receipt chains to the head through `walked` parents fetched by their links + /// (0: its parent is the head), and becomes the head. + Chained { walked: usize }, +} + +/// A client's hold on a live playlist's receipts (§8.6, §11.6): the head (the latest +/// receipt that verified and chained) and what every receipt in the chain bound (each +/// URI and media sequence number to its cid, [`Bindings`]). +#[derive(Clone, Debug)] +pub struct LiveChain { + head: Option, + bindings: Bindings, + bound: usize, +} + +impl Default for LiveChain { + fn default() -> Self { + LiveChain::new() + } +} + +impl LiveChain { + /// A chain that holds nothing, walking at most [`WALK_BOUND`] parents. + pub fn new() -> LiveChain { + LiveChain::with_bound(WALK_BOUND) + } + + /// A chain that walks at most `bound` parents for one revision. + pub fn with_bound(bound: usize) -> LiveChain { + LiveChain { head: None, bindings: Bindings::default(), bound } + } + + /// The receipt held as the head. + pub fn head(&self) -> Option<&ManifestReceipt> { + self.head.as_ref() + } + + /// What the chain's receipts bound. + pub fn bindings(&self) -> &Bindings { + &self.bindings + } + + /// Take `cur`, a revision's receipt already checked against its playlist + /// ([`check_playlist`]), into the chain. `parent(h)` gives the bytes served at + /// `mr/.json` beside the playlist, or `None`. Refused, with the hold unchanged: + /// + /// - `cur` under another key than the head (`playlist_receipt_unchained`); + /// - the walk from `cur` back by `parent_receipt_hash` reaches a receipt with no + /// parent before the head (`playlist_receipt_unchained`); needs more than the + /// bound (`playlist_receipt_walk_exceeded`); needs a parent that is not served + /// (`playlist_receipt_parent_missing`); or meets one that does not read + /// (`playlist_receipt_malformed`), whose receipt hash is not the link it was + /// fetched by (`receipt_name_mismatch`), that does not verify + /// (`playlist_receipt_invalid`) or that is under another key + /// (`playlist_receipt_unchained`); + /// - a receipt on the walk, oldest first, or `cur` lists another cid for a URI or + /// a media sequence number the chain bound (`playlist_listing_rebound`). + pub fn advance(&mut self, cur: &ManifestReceipt, mut parent: impl FnMut(&[u8; 32]) -> Option>) -> Result { + let Some(head) = &self.head else { + let mut b = self.bindings.clone(); + bind(&mut b, cur)?; + self.bindings = b; + self.head = Some(cur.clone()); + return Ok(Step::First); + }; + let target = head.receipt_hash(); + if cur.receipt_hash() == target { + return Ok(Step::Unchanged); } + if cur.signer_pubkey != head.signer_pubkey { + return Err(ReceiptError::Unchained); + } + let mut walked: Vec = Vec::new(); + let mut link = cur.parent_receipt_hash; + loop { + let l = match link { + None => return Err(ReceiptError::Unchained), + Some(l) if l == target => break, + Some(l) => l, + }; + if walked.len() == self.bound { + return Err(ReceiptError::WalkExceeded); + } + let bytes = parent(&l).ok_or(ReceiptError::ParentMissing)?; + let text = std::str::from_utf8(&bytes).map_err(|_| ReceiptError::Malformed("not UTF-8".into()))?; + let r = ManifestReceipt::from_json(text).map_err(ReceiptError::Malformed)?; + if r.receipt_hash() != l { + return Err(ReceiptError::NameMismatch); + } + if !r.verify() { + return Err(ReceiptError::Invalid); + } + if r.signer_pubkey != cur.signer_pubkey { + return Err(ReceiptError::Unchained); + } + link = r.parent_receipt_hash; + walked.push(r); + } + let mut b = self.bindings.clone(); + for r in walked.iter().rev().chain(std::iter::once(cur)) { + bind(&mut b, r)?; + } + self.bindings = b; + self.head = Some(cur.clone()); + Ok(Step::Chained { walked: walked.len() }) + } +} + +/// Bind what `r`'s rendering lists. +fn bind(b: &mut Bindings, r: &ManifestReceipt) -> Result<(), ReceiptError> { + let p = parse_media_playlist(&r.rendered()).map_err(|e| ReceiptError::Malformed(e.to_string()))?; + b.bind(&p).map_err(|e| match e { + PlaylistError::Rebound(_) => ReceiptError::Rebound, + e => ReceiptError::Malformed(e.to_string()), + }) +} + +/// A live playlist's receipts in the order a client fetched them (§8.6): the first is +/// held, and each after it is the head again or chains to the head, its parents found +/// among `revisions` by receipt hash. The first failing revision's index and reason. +pub fn check_chain(revisions: &[ManifestReceipt]) -> Result<(), (usize, ReceiptError)> { + let mut chain = LiveChain::new(); + for (k, r) in revisions.iter().enumerate() { + chain + .advance(r, |h| revisions.iter().find(|x| x.receipt_hash() == *h).map(|x| x.to_json().into_bytes())) + .map_err(|e| (k, e))?; } Ok(()) } diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index 320e1023e..60a4ef809 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -359,6 +359,82 @@ mod receipts { let _: Vec = vec![a, b]; let _ = cid(0); } + + /// A client's chain (§8.6): the head fetched again is unchanged; skipped revisions + /// are walked back by their links, up to the bound; a parent not served, served + /// under another receipt's name, or under another key is refused; an older revision + /// after a newer one does not chain; and a revision that lists another cid for an + /// alias or a sequence number is refused, in its own listing and as a parent walked. + /// Every refusal leaves the hold as it was. + #[test] + fn live_chain_walks_back_and_holds_bindings() { + let ll = parse_media_playlist(CLASSICAL_LL).unwrap(); + let mut s0 = seg(1, 2000); + s0.parts = (0..4).map(|i| wpart(1200, i, 500)).collect(); + let rev = |k: usize, parts: Vec| { + let open = WaiOpen { parts, preload_hint: Some(format!("live/1201.{}.wai", k + 1)) }; + wai_media_playlist(&ll, &[s0.clone()], Some(&open), "objects/", false).unwrap().3 + }; + let open = |n: u64| (0..n).map(|i| wpart(1201, i, 500)).collect::>(); // the classical open segment has two parts + let g = || GrantRef::unbounded("video.manifest"); + let a = seal_playlist(&rev(1, open(2)), &key(), "k", "live", g(), None).unwrap(); + let b = seal_playlist(&rev(2, open(2)), &key(), "k", "live", g(), Some(&a)).unwrap(); + let c = seal_playlist(&rev(3, open(2)), &key(), "k", "live", g(), Some(&b)).unwrap(); + let served = [a.clone(), b.clone(), c.clone()]; + let fetch = |h: &[u8; 32]| served.iter().find(|x| x.receipt_hash() == *h).map(|x| x.to_json().into_bytes()); + let mut chain = LiveChain::new(); + assert_eq!(chain.advance(&a, fetch).unwrap(), Step::First); + assert_eq!(chain.advance(&a, fetch).unwrap(), Step::Unchanged); + assert_eq!(chain.advance(&c, fetch).unwrap(), Step::Chained { walked: 1 }); + assert_eq!(chain.head(), Some(&c)); + assert_eq!(chain.bindings().cid_of("objects/live/1201.1.wai"), None); + assert_eq!(chain.bindings().cid_of("live/1201.1.wai"), Some(cid(0x81))); + // Each refusal, with the hold unchanged after it. + let held = |k: &LiveChain| (k.head().cloned(), k.bindings().clone()); + let mut at_a = LiveChain::new(); + at_a.advance(&a, fetch).unwrap(); + let before = held(&at_a); + let mut tight = LiveChain::with_bound(0); + tight.advance(&a, fetch).unwrap(); + assert_eq!(tight.advance(&c, fetch).unwrap_err(), ReceiptError::WalkExceeded); + assert_eq!(at_a.advance(&c, |_| None).unwrap_err(), ReceiptError::ParentMissing); + assert_eq!(at_a.advance(&c, |_| Some(a.to_json().into_bytes())).unwrap_err(), ReceiptError::NameMismatch); + assert_eq!(at_a.advance(&c, |_| Some(b"{".to_vec())).unwrap_err().code(), "playlist_receipt_malformed"); + let other = SigningKey::from_bytes(&[22u8; 32]); + let b_other = seal_playlist(&rev(2, open(2)), &other, "k", "live", g(), Some(&a)).unwrap(); + let c_over_other = seal_playlist(&rev(3, open(2)), &key(), "k", "live", g(), Some(&b_other)).unwrap(); + assert_eq!(at_a.advance(&c_over_other, |_| Some(b_other.to_json().into_bytes())).unwrap_err(), ReceiptError::Unchained); + assert_eq!(at_a.advance(&b_other, fetch).unwrap_err(), ReceiptError::Unchained); + // Part 1201.0 listed again with part 1's object. + let mut relisted = open(2); + relisted[0].cid = cid(0x81); + let b_re = seal_playlist(&rev(2, relisted.clone()), &key(), "k", "live", g(), Some(&a)).unwrap(); + assert_eq!(at_a.advance(&b_re, fetch).unwrap_err(), ReceiptError::Rebound); + let c_over_re = seal_playlist(&rev(3, open(2)), &key(), "k", "live", g(), Some(&b_re)).unwrap(); + assert_eq!(at_a.advance(&c_over_re, |_| Some(b_re.to_json().into_bytes())).unwrap_err(), ReceiptError::Rebound); + assert_eq!(held(&at_a), before); + // The listing alone, without receipts. + let mut listed = Bindings::default(); + listed.bind(&parse_media_playlist(&rev(1, open(2))).unwrap()).unwrap(); + assert_eq!(listed.bind(&parse_media_playlist(&rev(2, relisted)).unwrap()).unwrap_err().code(), "playlist_listing_rebound"); + // An older revision after a newer one walks back past the start and does not chain. + assert_eq!(check_chain(&[a.clone(), c.clone(), b.clone()]).unwrap_err(), (2, ReceiptError::Unchained)); + check_chain(&[a.clone(), a.clone(), b.clone(), c.clone(), c]).unwrap(); + } + + /// A media sequence number keeps its segment's cid across revisions; a gap binds + /// none. + #[test] + fn a_sequence_number_keeps_its_cid() { + let vod = parse_media_playlist(CLASSICAL_VOD).unwrap(); + let text = |segs: &[WaiSegment]| wai_media_playlist(&vod, segs, None, "../objects/", true).unwrap().3; + let mut b = Bindings::default(); + b.bind(&parse_media_playlist(&text(&[seg(1, 640), seg(2, 640), seg(3, 640)])).unwrap()).unwrap(); + let mut gap = seg(9, 640); + gap.gap = true; + b.clone().bind(&parse_media_playlist(&text(&[seg(1, 640), gap, seg(3, 640)])).unwrap()).unwrap(); + assert_eq!(b.bind(&parse_media_playlist(&text(&[seg(1, 640), seg(9, 640), seg(3, 640)])).unwrap()).unwrap_err().code(), "playlist_listing_rebound"); + } } diff --git a/wai/wai-rs/tests/cli_http.rs b/wai/wai-rs/tests/cli_http.rs index 86f0e8c6d..b02667f30 100644 --- a/wai/wai-rs/tests/cli_http.rs +++ b/wai/wai-rs/tests/cli_http.rs @@ -462,8 +462,10 @@ fn hls_and_mpd_write_aligned_playlists() { assert!(!run.status.success()); } -/// `wai http seal-playlist` writes `mr/.json`; `check-playlist` reports -/// it signed, refuses a changed playlist, and holds a revision to its parent. +/// `wai http seal-playlist` writes `mr/.json` and +/// `mr/.json`; `check-playlist` reports it signed, refuses a changed +/// playlist, and holds a revision to the head it chains to, walking the revisions +/// between. #[cfg(feature = "video_manifest")] #[test] fn seal_and_check_playlist() { @@ -488,15 +490,30 @@ fn seal_and_check_playlist() { let bad = wai(&["http", "check-playlist", d.join("changed.m3u8").to_str().unwrap(), "--receipt", receipt.to_str().unwrap()]); assert_eq!(bad.status.code(), Some(1)); assert_eq!(String::from_utf8_lossy(&bad.stdout), "refused\tplaylist_receipt_mismatch\n"); + // Each receipt is also written under its receipt hash. + let hash_of = |out: &std::process::Output| String::from_utf8_lossy(&out.stdout).trim_end().rsplit(' ').next().unwrap().to_string(); + let r1_hash = hash_of(&run); + assert_eq!(std::fs::read(d.join("mr").join(format!("{r1_hash}.json"))).unwrap(), std::fs::read(&receipt).unwrap()); // A second revision sealed with the first as its parent checks against it; the - // first does not chain to itself. + // first fetched again is the head unchanged. let run = wai(&["http", "seal-playlist", pl.to_str().unwrap(), "--key", &key, "--parent", receipt.to_str().unwrap(), "--out", d.join("r2").to_str().unwrap()]); assert!(run.status.success(), "{}", String::from_utf8_lossy(&run.stderr)); let r2 = d.join("r2/mr").join(&name); let ok = wai(&["http", "check-playlist", pl.to_str().unwrap(), "--receipt", r2.to_str().unwrap(), "--parent", receipt.to_str().unwrap()]); assert!(ok.status.success(), "{}", String::from_utf8_lossy(&ok.stdout)); - let bad = wai(&["http", "check-playlist", pl.to_str().unwrap(), "--receipt", receipt.to_str().unwrap(), "--parent", receipt.to_str().unwrap()]); - assert_eq!(String::from_utf8_lossy(&bad.stdout), "refused\tplaylist_receipt_unchained\n"); + assert!(String::from_utf8_lossy(&ok.stdout).contains("\tchained (0 parents walked)")); + let same = wai(&["http", "check-playlist", pl.to_str().unwrap(), "--receipt", receipt.to_str().unwrap(), "--parent", receipt.to_str().unwrap()]); + assert!(same.status.success(), "{}", String::from_utf8_lossy(&same.stdout)); + // A third revision, chained to the second, checked against the first: the second is + // fetched by its link from the third's directory; without it the walk stops. + let r2_file = d.join("r2/mr").join(format!("{}.json", hash_of(&run))); + let run = wai(&["http", "seal-playlist", pl.to_str().unwrap(), "--key", &key, "--parent", r2_file.to_str().unwrap(), "--out", d.join("r2").to_str().unwrap()]); + assert!(run.status.success(), "{}", String::from_utf8_lossy(&run.stderr)); + let ok = wai(&["http", "check-playlist", pl.to_str().unwrap(), "--receipt", r2.to_str().unwrap(), "--parent", receipt.to_str().unwrap()]); + assert!(String::from_utf8_lossy(&ok.stdout).contains("\tchained (1 parents walked)"), "{}", String::from_utf8_lossy(&ok.stdout)); + std::fs::remove_file(&r2_file).unwrap(); + let bad = wai(&["http", "check-playlist", pl.to_str().unwrap(), "--receipt", r2.to_str().unwrap(), "--parent", receipt.to_str().unwrap()]); + assert_eq!(String::from_utf8_lossy(&bad.stdout), "refused\tplaylist_receipt_parent_missing\n"); } /// The origin serves a playlist to be revalidated on every use, and its receipt -- 2.54.0 (Apple Git-157) From 416ba51c24bc55bc2e8266ae1474b8360fde7b95 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 09:23:00 -0400 Subject: [PATCH 20/55] playlist receipts: the walk test holds a parent that does not verify Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/http_binding/tests_playlists.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index 60a4ef809..b7dd14abb 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -400,6 +400,11 @@ mod receipts { assert_eq!(at_a.advance(&c, |_| None).unwrap_err(), ReceiptError::ParentMissing); assert_eq!(at_a.advance(&c, |_| Some(a.to_json().into_bytes())).unwrap_err(), ReceiptError::NameMismatch); assert_eq!(at_a.advance(&c, |_| Some(b"{".to_vec())).unwrap_err().code(), "playlist_receipt_malformed"); + // A parent named by its link that does not verify. + let mut b_bad = b.clone(); + b_bad.viewers_served = 2; + let c_over_bad = seal_playlist(&rev(3, open(2)), &key(), "k", "live", g(), Some(&b_bad)).unwrap(); + assert_eq!(at_a.advance(&c_over_bad, |_| Some(b_bad.to_json().into_bytes())).unwrap_err(), ReceiptError::Invalid); let other = SigningKey::from_bytes(&[22u8; 32]); let b_other = seal_playlist(&rev(2, open(2)), &other, "k", "live", g(), Some(&a)).unwrap(); let c_over_other = seal_playlist(&rev(3, open(2)), &key(), "k", "live", g(), Some(&b_other)).unwrap(); -- 2.54.0 (Apple Git-157) From f43cc79fe6343c8dea6d733beedff543ec9d6003 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 09:25:17 -0400 Subject: [PATCH 21/55] playlist receipts: the walk test sees what a walked parent binds Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/http_binding/tests_playlists.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index b7dd14abb..63b8c9a72 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -378,7 +378,10 @@ mod receipts { let open = |n: u64| (0..n).map(|i| wpart(1201, i, 500)).collect::>(); // the classical open segment has two parts let g = || GrantRef::unbounded("video.manifest"); let a = seal_playlist(&rev(1, open(2)), &key(), "k", "live", g(), None).unwrap(); - let b = seal_playlist(&rev(2, open(2)), &key(), "k", "live", g(), Some(&a)).unwrap(); + // The second revision lists its parts under aliases of its own, so what it binds + // is seen only through it. + let b_parts: Vec = open(2).into_iter().map(|mut q| { q.alias = q.alias.replace("live/", "live/b/"); q }).collect(); + let b = seal_playlist(&rev(2, b_parts), &key(), "k", "live", g(), Some(&a)).unwrap(); let c = seal_playlist(&rev(3, open(2)), &key(), "k", "live", g(), Some(&b)).unwrap(); let served = [a.clone(), b.clone(), c.clone()]; let fetch = |h: &[u8; 32]| served.iter().find(|x| x.receipt_hash() == *h).map(|x| x.to_json().into_bytes()); @@ -389,6 +392,7 @@ mod receipts { assert_eq!(chain.head(), Some(&c)); assert_eq!(chain.bindings().cid_of("objects/live/1201.1.wai"), None); assert_eq!(chain.bindings().cid_of("live/1201.1.wai"), Some(cid(0x81))); + assert_eq!(chain.bindings().cid_of("live/b/1201.0.wai"), Some(cid(0x80)), "the walked parent's listing is bound"); // Each refusal, with the hold unchanged after it. let held = |k: &LiveChain| (k.head().cloned(), k.bindings().clone()); let mut at_a = LiveChain::new(); -- 2.54.0 (Apple Git-157) From abfd4613dd74deff48aabc2d65a4f5828cad2e06 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 09:38:20 -0400 Subject: [PATCH 22/55] http-conformance: falsify the live chain's walk, bound, reload, key and bindings, and the listing's rebinding refusals Co-Authored-By: Claude Opus 5.5 --- .../falsify/chain-bindings-dropped.patch | 14 ++++++++++ .../falsify/chain-key-unchecked.patch | 24 +++++++++++++++++ .../falsify/chain-parent-unchecked.patch | 27 ++++++++++--------- .../falsify/chain-reload-as-revision.patch | 16 +++++++++++ .../falsify/chain-walk-unbounded.patch | 15 +++++++++++ .../falsify/rebind-sequence.patch | 15 +++++++++++ wai/http-conformance/falsify/rebind-uri.patch | 15 +++++++++++ 7 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 wai/http-conformance/falsify/chain-bindings-dropped.patch create mode 100644 wai/http-conformance/falsify/chain-key-unchecked.patch create mode 100644 wai/http-conformance/falsify/chain-reload-as-revision.patch create mode 100644 wai/http-conformance/falsify/chain-walk-unbounded.patch create mode 100644 wai/http-conformance/falsify/rebind-sequence.patch create mode 100644 wai/http-conformance/falsify/rebind-uri.patch diff --git a/wai/http-conformance/falsify/chain-bindings-dropped.patch b/wai/http-conformance/falsify/chain-bindings-dropped.patch new file mode 100644 index 000000000..b1fa5fc37 --- /dev/null +++ b/wai/http-conformance/falsify/chain-bindings-dropped.patch @@ -0,0 +1,14 @@ +# must fail: live_edge +# what the chain's receipts list not bound (LiveChain::advance) +diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs +index 6666c22a..14d8229f 100644 +--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs ++++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs +@@ -240,7 +240,6 @@ impl LiveChain { + for r in walked.iter().rev().chain(std::iter::once(cur)) { + bind(&mut b, r)?; + } +- self.bindings = b; + self.head = Some(cur.clone()); + Ok(Step::Chained { walked: walked.len() }) + } diff --git a/wai/http-conformance/falsify/chain-key-unchecked.patch b/wai/http-conformance/falsify/chain-key-unchecked.patch new file mode 100644 index 000000000..adba19af5 --- /dev/null +++ b/wai/http-conformance/falsify/chain-key-unchecked.patch @@ -0,0 +1,24 @@ +# must fail: live_edge_other_key live_edge_other_key_walked +# a receipt chained by hash under another key accepted (LiveChain::advance) +diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs +index 6666c22a..58f23c43 100644 +--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs ++++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs +@@ -207,7 +207,7 @@ impl LiveChain { + if cur.receipt_hash() == target { + return Ok(Step::Unchanged); + } +- if cur.signer_pubkey != head.signer_pubkey { ++ if false { + return Err(ReceiptError::Unchained); + } + let mut walked: Vec = Vec::new(); +@@ -230,7 +230,7 @@ impl LiveChain { + if !r.verify() { + return Err(ReceiptError::Invalid); + } +- if r.signer_pubkey != cur.signer_pubkey { ++ if false { + return Err(ReceiptError::Unchained); + } + link = r.parent_receipt_hash; diff --git a/wai/http-conformance/falsify/chain-parent-unchecked.patch b/wai/http-conformance/falsify/chain-parent-unchecked.patch index 03440eeac..343c519da 100644 --- a/wai/http-conformance/falsify/chain-parent-unchecked.patch +++ b/wai/http-conformance/falsify/chain-parent-unchecked.patch @@ -1,15 +1,18 @@ -# must fail: live_edge_unchained -# a live revision's parent not checked (playlist_receipt::check_chain) +# must fail: live_edge_unchained live_edge_skip live_edge_other_key_walked +# a live revision's parent links not walked (playlist_receipt::LiveChain::advance) diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs -index a42d4379..81ea1712 100644 +index 6666c22a..1df1188a 100644 --- a/wai/wai-rs/src/http_binding/playlist_receipt.rs +++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs -@@ -118,7 +118,7 @@ pub fn check_playlist(playlist: &[u8], receipt: &[u8], name: Option<&str>) -> Re - /// and `playlist_receipt_unchained`. - pub fn check_chain(revisions: &[ManifestReceipt]) -> Result<(), (usize, ReceiptError)> { - for (k, w) in revisions.windows(2).enumerate() { -- if w[1].parent_receipt_hash != Some(w[0].receipt_hash()) || w[1].signer_pubkey != w[0].signer_pubkey { -+ if w[1].signer_pubkey != w[0].signer_pubkey { - return Err((k + 1, ReceiptError::Unchained)); - } - } +@@ -214,9 +214,9 @@ impl LiveChain { + let mut link = cur.parent_receipt_hash; + loop { + let l = match link { +- None => return Err(ReceiptError::Unchained), ++ None => break, + Some(l) if l == target => break, +- Some(l) => l, ++ Some(_) => break, + }; + if walked.len() == self.bound { + return Err(ReceiptError::WalkExceeded); diff --git a/wai/http-conformance/falsify/chain-reload-as-revision.patch b/wai/http-conformance/falsify/chain-reload-as-revision.patch new file mode 100644 index 000000000..21ca5a3cd --- /dev/null +++ b/wai/http-conformance/falsify/chain-reload-as-revision.patch @@ -0,0 +1,16 @@ +# must fail: live_edge_unchanged_reload +# the head fetched again taken as a new revision (LiveChain::advance) +diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs +index 6666c22a..7acdf1be 100644 +--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs ++++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs +@@ -204,9 +204,6 @@ impl LiveChain { + return Ok(Step::First); + }; + let target = head.receipt_hash(); +- if cur.receipt_hash() == target { +- return Ok(Step::Unchanged); +- } + if cur.signer_pubkey != head.signer_pubkey { + return Err(ReceiptError::Unchained); + } diff --git a/wai/http-conformance/falsify/chain-walk-unbounded.patch b/wai/http-conformance/falsify/chain-walk-unbounded.patch new file mode 100644 index 000000000..f2c7b81a1 --- /dev/null +++ b/wai/http-conformance/falsify/chain-walk-unbounded.patch @@ -0,0 +1,15 @@ +# must fail: live_edge_walk_exceeded +# the walk back to the head not bounded (LiveChain::advance) +diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs +index 6666c22a..a63e80f3 100644 +--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs ++++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs +@@ -218,7 +218,7 @@ impl LiveChain { + Some(l) if l == target => break, + Some(l) => l, + }; +- if walked.len() == self.bound { ++ if false { + return Err(ReceiptError::WalkExceeded); + } + let bytes = parent(&l).ok_or(ReceiptError::ParentMissing)?; diff --git a/wai/http-conformance/falsify/rebind-sequence.patch b/wai/http-conformance/falsify/rebind-sequence.patch new file mode 100644 index 000000000..19acd3140 --- /dev/null +++ b/wai/http-conformance/falsify/rebind-sequence.patch @@ -0,0 +1,15 @@ +# must fail: live_resequenced +# a media sequence number listed again with another object accepted (hls::Bindings::bind) +diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs +index 265df4e9..1eba790b 100644 +--- a/wai/wai-rs/src/http_binding/hls.rs ++++ b/wai/wai-rs/src/http_binding/hls.rs +@@ -936,7 +936,7 @@ impl Bindings { + let Some((_, cid)) = uri_cid(&s.uri).filter(|_| !s.extra.gap) else { continue }; + let n = p.media_sequence.checked_add(i as u64).ok_or_else(|| malformed("the media sequence number passes 2^64"))?; + match next.sequences.get(&n) { +- Some(held) if *held != cid => { ++ Some(held) if false && *held != cid => { + return Err(PlaylistError::Rebound(format!("media sequence {n} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))); + } + Some(_) => {} diff --git a/wai/http-conformance/falsify/rebind-uri.patch b/wai/http-conformance/falsify/rebind-uri.patch new file mode 100644 index 000000000..c524e1b9c --- /dev/null +++ b/wai/http-conformance/falsify/rebind-uri.patch @@ -0,0 +1,15 @@ +# must fail: live_relisted_alias live_relisted_alias_walked +# a URI listed again with another cid accepted (hls::Bindings::bind) +diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs +index 265df4e9..69811c41 100644 +--- a/wai/wai-rs/src/http_binding/hls.rs ++++ b/wai/wai-rs/src/http_binding/hls.rs +@@ -905,7 +905,7 @@ impl Bindings { + let mut uri = |line: &str| -> Result<(), PlaylistError> { + let Some((base, cid)) = uri_cid(line) else { return Ok(()) }; + match next.uris.get(&base) { +- Some(held) if *held != cid => Err(PlaylistError::Rebound(format!("{base} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))), ++ Some(held) if false && *held != cid => Err(PlaylistError::Rebound(format!("{base} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))), + Some(_) => Ok(()), + None => { + next.uris.insert(base, cid); -- 2.54.0 (Apple Git-157) From 26011a200ca35a4b73c5f18fac779328e3a4260e Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 10:01:11 -0400 Subject: [PATCH 23/55] wai: DASH adjacency in Rust and http_verify.py; wai.mpd on the classical MPD's timeline, a gap left out; MPD values escaped and checked as XML MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wai http mpd takes the classical MPD's period, @start, @timescale, t and d (--classical-mpd), leaves a gap segment's S and SegmentURL out, and checks the result adjacent. dash_read reads the XML subset of the new Appendix I and an MPD's timelines (@r expanded, timescale inherited), and check_dash_adjacent holds §11.2 in DASH. render_mpd escapes tab, LF and CR as references and refuses a character outside XML; a Period's @start is in milliseconds and its @id optional; scaled_timeline is the checked segment-time multiply the packager uses. Corpus: a classical MPD at 90 kHz, wai-gap.mpd, dash_adjacency cases, MPD id cases, and an MPD-pair fuzz family. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/http-delivery.md | 67 ++- wai/http-conformance/README.md | 5 +- wai/http-conformance/cases.json | 35 +- wai/http-conformance/falsify/run.sh | 3 +- wai/http-conformance/http_verify.py | 419 +++++++++++++++- wai/http-conformance/playlists/o9/wai-gap.mpd | 19 + wai/http-conformance/playlists/o9/wai.mpd | 10 +- wai/http-conformance/src/SOURCES.json | 5 + .../src/playlists/o9-classical.mpd | 24 + wai/wai-pack/src/catalog.rs | 22 +- wai/wai-rs/src/bin/wai_cli/http_playlists.rs | 35 +- .../src/bin/wai_cli/http_vectors_playlists.rs | 153 +++++- wai/wai-rs/src/http_binding/dash.rs | 160 ++++-- wai/wai-rs/src/http_binding/dash_read.rs | 471 ++++++++++++++++++ wai/wai-rs/src/http_binding/mod.rs | 1 + .../src/http_binding/tests_playlists.rs | 99 +++- wai/wai-rs/tests/cli_http.rs | 26 + 17 files changed, 1447 insertions(+), 107 deletions(-) create mode 100644 wai/http-conformance/playlists/o9/wai-gap.mpd create mode 100644 wai/http-conformance/src/playlists/o9-classical.mpd create mode 100644 wai/wai-rs/src/http_binding/dash_read.rs diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index c18d00209..dcf920ab2 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -660,13 +660,21 @@ receipt stays valid for the objects it did receive ([jwp-receipts](jwp-receipts. so a client that does not know it ignores the set, and a `SupplementalProperty schemeIdUri="tag:transaction.science,2026:wai/floor"` whose `value` is the floor. The reference `wai.mpd` is static: one `Period` with the - classical one's `@id` and `@start` (in whole seconds), `mediaPresentationDuration` the - timeline's length in seconds with three decimals, `minBufferTime` its longest segment - rounded up to whole seconds, and in each `Representation` a `SegmentList` whose - `SegmentTimeline` has one `S` per segment with its `t` and `d` at the classical - timescale, and one `SegmentURL` per segment naming its object. A `Representation` - whose timeline leaves out a segment offers none there (the gap of HLS), and a layer's - `Representation` names the one below it by `@dependencyId`. + classical one's `@id` (none when it has none) and `@start` (in seconds, without a + fraction when whole and with three decimals otherwise), `mediaPresentationDuration` + the timeline's length in seconds with three decimals, `minBufferTime` its longest + segment rounded up to whole seconds, and in each `Representation` a `SegmentList` with + the classical timeline's `@timescale` whose `SegmentTimeline` has one `S` per segment + it offers, with the classical `t` and `d`, and one `SegmentURL` per such segment naming + its object. A segment the WAI media playlist marks `EXT-X-GAP` is left out, `S` and + `SegmentURL` both: a `Representation` whose timeline leaves out a segment offers none + there. A layer's `Representation` names the one below it by `@dependencyId`. The + classical timeline is the classical MPD's, read as Appendix I states: by default its + first `Period` and that period's first `AdaptationSet` with a timeline. Beside a + presentation with no classical MPD, it is the classical HLS media playlist's at + timescale 1000, `t` from 0, in a `Period` starting at 0. Attribute values are escaped, + tab, LF and CR as character references, and a value holding a character that is not + an XML `Char` is not written (`playlist_malformed`). - The published classical multivariant playlist is its original, byte for byte, or the original with the session-data line inserted after its first line (its own line end, LF or CR LF). A packager MUST NOT publish one that differs otherwise @@ -678,8 +686,12 @@ A WAI media playlist is **adjacent** to a classical media playlist when, segment segment, they have the same `EXT-X-MEDIA-SEQUENCE`, `EXT-X-DISCONTINUITY-SEQUENCE`, `EXTINF` durations to the millisecond, discontinuities and `EXT-X-PROGRAM-DATE-TIME` values, and, where both carry parts, the same `EXT-X-PART-INF:PART-TARGET` and the same -part durations to the millisecond in each segment. In DASH: the same `Period@id`s and -`@start`s, and a `SegmentTimeline` with the same timescale, `t` and `d`. The WAI +part durations to the millisecond in each segment. In DASH, both read as Appendix I +states: each WAI `Period` has a classical `Period` with the same `@id` and `@start` +(compared in milliseconds), and has an `AdaptationSet`; each WAI `AdaptationSet` has a +timeline; and each of its timelines is not empty, has the `@timescale` of a timeline of +that classical period, and lists only that timeline's segments, each with its `t` and +`d`, in order (a segment left out is a gap). The WAI segment (or part) with a sequence number covers exactly the presentation interval of the classical one, so a sink can move between them at any boundary. A packager MUST NOT publish a WAI playlist that breaks this (`adjacency_misaligned`). `#WAI-LAYER` lines @@ -848,7 +860,7 @@ is a share of sessions, not of devices or people. | `base_unanswered` | §10.1 | | `not_a_layer`, `layer_tag_malformed` | §10.4 | | `adjacency_misaligned` | §11.2 | -| `playlist_malformed` | §10.4, §11.2, Appendix G | +| `playlist_malformed` | §10.4, §11.2, Appendix G, Appendix I | | `classical_changed` | §11.1 | | `playlist_receipt_malformed`, `playlist_receipt_mismatch`, `playlist_receipt_invalid`, `playlist_receipt_unchained`, `playlist_receipt_parent_missing`, `playlist_receipt_walk_exceeded` | §8.6 | | `playlist_listing_rebound` | §8.6, §11.5 | @@ -873,7 +885,7 @@ step 5 states. skipped or fetched again), a published classical master and a playable classical presentation for players, and the policies' choices; the differential adds playlists, adjacent pairs, published masters, renderings, receipt - files and adaptation states. + files, adaptation states and MPD pairs. - [`cache-conformance/`](../cache-conformance/) checks §9 against HTTP caches and reports, per cache label and version, the configuration that meets it. Products are not named in published reports. @@ -1149,3 +1161,36 @@ verified strictly: `signer_pubkey` decodes (§5.1.3) and is the one encoding of byte for byte, so an `R` that is not its point's one encoding never verifies. A verifier that accepts a key or an `R` this refuses accepts a signature any party can make: under a key of small order, `(R, S) = (B, 1)` verifies over every message. + +## Appendix I — Reading an MPD + +An MPD is read as XML 1.0 in this subset, and refused (`playlist_malformed`) outside it: + +- UTF-8, with an optional byte order mark; CR LF and a lone CR are read as LF (XML 1.0 + §2.11), and every character is an XML `Char`; +- an optional XML declaration first (``); before and after the root element, and among content, comments (with no + `--` inside) and white space; no document type declaration, CDATA section or other + processing instruction; +- names of ASCII letters, digits, `_`, `:`, `-` and `.`, not beginning with a digit, + `-` or `.`; an element's local name is its name after its last `:`; +- attribute values in `"` or `'`, holding no `<`, a literal tab or LF read as a space + (XML 1.0 §3.3.3); no attribute twice in an element; +- references, in values and in text, to `amp`, `lt`, `gt`, `quot` and `apos`, and + character references (`&#` and at most 10 decimal digits, or `&#x` and at most 8 + hexadecimal digits) to an XML `Char`; no `]]>` in text; +- elements nested at most 64 deep, and nothing after the root element but comments and + white space. + +Text is not read. The root's local name is `MPD`, with at least one `Period`. A +`Period`'s `@start` is an `xs:duration` of days, hours, minutes and seconds +(`P[nD][T[nH][nM][n[.n]S]]`, with at least one of them, in digits alone), read in +milliseconds as §11.2 reads a duration; absent, it is 0 for the first period and refused +for a later one. An `AdaptationSet`'s timelines are the `SegmentTimeline` of its own +`SegmentTemplate` or `SegmentList`, and of each of its `Representation`s'. A timeline's +`@timescale` is that element's, else that of the same kind of element in the +`AdaptationSet` and then the `Period`, else 1; 0 is refused. Each `S` has `@d`, a +positive integer, and may have `@t` (by default the end of the `S` before it, 0 for +the first) and `@r` (by default 0: the segment repeated `@r` more times). `@t`, `@d`, +`@r` and `@timescale` are digits alone; a time past 2^64 − 1, or a timeline of more +than 2^20 segments, is refused. Other elements and attributes are not read. diff --git a/wai/http-conformance/README.md b/wai/http-conformance/README.md index 0fccdad3b..12576668e 100644 --- a/wai/http-conformance/README.md +++ b/wai/http-conformance/README.md @@ -32,7 +32,7 @@ reference adaptation policies. Two implementations check every case and must agr | `http_verify.py` | the independent verifier | | `falsify/*.patch` | defects that `wai_http_vectors verify` must catch (below) | | `src/live/`, `src/players/`, `src/playlists/` | frozen sources of the playlist side: the integer video payloads of O12 and O13, the classical playlists the WAI ones sit beside, and the player fixture's classical master as authored | -| `playlists/o9/`, `playlists/o10/`, `playlists/o11/` | WAI media and multivariant playlists (and an MPD) over O9 (and over O9 with its second segment a gap), O10 with its layers, and O11, each beside its classical playlist in `src/playlists/` | +| `playlists/o9/`, `playlists/o10/`, `playlists/o11/` | WAI media and multivariant playlists (and MPDs) over O9 (and over O9 with its second segment a gap, which `wai-gap.mpd` leaves out), O10 with its layers, and O11, each beside its classical playlist in `src/playlists/`; O9's MPDs are on the timeline of `src/playlists/o9-classical.mpd` (90 kHz, written with `@r`, beside an audio set on its own timescale) | | `live/` | four revisions of a low-latency WAI playlist over O12 beside `src/live/classical-r*.m3u8`, and its multivariant playlist; the parts are listed under aliases `720/..wai` with their cids (the alias files are not in the corpus: the cases serve the objects under them, and `wai http origin --root . --live-edge live/wai-720.m3u8=live/wai-r0.m3u8,live/wai-r1.m3u8,live/wai-r2.m3u8,live/wai-r3.m3u8` replays the edge over HTTP, blocking reloads and hinted parts included) | | `players/` | the player fixture: `classical/` (frozen) is a playable classical presentation, `master.m3u8` its master as published with the session-data line, and the WAI multivariant and media playlists and MPD over O13 | | `*/mr/.json` | each WAI media playlist's receipt (§8.6, Appendix H), sealed with the corpus's test key (seed 32 bytes of `0x57`, signer `did:example:http-vectors`; it attests nothing); the live revisions are chained | @@ -101,7 +101,8 @@ outside this repository, and the AV1 streams were checked to decode. | `layer_tag` | a `#WAI-LAYER` line | `n` and `uri`, or `error` | | `media_playlist` | a `classical` playlist, the `segments` (each an `object`, its `layers`, its `parts` with their `alias`), an `open` segment, a `uri_prefix`, `end_list` and the `file` it is committed as | the rendering's BLAKE3 and length, and whether the file holds those bytes; or `error` | | `multivariant` | each variant's `uri`, the WAI media `playlist` it names, `resolution`, `frame_rate_milli` | as `media_playlist`; the objects are found by the cids the playlist lists | -| `mpd` | a WAI media `playlist`, its `classical` playlist, `period_id`, `set_id`, `rep_id` | as `media_playlist` | +| `mpd` | a WAI media `playlist`, the classical timeline (`classical_mpd` and its `classical_set` and `period_id`, Appendix I; else the `classical` HLS playlist at timescale 1000), `set_id`, `rep_id` | as `media_playlist`; a gap segment is left out | +| `dash_adjacency` | a `wai` and a `classical` MPD (files, or `*_text`), optionally edited | `adjacent`, or `error` (§11.2 in DASH, Appendix I) | | `playlist_parse` | a playlist `file` or `text` | its parameters, segments (duration, discontinuity, date-time, parts, layer numbers, URI), open segment and hint, and whether it is in the rendered form; or `error` | | `adjacency` | a `wai` and a `classical` playlist (files, or `*_text`), optionally edited (`edit_wai`, `edit_classical`: `[from, to]` pairs, each replaced once) | `adjacent`, or `error` | | `unchanged` | an `original` and a `published` classical master (files, or `*_text`), `edit_published` | `unchanged`, or `error` | diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index 0dfbb5f31..c2ebbb58d 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -366,8 +366,12 @@ {"name":"multivariant_playlists_o11_wai_m3u8","op":"multivariant","input":{"file":"playlists/o11/wai.m3u8","variants":[{"uri":"wai-v.m3u8","playlist":"playlists/o11/wai-v.m3u8"}]},"expect":{"blake3":"aaba193bc0f8f3c81f49823bcfdad0f0e05daad81157697a840f5daaed6bbae1","bytes":161,"same_as_file":true}}, {"name":"multivariant_live_wai_m3u8","op":"multivariant","input":{"file":"live/wai.m3u8","variants":[{"uri":"wai-720.m3u8","playlist":"live/wai-r3.m3u8","resolution":[32,18],"frame_rate_milli":25000}]},"expect":{"blake3":"43d8a9cac1a662298e42fe9f315a9a5101adfd51a23349d87d68f1c1f76d1b1a","bytes":181,"same_as_file":true}}, {"name":"multivariant_players_wai_m3u8","op":"multivariant","input":{"file":"players/wai.m3u8","variants":[{"uri":"wai-v0.m3u8","playlist":"players/wai-v0.m3u8","resolution":[64,36],"frame_rate_milli":25000}]},"expect":{"blake3":"48967c4eb624a91ac9fb3a2849de4609f02fa093eced0aa31842189ea1793db7","bytes":182,"same_as_file":true}}, -{"name":"mpd_playlists_o9_wai_mpd","op":"mpd","input":{"file":"playlists/o9/wai.mpd","playlist":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","period_id":"p0","set_id":1,"rep_id":"wai-180"},"expect":{"blake3":"66a843a9403c3e88e64a6ad4bae9dc109f3313368f08f255ef715fd5362e0333","bytes":1258,"same_as_file":true}}, +{"name":"mpd_playlists_o9_wai_mpd","op":"mpd","input":{"file":"playlists/o9/wai.mpd","playlist":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","classical_mpd":"src/playlists/o9-classical.mpd","classical_set":"1","set_id":1,"rep_id":"wai-180"},"expect":{"blake3":"d630fe02e4490e6ff4665fe145776d464299c4c8411fb2e5c16f382bdec6b397","bytes":1268,"same_as_file":true}}, +{"name":"mpd_playlists_o9_wai_gap_mpd","op":"mpd","input":{"file":"playlists/o9/wai-gap.mpd","playlist":"playlists/o9/wai-180-gap.m3u8","classical":"src/playlists/o9-classical.m3u8","classical_mpd":"src/playlists/o9-classical.mpd","classical_set":"1","set_id":1,"rep_id":"wai-180"},"expect":{"blake3":"c30a9c1e045155e75b6bf50349fddc0e72361da34b22c3e0a37a4b5b2852de6c","bytes":1108,"same_as_file":true}}, {"name":"mpd_players_wai_mpd","op":"mpd","input":{"file":"players/wai.mpd","playlist":"players/wai-v0.m3u8","classical":"players/classical/v0.m3u8","period_id":"p0","set_id":1,"rep_id":"wai-v0"},"expect":{"blake3":"b09dfb6387b814f6e576274a41e630629bb3252e17d10d61c9a1730905ae1fb4","bytes":1239,"same_as_file":true}}, +{"name":"mpd_rep_id_white_space","op":"mpd","input":{"rep_id":"wai 180","playlist":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","classical_mpd":"src/playlists/o9-classical.mpd","classical_set":"1","set_id":1},"expect":{"error":"playlist_malformed"}}, +{"name":"mpd_rep_id_not_a_char","op":"mpd","input":{"rep_id":"wai\u0001180","playlist":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","classical_mpd":"src/playlists/o9-classical.mpd","classical_set":"1","set_id":1},"expect":{"error":"playlist_malformed"}}, +{"name":"mpd_rep_id_unicode_space","op":"mpd","input":{"rep_id":"wai 180","playlist":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","classical_mpd":"src/playlists/o9-classical.mpd","classical_set":"1","set_id":1},"expect":{"error":"playlist_malformed"}}, {"name":"media_playlist_short_O9","op":"media_playlist","input":{"classical":"src/playlists/o9-classical.m3u8","uri_prefix":"","segments":[{"object":"O9a"},{"object":"O9b"}]},"expect":{"error":"adjacency_misaligned"}}, {"name":"media_playlist_parts_beside_no_target","op":"media_playlist","input":{"classical":"src/playlists/o9-classical.m3u8","uri_prefix":"","segments":[{"object":"O9a","parts":[{"object":"O12p0_00","alias":"p0.wai","independent":true,"duration_ms":320},{"object":"O12p0_01","alias":"p1.wai","independent":true,"duration_ms":320}]},{"object":"O9b"},{"object":"O9c"}]},"expect":{"error":"adjacency_misaligned"}}, {"name":"media_playlist_layer_not_a_layer","op":"media_playlist","input":{"classical":"src/playlists/o9-classical.m3u8","uri_prefix":"","segments":[{"object":"O9a","layers":["O9b"]},{"object":"O9b"},{"object":"O9c"}]},"expect":{"error":"not_a_layer"}}, @@ -427,6 +431,35 @@ {"name":"adjacency_layer_line_ignored","op":"adjacency","input":{"wai":"live/wai-r1.m3u8","classical":"src/live/classical-r1.m3u8","edit_wai":[["#EXTINF:3.840,\n","#EXTINF:3.840,\n#WAI-LAYER:N=1,URI=\"../objects/l.wai#n=9\"\n"]]},"expect":{"adjacent":true}}, {"name":"adjacency_live_s_beside_live_p","op":"adjacency","input":{"wai":"live/wai-r3.m3u8","classical":"src/live/classical-r3.m3u8","edit_wai":[["#EXT-X-ENDLIST\n",""]],"edit_classical":[["#EXT-X-ENDLIST\n",""]]},"expect":{"adjacent":true}}, {"name":"adjacency_malformed_wai","op":"adjacency","input":{"wai":"live/wai-r1.m3u8","classical":"src/live/classical-r1.m3u8","edit_wai":[["#EXTM3U","#EXTM3X"]]},"expect":{"error":"playlist_malformed"}}, +{"name":"dash_adjacency_o9","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd"},"expect":{"adjacent":true}}, +{"name":"dash_adjacency_o9_gap","op":"dash_adjacency","input":{"wai":"playlists/o9/wai-gap.mpd","classical":"src/playlists/o9-classical.mpd"},"expect":{"adjacent":true}}, +{"name":"dash_adjacency_period_id","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["",""]],"edit_classical":[]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"dash_adjacency_t","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["",""]],"edit_classical":[]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"dash_adjacency_extra_segment","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["",""]],"edit_classical":[]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"dash_adjacency_out_of_order","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["\n ","\n "]],"edit_classical":[]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"dash_adjacency_written_with_r","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["\n \n ",""]],"edit_classical":[]},"expect":{"adjacent":true}}, +{"name":"dash_adjacency_classical_written_out","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["",""]]},"expect":{"adjacent":true}}, +{"name":"dash_adjacency_classical_shorter","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["d=\"57600\" r=\"2\"","d=\"57600\" r=\"1\""]]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"dash_adjacency_no_timeline","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["",""]],"edit_classical":[]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"dash_adjacency_crlf_and_comment","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["\r\n \n",""]],"edit_classical":[]},"expect":{"error":"playlist_malformed"}}, +{"name":"dash_adjacency_unclosed","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["",""]],"edit_classical":[]},"expect":{"error":"playlist_malformed"}}, +{"name":"dash_adjacency_r_negative","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["r=\"2\"","r=\"-1\""]]},"expect":{"error":"playlist_malformed"}}, +{"name":"dash_adjacency_r_past_the_subset","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["r=\"2\"","r=\"1048576\""]]},"expect":{"error":"playlist_malformed"}}, +{"name":"dash_adjacency_t_past_u64","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["",""]]},"expect":{"error":"playlist_malformed"}}, +{"name":"dash_adjacency_start_in_months","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["start=\"PT0S\"","start=\"P0M\""]],"edit_classical":[]},"expect":{"error":"playlist_malformed"}}, +{"name":"dash_adjacency_not_an_mpd","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["",""]],"edit_classical":[]},"expect":{"error":"playlist_malformed"}}, +{"name":"dash_adjacency_zero_d","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["d=\"30720\"","d=\"0\""]]},"expect":{"error":"playlist_malformed"}}, {"name":"unchanged_players","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[]},"expect":{"unchanged":true}}, {"name":"unchanged_original_itself","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"src/players/master.m3u8"},"expect":{"unchanged":true}}, {"name":"unchanged_two_lines","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n","#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n"]]},"expect":{"error":"classical_changed"}}, diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh index 61f42fbb6..d4f2a4703 100755 --- a/wai/http-conformance/falsify/run.sh +++ b/wai/http-conformance/falsify/run.sh @@ -48,7 +48,8 @@ cd "$ROOT/wai/http-conformance" for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done tick-noop refinement-unclipped \ pdt-ignored dseq-ignored parts-after-extinf average-floor render-digest-off name-unchecked chain-unchecked \ ilog2-no-interp bola-waits unlabelled-as-zero round-half-down small-order-key small-order-r \ - rebind-uri rebind-sequence key-unchecked walk-unbounded reload-walked; do + rebind-uri rebind-sequence key-unchecked walk-unbounded reload-walked \ + xml-char-unchecked xml-attribute-twice dash-start-ignored dash-timescale-ignored; do if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then echo "::error::http_verify.py --mutate $m passed"; fail=1 else diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index e3a11bc86..9cf2fc5db 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -1929,33 +1929,406 @@ def bind(b, p): return (uris, seqs) +def xml_char(c): + o = ord(c) + return o in (9, 10, 13) or 0x20 <= o <= 0xD7FF or 0xE000 <= o <= 0xFFFD or 0x10000 <= o <= 0x10FFFF + + def esc_xml(s): - return s.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """) + """An attribute value: & < > " as entities, tab, LF and CR as character references; + Refusal for a character that is not an XML Char.""" + out = [] + for c in s: + if c in '&<>"\t\n\r': + out.append({"&": "&", "<": "<", ">": ">", '"': """, "\t": " ", "\n": " ", "\r": " "}[c]) + elif xml_char(c) or MUTATE == "xml-char-unchecked": + out.append(c) + else: + raise Refusal("playlist_malformed") + return "".join(out) -def wai_mpd(c, period_id, set_id, union, floor, rep_id, bandwidth, media): - """wai.mpd (§11.1) on the classical HLS timeline at timescale 1000.""" - t, segs = 0, [] - for s in c["segments"]: - segs.append((t, s["ms"])) - t += s["ms"] - if not segs or len(media) != len(segs) or union[0] != floor[0]: +# The characters of Unicode's White_Space property: an MPD @id holds none. +WHITE_SPACE = set("\t\n\x0b\x0c\r \x85\xa0\u1680\u2028\u2029\u202f\u205f\u3000") | {chr(c) for c in range(0x2000, 0x200B)} + + +def id_ok(s): + return s != "" and not any(c in WHITE_SPACE for c in s) + + +def xs_seconds(ms): + return "PT%dS" % (ms // 1000) if ms % 1000 == 0 else "PT%sS" % secs(ms) + + +def wai_mpd(t, set_id, union, floor, rep_id, bandwidth, media): + """wai.mpd (§11.1) on the timeline t = (period id or None, start ms, timescale, + [(t, d)]); media[i] None leaves segment i out (a gap).""" + period_id, start_ms, ts, segs = t + if not segs or ts == 0 or len(media) != len(segs) or union[0] != floor[0]: raise Refusal("playlist_malformed") - pt = lambda ms: "PT%sS" % secs(ms) + if not id_ok(rep_id) or (period_id is not None and not id_ok(period_id)): + raise Refusal("playlist_malformed") + offered = [(sg, m) for sg, m in zip(segs, media) if m is not None] + if not offered: + raise Refusal("playlist_malformed") + to_ms = lambda d: min(d * 1000 // ts, U64) + total = min(sum(to_ms(d) for _, d in segs), U64) + longest = max(to_ms(d) for _, d in segs) + period = (' ' % (esc_xml(period_id), xs_seconds(start_ms))) if period_id is not None else (' ' % xs_seconds(start_ms)) out = ['', - '' % (pt(sum(d for _, d in segs)), -(-max(d for _, d in segs) // 1000)), - ' ' % esc_xml(period_id), + '' % (secs(total), -(-longest // 1000)), + period, ' ' % (set_id, esc_xml(render(union))), ' ', ' ' % esc_xml(render(floor)), ' ' % (esc_xml(rep_id), bandwidth), - ' ', ' '] - out += [' ' % s for s in segs] - out += [' '] + [' ' % esc_xml(m) for m in media] + ' ' % ts, ' '] + out += [' ' % sg for sg, _ in offered] + out += [' '] + [' ' % esc_xml(m) for _, m in offered] out += [' ', ' ', ' ', ' ', ''] return "\n".join(out) + "\n" +# ---------------------------------------------- reading an MPD (Appendix I) + +MAX_TIMELINE_SEGMENTS = 1 << 20 +MAX_DEPTH = 64 +NAME = re.compile(r"[A-Za-z_:][A-Za-z0-9_:.\-]*", re.ASCII) + + +class Xml: + """A reader of the XML subset of Appendix I over a normalised text.""" + + def __init__(self, s): + self.s, self.at = s, 0 + + def bad(self): + return Refusal("playlist_malformed") + + def rest(self): + return self.s[self.at:] + + def eat(self, lit): + if self.s.startswith(lit, self.at): + self.at += len(lit) + return True + return False + + def space(self): + n = 0 + while self.at + n < len(self.s) and self.s[self.at + n] in " \t\n": + n += 1 + self.at += n + return n > 0 + + def name(self): + m = NAME.match(self.s, self.at) + if m is None: + raise self.bad() + self.at = m.end() + return m.group(0) + + def reference(self): + end = self.s.find(";", self.at) + if end < 0: + raise self.bad() + body = self.s[self.at:end] + named = {"amp": "&", "lt": "<", "gt": ">", "quot": '"', "apos": "'"} + if body in named: + c = named[body] + else: + if body.startswith("#x") and re.fullmatch(r"[0-9A-Fa-f]{1,8}", body[2:], re.ASCII): + n = int(body[2:], 16) + elif body.startswith("#") and re.fullmatch(r"[0-9]{1,10}", body[1:], re.ASCII) and int(body[1:]) <= 0xFFFFFFFF: + n = int(body[1:]) + else: + raise self.bad() + if 0xD800 <= n <= 0xDFFF or n > 0x10FFFF or not xml_char(chr(n)): + raise self.bad() + c = chr(n) + self.at = end + 1 + return c + + def attr_value(self): + if self.at >= len(self.s) or self.s[self.at] not in "\"'": + raise self.bad() + q = self.s[self.at] + self.at += 1 + out = [] + while True: + if self.at >= len(self.s): + raise self.bad() + c = self.s[self.at] + self.at += 1 + if c == q: + return "".join(out) + if c == "<": + raise self.bad() + if c == "&": + out.append(self.reference()) + elif c in "\t\n": + out.append(" ") + else: + out.append(c) + + def comment(self): + self.at += 4 + end = self.s.find("--", self.at) + if end < 0 or not self.s.startswith("-->", end): + raise self.bad() + self.at = end + 3 + + def misc(self): + while True: + if self.space(): + continue + if self.s.startswith(" + + + + + + + + + + + + + + + + + + + + + diff --git a/wai/wai-pack/src/catalog.rs b/wai/wai-pack/src/catalog.rs index ee218e342..2bd207b71 100644 --- a/wai/wai-pack/src/catalog.rs +++ b/wai/wai-pack/src/catalog.rs @@ -313,23 +313,25 @@ fn total_units(ix: &StageIndex) -> u64 { } /// The timeline over the pieces `ps`: each `t` from the first piece's start and its `d`, -/// in units of the timebase's numerator. -fn timeline(ix: &StageIndex, ps: &[usize], tb: Timebase) -> Vec<(u64, u64)> { - ps.iter().map(|&p| ((ix.pieces[p].start - ix.pieces[0].start) * tb.den as u64, ix.pieces[p].duration as u64 * tb.den as u64)).collect() +/// in units of the timebase's numerator, multiplied checked (a time past 2^64 − 1 has +/// no timeline). +fn timeline(ix: &StageIndex, ps: &[usize], tb: Timebase) -> Result, CatalogError> { + let spans: Vec<(u64, u64)> = ps.iter().map(|&p| (ix.pieces[p].start, ix.pieces[p].duration as u64)).collect(); + Ok(dash::scaled_timeline(&spans, ix.pieces[0].start, tb.den as u64)?) } fn mpd(v: &CompView, set: AdaptationSet) -> Result { Ok(render_mpd(&Mpd { duration_ms: crate::plan::units_to_ms(total_units(v.index), v.tb), min_buffer_s: target_duration(v.index, v.tb), - period_id: "p0".into(), - period_start_s: 0, + period_id: Some("p0".into()), + period_start_ms: 0, adaptation_sets: vec![set], })?) } /// `dash/classic.mpd`. -pub fn render_dash_classic(v: &CompView) -> String { +pub fn render_dash_classic(v: &CompView) -> Result { let f = v.floor.as_ref().expect("a floor"); let (peak, _) = v.floor_rates(); let all: Vec = (0..v.index.pieces.len()).collect(); @@ -339,14 +341,14 @@ pub fn render_dash_classic(v: &CompView) -> String { bandwidth: peak, timescale: v.tb.num as u64, initialization: Some(format!("../{}", f.init)), - timeline: timeline(v.index, &all, v.tb), + timeline: timeline(v.index, &all, v.tb)?, media: f.fragments.iter().map(|(path, _)| format!("../{path}")).collect(), }; let set = AdaptationSet { id: 1, content_type: Some("video".into()), mime_type: "video/mp4".into(), codecs: f.codec.clone(), size: Some((f.w, f.h)), segment_alignment: true, start_with_sap: Some(1), essential: Vec::new(), supplemental: Vec::new(), representations: vec![rep], }; - mpd(v, set).expect("the classical MPD renders") + mpd(v, set) } /// `dash/.mpd`, or `None` when the class lists no WAI object. @@ -366,7 +368,7 @@ pub fn render_dash_class(v: &CompView, c: usize) -> Result, Catal bandwidth: peak, timescale: v.tb.num as u64, initialization: None, - timeline: timeline(v.index, &ps.iter().map(|(p, _)| *p).collect::>(), v.tb), + timeline: timeline(v.index, &ps.iter().map(|(p, _)| *p).collect::>(), v.tb)?, media: ps.iter().map(|(_, e)| format!("../objects/{}.wai", crate::hex(&e.content_hash))).collect(), }); } @@ -485,7 +487,7 @@ pub fn render_all(v: &CompView, layer_lag_ms: &[u64], default_deadline: u64, pol } out.insert("hls/classic/master.m3u8".into(), render_classic_master(v).into_bytes()); out.insert(format!("hls/classic/floor-{id}.m3u8"), render_floor_media(v).into_bytes()); - out.insert("dash/classic.mpd".into(), render_dash_classic(v).into_bytes()); + out.insert("dash/classic.mpd".into(), render_dash_classic(v)?.into_bytes()); out.insert("moq/catalog.json".into(), render_moq_catalog(v, layer_lag_ms, default_deadline).into_bytes()); for c in 0..v.index.classes.len() { let class = v.class_id(c).to_string(); diff --git a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs index 520b2d859..620dc76bb 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs @@ -4,8 +4,12 @@ //! wai http hls --objects --out [--master wai.m3u8] //! write each variant's WAI media playlist, aligned with its classical media //! playlist, and the WAI multivariant playlist; print the union and the floor -//! wai http mpd --objects --out [--period-id p0] [--set-id 1] -//! write wai.mpd on the classical timeline, one Representation per variant +//! wai http mpd --objects --out [--classical-mpd ] +//! [--period-id ID] [--classical-set ID] [--set-id 1] +//! write wai.mpd on the classical timeline, one Representation per variant, a gap +//! left out: the classical MPD's period, timescale, t and d (its first period and +//! the first adaptation set with a timeline, or those named), checked adjacent to +//! it; with no classical MPD, the classical HLS timeline at timescale 1000 from 0 //! wai http seal-playlist --key //! [--signer-id ID] [--content-id ID] [--parent ] //! [--out ] @@ -187,26 +191,43 @@ pub fn hls(args: &[String]) -> Result<(), String> { } pub fn mpd(args: &[String]) -> Result<(), String> { + use wai::http_binding::dash_read::check_dash_adjacent; let list = load(Path::new(opt(args, "--objects").ok_or("mpd needs --objects ")?))?; let out = PathBuf::from(opt(args, "--out").ok_or("mpd needs --out ")?); - let period = opt(args, "--period-id").unwrap_or("p0"); let set_id: u32 = opt(args, "--set-id").unwrap_or("1").parse().map_err(|_| "--set-id is an unsigned integer")?; let first = list.variants.first().ok_or("the list has no variant")?; - let timeline = DashTimeline::from_hls(&first.classical, period, 0); + // §11.1, §11.2: the classical MPD's period, timescale, t and d; without one, the + // classical HLS timeline at timescale 1000 from 0. + let classical_mpd = opt(args, "--classical-mpd").map(|p| String::from_utf8(read(Path::new(p))?).map_err(|_| format!("{p}: not UTF-8"))).transpose()?; + let hls_timeline = |v: &Variant| DashTimeline::from_hls(&v.classical, opt(args, "--period-id").unwrap_or("p0"), 0).map_err(|e| format!("{}: {e}", v.uri)); + let timeline = match &classical_mpd { + Some(text) => DashTimeline::from_mpd(text, opt(args, "--period-id"), opt(args, "--classical-set")).map_err(|e| format!("the classical MPD: {e}"))?, + None => hls_timeline(first)?, + }; let mut reps = Vec::new(); let mut all = Vec::new(); for v in &list.variants { - if DashTimeline::from_hls(&v.classical, period, 0) != timeline { + if v.segments.len() != timeline.segments.len() { + return Err(format!("{}: {} segments, and the classical timeline has {}", v.uri, v.segments.len(), timeline.segments.len())); + } + if classical_mpd.is_none() && hls_timeline(v)? != timeline { return Err(format!("{}: its classical timeline is not the first variant's; one AdaptationSet shares one timeline", v.uri)); } let (peak, _) = rates(v)?; let id = v.uri.trim_end_matches(".m3u8").rsplit('/').next().unwrap_or(&v.uri).to_string(); - let media = v.segments.iter().map(|s| format!("{}{}.wai#n={}{}", list.uri_prefix, hex(&s.cid), s.n, s.h.map(|h| format!("&h={h}")).unwrap_or_default())).collect(); + // A gap offers no WAI object there: its S and SegmentURL are left out. + let media = v.segments.iter() + .map(|s| (!s.gap).then(|| format!("{}{}.wai#n={}{}", list.uri_prefix, hex(&s.cid), s.n, s.h.map(|h| format!("&h={h}")).unwrap_or_default()))) + .collect(); reps.push(WaiRepresentation { id, bandwidth: peak, media }); all.extend(v.envelopes.iter().cloned()); } let (union, floor) = union_floor(&all)?; - write(&out, wai_mpd(&timeline, set_id, &union, &floor, &reps).map_err(|e| e.to_string())?.as_bytes())?; + let text = wai_mpd(&timeline, set_id, &union, &floor, &reps).map_err(|e| e.to_string())?; + if let Some(c) = &classical_mpd { + check_dash_adjacent(&text, c).map_err(|e| format!("the WAI MPD is not adjacent to the classical one: {e}"))?; + } + write(&out, text.as_bytes())?; println!("{}", out.display()); Ok(()) } diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index ae3709576..be66d9a29 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -121,10 +121,15 @@ fn multivariant_specs() -> Vec { ] } +/// The WAI MPDs: the media playlist whose objects they list, the classical timeline +/// (the classical MPD's, `classical_mpd` and its `classical_set`; else the classical HLS +/// playlist's at timescale 1000), and the ids written. fn mpd_specs() -> Vec { vec![ json!({ "file": "playlists/o9/wai.mpd", "playlist": "playlists/o9/wai-180.m3u8", "classical": "src/playlists/o9-classical.m3u8", - "period_id": "p0", "set_id": 1, "rep_id": "wai-180" }), + "classical_mpd": "src/playlists/o9-classical.mpd", "classical_set": "1", "set_id": 1, "rep_id": "wai-180" }), + json!({ "file": "playlists/o9/wai-gap.mpd", "playlist": "playlists/o9/wai-180-gap.m3u8", "classical": "src/playlists/o9-classical.m3u8", + "classical_mpd": "src/playlists/o9-classical.mpd", "classical_set": "1", "set_id": 1, "rep_id": "wai-180" }), json!({ "file": "players/wai.mpd", "playlist": "players/wai-v0.m3u8", "classical": "players/classical/v0.m3u8", "period_id": "p0", "set_id": 1, "rep_id": "wai-v0" }), ] @@ -349,13 +354,19 @@ fn render_multivariant(c: &Corpus, input: &Value) -> Result { } fn render_mpd(c: &Corpus, input: &Value) -> Result { - let classical = parse_media_playlist(&text(c, input["classical"].as_str().ok_or("classical")?)?).map_err(|e| pl_err(&e))?; let p = parse_media_playlist(&text(c, input["playlist"].as_str().ok_or("playlist")?)?).map_err(|e| pl_err(&e))?; let (segs, _, all) = listed_objects(c, &p)?; let (union, floor) = union_floor(&all)?; let (peak, _) = bandwidths(&segs).ok_or("no segment")?; - let t = DashTimeline::from_hls(&classical, input["period_id"].as_str().unwrap_or("p0"), 0); - let rep = WaiRepresentation { id: input["rep_id"].as_str().ok_or("rep_id")?.into(), bandwidth: peak, media: p.segments.iter().map(|s| s.uri.clone()).collect() }; + let t = match input["classical_mpd"].as_str() { + Some(f) => DashTimeline::from_mpd(&text(c, f)?, input["period_id"].as_str(), input["classical_set"].as_str()).map_err(|e| pl_err(&e))?, + None => { + let classical = parse_media_playlist(&text(c, input["classical"].as_str().ok_or("classical")?)?).map_err(|e| pl_err(&e))?; + DashTimeline::from_hls(&classical, input["period_id"].as_str().unwrap_or("p0"), 0).map_err(|e| pl_err(&e))? + } + }; + let media = p.segments.iter().map(|s| (!s.extra.gap).then(|| s.uri.clone())).collect(); + let rep = WaiRepresentation { id: input["rep_id"].as_str().ok_or("rep_id")?.into(), bandwidth: peak, media }; wai_mpd(&t, input["set_id"].as_u64().unwrap_or(1) as u32, &union, &floor, &[rep]).map_err(|e| pl_err(&e)) } @@ -428,6 +439,12 @@ pub(super) fn outcome(c: &Corpus, op: &str, input: &Value) -> Option (|| { + let w = edit(inline_or_file(c, input, "wai")?, &input["edit_wai"])?; + let k = edit(inline_or_file(c, input, "classical")?, &input["edit_classical"])?; + wai::http_binding::dash_read::check_dash_adjacent(&w, &k).map_err(|e| pl_err(&e))?; + Ok(json!({ "adjacent": true })) + })(), "adjacency" => (|| { let w = edit(inline_or_file(c, input, "wai")?, &input["edit_wai"])?; let k = edit(inline_or_file(c, input, "classical")?, &input["edit_classical"])?; @@ -853,6 +870,14 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { let f = spec["file"].as_str().expect("file"); v.push(case(format!("mpd_{}", f.replace(['/', '.', '-'], "_")), "mpd", spec.clone())); } + // An MPD's ids and values are XML: white space in an @id, and a character outside + // XML, are refused. + for (name, id) in [("mpd_rep_id_white_space", "wai 180"), ("mpd_rep_id_not_a_char", "wai\u{1}180"), ("mpd_rep_id_unicode_space", "wai\u{2009}180")] { + let mut spec = mpd_specs()[0].clone(); + spec["rep_id"] = json!(id); + spec.as_object_mut().expect("object").remove("file"); + v.push(refused(name, "mpd", spec, "playlist_malformed")); + } // Objects that do not cover a timeline. let o9 = |segs: Value| json!({ "classical": "src/playlists/o9-classical.m3u8", "uri_prefix": "", "segments": segs }); v.push(refused("media_playlist_short_O9", "media_playlist", o9(json!([seg("O9a"), seg("O9b")])), "adjacency_misaligned")); @@ -929,6 +954,52 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { "edit_wai": [["#EXT-X-ENDLIST\n", ""]], "edit_classical": [["#EXT-X-ENDLIST\n", ""]] }))); v.push(refused("adjacency_malformed_wai", "adjacency", json!({ "wai": "live/wai-r1.m3u8", "classical": "src/live/classical-r1.m3u8", "edit_wai": [["#EXTM3U", "#EXTM3X"]] }), "playlist_malformed")); + // DASH adjacency (§11.2, Appendix I): wai.mpd beside the classical MPD it was rendered + // on, a gap left out, and every way it can be broken or fail to read. + let mpd_pair = |w: &str| json!({ "wai": w, "classical": "src/playlists/o9-classical.mpd" }); + v.push(case("dash_adjacency_o9", "dash_adjacency", mpd_pair("playlists/o9/wai.mpd"))); + v.push(case("dash_adjacency_o9_gap", "dash_adjacency", mpd_pair("playlists/o9/wai-gap.mpd"))); + let dash = |name: &str, ew: Value, ec: Value, code: Option<&'static str>| { + let mut i = mpd_pair("playlists/o9/wai.mpd"); + i["edit_wai"] = ew; + i["edit_classical"] = ec; + match code { Some(code) => refused(name, "dash_adjacency", i, code), None => case(name, "dash_adjacency", i) } + }; + let none = json!([]); + let mis = Some("adjacency_misaligned"); + let bad = Some("playlist_malformed"); + for (name, ew, ec, code) in [ + ("dash_adjacency_period_id", json!([["", ""]]), none.clone(), mis), + ("dash_adjacency_t", json!([["", ""]]), none.clone(), mis), + ("dash_adjacency_extra_segment", json!([["", ""]]), none.clone(), mis), + ("dash_adjacency_out_of_order", json!([["\n ", "\n "]]), none.clone(), mis), + ("dash_adjacency_written_with_r", json!([["\n \n ", ""]]), none.clone(), None), + ("dash_adjacency_classical_written_out", none.clone(), json!([["", ""]]), None), + ("dash_adjacency_classical_shorter", none.clone(), json!([["d=\"57600\" r=\"2\"", "d=\"57600\" r=\"1\""]]), mis), + ("dash_adjacency_no_timeline", json!([["", ""]]), none.clone(), mis), + ("dash_adjacency_crlf_and_comment", json!([["\r\n \n", ""]]), none.clone(), bad), + ("dash_adjacency_unclosed", json!([["", ""]]), none.clone(), bad), + ("dash_adjacency_r_negative", none.clone(), json!([["r=\"2\"", "r=\"-1\""]]), bad), + ("dash_adjacency_r_past_the_subset", none.clone(), json!([["r=\"2\"", "r=\"1048576\""]]), bad), + ("dash_adjacency_t_past_u64", none.clone(), json!([["", ""]]), bad), + ("dash_adjacency_start_in_months", json!([["start=\"PT0S\"", "start=\"P0M\""]]), none.clone(), bad), + ("dash_adjacency_not_an_mpd", json!([["", ""]]), none.clone(), bad), + ("dash_adjacency_zero_d", none.clone(), json!([["d=\"30720\"", "d=\"0\""]]), bad), + ] { + v.push(dash(name, ew, ec, code)); + } + // The classical master, published with at most the session-data line (§11.1). let un = |edits: Value| json!({ "original": "src/players/master.m3u8", "published": "players/master.m3u8", "edit_published": edits }); v.push(case("unchanged_players", "unchanged", un(json!([])))); @@ -1425,5 +1496,79 @@ pub(super) fn fuzz_cases(c: &Corpus, s: &mut u64, i: usize) -> Result Result<(String, String), String> { + let mut texts = [String::from_utf8(c.file("playlists/o9/wai.mpd")?).map_err(|_| "not UTF-8")?, + String::from_utf8(c.file("src/playlists/o9-classical.mpd")?).map_err(|_| "not UTF-8")?]; + const NUMBERS: &[&str] = &["0", "1", "57600", "57601", "115200", "172800", "", "-1", "+1", "01", "1e3", "18446744073709551615", "18446744073709551616", + "1048575", "1048576", "2", " 2", "90000", "48000", "1000", "0x10", "٣"]; + const DURATIONS: &[&str] = &["PT0S", "PT0.000S", "PT0.0004S", "PT0.0005S", "P0D", "P0DT0H0M0S", "PT", "P", "P1M", "P1Y", "PT1M", "PT60S", "-PT0S", + "PT0,5S", "pt0s", "PT0.S", "PT.5S", "P0DT", "PT0H0M0.0001S"]; + const MARKUP: &[&str] = &["", "", "", "", "&", "&bogus;", "�", "A", "A", "�", + "\r\n", "\r", "]]>", "", "", "\u{1}", "\u{FEFF}", "\u{FFFE}", "", "", "", + "", "", "", "<é/>", "\t\n"]; + for _ in 0..1 + next(s) % 3 { + let side = (next(s) % 2) as usize; + let t = &mut texts[side]; + match next(s) % 6 { + 0 => { + // A number of the timeline respelled. + let at: Vec = t.match_indices(['t', 'd', 'r']).map(|(i, _)| i).filter(|&i| t[i + 1..].starts_with("=\"") && t[..i].ends_with(' ')).collect(); + if let Some(&i) = at.get((next(s) % at.len().max(1) as u64) as usize) { + let start = i + 3; + let end = start + t[start..].find('"').unwrap_or(0); + t.replace_range(start..end, pick(s, NUMBERS)); + } + } + 1 => { + // An S dropped or repeated. + let at: Vec = t.match_indices("").map_or(0, |e| e + 2); + let el = t[i..end].to_string(); + if next(s) % 2 == 0 { t.replace_range(i..end, "") } else { t.insert_str(end, &el) } + } + } + 2 => { + if let Some(i) = t.find("timescale=\"") { + let start = i + 11; + let end = start + t[start..].find('"').unwrap_or(0); + t.replace_range(start..end, pick(s, NUMBERS)); + } + } + 3 => { + if let Some(i) = t.find("start=\"") { + let start = i + 7; + let end = start + t[start..].find('"').unwrap_or(0); + t.replace_range(start..end, pick(s, DURATIONS)); + } + } + 4 => { + // An attribute written twice. + let at: Vec = t.match_indices(" id=\"").map(|(i, _)| i).collect(); + if let Some(&i) = at.get((next(s) % at.len().max(1) as u64) as usize) { + let end = i + 5 + t[i + 5..].find('"').map_or(0, |e| e + 1); + let a = t[i..end].to_string(); + t.insert_str(end, &a); + } + } + _ => { + // Markup before a tag. + let at: Vec = t.match_indices('<').map(|(i, _)| i).collect(); + if let Some(&i) = at.get((next(s) % at.len().max(1) as u64) as usize) { + t.insert_str(i, pick(s, MARKUP)); + } + } + } + } + let [w, k] = texts; + Ok((w, k)) +} diff --git a/wai/wai-rs/src/http_binding/dash.rs b/wai/wai-rs/src/http_binding/dash.rs index 449d52bfa..7dd63c36d 100644 --- a/wai/wai-rs/src/http_binding/dash.rs +++ b/wai/wai-rs/src/http_binding/dash.rs @@ -3,8 +3,10 @@ //! `EssentialProperty` a client that does not implement the extension ignores the set //! by, and a `SupplementalProperty` carrying the floor. Its timeline is the classical //! one (§11.2: the same `Period@id` and `@start`, and a `SegmentTimeline` with the same -//! timescale, `t` and `d`), given by the caller, or read from the classical HLS media -//! playlist it shares a timeline with ([`DashTimeline::from_hls`]). +//! timescale, `t` and `d`), read from the classical MPD ([`DashTimeline::from_mpd`]), or, +//! beside a presentation with no MPD, from the classical HLS media playlist it shares a +//! timeline with ([`DashTimeline::from_hls`]). [`super::dash_read::check_dash_adjacent`] +//! checks the result against the classical MPD. //! //! [`render_mpd`] writes any static MPD of one period this binding's packagers need — //! the classical one beside it too — and [`wai_mpd`] is `wai.mpd` on a classical @@ -12,6 +14,7 @@ //! receipt (§8.6) signs HLS renderings only. use super::codecs::{self, Element}; +use super::dash_read::{read_mpd, xml_char}; use super::hls::{MediaPlaylist, PlaylistError}; /// The `EssentialProperty` scheme a client that does not implement the extension @@ -21,26 +24,57 @@ pub const SCHEME_DELIVERY: &str = "tag:transaction.science,2026:wai/http-deliver pub const SCHEME_FLOOR: &str = "tag:transaction.science,2026:wai/floor"; /// The classical timeline a WAI `AdaptationSet` is aligned with: one period's `@id` -/// and `@start` (whole seconds), and its segments' `t` and `d` at `timescale`. +/// (none when the classical period has none) and `@start` in milliseconds, and its +/// segments' `t` and `d` at `timescale`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct DashTimeline { - pub period_id: String, - pub period_start_s: u64, + pub period_id: Option, + pub period_start_ms: u64, pub timescale: u64, pub segments: Vec<(u64, u64)>, } +/// Segment times at a timescale: each `(start, duration)` in some unit, `t` counted from +/// `origin`, both multiplied by `per_unit` ticks. Refused (`playlist_malformed`) when a +/// start precedes `origin` or a product passes 2^64 − 1. +pub fn scaled_timeline(spans: &[(u64, u64)], origin: u64, per_unit: u64) -> Result, PlaylistError> { + spans.iter().map(|&(start, d)| { + let t = start.checked_sub(origin).ok_or_else(|| PlaylistError::Malformed("a segment before the timeline's start".into()))?; + let past = || PlaylistError::Malformed("a segment time past 2^64 - 1".into()); + Ok((t.checked_mul(per_unit).ok_or_else(past)?, d.checked_mul(per_unit).ok_or_else(past)?)) + }).collect() +} + impl DashTimeline { - /// The timeline of a classical HLS media playlist at timescale 1000: `t` from 0, - /// each `d` a segment's duration in milliseconds. - pub fn from_hls(p: &MediaPlaylist, period_id: &str, period_start_s: u64) -> DashTimeline { + /// The timeline of a classical HLS media playlist at timescale 1000, for a + /// presentation with no classical MPD: `t` from 0, each `d` a segment's duration in + /// milliseconds. Refused (`playlist_malformed`) when a time passes 2^64 − 1. + pub fn from_hls(p: &MediaPlaylist, period_id: &str, period_start_ms: u64) -> Result { let mut t = 0u64; - let segments = p.segments.iter().map(|s| { - let at = t; - t = t.saturating_add(s.duration_ms); - (at, s.duration_ms) - }).collect(); - DashTimeline { period_id: period_id.to_string(), period_start_s, timescale: 1000, segments } + let mut segments = Vec::with_capacity(p.segments.len()); + for s in &p.segments { + segments.push((t, s.duration_ms)); + t = t.checked_add(s.duration_ms).ok_or_else(|| PlaylistError::Malformed("a segment time past 2^64 - 1".into()))?; + } + Ok(DashTimeline { period_id: Some(period_id.to_string()), period_start_ms, timescale: 1000, segments }) + } + + /// The timeline of a classical MPD (§11.1): the period with `@id` `period_id` (the + /// first by default), its adaptation set with `@id` `set_id` (by default the first + /// that has a timeline), and that set's first timeline (Appendix I). Refused + /// (`playlist_malformed`) when the MPD does not read or holds no such timeline. + pub fn from_mpd(text: &str, period_id: Option<&str>, set_id: Option<&str>) -> Result { + let periods = read_mpd(text)?; + let p = match period_id { + Some(id) => periods.iter().find(|p| p.id.as_deref() == Some(id)), + None => periods.first(), + }.ok_or_else(|| PlaylistError::Malformed("no such Period in the classical MPD".into()))?; + let set = match set_id { + Some(id) => p.sets.iter().find(|s| s.id.as_deref() == Some(id)), + None => p.sets.iter().find(|s| !s.timelines.is_empty()), + }.ok_or_else(|| PlaylistError::Malformed("no such AdaptationSet in the classical MPD".into()))?; + let tl = set.timelines.first().ok_or_else(|| PlaylistError::Malformed("the classical AdaptationSet has no SegmentTimeline".into()))?; + Ok(DashTimeline { period_id: p.id.clone(), period_start_ms: p.start_ms, timescale: tl.timescale, segments: tl.segments.clone() }) } /// The timeline's length in milliseconds, and its longest segment in milliseconds. @@ -51,12 +85,13 @@ impl DashTimeline { } /// One `Representation` of the WAI `AdaptationSet`: its `@id`, its `@bandwidth` (the -/// peak of `hls::bandwidths`) and each segment's object URI with its fragment. +/// peak of `hls::bandwidths`) and, per segment of the timeline, its object's URI with its +/// fragment, or `None` for a gap, which the representation leaves out (§11.1). #[derive(Clone, Debug, PartialEq, Eq)] pub struct WaiRepresentation { pub id: String, pub bandwidth: u64, - pub media: Vec, + pub media: Vec>, } /// A descriptor (`EssentialProperty`, `SupplementalProperty`): its scheme and value. @@ -102,15 +137,38 @@ pub struct Mpd { pub duration_ms: u64, /// `minBufferTime`, in whole seconds. pub min_buffer_s: u64, - pub period_id: String, - /// `Period@start`, in whole seconds. - pub period_start_s: u64, + /// `Period@id`, none when the classical period has none. + pub period_id: Option, + /// `Period@start`, in milliseconds. + pub period_start_ms: u64, pub adaptation_sets: Vec, } -/// An XML attribute value, escaped. -fn esc(s: &str) -> String { - s.replace('&', "&").replace('<', "<").replace('>', ">").replace('"', """) +/// An XML attribute value, escaped: `&`, `<`, `>` and `"` as entities, and tab, LF and +/// CR as character references, which XML 1.0 §3.3.3 does not normalise to a space. +/// Refused (`playlist_malformed`) for a character that is not an XML `Char`. +fn esc(s: &str) -> Result { + let mut out = String::with_capacity(s.len()); + for c in s.chars() { + match c { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '"' => out.push_str("""), + '\t' => out.push_str(" "), + '\n' => out.push_str(" "), + '\r' => out.push_str(" "), + c if xml_char(c) => out.push(c), + c => return Err(PlaylistError::Malformed(format!("U+{:04X} is not an XML character", c as u32))), + } + } + Ok(out) +} + +/// A duration of whole milliseconds as `xs:duration`: whole seconds without a fraction, +/// otherwise with three decimals. +fn xs_seconds(ms: u64) -> String { + if ms % 1000 == 0 { format!("PT{}S", ms / 1000) } else { format!("PT{}S", crate::hls_render::seconds(ms)) } } fn id_ok(s: &str) -> bool { @@ -119,7 +177,8 @@ fn id_ok(s: &str) -> bool { /// An MPD's text: ``, `` (the DASH namespace, the full profile, `static`, /// `mediaPresentationDuration` in seconds with three decimals, `minBufferTime` in whole -/// seconds), one `` (`@id`, `@start` in whole seconds), each `AdaptationSet` +/// seconds), one `` (`@id` when it has one, `@start` in seconds, with three +/// decimals when not whole), each `AdaptationSet` /// (`@id`, `@contentType`, `@mimeType`, `@codecs`, `@width`, `@height`, /// `@segmentAlignment`, `@startWithSAP`, those given, in that order) with its essential /// then supplemental descriptors and its representations (`@id`, `@dependencyId`, @@ -127,25 +186,29 @@ fn id_ok(s: &str) -> bool { /// `SegmentTimeline` of one `S` per segment with its `t` and `d`, and one `SegmentURL` /// per segment. Attribute values are escaped, elements are indented by two spaces a /// level, and lines end in LF. Refused (`playlist_malformed`) for no adaptation set or -/// representation, an `@id` that is empty or holds white space, a timescale of 0, or a -/// representation whose timeline and segment URLs differ in number. +/// representation, an `@id` that is empty or holds white space, a timescale of 0, a +/// representation with no segment or whose timeline and segment URLs differ in number, +/// or a value holding a character that is not an XML `Char`. pub fn render_mpd(m: &Mpd) -> Result { let bad = |s: &str| PlaylistError::Malformed(s.to_string()); - if m.adaptation_sets.is_empty() || m.adaptation_sets.iter().any(|a| a.representations.is_empty()) || !id_ok(&m.period_id) { - return Err(bad("an MPD needs a period id, an adaptation set and a representation")); + if m.adaptation_sets.is_empty() || m.adaptation_sets.iter().any(|a| a.representations.is_empty()) || m.period_id.as_deref().is_some_and(|i| !id_ok(i)) { + return Err(bad("an MPD needs an adaptation set and a representation, and a period id without white space")); } let mut out = String::from("\n"); out.push_str(&format!( "\n", crate::hls_render::seconds(m.duration_ms), m.min_buffer_s )); - out.push_str(&format!(" \n", esc(&m.period_id), m.period_start_s)); + match &m.period_id { + Some(id) => out.push_str(&format!(" \n", esc(id)?, xs_seconds(m.period_start_ms))), + None => out.push_str(&format!(" \n", xs_seconds(m.period_start_ms))), + } for a in &m.adaptation_sets { out.push_str(&format!(" Result { out.push_str(">\n"); for (tag, ds) in [("EssentialProperty", &a.essential), ("SupplementalProperty", &a.supplemental)] { for d in ds { - out.push_str(&format!(" <{tag} schemeIdUri=\"{}\"", esc(&d.scheme))); + out.push_str(&format!(" <{tag} schemeIdUri=\"{}\"", esc(&d.scheme)?)); if let Some(v) = &d.value { - out.push_str(&format!(" value=\"{}\"", esc(v))); + out.push_str(&format!(" value=\"{}\"", esc(v)?)); } out.push_str("/>\n"); } } for r in &a.representations { - if !id_ok(&r.id) || r.dependency_id.as_deref().is_some_and(|d| !id_ok(d)) || r.timescale == 0 || r.timeline.len() != r.media.len() { + if !id_ok(&r.id) || r.dependency_id.as_deref().is_some_and(|d| !id_ok(d)) || r.timescale == 0 || r.timeline.is_empty() || r.timeline.len() != r.media.len() { return Err(bad("a representation's id, dependency id, timescale or segment count")); } - out.push_str(&format!(" \n \n", r.bandwidth, r.timescale)); if let Some(i) = &r.initialization { - out.push_str(&format!(" \n", esc(i))); + out.push_str(&format!(" \n", esc(i)?)); } out.push_str(" \n"); for &(t, d) in &r.timeline { @@ -183,7 +246,7 @@ pub fn render_mpd(m: &Mpd) -> Result { } out.push_str(" \n"); for u in &r.media { - out.push_str(&format!(" \n", esc(u))); + out.push_str(&format!(" \n", esc(u)?)); } out.push_str(" \n \n"); } @@ -197,9 +260,10 @@ pub fn render_mpd(m: &Mpd) -> Result { /// length the timeline's and `minBufferTime` its longest segment rounded up to whole /// seconds, and one WAI `AdaptationSet` (`@id` `set_id`, `application/wai`, the union as /// `@codecs`, segment-aligned, the delivery `EssentialProperty` and the floor -/// `SupplementalProperty`), each representation listing every segment of the timeline. -/// Refused (`playlist_malformed`) as [`render_mpd`] refuses, for no segment, or a floor -/// whose container is not the union's. +/// `SupplementalProperty`), each representation listing the segments of the timeline it +/// offers and leaving out its gaps. Refused (`playlist_malformed`) as [`render_mpd`] +/// refuses, for no segment, a representation whose segments are not the timeline's in +/// number, or a floor whose container is not the union's. pub fn wai_mpd(t: &DashTimeline, set_id: u32, union: &Element, floor: &Element, reps: &[WaiRepresentation]) -> Result { if t.segments.is_empty() || t.timescale == 0 { return Err(PlaylistError::Malformed("an MPD needs a segment and a timescale".into())); @@ -212,7 +276,7 @@ pub fn wai_mpd(t: &DashTimeline, set_id: u32, union: &Element, floor: &Element, duration_ms: total, min_buffer_s: longest.div_ceil(1000), period_id: t.period_id.clone(), - period_start_s: t.period_start_s, + period_start_ms: t.period_start_ms, adaptation_sets: vec![AdaptationSet { id: set_id, content_type: None, @@ -223,10 +287,16 @@ pub fn wai_mpd(t: &DashTimeline, set_id: u32, union: &Element, floor: &Element, start_with_sap: None, essential: vec![Descriptor { scheme: SCHEME_DELIVERY.into(), value: None }], supplemental: vec![Descriptor { scheme: SCHEME_FLOOR.into(), value: Some(codecs::render(floor)) }], - representations: reps.iter().map(|r| Representation { - id: r.id.clone(), dependency_id: None, bandwidth: r.bandwidth, timescale: t.timescale, initialization: None, - timeline: t.segments.clone(), media: r.media.clone(), - }).collect(), + representations: reps.iter().map(|r| { + if r.media.len() != t.segments.len() { + return Err(PlaylistError::Malformed(format!("representation {}: {} segments on a timeline of {}", r.id, r.media.len(), t.segments.len()))); + } + let offered: Vec<(&(u64, u64), &String)> = t.segments.iter().zip(&r.media).filter_map(|(s, m)| m.as_ref().map(|m| (s, m))).collect(); + Ok(Representation { + id: r.id.clone(), dependency_id: None, bandwidth: r.bandwidth, timescale: t.timescale, initialization: None, + timeline: offered.iter().map(|(s, _)| **s).collect(), media: offered.iter().map(|(_, m)| (*m).clone()).collect(), + }) + }).collect::>()?, }], }) } diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs new file mode 100644 index 000000000..fee197156 --- /dev/null +++ b/wai/wai-rs/src/http_binding/dash_read.rs @@ -0,0 +1,471 @@ +//! Reading an MPD (http-delivery §11.2, Appendix I): the XML subset this binding reads, +//! an MPD's periods and the segment timelines of their adaptation sets, and the DASH +//! adjacency check — a `wai.mpd` beside its classical MPD has the same `Period@id`s and +//! `@start`s, and each of its timelines has a classical timeline's timescale and lists +//! only that timeline's segments, each with its `t` and `d`, in order. +//! +//! The XML read is a subset of XML 1.0, stated in Appendix I, so that every conforming +//! reader refuses the same documents: no document type declaration, CDATA section or +//! processing instruction past the declaration; ASCII names; the five predefined +//! entities and character references to XML `Char`s; no attribute twice. + +use super::hls::{decimal_ms, PlaylistError}; + +/// The most segments one timeline expands to (Appendix I); a timeline past it is +/// refused. +pub const MAX_TIMELINE_SEGMENTS: usize = 1 << 20; + +/// The deepest element nesting read (Appendix I). +pub const MAX_DEPTH: usize = 64; + +fn malformed(s: impl Into) -> PlaylistError { + PlaylistError::Malformed(s.into()) +} + +fn misaligned(s: impl Into) -> PlaylistError { + PlaylistError::Misaligned(s.into()) +} + +/// An element read: its name, its attributes in document order, and its child elements +/// (text is not kept). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Element { + pub name: String, + pub attrs: Vec<(String, String)>, + pub children: Vec, +} + +impl Element { + /// The name after its last `:`. + pub fn local(&self) -> &str { + self.name.rsplit(':').next().unwrap_or(&self.name) + } + + pub fn attr(&self, name: &str) -> Option<&str> { + self.attrs.iter().find(|(k, _)| k == name).map(|(_, v)| v.as_str()) + } + + fn children_named<'a>(&'a self, local: &'a str) -> impl Iterator + 'a { + self.children.iter().filter(move |c| c.local() == local) + } +} + +/// Whether `c` is an XML `Char`. +pub fn xml_char(c: char) -> bool { + matches!(c, '\t' | '\n' | '\r' | '\u{20}'..='\u{D7FF}' | '\u{E000}'..='\u{FFFD}' | '\u{10000}'..='\u{10FFFF}') +} + +fn name_start(b: u8) -> bool { + b.is_ascii_alphabetic() || b == b'_' || b == b':' +} + +fn name_char(b: u8) -> bool { + name_start(b) || b.is_ascii_digit() || b == b'-' || b == b'.' +} + +struct Reader<'a> { + s: &'a str, + at: usize, +} + +impl<'a> Reader<'a> { + fn rest(&self) -> &'a str { + &self.s[self.at..] + } + + fn eat(&mut self, lit: &str) -> bool { + if self.rest().starts_with(lit) { + self.at += lit.len(); + true + } else { + false + } + } + + fn space(&mut self) -> bool { + let n = self.rest().bytes().take_while(|b| matches!(b, b' ' | b'\t' | b'\n')).count(); + self.at += n; + n > 0 + } + + fn name(&mut self) -> Result { + let r = self.rest().as_bytes(); + if r.first().is_none_or(|&b| !name_start(b)) { + return Err(malformed("a name")); + } + let n = r.iter().take_while(|&&b| name_char(b)).count(); + let out = self.rest()[..n].to_string(); + self.at += n; + Ok(out) + } + + /// A reference after its `&`, decoded. + fn reference(&mut self) -> Result { + let r = self.rest(); + let end = r.find(';').ok_or_else(|| malformed("an unterminated reference"))?; + let body = &r[..end]; + let c = match body { + "amp" => '&', + "lt" => '<', + "gt" => '>', + "quot" => '"', + "apos" => '\'', + _ => { + let n = if let Some(h) = body.strip_prefix("#x") { + (!h.is_empty() && h.len() <= 8 && h.bytes().all(|b| b.is_ascii_hexdigit())).then(|| u32::from_str_radix(h, 16).ok()).flatten() + } else if let Some(d) = body.strip_prefix('#') { + (!d.is_empty() && d.len() <= 10 && d.bytes().all(|b| b.is_ascii_digit())).then(|| d.parse::().ok()).flatten() + } else { + None + }; + n.and_then(char::from_u32).filter(|&c| xml_char(c)).ok_or_else(|| malformed("a reference to an entity or character outside the subset"))? + } + }; + self.at += end + 1; + Ok(c) + } + + fn attr_value(&mut self) -> Result { + let q = match self.rest().as_bytes().first() { + Some(b'"') => '"', + Some(b'\'') => '\'', + _ => return Err(malformed("an attribute value is quoted")), + }; + self.at += 1; + let mut out = String::new(); + loop { + let c = self.rest().chars().next().ok_or_else(|| malformed("an unterminated attribute value"))?; + self.at += c.len_utf8(); + match c { + _ if c == q => return Ok(out), + '<' => return Err(malformed("< in an attribute value")), + '&' => out.push(self.reference()?), + // XML 1.0 §3.3.3: white space in a value is normalised to a space. + '\t' | '\n' => out.push(' '), + c => out.push(c), + } + } + } + + /// Comments and white space; `false` at anything else. + fn misc(&mut self) -> Result<(), PlaylistError> { + loop { + if self.space() { + continue; + } + if self.rest().starts_with("") { + return Err(malformed("-- inside a comment")); + } + self.at += end + 3; + Ok(()) + } + + /// A start tag after its `<`: the element (children empty) and whether it closed + /// itself. + fn start_tag(&mut self) -> Result<(Element, bool), PlaylistError> { + let name = self.name()?; + let mut attrs: Vec<(String, String)> = Vec::new(); + loop { + let spaced = self.space(); + if self.eat("/>") { + return Ok((Element { name, attrs, children: Vec::new() }, true)); + } + if self.eat(">") { + return Ok((Element { name, attrs, children: Vec::new() }, false)); + } + if !spaced { + return Err(malformed("an attribute is preceded by white space")); + } + let k = self.name()?; + self.space(); + if !self.eat("=") { + return Err(malformed("an attribute has a value")); + } + self.space(); + let v = self.attr_value()?; + if attrs.iter().any(|(a, _)| *a == k) { + return Err(malformed(format!("the attribute {k} twice"))); + } + attrs.push((k, v)); + } + } +} + +/// Read an XML document in the subset of Appendix I: its root element. +pub fn read_xml(text: &str) -> Result { + // XML 1.0 §2.11: CR LF and a lone CR are read as LF. + let normalised = text.strip_prefix('\u{FEFF}').unwrap_or(text).replace("\r\n", "\n").replace('\r', "\n"); + if let Some(c) = normalised.chars().find(|&c| !xml_char(c)) { + return Err(malformed(format!("U+{:04X} is not an XML character", c as u32))); + } + let mut r = Reader { s: &normalised, at: 0 }; + if r.rest().starts_with("").ok_or_else(|| malformed("an unterminated declaration"))?; + r.at += end + 2; + } + r.misc()?; + if !r.eat("<") { + return Err(malformed("no root element")); + } + let mut stack: Vec = Vec::new(); + let (root, closed) = r.start_tag()?; + let mut done: Option = None; + if closed { + done = Some(root); + } else { + stack.push(root); + } + while done.is_none() { + let c = r.rest().chars().next().ok_or_else(|| malformed("an element is not closed"))?; + match c { + '<' => { + if r.rest().starts_with("text < \n\n"); + assert_eq!(e.attr("x"), Some("1&AB")); + assert_eq!(e.attr("y"), Some("t u v")); + assert_eq!(e.children.iter().map(|c| c.name.as_str()).collect::>(), ["b", "c"]); + for bad in ["", "", "", "", "", "", "", "", "", + "", "", "", "", "", "￾", "]]>", + "", "<é/>", "", "", "junk", "".repeat(MAX_DEPTH + 1), "".repeat(MAX_DEPTH + 1)); + assert!(read_xml(&deep).is_err()); + let ok_deep = format!("{}{}", "".repeat(MAX_DEPTH), "".repeat(MAX_DEPTH)); + assert!(read_xml(&ok_deep).is_ok()); + for (d, ms) in [("PT0S", Some(0)), ("PT1.5S", Some(1500)), ("PT0.0005S", Some(1)), ("PT0.0004S", Some(0)), ("P1D", Some(86_400_000)), ("PT1H1M", Some(3_660_000)), + ("P1DT1S", Some(86_401_000)), ("P", None), ("PT", None), ("P1DT", None), ("P1Y", None), ("P1M", None), ("PT1.5M", None), ("PT-1S", None), + ("PT1M1H", None), ("1S", None), ("PT1S ", None), ("PT+1S", None)] { + assert_eq!(duration_ms(d), ms, "{d}"); + } +} + +/// §11.2 in DASH: a WAI MPD beside its classical MPD, and each way it is not adjacent. +#[test] +fn dash_adjacency() { + use super::dash_read::check_dash_adjacent; + let classical = ""; + let wai = |period: &str, ts: u64, ss: &str| format!("{ss}"); + let code = |w: &str| check_dash_adjacent(w, classical).err().map(|e| e.code()); + assert_eq!(code(&wai("id=\"p\" start=\"PT2S\"", 25, "")), None); + assert_eq!(code(&wai("id=\"p\" start=\"PT2.000S\"", 25, "")), None); + assert_eq!(code(&wai("id=\"q\" start=\"PT2S\"", 25, "")), Some("adjacency_misaligned")); + assert_eq!(code(&wai("id=\"p\"", 25, "")), Some("adjacency_misaligned")); + assert_eq!(code(&wai("id=\"p\" start=\"PT2S\"", 50, "")), Some("adjacency_misaligned")); + assert_eq!(code(&wai("id=\"p\" start=\"PT2S\"", 25, "")), Some("adjacency_misaligned")); + assert_eq!(code(&wai("id=\"p\" start=\"PT2S\"", 25, "")), Some("adjacency_misaligned")); + assert_eq!(code(&wai("id=\"p\" start=\"PT2S\"", 25, "")), Some("adjacency_misaligned")); + assert_eq!(code(&wai("id=\"p\" start=\"PT2S\"", 25, "")), Some("adjacency_misaligned")); + assert_eq!(code(&wai("id=\"p\" start=\"PT2S\"", 25, "")), Some("playlist_malformed")); + assert_eq!(check_dash_adjacent(&wai("id=\"p\" start=\"PT2S\"", 25, ""), "").unwrap_err().code(), "playlist_malformed"); } /// §5.2, §11.4: the cid a playlist lists for a part's alias (its `cid` fragment) and @@ -527,7 +600,7 @@ fn render_mpd_of_either_kind() { id: 1, content_type: Some("video".into()), mime_type: mime.into(), codecs: "c".into(), size: Some((64, 36)), segment_alignment: align, start_with_sap: sap, essential: vec![], supplemental: vec![Descriptor { scheme: SCHEME_FLOOR.into(), value: Some("f&g".into()) }], representations: reps, }; - let m = Mpd { duration_ms: 8000, min_buffer_s: 3, period_id: "p0".into(), period_start_s: 0, adaptation_sets: vec![ + let m = Mpd { duration_ms: 8000, min_buffer_s: 3, period_id: Some("p0".into()), period_start_ms: 0, adaptation_sets: vec![ set("video/mp4", true, Some(1), vec![rep("v-floor", None, Some("../floor/v/init.mp4"), vec![(0, 60), (60, 25)])])] }; let t = render_mpd(&m).unwrap(); assert!(t.contains("mediaPresentationDuration=\"PT8.000S\" minBufferTime=\"PT3S\">\n \n")); @@ -540,9 +613,25 @@ fn render_mpd_of_either_kind() { let mut bad = w.clone(); bad.adaptation_sets[0].representations[1].media.pop(); assert_eq!(render_mpd(&bad).unwrap_err().code(), "playlist_malformed"); - let mut bad = w; + let mut bad = w.clone(); bad.adaptation_sets[0].representations[0].dependency_id = Some("a b".into()); assert!(render_mpd(&bad).is_err()); + // A start with a fraction, a period with no id, and values escaped as XML reads them + // back: tab, LF and CR as references, a character outside XML refused. + let mut odd = w.clone(); + odd.period_start_ms = 2500; + odd.period_id = None; + odd.adaptation_sets[0].supplemental[0].value = Some("a\tb\nc\rd".into()); + let t = render_mpd(&odd).unwrap(); + assert!(t.contains(" \n") && t.contains("value=\"a b c d\""), "{t}"); + let back = super::dash_read::read_xml(&t).unwrap(); + let sup = &back.children[0].children[0].children[0]; + assert_eq!(sup.attr("value"), Some("a\tb\nc\rd")); + for c in ['\u{0}', '\u{1}', '\u{b}', '\u{1f}', '\u{fffe}', '\u{ffff}'] { + let mut bad = w.clone(); + bad.adaptation_sets[0].codecs = format!("a{c}b"); + assert_eq!(render_mpd(&bad).unwrap_err().code(), "playlist_malformed", "{c:?}"); + } } /// §5.4: a segment's object is listed with its `n`, and with its `h` only for a `WAI2`. diff --git a/wai/wai-rs/tests/cli_http.rs b/wai/wai-rs/tests/cli_http.rs index b02667f30..1b704a631 100644 --- a/wai/wai-rs/tests/cli_http.rs +++ b/wai/wai-rs/tests/cli_http.rs @@ -462,6 +462,32 @@ fn hls_and_mpd_write_aligned_playlists() { assert!(!run.status.success()); } +/// `wai http mpd --classical-mpd` takes the classical MPD's period, timescale, `t` and +/// `d`, leaves a gap's `S` and `SegmentURL` out, and checks the result adjacent to the +/// classical MPD; a list that does not cover the classical timeline is refused. +#[test] +fn mpd_takes_the_classical_mpd_timeline_and_leaves_gaps_out() { + let d = o9_presentation("mpd-classical"); + let classical = "\n\n \n \n \n \n \n \n \n \n\n"; + std::fs::write(d.join("classical.mpd"), classical).unwrap(); + let mut list: serde_json::Value = serde_json::from_slice(&std::fs::read(d.join("list.json")).unwrap()).unwrap(); + list["variants"][0]["segments"][1]["gap"] = serde_json::json!(true); + std::fs::write(d.join("list.json"), serde_json::to_vec(&list).unwrap()).unwrap(); + let args = |out: &str| vec!["http".to_string(), "mpd".into(), "--objects".into(), d.join("list.json").to_str().unwrap().into(), "--out".into(), + d.join(out).to_str().unwrap().into(), "--classical-mpd".into(), d.join("classical.mpd").to_str().unwrap().into()]; + let run = wai(&args("wai.mpd").iter().map(String::as_str).collect::>()); + assert!(run.status.success(), "{}", String::from_utf8_lossy(&run.stderr)); + let mpd = std::fs::read_to_string(d.join("wai.mpd")).unwrap(); + assert!(mpd.contains("") && mpd.contains(""), "{mpd}"); + assert!(mpd.contains("") && mpd.contains("") && !mpd.contains("t=\"57600\""), "{mpd}"); + assert_eq!(mpd.matches(">()); + assert!(!run.status.success()); + assert!(String::from_utf8_lossy(&run.stderr).contains("3 segments, and the classical timeline has 2"), "{}", String::from_utf8_lossy(&run.stderr)); +} + /// `wai http seal-playlist` writes `mr/.json` and /// `mr/.json`; `check-playlist` reports it signed, refuses a changed /// playlist, and holds a revision to the head it chains to, walking the revisions -- 2.54.0 (Apple Git-157) From 834de6b278fdeb4eb69c2704b17d84caa8997906 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 10:01:31 -0400 Subject: [PATCH 24/55] wai: the walk bound and the MPD reader's limits stay out of the C header Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/http_binding/dash_read.rs | 2 ++ wai/wai-rs/src/http_binding/playlist_receipt.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs index fee197156..79d55df6f 100644 --- a/wai/wai-rs/src/http_binding/dash_read.rs +++ b/wai/wai-rs/src/http_binding/dash_read.rs @@ -13,9 +13,11 @@ use super::hls::{decimal_ms, PlaylistError}; /// The most segments one timeline expands to (Appendix I); a timeline past it is /// refused. +/// cbindgen:ignore pub const MAX_TIMELINE_SEGMENTS: usize = 1 << 20; /// The deepest element nesting read (Appendix I). +/// cbindgen:ignore pub const MAX_DEPTH: usize = 64; fn malformed(s: impl Into) -> PlaylistError { diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs index 6666c22af..d5014d158 100644 --- a/wai/wai-rs/src/http_binding/playlist_receipt.rs +++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs @@ -129,6 +129,7 @@ pub fn check_playlist(playlist: &[u8], receipt: &[u8], name: Option<&str>) -> Re /// How many parent receipts a client fetches for one revision, at most (§8.6): a /// revision further from the held head than this is refused /// (`playlist_receipt_walk_exceeded`). A client MAY hold a lower bound. +/// cbindgen:ignore pub const WALK_BOUND: usize = 64; /// What a revision's receipt did to the chain a client holds (§8.6). -- 2.54.0 (Apple Git-157) From aab8a88ba97b5b25cd4520c1974b8b25c5cff24b Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 10:03:45 -0400 Subject: [PATCH 25/55] dash_read: the timeline test holds an S of no duration, the segment bound, a time past 2^64 - 1 and a later Period without @start Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/http_binding/tests_playlists.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index 7f09195aa..fca0fdee6 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -298,6 +298,15 @@ fn dash_timeline_from_the_classical_mpd() { assert_eq!(t, DashTimeline { period_id: Some("a".into()), period_start_ms: 62_500, timescale: 90_000, segments: vec![(10, 5), (15, 5), (20, 7)] }); assert_eq!(DashTimeline::from_mpd(mpd, None, Some("9")).unwrap_err().code(), "playlist_malformed"); assert_eq!(DashTimeline::from_mpd(mpd, Some("b"), None).unwrap_err().code(), "playlist_malformed"); + // An S of no duration, a timeline past 2^20 segments, a time past 2^64 − 1, and a + // later Period without @start are refused; the first Period's start defaults to 0. + let one = |period: &str, ss: &str| format!("{period}{ss}"); + assert_eq!(DashTimeline::from_mpd(&one("", ""), None, None).unwrap().period_start_ms, 0); + for bad in [one("", ""), one("", ""), one("", ""), + one("", ""), one("", "")] { + assert_eq!(super::dash_read::read_mpd(&bad).unwrap_err().code(), "playlist_malformed", "{}", &bad[..bad.len().min(120)]); + } + assert_eq!(super::dash_read::read_mpd(&one("", "")).unwrap()[0].sets[0].timelines[0].segments.len(), 1 << 20); assert_eq!(scaled_timeline(&[(10, 2), (12, 3)], 10, 1000).unwrap(), vec![(0, 2000), (2000, 3000)]); assert_eq!(scaled_timeline(&[(u64::MAX, 1)], 0, 2).unwrap_err().code(), "playlist_malformed"); assert_eq!(scaled_timeline(&[(1, u64::MAX)], 0, 2).unwrap_err().code(), "playlist_malformed"); -- 2.54.0 (Apple Git-157) From 507aa2ef9dca4640194e49f6cd7d054da9ebf546 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 10:10:01 -0400 Subject: [PATCH 26/55] http-conformance: falsify DASH adjacency, the MPD reader's subset and bounds, MPD escaping and the gap left out Co-Authored-By: Claude Opus 5.5 --- .../falsify/dash-order-unchecked.patch | 14 ++++++++++++++ .../falsify/dash-r-not-expanded.patch | 15 +++++++++++++++ .../falsify/dash-start-unchecked.patch | 15 +++++++++++++++ .../falsify/dash-timeline-unbounded.patch | 15 +++++++++++++++ .../falsify/dash-timescale-unchecked.patch | 15 +++++++++++++++ wai/http-conformance/falsify/dash-zero-d.patch | 15 +++++++++++++++ .../falsify/mpd-char-unchecked.patch | 15 +++++++++++++++ .../falsify/mpd-gap-listed.patch | 15 +++++++++++++++ .../falsify/xml-attribute-twice.patch | 15 +++++++++++++++ .../falsify/xml-cdata-read.patch | 16 ++++++++++++++++ .../falsify/xml-char-ref-unchecked.patch | 15 +++++++++++++++ 11 files changed, 165 insertions(+) create mode 100644 wai/http-conformance/falsify/dash-order-unchecked.patch create mode 100644 wai/http-conformance/falsify/dash-r-not-expanded.patch create mode 100644 wai/http-conformance/falsify/dash-start-unchecked.patch create mode 100644 wai/http-conformance/falsify/dash-timeline-unbounded.patch create mode 100644 wai/http-conformance/falsify/dash-timescale-unchecked.patch create mode 100644 wai/http-conformance/falsify/dash-zero-d.patch create mode 100644 wai/http-conformance/falsify/mpd-char-unchecked.patch create mode 100644 wai/http-conformance/falsify/mpd-gap-listed.patch create mode 100644 wai/http-conformance/falsify/xml-attribute-twice.patch create mode 100644 wai/http-conformance/falsify/xml-cdata-read.patch create mode 100644 wai/http-conformance/falsify/xml-char-ref-unchecked.patch diff --git a/wai/http-conformance/falsify/dash-order-unchecked.patch b/wai/http-conformance/falsify/dash-order-unchecked.patch new file mode 100644 index 000000000..c41d2974d --- /dev/null +++ b/wai/http-conformance/falsify/dash-order-unchecked.patch @@ -0,0 +1,14 @@ +# must fail: dash_adjacency_out_of_order +# segments out of order accepted (dash_read::within) +diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs +index 79d55df6..4ed32a04 100644 +--- a/wai/wai-rs/src/http_binding/dash_read.rs ++++ b/wai/wai-rs/src/http_binding/dash_read.rs +@@ -428,6 +428,7 @@ pub fn read_mpd(text: &str) -> Result, PlaylistError> { + fn within(wai: &[(u64, u64)], classical: &[(u64, u64)]) -> bool { + let mut j = 0; + for &(t, d) in wai { ++ j = 0; + while j < classical.len() && classical[j].0 != t { + j += 1; + } diff --git a/wai/http-conformance/falsify/dash-r-not-expanded.patch b/wai/http-conformance/falsify/dash-r-not-expanded.patch new file mode 100644 index 000000000..971211b0c --- /dev/null +++ b/wai/http-conformance/falsify/dash-r-not-expanded.patch @@ -0,0 +1,15 @@ +# must fail: dash_adjacency_o9 mpd_playlists_o9_wai_mpd +# @r not expanded (dash_read::timeline_of) +diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs +index 79d55df6..f725df12 100644 +--- a/wai/wai-rs/src/http_binding/dash_read.rs ++++ b/wai/wai-rs/src/http_binding/dash_read.rs +@@ -370,7 +370,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result, + if r >= MAX_TIMELINE_SEGMENTS as u64 || segments.len() as u64 + r + 1 > MAX_TIMELINE_SEGMENTS as u64 { + return Err(malformed("a timeline past the subset's segments")); + } +- for _ in 0..=r { ++ for _ in 0..=0 { + segments.push((t, d)); + t = t.checked_add(d).ok_or_else(|| malformed("a segment time past 2^64 - 1"))?; + } diff --git a/wai/http-conformance/falsify/dash-start-unchecked.patch b/wai/http-conformance/falsify/dash-start-unchecked.patch new file mode 100644 index 000000000..1c0f951a0 --- /dev/null +++ b/wai/http-conformance/falsify/dash-start-unchecked.patch @@ -0,0 +1,15 @@ +# must fail: dash_adjacency_period_start +# a Period@start not compared (dash_read::check_dash_adjacent) +diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs +index 79d55df6..dd967c6c 100644 +--- a/wai/wai-rs/src/http_binding/dash_read.rs ++++ b/wai/wai-rs/src/http_binding/dash_read.rs +@@ -450,7 +450,7 @@ pub fn check_dash_adjacent(wai: &str, classical: &str) -> Result<(), PlaylistErr + let k = read_mpd(classical)?; + for wp in &w { + let kp = k.iter().find(|p| p.id == wp.id).ok_or_else(|| misaligned(format!("no classical Period with the @id {:?}", wp.id)))?; +- if kp.start_ms != wp.start_ms { ++ if false { + return Err(misaligned(format!("Period {:?} starts at {} ms, the classical one at {} ms", wp.id, wp.start_ms, kp.start_ms))); + } + if wp.sets.is_empty() { diff --git a/wai/http-conformance/falsify/dash-timeline-unbounded.patch b/wai/http-conformance/falsify/dash-timeline-unbounded.patch new file mode 100644 index 000000000..804e1f56f --- /dev/null +++ b/wai/http-conformance/falsify/dash-timeline-unbounded.patch @@ -0,0 +1,15 @@ +# must fail: dash_adjacency_r_past_the_subset +# a timeline past the subset's segments read (dash_read::timeline_of) +diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs +index 79d55df6..c80ac01c 100644 +--- a/wai/wai-rs/src/http_binding/dash_read.rs ++++ b/wai/wai-rs/src/http_binding/dash_read.rs +@@ -367,7 +367,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result, + } + let mut t = s.attr("t").map(|v| unsigned(v, "@t")).transpose()?.unwrap_or(next); + let r = s.attr("r").map(|v| unsigned(v, "@r")).transpose()?.unwrap_or(0); +- if r >= MAX_TIMELINE_SEGMENTS as u64 || segments.len() as u64 + r + 1 > MAX_TIMELINE_SEGMENTS as u64 { ++ if r >= u64::MAX / 2 { + return Err(malformed("a timeline past the subset's segments")); + } + for _ in 0..=r { diff --git a/wai/http-conformance/falsify/dash-timescale-unchecked.patch b/wai/http-conformance/falsify/dash-timescale-unchecked.patch new file mode 100644 index 000000000..bd25c7ee7 --- /dev/null +++ b/wai/http-conformance/falsify/dash-timescale-unchecked.patch @@ -0,0 +1,15 @@ +# must fail: dash_adjacency_timescale dash_adjacency_the_audio_timescale +# a timescale not compared (dash_read::check_dash_adjacent) +diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs +index 79d55df6..155e1541 100644 +--- a/wai/wai-rs/src/http_binding/dash_read.rs ++++ b/wai/wai-rs/src/http_binding/dash_read.rs +@@ -462,7 +462,7 @@ pub fn check_dash_adjacent(wai: &str, classical: &str) -> Result<(), PlaylistErr + } + for tl in &s.timelines { + let aligned = !tl.segments.is_empty() +- && kp.sets.iter().flat_map(|c| &c.timelines).any(|c| c.timescale == tl.timescale && within(&tl.segments, &c.segments)); ++ && kp.sets.iter().flat_map(|c| &c.timelines).any(|c| within(&tl.segments, &c.segments)); + if !aligned { + return Err(misaligned(format!("a timeline of AdaptationSet {:?} is not a classical one's", s.id))); + } diff --git a/wai/http-conformance/falsify/dash-zero-d.patch b/wai/http-conformance/falsify/dash-zero-d.patch new file mode 100644 index 000000000..caea84fb8 --- /dev/null +++ b/wai/http-conformance/falsify/dash-zero-d.patch @@ -0,0 +1,15 @@ +# must fail: dash_adjacency_zero_d +# an S of no duration read (dash_read::timeline_of) +diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs +index 79d55df6..25538221 100644 +--- a/wai/wai-rs/src/http_binding/dash_read.rs ++++ b/wai/wai-rs/src/http_binding/dash_read.rs +@@ -362,7 +362,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result, + let mut next = 0u64; + for s in st.children_named("S") { + let d = unsigned(s.attr("d").ok_or_else(|| malformed("an S has no @d"))?, "@d")?; +- if d == 0 { ++ if false { + return Err(malformed("an S of no duration")); + } + let mut t = s.attr("t").map(|v| unsigned(v, "@t")).transpose()?.unwrap_or(next); diff --git a/wai/http-conformance/falsify/mpd-char-unchecked.patch b/wai/http-conformance/falsify/mpd-char-unchecked.patch new file mode 100644 index 000000000..e05d9572e --- /dev/null +++ b/wai/http-conformance/falsify/mpd-char-unchecked.patch @@ -0,0 +1,15 @@ +# must fail: mpd_rep_id_not_a_char +# a value outside XML written (dash::esc) +diff --git a/wai/wai-rs/src/http_binding/dash.rs b/wai/wai-rs/src/http_binding/dash.rs +index 7dd63c36..1ee1165b 100644 +--- a/wai/wai-rs/src/http_binding/dash.rs ++++ b/wai/wai-rs/src/http_binding/dash.rs +@@ -158,7 +158,7 @@ fn esc(s: &str) -> Result { + '\t' => out.push_str(" "), + '\n' => out.push_str(" "), + '\r' => out.push_str(" "), +- c if xml_char(c) => out.push(c), ++ c if true || xml_char(c) => out.push(c), + c => return Err(PlaylistError::Malformed(format!("U+{:04X} is not an XML character", c as u32))), + } + } diff --git a/wai/http-conformance/falsify/mpd-gap-listed.patch b/wai/http-conformance/falsify/mpd-gap-listed.patch new file mode 100644 index 000000000..711e6e0e8 --- /dev/null +++ b/wai/http-conformance/falsify/mpd-gap-listed.patch @@ -0,0 +1,15 @@ +# must fail: mpd_playlists_o9_wai_gap_mpd +# a gap segment listed in wai.mpd (the corpus tool's mpd rendering) +diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +index be66d9a2..027a96e5 100644 +--- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs ++++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +@@ -365,7 +365,7 @@ fn render_mpd(c: &Corpus, input: &Value) -> Result { + DashTimeline::from_hls(&classical, input["period_id"].as_str().unwrap_or("p0"), 0).map_err(|e| pl_err(&e))? + } + }; +- let media = p.segments.iter().map(|s| (!s.extra.gap).then(|| s.uri.clone())).collect(); ++ let media = p.segments.iter().map(|s| Some(s.uri.clone())).collect(); + let rep = WaiRepresentation { id: input["rep_id"].as_str().ok_or("rep_id")?.into(), bandwidth: peak, media }; + wai_mpd(&t, input["set_id"].as_u64().unwrap_or(1) as u32, &union, &floor, &[rep]).map_err(|e| pl_err(&e)) + } diff --git a/wai/http-conformance/falsify/xml-attribute-twice.patch b/wai/http-conformance/falsify/xml-attribute-twice.patch new file mode 100644 index 000000000..743c2fe01 --- /dev/null +++ b/wai/http-conformance/falsify/xml-attribute-twice.patch @@ -0,0 +1,15 @@ +# must fail: dash_adjacency_attribute_twice +# an attribute twice read (dash_read::read_xml) +diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs +index 79d55df6..245e70fd 100644 +--- a/wai/wai-rs/src/http_binding/dash_read.rs ++++ b/wai/wai-rs/src/http_binding/dash_read.rs +@@ -196,7 +196,7 @@ impl<'a> Reader<'a> { + } + self.space(); + let v = self.attr_value()?; +- if attrs.iter().any(|(a, _)| *a == k) { ++ if false { + return Err(malformed(format!("the attribute {k} twice"))); + } + attrs.push((k, v)); diff --git a/wai/http-conformance/falsify/xml-cdata-read.patch b/wai/http-conformance/falsify/xml-cdata-read.patch new file mode 100644 index 000000000..3174120ce --- /dev/null +++ b/wai/http-conformance/falsify/xml-cdata-read.patch @@ -0,0 +1,16 @@ +# must fail: dash_adjacency_cdata +# a CDATA section read (dash_read::read_xml) +diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs +index 79d55df6..c268e915 100644 +--- a/wai/wai-rs/src/http_binding/dash_read.rs ++++ b/wai/wai-rs/src/http_binding/dash_read.rs +@@ -248,6 +248,9 @@ pub fn read_xml(text: &str) -> Result { + Some(parent) => parent.children.push(e), + None => done = Some(e), + } ++ } else if r.rest().starts_with("").unwrap_or(0); ++ r.at += end + 3; + } else if r.rest().starts_with(" Reader<'a> { + } else { + None + }; +- n.and_then(char::from_u32).filter(|&c| xml_char(c)).ok_or_else(|| malformed("a reference to an entity or character outside the subset"))? ++ n.and_then(char::from_u32).ok_or_else(|| malformed("a reference to an entity or character outside the subset"))? + } + }; + self.at += end + 1; -- 2.54.0 (Apple Git-157) From 8ee2d4093a527a8465bd540d6097409aa7c8fbbe Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 10:25:09 -0400 Subject: [PATCH 27/55] wai: adaptation within stated bounds, computed exactly; under a budget an unlabelled option never escapes it Appendix F states each input's bounds (its field's type, at most 256 options and layer objects, depth sums below 2^64) and refuses beyond them (abr_state_out_of_bounds). Within them every quantity is exact: throughputs and rates in 128 bits, the look-ahead without saturation, and the utility-over-buffer cross-multiplication widened to 192 bits. Under a budget only an option with a labelled figure within it is chosen; not_applied only when no option has a labelled figure. Vectors for each budget outcome, the extremes of every type and each bound. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/http-delivery.md | 24 +- wai/http-conformance/README.md | 4 +- wai/http-conformance/cases.json | 76 ++++-- wai/http-conformance/falsify/run.sh | 3 +- wai/http-conformance/http_verify.py | 92 +++++-- .../src/bin/wai_cli/http_vectors_playlists.rs | 150 +++++++++--- wai/wai-rs/src/http_binding/abr.rs | 230 +++++++++++++----- wai/wai-rs/src/http_binding/tests_abr.rs | 98 ++++++-- 8 files changed, 513 insertions(+), 164 deletions(-) diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index dcf920ab2..316f62769 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -864,6 +864,7 @@ is a share of sessions, not of devices or people. | `classical_changed` | §11.1 | | `playlist_receipt_malformed`, `playlist_receipt_mismatch`, `playlist_receipt_invalid`, `playlist_receipt_unchained`, `playlist_receipt_parent_missing`, `playlist_receipt_walk_exceeded` | §8.6 | | `playlist_listing_rebound` | §8.6, §11.5 | +| `abr_state_out_of_bounds` | Appendix F | Selection, pin, receipt, key and module reasons are those of [SPEC](../SPEC.md) §3.1 and §7.1 and [jwp-receipts](jwp-receipts.md) §3, §7.7 and §7.9, and the layer reasons @@ -1026,6 +1027,17 @@ labels it with its acquisition class ([energy-measurement](energy-measurement.md none otherwise), a joule budget if held, and the media duration `D` one option covers (ms). Divisions round down unless stated; `⌈x/y⌉` rounds up. +**Bounds.** Each value lies within its field's type: `b`, `D`, and the policies' `R`, +`C`, `v` and `M` below 2^32; `n` and `h` below 2^8; `p` below 2^16; each sample's +`at`, `bytes` and `ms`, the last choice, each `S_k`, each figure and the budget below +2^64. A state holds at most 256 options (and at most 256 figures), and a layer depth is +chosen over at most 256 objects, the bytes and the figure through each depth below 2^64. +A state or a policy beyond these is refused (`abr_state_out_of_bounds`). Within them +every quantity below is computed exactly, never saturated: a rate or a throughput +reaches 2^85, the look-ahead's stall times `max_k u_k + 65536` reaches 2^103, and the +utility-over-buffer cross-multiplication reaches 2^141, so an implementation in fixed +widths compares those products in 192 bits. + - The throughput of the last `n` samples (`n` 0 taken as 1): `⌊8000·Σbytes / Σmax(ms, 1)⌋` b/s, 0 with no sample. - An option's rate: `⌈8000·S_k / max(D, 1)⌉` b/s. "The highest option under `r`" is the @@ -1058,10 +1070,14 @@ none otherwise), a joule budget if held, and the media duration `D` one option c in ms times `(max_k u_k + 65536)`. The first choice of the first best sequence is taken. -**Budget.** Without a budget the choice stands (`not_set`). A choice with no labelled -figure stands, and the budget is not applied (`not_applied`): an unlabelled figure is -never used. A choice within the budget stands (`within`); otherwise the highest option -below it whose labelled figure is within the budget (`lowered`), else option 0 (`floor`). +**Budget.** Without a budget the choice stands (`not_set`). When no option has a +labelled figure, the choice stands and the budget is not applied (`not_applied`): an +unlabelled figure is never used. Otherwise only an option whose labelled figure is +within the budget is chosen, so an unlabelled option never escapes it: the choice when +it is one (`within`); else the highest such option below it (`lowered`); else the lowest +such option above it, or, when no option's figure is within the budget, the option with +the least labelled figure, the lower on a tie (`floor`). A figure past the options names +none. **Entry order.** Within a `WAI2`, the selectable entries whose capability the sink has are grouped by capability in order of first appearance; a group's options are its diff --git a/wai/http-conformance/README.md b/wai/http-conformance/README.md index 12576668e..208397e1d 100644 --- a/wai/http-conformance/README.md +++ b/wai/http-conformance/README.md @@ -108,9 +108,9 @@ outside this repository, and the AV1 streams were checked to decode. | `unchanged` | an `original` and a `published` classical master (files, or `*_text`), `edit_published` | `unchanged`, or `error` | | `playlist_receipt` | a `playlist`, its `receipt` file and the `name` it is fetched as (the file's own by default), each optionally edited | the receipt hash and its parent, or `error` | | `live` | `revisions` in the order a client fetched them (each a WAI playlist, its receipt, its classical playlist, `edit_wai`), `fetches` (an `alias` and the `object` served under it, optionally patched), and where parents are fetched from (`parents`, a directory holding `.json`; the first revision's `mr/` by default) and the walk's bound (`walk_bound`, 64 by default) | per revision its adjacency, its listing against what accepted revisions bound, its receipt, what it did to the chain (`true`, `unchanged`, or the reason) and the parents walked; per fetch the accepted revision that first lists the alias, the outcome and what held (`listed`, or `signed` when a receipt in the chain binds the alias to that cid) | -| `abr` | a `policy` and a `state` (Appendix F) | the `choice` and what the `budget` did | +| `abr` | a `policy` and a `state` (Appendix F) | the `choice` and what the `budget` did, or `error` beyond Appendix F's bounds | | `abr_order` | an object, what the sink `advertises`, a `policy`, a `state` | the entry `order` | -| `depth` | layer `objects` (or `layers_n`), a `policy`, a `state` | the `depth` and what the `budget` did | +| `depth` | layer `objects` (or `layers_n`), a `policy`, a `state` | the `depth` and what the `budget` did, or `error` | | `ilog2` | `a`, `b` | `log2_q16` | | `render` | `segments`, `render` and `extras` written inline | the rendering's BLAKE3 and length, or `error` | diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index c2ebbb58d..f83d56b2f 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -508,23 +508,23 @@ {"name":"abr_throughput_02","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":27339,"samples":[],"last":null,"option_bytes":[132854,232413],"option_joules_micro":[null,315],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_throughput_03","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":5325,"samples":[[1000,691100,1108],[2000,1798923,186],[3000,499287,1537],[4000,729119,142]],"last":null,"option_bytes":[47614],"option_joules_micro":[2441],"joule_budget_micro":75,"segment_ms":3840}},"expect":{"choice":0,"budget":"floor"}}, {"name":"abr_throughput_04","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":29234,"samples":[[1000,96310,457],[2000,692823,1361],[3000,532395,2859],[4000,1569349,2674],[5000,309913,2047]],"last":2,"option_bytes":[31692,61260,114256,118871,321713],"option_joules_micro":[3464,1013,null,1819,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, -{"name":"abr_throughput_05","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":38786,"samples":[[1000,1346651,872],[2000,1898768,937],[3000,1172638,1448]],"last":3,"option_bytes":[96715,141611,162979,171720,184391,300283],"option_joules_micro":[null,1397,3978,58,3341,null],"joule_budget_micro":3328,"segment_ms":1000}},"expect":{"choice":5,"budget":"not_applied"}}, +{"name":"abr_throughput_05","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":38786,"samples":[[1000,1346651,872],[2000,1898768,937],[3000,1172638,1448]],"last":3,"option_bytes":[96715,141611,162979,171720,184391,300283],"option_joules_micro":[null,1397,3978,58,3341,null],"joule_budget_micro":3328,"segment_ms":1000}},"expect":{"choice":3,"budget":"lowered"}}, {"name":"abr_throughput_06","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":39560,"samples":[],"last":null,"option_bytes":[82831,144942],"option_joules_micro":[null,1121],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_throughput_07","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":38581,"samples":[[1000,751117,26]],"last":null,"option_bytes":[237209],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_throughput_08","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":19262,"samples":[[1000,597546,2693],[2000,684159,2225]],"last":0,"option_bytes":[168294],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":0,"budget":"not_set"}}, -{"name":"abr_throughput_09","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":8381,"samples":[[1000,1385410,319],[2000,1460612,2797]],"last":1,"option_bytes":[65202,65696,184785,346631,371704],"option_joules_micro":[1624,3288,4389,3212,null],"joule_budget_micro":4577,"segment_ms":4000}},"expect":{"choice":4,"budget":"not_applied"}}, +{"name":"abr_throughput_09","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":8381,"samples":[[1000,1385410,319],[2000,1460612,2797]],"last":1,"option_bytes":[65202,65696,184785,346631,371704],"option_joules_micro":[1624,3288,4389,3212,null],"joule_budget_micro":4577,"segment_ms":4000}},"expect":{"choice":3,"budget":"lowered"}}, {"name":"abr_throughput_10","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":6439,"samples":[[1000,1642862,2072],[2000,1724810,2415]],"last":null,"option_bytes":[221303,254054,282799,397258],"option_joules_micro":[2984,4714,951,2746],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":3,"budget":"not_set"}}, -{"name":"abr_throughput_11","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":12659,"samples":[[1000,1332812,1319],[2000,939957,2721],[3000,1367969,837],[4000,1599764,1867]],"last":2,"option_bytes":[80403,120456,245927],"option_joules_micro":[3147,null,2885],"joule_budget_micro":5230,"segment_ms":200}},"expect":{"choice":1,"budget":"not_applied"}}, +{"name":"abr_throughput_11","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":12659,"samples":[[1000,1332812,1319],[2000,939957,2721],[3000,1367969,837],[4000,1599764,1867]],"last":2,"option_bytes":[80403,120456,245927],"option_joules_micro":[3147,null,2885],"joule_budget_micro":5230,"segment_ms":200}},"expect":{"choice":0,"budget":"lowered"}}, {"name":"abr_throughput_12","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":16498,"samples":[[1000,495433,2847],[2000,1138099,1332],[3000,118438,752]],"last":null,"option_bytes":[5136,209878,237512,306265,314855],"option_joules_micro":[2083,1131,2370,3206,2827],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, -{"name":"abr_throughput_13","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":23508,"samples":[],"last":null,"option_bytes":[106126,244473,290603,332362,353222,363306],"option_joules_micro":[null,2545,null,4788,4650,null],"joule_budget_micro":5986,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_applied"}}, +{"name":"abr_throughput_13","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":23508,"samples":[],"last":null,"option_bytes":[106126,244473,290603,332362,353222,363306],"option_joules_micro":[null,2545,null,4788,4650,null],"joule_budget_micro":5986,"segment_ms":4000}},"expect":{"choice":1,"budget":"floor"}}, {"name":"abr_throughput_14","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":25907,"samples":[[1000,1865109,2358],[2000,1599056,2350]],"last":null,"option_bytes":[27081,34658],"option_joules_micro":[789,4649],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_throughput_15","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":34250,"samples":[],"last":null,"option_bytes":[35851,202585,220271,236085,248931,345739],"option_joules_micro":[1723,1554,null,199,781,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_throughput_16","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":23347,"samples":[],"last":null,"option_bytes":[200798,293314,359455],"option_joules_micro":[1339,null,2473],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, -{"name":"abr_throughput_17","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":600,"samples":[[1000,1896617,2224],[2000,1558447,1692],[3000,765327,1419]],"last":1,"option_bytes":[163436,186366,272666,275011,340603],"option_joules_micro":[3991,4424,null,3790,3355],"joule_budget_micro":1411,"segment_ms":2000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_throughput_17","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":600,"samples":[[1000,1896617,2224],[2000,1558447,1692],[3000,765327,1419]],"last":1,"option_bytes":[163436,186366,272666,275011,340603],"option_joules_micro":[3991,4424,null,3790,3355],"joule_budget_micro":1411,"segment_ms":2000}},"expect":{"choice":4,"budget":"floor"}}, {"name":"abr_throughput_18","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":13891,"samples":[[1000,954290,1322],[2000,1359600,1307],[3000,579442,570],[4000,1316832,1762],[5000,1974497,2440]],"last":null,"option_bytes":[73894,79372,276740],"option_joules_micro":[2593,1518,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":2,"budget":"not_set"}}, -{"name":"abr_throughput_19","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":24789,"samples":[[1000,560355,44],[2000,1168366,2494],[3000,1768747,727],[4000,1437641,603]],"last":null,"option_bytes":[163014,260410],"option_joules_micro":[null,1131],"joule_budget_micro":4590,"segment_ms":200}},"expect":{"choice":0,"budget":"not_applied"}}, +{"name":"abr_throughput_19","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":24789,"samples":[[1000,560355,44],[2000,1168366,2494],[3000,1768747,727],[4000,1437641,603]],"last":null,"option_bytes":[163014,260410],"option_joules_micro":[null,1131],"joule_budget_micro":4590,"segment_ms":200}},"expect":{"choice":1,"budget":"floor"}}, {"name":"abr_throughput_20","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":15721,"samples":[[1000,1211483,39],[2000,301375,2007]],"last":0,"option_bytes":[10369,280040],"option_joules_micro":[null,668],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, -{"name":"abr_throughput_21","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":33334,"samples":[[1000,1447610,2507],[2000,1657478,2231],[3000,450998,1363]],"last":null,"option_bytes":[183723,251782,272819,337959,393865,395099],"option_joules_micro":[21,null,1077,4664,303,null],"joule_budget_micro":4277,"segment_ms":3840}},"expect":{"choice":5,"budget":"not_applied"}}, +{"name":"abr_throughput_21","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":33334,"samples":[[1000,1447610,2507],[2000,1657478,2231],[3000,450998,1363]],"last":null,"option_bytes":[183723,251782,272819,337959,393865,395099],"option_joules_micro":[21,null,1077,4664,303,null],"joule_budget_micro":4277,"segment_ms":3840}},"expect":{"choice":4,"budget":"lowered"}}, {"name":"abr_throughput_22","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":35031,"samples":[[1000,324127,2144],[2000,1092870,1089]],"last":null,"option_bytes":[352081],"option_joules_micro":[1053],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_throughput_23","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":19071,"samples":[[1000,287343,1171],[2000,964313,2942],[3000,1704757,2128],[4000,1385776,95]],"last":null,"option_bytes":[72845,114716,340475,374581,379279],"option_joules_micro":[4160,2173,704,null,4235],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":4,"budget":"not_set"}}, {"name":"abr_throughput_24","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":6320,"samples":[[1000,180442,1433],[2000,1296876,1896],[3000,1459973,2827],[4000,906413,1788],[5000,708548,914]],"last":2,"option_bytes":[18928,32680,119510,244405,348963],"option_joules_micro":[4323,null,3719,334,2024],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":4,"budget":"not_set"}}, @@ -532,11 +532,11 @@ {"name":"abr_throughput_26","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":12394,"samples":[[1000,1228723,1636],[2000,1510412,794],[3000,100188,1057]],"last":3,"option_bytes":[10898,111800,122856,238090],"option_joules_micro":[4652,null,null,3096],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_throughput_27","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":28905,"samples":[[1000,694916,82],[2000,112506,981],[3000,1300898,439],[4000,1895031,165]],"last":null,"option_bytes":[256221],"option_joules_micro":[1154],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_throughput_28","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":10426,"samples":[[1000,1429554,3],[2000,119924,2406],[3000,692890,1773],[4000,1863026,2411]],"last":null,"option_bytes":[118910,163595,350359],"option_joules_micro":[null,2770,3426],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":2,"budget":"not_set"}}, -{"name":"abr_throughput_29","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":9070,"samples":[[1000,1012061,1185]],"last":null,"option_bytes":[18854,33308,77115,281011],"option_joules_micro":[1937,1674,2859,1446],"joule_budget_micro":371,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_throughput_29","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":9070,"samples":[[1000,1012061,1185]],"last":null,"option_bytes":[18854,33308,77115,281011],"option_joules_micro":[1937,1674,2859,1446],"joule_budget_micro":371,"segment_ms":1000}},"expect":{"choice":3,"budget":"floor"}}, {"name":"abr_throughput_30","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":15557,"samples":[[1000,756365,795],[2000,1757679,1638]],"last":null,"option_bytes":[368004],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, -{"name":"abr_throughput_31","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":26456,"samples":[[1000,1148772,1885],[2000,1378089,1925],[3000,1488056,561],[4000,615024,2153],[5000,203736,1988]],"last":null,"option_bytes":[25456,55188,175334,372052,395592],"option_joules_micro":[2982,null,1043,null,null],"joule_budget_micro":1948,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_applied"}}, +{"name":"abr_throughput_31","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":26456,"samples":[[1000,1148772,1885],[2000,1378089,1925],[3000,1488056,561],[4000,615024,2153],[5000,203736,1988]],"last":null,"option_bytes":[25456,55188,175334,372052,395592],"option_joules_micro":[2982,null,1043,null,null],"joule_budget_micro":1948,"segment_ms":3840}},"expect":{"choice":2,"budget":"lowered"}}, {"name":"abr_throughput_32","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":13482,"samples":[[1000,353535,461],[2000,100125,2264],[3000,1549597,940]],"last":null,"option_bytes":[71315,160507,171934,384830],"option_joules_micro":[null,4992,null,4405],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":3,"budget":"not_set"}}, -{"name":"abr_throughput_33","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":33100,"samples":[[1000,1213885,1167],[2000,361434,1921]],"last":null,"option_bytes":[86339,106234,179258,247949],"option_joules_micro":[null,123,null,null],"joule_budget_micro":5271,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_applied"}}, +{"name":"abr_throughput_33","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":33100,"samples":[[1000,1213885,1167],[2000,361434,1921]],"last":null,"option_bytes":[86339,106234,179258,247949],"option_joules_micro":[null,123,null,null],"joule_budget_micro":5271,"segment_ms":1000}},"expect":{"choice":1,"budget":"lowered"}}, {"name":"abr_throughput_34","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":9326,"samples":[[1000,49874,1866],[2000,429238,1510],[3000,1332114,425],[4000,763830,1566],[5000,1149134,46]],"last":null,"option_bytes":[69890,101912,107255,206376,244677,356206],"option_joules_micro":[373,740,2549,383,2834,4872],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":5,"budget":"not_set"}}, {"name":"abr_throughput_35","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":2542,"samples":[[1000,293117,2313]],"last":0,"option_bytes":[63930,141450,153125,187898,230096,347497],"option_joules_micro":[58,3606,4416,null,null,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":5,"budget":"not_set"}}, {"name":"abr_throughput_36","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":33929,"samples":[[1000,59337,1305]],"last":2,"option_bytes":[160052,162794,223319,364293,366948],"option_joules_micro":[2111,2846,2816,774,null],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, @@ -544,13 +544,13 @@ {"name":"abr_throughput_38","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":34202,"samples":[[1000,2498,1932],[2000,740286,1424],[3000,1964512,2166],[4000,1463791,994],[5000,833109,438]],"last":null,"option_bytes":[4006,177173,200028,293451,296992,351459],"option_joules_micro":[null,2760,3007,4566,1624,2408],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":5,"budget":"not_set"}}, {"name":"abr_throughput_39","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":6714,"samples":[[1000,1739427,69],[2000,1968256,622],[3000,1020785,2536],[4000,1578878,2178],[5000,456040,2675]],"last":null,"option_bytes":[113412,155507,211677,215466,331531,346524],"option_joules_micro":[4643,4087,1312,2992,null,4122],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":5,"budget":"not_set"}}, {"name":"abr_buffer_based_00","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":37166,"samples":[[1000,1979410,9]],"last":1,"option_bytes":[210016,308333,345583],"option_joules_micro":[null,2474,null],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, -{"name":"abr_buffer_based_01","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":16121,"samples":[[1000,570802,767]],"last":5,"option_bytes":[204621,284313,289434,306386,318982,351290],"option_joules_micro":[null,1352,null,null,3924,null],"joule_budget_micro":4692,"segment_ms":200}},"expect":{"choice":5,"budget":"not_applied"}}, +{"name":"abr_buffer_based_01","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":16121,"samples":[[1000,570802,767]],"last":5,"option_bytes":[204621,284313,289434,306386,318982,351290],"option_joules_micro":[null,1352,null,null,3924,null],"joule_budget_micro":4692,"segment_ms":200}},"expect":{"choice":4,"budget":"lowered"}}, {"name":"abr_buffer_based_02","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":29777,"samples":[[1000,1565417,2877],[2000,1095405,1838],[3000,1213452,1830],[4000,1561082,2035],[5000,514815,2890]],"last":null,"option_bytes":[125752,240457],"option_joules_micro":[null,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":1,"budget":"not_set"}}, -{"name":"abr_buffer_based_03","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":2271,"samples":[[1000,1844685,1561],[2000,1660953,1396],[3000,1046634,1653],[4000,572996,984]],"last":null,"option_bytes":[78556,128536,136533,216821,223315],"option_joules_micro":[null,null,null,563,4718],"joule_budget_micro":4470,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_applied"}}, +{"name":"abr_buffer_based_03","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":2271,"samples":[[1000,1844685,1561],[2000,1660953,1396],[3000,1046634,1653],[4000,572996,984]],"last":null,"option_bytes":[78556,128536,136533,216821,223315],"option_joules_micro":[null,null,null,563,4718],"joule_budget_micro":4470,"segment_ms":3840}},"expect":{"choice":3,"budget":"floor"}}, {"name":"abr_buffer_based_04","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":10913,"samples":[[1000,1293814,1998],[2000,1252255,1484],[3000,443443,1964]],"last":null,"option_bytes":[170679],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_buffer_based_05","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":952,"samples":[[1000,787637,1089],[2000,1884870,2269],[3000,1428241,684],[4000,663641,1609],[5000,512905,917]],"last":null,"option_bytes":[27588,215824,225817,302822,314142,359134],"option_joules_micro":[286,2738,439,519,2188,null],"joule_budget_micro":3150,"segment_ms":200}},"expect":{"choice":0,"budget":"within"}}, {"name":"abr_buffer_based_06","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":4978,"samples":[[1000,331389,564],[2000,827587,787],[3000,797362,361],[4000,253656,2761],[5000,772285,657]],"last":0,"option_bytes":[145164],"option_joules_micro":[2801],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, -{"name":"abr_buffer_based_07","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":29335,"samples":[],"last":null,"option_bytes":[70464,132583,186315],"option_joules_micro":[2199,4129,null],"joule_budget_micro":662,"segment_ms":4000}},"expect":{"choice":2,"budget":"not_applied"}}, +{"name":"abr_buffer_based_07","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":29335,"samples":[],"last":null,"option_bytes":[70464,132583,186315],"option_joules_micro":[2199,4129,null],"joule_budget_micro":662,"segment_ms":4000}},"expect":{"choice":0,"budget":"floor"}}, {"name":"abr_buffer_based_08","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":37749,"samples":[[1000,1709563,19]],"last":null,"option_bytes":[69175,85110,139703,199776,217765],"option_joules_micro":[4958,null,2129,null,2528],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, {"name":"abr_buffer_based_09","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":39078,"samples":[[1000,1551825,1508],[2000,654267,1691],[3000,338056,1616]],"last":null,"option_bytes":[2240,35831,107259,143553,219593],"option_joules_micro":[2163,238,3509,2438,3908],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":4,"budget":"not_set"}}, {"name":"abr_buffer_based_10","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":36561,"samples":[[1000,271282,2798],[2000,430309,868],[3000,46995,1685]],"last":null,"option_bytes":[27471,245810],"option_joules_micro":[1924,102],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, @@ -574,7 +574,7 @@ {"name":"abr_buffer_based_28","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":27041,"samples":[[1000,875003,2914],[2000,475096,1811]],"last":0,"option_bytes":[190765,197897,220330,321218,332550],"option_joules_micro":[1384,628,null,5000,4239],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, {"name":"abr_buffer_based_29","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":28564,"samples":[[1000,1903050,1616],[2000,805741,715],[3000,1156735,928]],"last":0,"option_bytes":[132147,391510],"option_joules_micro":[3736,2596],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_buffer_based_30","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":21410,"samples":[[1000,1469834,659]],"last":null,"option_bytes":[283475,308777,311223],"option_joules_micro":[765,2079,4078],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, -{"name":"abr_buffer_based_31","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":23415,"samples":[[1000,442019,2287],[2000,1783989,771],[3000,1466540,996]],"last":null,"option_bytes":[32947,125397,157859,246761,271145],"option_joules_micro":[null,null,2092,1535,4449],"joule_budget_micro":1292,"segment_ms":3840}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_buffer_based_31","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":23415,"samples":[[1000,442019,2287],[2000,1783989,771],[3000,1466540,996]],"last":null,"option_bytes":[32947,125397,157859,246761,271145],"option_joules_micro":[null,null,2092,1535,4449],"joule_budget_micro":1292,"segment_ms":3840}},"expect":{"choice":3,"budget":"floor"}}, {"name":"abr_buffer_based_32","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":11395,"samples":[[1000,343990,2406],[2000,777840,551]],"last":0,"option_bytes":[270717],"option_joules_micro":[2843],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_buffer_based_33","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":22365,"samples":[[1000,17516,1911],[2000,1837902,2638]],"last":0,"option_bytes":[102200,185038,186610],"option_joules_micro":[2128,4256,1647],"joule_budget_micro":2909,"segment_ms":1000}},"expect":{"choice":2,"budget":"within"}}, {"name":"abr_buffer_based_34","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":2245,"samples":[],"last":null,"option_bytes":[137626,366867],"option_joules_micro":[2133,32],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, @@ -590,9 +590,9 @@ {"name":"abr_bola_like_04","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":24993,"samples":[[1000,628261,266],[2000,1311093,2490],[3000,1896231,2508],[4000,1899946,1549],[5000,1473270,521]],"last":3,"option_bytes":[12156,93174,102688,115312,200006],"option_joules_micro":[null,null,null,null,3620],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":4,"budget":"not_set"}}, {"name":"abr_bola_like_05","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":36964,"samples":[[1000,1635778,1330]],"last":0,"option_bytes":[180898,209837,230082,260672],"option_joules_micro":[2254,null,null,3133],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, {"name":"abr_bola_like_06","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":33333,"samples":[[1000,1928993,2256],[2000,1990635,1460],[3000,985446,157],[4000,1448177,1178]],"last":null,"option_bytes":[174914,277374,359614,400433],"option_joules_micro":[3301,2076,null,2638],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, -{"name":"abr_bola_like_07","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":14770,"samples":[[1000,1194018,1373],[2000,259042,1066],[3000,1462106,1136]],"last":null,"option_bytes":[243135,292490],"option_joules_micro":[4054,null],"joule_budget_micro":1151,"segment_ms":4000}},"expect":{"choice":1,"budget":"not_applied"}}, +{"name":"abr_bola_like_07","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":14770,"samples":[[1000,1194018,1373],[2000,259042,1066],[3000,1462106,1136]],"last":null,"option_bytes":[243135,292490],"option_joules_micro":[4054,null],"joule_budget_micro":1151,"segment_ms":4000}},"expect":{"choice":0,"budget":"floor"}}, {"name":"abr_bola_like_08","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":27980,"samples":[[1000,141644,2588],[2000,1022414,964],[3000,388313,362],[4000,900844,2246]],"last":null,"option_bytes":[35175,224251,379021],"option_joules_micro":[3222,3467,1675],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":2,"budget":"not_set"}}, -{"name":"abr_bola_like_09","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":25379,"samples":[[1000,1891490,2917],[2000,1154608,571],[3000,919253,627],[4000,77454,1835]],"last":null,"option_bytes":[33791,62122,140623,207294,208269,277356],"option_joules_micro":[1522,2250,1056,null,3249,4928],"joule_budget_micro":854,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_bola_like_09","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":25379,"samples":[[1000,1891490,2917],[2000,1154608,571],[3000,919253,627],[4000,77454,1835]],"last":null,"option_bytes":[33791,62122,140623,207294,208269,277356],"option_joules_micro":[1522,2250,1056,null,3249,4928],"joule_budget_micro":854,"segment_ms":1000}},"expect":{"choice":2,"budget":"floor"}}, {"name":"abr_bola_like_10","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":32840,"samples":[[1000,1081027,2618]],"last":0,"option_bytes":[104494,230609],"option_joules_micro":[1662,62],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_bola_like_11","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":36145,"samples":[[1000,1966685,548]],"last":null,"option_bytes":[53981,203768],"option_joules_micro":[1104,777],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_bola_like_12","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":25512,"samples":[[1000,1037585,2300],[2000,1988690,1445],[3000,258576,2439]],"last":1,"option_bytes":[2816,214121,330531],"option_joules_micro":[3106,4288,2871],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, @@ -614,7 +614,7 @@ {"name":"abr_bola_like_28","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":3895,"samples":[[1000,1642417,488]],"last":0,"option_bytes":[178847,197381],"option_joules_micro":[805,1330],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_bola_like_29","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":5043,"samples":[[1000,320516,856],[2000,455944,2400],[3000,1090390,307],[4000,1503545,2066],[5000,1571448,2156]],"last":3,"option_bytes":[118399,166307,207779,350673],"option_joules_micro":[3407,null,4989,4316],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":3,"budget":"not_set"}}, {"name":"abr_bola_like_30","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":10880,"samples":[[1000,496251,2984],[2000,1410869,227]],"last":5,"option_bytes":[53586,190914,212753,222066,293341,388656],"option_joules_micro":[null,null,2722,1228,2710,null],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":5,"budget":"not_set"}}, -{"name":"abr_bola_like_31","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":32168,"samples":[[1000,1586751,2771],[2000,550958,2532],[3000,281722,1964],[4000,1084201,924]],"last":0,"option_bytes":[95722,138681,162936,182448,240430,352697],"option_joules_micro":[203,917,3136,4882,3788,null],"joule_budget_micro":2702,"segment_ms":4000}},"expect":{"choice":5,"budget":"not_applied"}}, +{"name":"abr_bola_like_31","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":32168,"samples":[[1000,1586751,2771],[2000,550958,2532],[3000,281722,1964],[4000,1084201,924]],"last":0,"option_bytes":[95722,138681,162936,182448,240430,352697],"option_joules_micro":[203,917,3136,4882,3788,null],"joule_budget_micro":2702,"segment_ms":4000}},"expect":{"choice":1,"budget":"lowered"}}, {"name":"abr_bola_like_32","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":27832,"samples":[],"last":null,"option_bytes":[217137,290770],"option_joules_micro":[null,null],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_bola_like_33","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":1133,"samples":[[1000,1883348,310],[2000,1503581,219],[3000,257624,2456]],"last":null,"option_bytes":[376873],"option_joules_micro":[3600],"joule_budget_micro":2937,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, {"name":"abr_bola_like_34","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":660,"samples":[],"last":2,"option_bytes":[35333,90913,214404,288921,307074,359893],"option_joules_micro":[null,1183,null,3405,4668,1069],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":1,"budget":"not_set"}}, @@ -638,7 +638,7 @@ {"name":"abr_mpc_12","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":35549,"samples":[[1000,1673989,696],[2000,1035206,2893],[3000,1016484,39]],"last":null,"option_bytes":[113914,156377],"option_joules_micro":[3044,1235],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_mpc_13","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":10338,"samples":[[1000,1224110,481],[2000,955881,1007]],"last":null,"option_bytes":[195816],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_mpc_14","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":17127,"samples":[[1000,1369275,1143]],"last":null,"option_bytes":[79050,79475,97585,272705,398504],"option_joules_micro":[null,null,2589,2622,4835],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":4,"budget":"not_set"}}, -{"name":"abr_mpc_15","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":9841,"samples":[[1000,1168458,1567],[2000,1869218,627],[3000,138851,1938],[4000,1432156,427]],"last":null,"option_bytes":[78956,316949],"option_joules_micro":[4624,null],"joule_budget_micro":5428,"segment_ms":200}},"expect":{"choice":1,"budget":"not_applied"}}, +{"name":"abr_mpc_15","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":9841,"samples":[[1000,1168458,1567],[2000,1869218,627],[3000,138851,1938],[4000,1432156,427]],"last":null,"option_bytes":[78956,316949],"option_joules_micro":[4624,null],"joule_budget_micro":5428,"segment_ms":200}},"expect":{"choice":0,"budget":"lowered"}}, {"name":"abr_mpc_16","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":34129,"samples":[[1000,696042,877]],"last":0,"option_bytes":[227388],"option_joules_micro":[2005],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_mpc_17","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":34612,"samples":[[1000,1113860,2847],[2000,1744199,781],[3000,1458008,2986],[4000,699431,503]],"last":null,"option_bytes":[87198,305540],"option_joules_micro":[535,2971],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_mpc_18","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":11904,"samples":[[1000,1738597,2894],[2000,742606,673],[3000,758290,562],[4000,585883,266]],"last":1,"option_bytes":[175744,300621,311244],"option_joules_micro":[null,null,2800],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, @@ -652,7 +652,7 @@ {"name":"abr_mpc_26","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":7985,"samples":[[1000,679797,1014],[2000,1388646,1629],[3000,1287532,995],[4000,673199,2771],[5000,1693506,6]],"last":null,"option_bytes":[148612,182814,263724],"option_joules_micro":[1240,3043,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_mpc_27","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":25285,"samples":[],"last":2,"option_bytes":[205132,209606,216912,263414,353879,372690],"option_joules_micro":[3514,1145,1369,4951,null,736],"joule_budget_micro":5625,"segment_ms":1000}},"expect":{"choice":0,"budget":"within"}}, {"name":"abr_mpc_28","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":19124,"samples":[[1000,331098,1453],[2000,536721,2331]],"last":null,"option_bytes":[80660,128098,193362,237934,287501,324214],"option_joules_micro":[null,null,4493,null,3390,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":5,"budget":"not_set"}}, -{"name":"abr_mpc_29","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":13960,"samples":[[1000,1446941,305],[2000,1491488,581],[3000,765645,743],[4000,179479,1628],[5000,1937348,312]],"last":null,"option_bytes":[166082,190860],"option_joules_micro":[null,4831],"joule_budget_micro":457,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_mpc_29","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":13960,"samples":[[1000,1446941,305],[2000,1491488,581],[3000,765645,743],[4000,179479,1628],[5000,1937348,312]],"last":null,"option_bytes":[166082,190860],"option_joules_micro":[null,4831],"joule_budget_micro":457,"segment_ms":1000}},"expect":{"choice":1,"budget":"floor"}}, {"name":"abr_mpc_30","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":6763,"samples":[],"last":3,"option_bytes":[114132,121182,345843,346021,355368],"option_joules_micro":[3690,null,3581,1578,1526],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_mpc_31","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":12795,"samples":[[1000,904626,2824],[2000,712775,1599],[3000,884425,2063]],"last":null,"option_bytes":[28835,223843,227852,310268],"option_joules_micro":[null,893,3370,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":3,"budget":"not_set"}}, {"name":"abr_mpc_32","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":28372,"samples":[],"last":0,"option_bytes":[27801,54184],"option_joules_micro":[1568,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, @@ -664,11 +664,45 @@ {"name":"abr_mpc_38","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":4614,"samples":[],"last":2,"option_bytes":[124032,147831,179787,264257,317602,373533],"option_joules_micro":[464,3627,null,2541,null,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_mpc_39","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":2169,"samples":[[1000,1664812,2029],[2000,1199226,396],[3000,1066747,1306],[4000,934893,1177],[5000,937707,462]],"last":2,"option_bytes":[69029,158033,286806,379267],"option_joules_micro":[null,null,1240,4653],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":3,"budget":"not_set"}}, {"name":"abr_budget_not_set","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,500],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":2,"budget":"not_set"}}, -{"name":"abr_budget_not_applied","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,null],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":2,"budget":"not_applied"}}, +{"name":"abr_budget_not_applied","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":2,"budget":"not_applied"}}, {"name":"abr_budget_within","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,100],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":2,"budget":"within"}}, {"name":"abr_budget_lowered","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":1,"budget":"lowered"}}, {"name":"abr_budget_lowered_past_unlabelled","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,null,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":0,"budget":"lowered"}}, +{"name":"abr_budget_unlabelled_choice","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,null],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":1,"budget":"lowered"}}, {"name":"abr_budget_floor","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[200,null,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_budget_floor_least_figure","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,300,200],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":2,"budget":"floor"}}, +{"name":"abr_budget_floor_above_the_choice","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1250,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,50,80],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":1,"budget":"floor"}}, +{"name":"abr_budget_figures_past_the_options","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null,5],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":2,"budget":"not_applied"}}, +{"name":"abr_extreme_throughput_0","op":"abr","input":{"policy":{"id":"throughput","last_n":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":0}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_extreme_throughput_1","op":"abr","input":{"policy":{"id":"throughput","last_n":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_extreme_throughput_4294967295","op":"abr","input":{"policy":{"id":"throughput","last_n":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":4294967295}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_extreme_buffer_based_0","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":0,"cushion_ms":4294967295},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":0}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_extreme_buffer_based_1","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":0,"cushion_ms":4294967295},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_extreme_buffer_based_4294967295","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":0,"cushion_ms":4294967295},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":4294967295}},"expect":{"choice":3,"budget":"not_set"}}, +{"name":"abr_extreme_bola_like_0","op":"abr","input":{"policy":{"id":"bola_like","v_milli":4294967295,"max_buffer_ms":4294967295},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":0}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_extreme_bola_like_1","op":"abr","input":{"policy":{"id":"bola_like","v_milli":4294967295,"max_buffer_ms":4294967295},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_extreme_bola_like_4294967295","op":"abr","input":{"policy":{"id":"bola_like","v_milli":4294967295,"max_buffer_ms":4294967295},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":4294967295}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_extreme_mpc_0","op":"abr","input":{"policy":{"id":"mpc","horizon":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":0}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_extreme_mpc_1","op":"abr","input":{"policy":{"id":"mpc","horizon":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_extreme_mpc_4294967295","op":"abr","input":{"policy":{"id":"mpc","horizon":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":4294967295}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_extreme_budget","op":"abr","input":{"policy":{"id":"bola_like","v_milli":4294967295,"max_buffer_ms":4294967295},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":18446744073709551614,"segment_ms":1}},"expect":{"choice":2,"budget":"floor"}}, +{"name":"abr_extreme_mpc_stalls","op":"abr","input":{"policy":{"id":"mpc","horizon":255,"safety_permille":65535},"state":{"buffer_ms":0,"samples":[[1,1,1]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_out_of_bounds_buffer","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":4294967296,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"abr_out_of_bounds_segment","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":4294967296}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"abr_out_of_bounds_segment_fraction","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000.5}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"abr_out_of_bounds_budget","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":1.8446744073709552e+19,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"abr_out_of_bounds_last_negative","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":-1,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"abr_out_of_bounds_options","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"abr_out_of_bounds_figures","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"abr_out_of_bounds_sample","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1,1,1.8446744073709552e+19]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"abr_out_of_bounds_last_n","op":"abr","input":{"policy":{"id":"throughput","last_n":256,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"abr_out_of_bounds_safety","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":65536},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"abr_out_of_bounds_v","op":"abr","input":{"policy":{"id":"bola_like","v_milli":4294967296,"max_buffer_ms":20000},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"abr_out_of_bounds_horizon","op":"abr","input":{"policy":{"id":"mpc","horizon":256,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"depth_out_of_bounds_bytes","op":"depth","input":{"layers_n":[18446744073709551615,1],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[18446744073709551615,1],"joule_budget_micro":10,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"depth_out_of_bounds_figure","op":"depth","input":{"layers_n":[1,1],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[18446744073709551615,1],"joule_budget_micro":10,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"depth_out_of_bounds_layers","op":"depth","input":{"layers_n":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"depth_at_the_bound","op":"depth","input":{"layers_n":[18446744073709551614,1],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":0,"budget":"not_set"}}, {"name":"ilog2_1_1","op":"ilog2","input":{"a":1,"b":1},"expect":{"log2_q16":0}}, {"name":"ilog2_2_1","op":"ilog2","input":{"a":2,"b":1},"expect":{"log2_q16":65536}}, {"name":"ilog2_3_2","op":"ilog2","input":{"a":3,"b":2},"expect":{"log2_q16":38336}}, @@ -790,7 +824,7 @@ {"name":"depth_O11b_mpc_1","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":6000,"samples":[[1000,37500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, {"name":"depth_O11b_mpc_2","op":"depth","input":{"objects":["O11b","O11bL1"],"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":30000,"samples":[[1000,625000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, {"name":"depth_O10_throughput_between","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,25000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":1,"budget":"not_set"}}, -{"name":"depth_O10_budget_unlabelled_layer","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,10000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[5,null,20],"joule_budget_micro":30,"segment_ms":1000}},"expect":{"depth":2,"budget":"not_applied"}}, +{"name":"depth_O10_budget_unlabelled_layer","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,10000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[5,null,20],"joule_budget_micro":30,"segment_ms":1000}},"expect":{"depth":0,"budget":"lowered"}}, {"name":"depth_O10_budget_lowered","op":"depth","input":{"objects":["O10","O10L1","O10L2"],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,10000000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[5,10,20],"joule_budget_micro":30,"segment_ms":1000}},"expect":{"depth":1,"budget":"lowered"}}, {"name":"fetch_abr_O3_low","op":"fetch","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"applies":["wai.meta.hdr10plus"],"abr":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"fragment":"#h=1009"},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":21650,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"24003cee2a7bf7fa45caa412ab4ea9f5f08b6bf44842a73d5a11c2bcf36df464"}}, {"name":"same_pick_abr_O3_low","op":"same_pick","input":{"object":"O3","advertises":["wai.video.av1","wai.meta.hdr10plus"],"applies":["wai.meta.hdr10plus"],"abr":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[],"option_joules_micro":[],"joule_budget_micro":null,"segment_ms":2000}},"fragment":"#h=1009"},"expect":{"pick":0}}, diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh index d4f2a4703..ddfef3be4 100755 --- a/wai/http-conformance/falsify/run.sh +++ b/wai/http-conformance/falsify/run.sh @@ -49,7 +49,8 @@ for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done t pdt-ignored dseq-ignored parts-after-extinf average-floor render-digest-off name-unchecked chain-unchecked \ ilog2-no-interp bola-waits unlabelled-as-zero round-half-down small-order-key small-order-r \ rebind-uri rebind-sequence key-unchecked walk-unbounded reload-walked \ - xml-char-unchecked xml-attribute-twice dash-start-ignored dash-timescale-ignored; do + xml-char-unchecked xml-attribute-twice dash-start-ignored dash-timescale-ignored \ + unlabelled-escapes abr-unbounded; do if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then echo "::error::http_verify.py --mutate $m passed"; fail=1 else diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index 9cf2fc5db..cc412041a 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -1233,7 +1233,14 @@ def sink_of(d, inp, objs): order = inp.get("order") if inp.get("abr") is not None: adv, pol, st = inp.get("advertises") or [], inp["abr"]["policy"], abr_state(inp["abr"]["state"]) - order = lambda head: order_entries(head, adv, pol, st) + check_policy(pol) + check_state(st) + + def order(head): + try: + return order_entries(head, adv, pol, st) + except Refusal: + return list(range(len(head["manifest"]["renditions"]))) return (inp.get("advertises") or [], inp.get("applies") or [], held, order, declared, base) @@ -1349,53 +1356,103 @@ def choose(p, st): def clamp_to_budget(st, choice): + """Appendix F's budget: only an option with a labelled figure within it is chosen; + not applied only when no option has a labelled figure.""" budget = st["joule_budget_micro"] if budget is None: return choice, "not_set" figs = st["option_joules_micro"] - fig = lambda j: figs[j] if j < len(figs) else None + n = max(len(st["option_bytes"]), choice + 1) + fig = lambda j: figs[j] if j < len(figs) and j < n else None if MUTATE == "unlabelled-as-zero": - fig = lambda j: (figs[j] if j < len(figs) else None) or 0 - f = fig(choice) - if f is None: + fig = lambda j: (figs[j] if j < len(figs) and j < n else None) or 0 + labelled = [(j, fig(j)) for j in range(n) if fig(j) is not None] + if not labelled or (MUTATE == "unlabelled-escapes" and fig(choice) is None): return choice, "not_applied" - if f <= budget: + if fig(choice) is not None and fig(choice) <= budget: return choice, "within" - for j in range(choice - 1, -1, -1): - if fig(j) is not None and fig(j) <= budget: - return j, "lowered" - return 0, "floor" + below = [j for j, f in labelled if j < choice and f <= budget] + if below: + return below[-1], "lowered" + above = [j for j, f in labelled if j > choice and f <= budget] + if above: + return above[0], "floor" + return min(labelled, key=lambda x: (x[1], x[0]))[0], "floor" + + +MAX_OPTIONS = 256 +OUT_OF_BOUNDS = "abr_state_out_of_bounds" + + +def bounded(v, top): + """An unsigned integer of at most top; out of bounds for any other number.""" + if isinstance(v, bool) or not isinstance(v, (int, float)): + raise SystemExit("http_verify: an adaptation value is not a number") + if not isinstance(v, int) or not 0 <= v <= top: + raise Refusal(OUT_OF_BOUNDS) + return v + + +def check_policy(p): + """Appendix F: each parameter within its type.""" + tops = {"throughput": {"last_n": 255, "safety_permille": 65535}, "buffer_based": {"reservoir_ms": U32, "cushion_ms": U32}, + "bola_like": {"v_milli": U32, "max_buffer_ms": U32}, "mpc": {"horizon": 255, "safety_permille": 65535}} + if p.get("id") not in tops: + raise SystemExit("http_verify: an unknown policy " + str(p.get("id"))) + for k, top in tops[p["id"]].items(): + bounded(p[k], top) + + +def check_state(st): + if (len(st["option_bytes"]) > MAX_OPTIONS or len(st["option_joules_micro"]) > MAX_OPTIONS) and MUTATE != "abr-unbounded": + raise Refusal(OUT_OF_BOUNDS) def decide(p, st): + check_policy(p) + check_state(st) return clamp_to_budget(st, choose(p, st)) def abr_state(v): - return {"buffer_ms": v["buffer_ms"], "samples": [tuple(s) for s in v.get("samples") or []], "last": v.get("last"), - "option_bytes": list(v.get("option_bytes") or []), "option_joules_micro": list(v.get("option_joules_micro") or []), - "joule_budget_micro": v.get("joule_budget_micro"), "segment_ms": v["segment_ms"]} + """A state from its JSON form, each value within its field's type (Appendix F).""" + opt = lambda x, top: None if x is None else bounded(x, top) + return {"buffer_ms": bounded(v["buffer_ms"], U32), "samples": [tuple(bounded(x, U64) for x in s[:3]) for s in v.get("samples") or []], + "last": opt(v.get("last"), U64), "option_bytes": [bounded(x, U64) for x in v.get("option_bytes") or []], + "option_joules_micro": [opt(x, U64) for x in v.get("option_joules_micro") or []], + "joule_budget_micro": opt(v.get("joule_budget_micro"), U64), "segment_ms": bounded(v["segment_ms"], U32)} def depth(layers_n, p, st): + check_policy(p) + check_state(st) + if len(layers_n) > MAX_OPTIONS: + raise Refusal(OUT_OF_BOUNDS) st = dict(st) - acc, cum, jcum = 0, [], [] + acc, cum, jcum, fsum = 0, [], [], 0 figs = st["option_joules_micro"] for k, n in enumerate(layers_n): acc += n + if acc > U64 and MUTATE != "abr-unbounded": + raise Refusal(OUT_OF_BOUNDS) cum.append(acc) - fs = [figs[j] if j < len(figs) else None for j in range(k + 1)] - jcum.append(None if any(f is None for f in fs) else sum(fs)) + f = figs[k] if k < len(figs) else None + fsum = None if fsum is None or f is None else fsum + f + if fsum is not None and fsum > U64 and MUTATE != "abr-unbounded": + raise Refusal(OUT_OF_BOUNDS) + jcum.append(fsum) st["option_bytes"], st["option_joules_micro"] = cum, jcum if not layers_n: return 0, clamp_to_budget(st, 0)[1] - return decide(p, st) + return clamp_to_budget(st, choose(p, st)) def order_entries(head, adv, p, st): """SPEC §7.1 step 2's order under a policy: each advertised selectable capability's entries by component length, the choice first, then those below it nearest first, then those above; every other entry after, in table order.""" + check_policy(p) + check_state(st) if head["version"] != "WAI2": return [0] entries = head["manifest"]["renditions"] @@ -1424,6 +1481,7 @@ def order_entries(head, adv, p, st): # ------------------------------------------------------ playlists (§10.4, §11) U64 = 2 ** 64 - 1 +U32 = 2 ** 32 - 1 DIGITS3 = re.compile(r"[0-9]+(\.[0-9]+)?", re.ASCII) diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index be66d9a29..3d4fecbb4 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -397,35 +397,61 @@ fn parsed(p: &MediaPlaylist, t: &str) -> Value { // ---------------------------------------------------------------- adaptation +/// Appendix F's refusal of a value beyond its field's type. +fn out_of_bounds() -> Stop { + Stop::Refused(json!({ "error": "abr_state_out_of_bounds" })) +} + +/// An unsigned integer of at most `max`: a number beyond it (or negative, or with a +/// fraction) is out of bounds; anything else a fault in the case. +fn bounded(v: &Value, max: u64, what: &str) -> Result { + match v.as_u64() { + Some(x) if x <= max => Ok(x), + _ if v.is_number() => Err(out_of_bounds()), + _ => Err(Stop::Fault(format!("{what} is a number"))), + } +} + +fn bounded_opt(v: &Value, max: u64, what: &str) -> Result, Stop> { + if v.is_null() { Ok(None) } else { bounded(v, max, what).map(Some) } +} + /// A policy from its JSON form: `{"id": "throughput", "last_n": …, "safety_permille": …}` -/// and the like. -pub(super) fn policy(v: &Value) -> Result, String> { - let u = |k: &str| v[k].as_u64().ok_or(format!("policy.{k}")); +/// and the like, each parameter within its type (Appendix F). +pub(super) fn policy(v: &Value) -> Result, Stop> { + let u = |k: &str, max: u64| bounded(&v[k], max, &format!("policy.{k}")); + let (u8m, u16m, u32m) = (u64::from(u8::MAX), u64::from(u16::MAX), u64::from(u32::MAX)); Ok(match v["id"].as_str() { - Some("throughput") => Box::new(Throughput { last_n: u("last_n")? as u8, safety_permille: u("safety_permille")? as u16 }), - Some("buffer_based") => Box::new(BufferBased { reservoir_ms: u("reservoir_ms")? as u32, cushion_ms: u("cushion_ms")? as u32 }), - Some("bola_like") => Box::new(BolaLike { v_milli: u("v_milli")? as u32, max_buffer_ms: u("max_buffer_ms")? as u32 }), - Some("mpc") => Box::new(Mpc { horizon: u("horizon")? as u8, safety_permille: u("safety_permille")? as u16 }), + Some("throughput") => Box::new(Throughput { last_n: u("last_n", u8m)? as u8, safety_permille: u("safety_permille", u16m)? as u16 }), + Some("buffer_based") => Box::new(BufferBased { reservoir_ms: u("reservoir_ms", u32m)? as u32, cushion_ms: u("cushion_ms", u32m)? as u32 }), + Some("bola_like") => Box::new(BolaLike { v_milli: u("v_milli", u32m)? as u32, max_buffer_ms: u("max_buffer_ms", u32m)? as u32 }), + Some("mpc") => Box::new(Mpc { horizon: u("horizon", u8m)? as u8, safety_permille: u("safety_permille", u16m)? as u16 }), _ => return Err("an unknown policy".into()), }) } -/// A state from its JSON form. -pub(super) fn state(v: &Value) -> Result { - let nums = |k: &str| v[k].as_array().into_iter().flatten().map(|x| x.as_u64().ok_or(format!("state.{k}"))).collect::, String>>(); +/// A state from its JSON form, each value within its field's type (Appendix F). +pub(super) fn state(v: &Value) -> Result { + let (u32m, u64m) = (u64::from(u32::MAX), u64::MAX); + let list = |k: &str| v[k].as_array().cloned().unwrap_or_default(); + let samples = list("samples").iter().map(|s| { + Ok(Sample { at_ms: bounded(&s[0], u64m, "a sample's at_ms")?, bytes: bounded(&s[1], u64m, "a sample's bytes")?, ms: bounded(&s[2], u64m, "a sample's ms")? }) + }).collect::, Stop>>()?; Ok(AbrState { - buffer_ms: v["buffer_ms"].as_u64().ok_or("state.buffer_ms")? as u32, - samples: v["samples"].as_array().into_iter().flatten() - .map(|s| Some(Sample { at_ms: s[0].as_u64()?, bytes: s[1].as_u64()?, ms: s[2].as_u64()? })) - .collect::>>().ok_or("state.samples")?, - last: v["last"].as_u64().map(|l| l as usize), - option_bytes: nums("option_bytes")?, - option_joules_micro: v["option_joules_micro"].as_array().into_iter().flatten().map(Value::as_u64).collect(), - joule_budget_micro: v["joule_budget_micro"].as_u64(), - segment_ms: v["segment_ms"].as_u64().ok_or("state.segment_ms")? as u32, + buffer_ms: bounded(&v["buffer_ms"], u32m, "state.buffer_ms")? as u32, + samples, + last: bounded_opt(&v["last"], u64m, "state.last")?.map(|l| l as usize), + option_bytes: list("option_bytes").iter().map(|x| bounded(x, u64m, "an option's bytes")).collect::>()?, + option_joules_micro: list("option_joules_micro").iter().map(|x| bounded_opt(x, u64m, "an option's figure")).collect::>()?, + joule_budget_micro: bounded_opt(&v["joule_budget_micro"], u64m, "state.joule_budget_micro")?, + segment_ms: bounded(&v["segment_ms"], u32m, "state.segment_ms")? as u32, }) } +fn abr_err(e: abr::AbrError) -> Stop { + Stop::Refused(json!({ "error": e.code() })) +} + // ------------------------------------------------------------------ the outcomes /// The outcome of a playlist or adaptation op; `None` for an op this side does not hold. @@ -471,7 +497,7 @@ pub(super) fn outcome(c: &Corpus, op: &str, input: &Value) -> Option (|| { let p = policy(&input["policy"])?; - let (k, b) = abr::decide(p.as_ref(), &state(&input["state"])?); + let (k, b) = abr::decide(p.as_ref(), &state(&input["state"])?).map_err(abr_err)?; Ok(json!({ "choice": k, "budget": b.label() })) })(), "abr_order" => (|| { @@ -481,7 +507,7 @@ pub(super) fn outcome(c: &Corpus, op: &str, input: &Value) -> Option return Err(Stop::Fault("abr_order needs a whole object".into())), }; let adv = strs(&input["advertises"]); - let order = abr::order_entries(&h, &|cap: &str| adv.iter().any(|a| a == cap), policy(&input["policy"])?.as_ref(), &state(&input["state"])?); + let order = abr::order_entries(&h, &|cap: &str| adv.iter().any(|a| a == cap), policy(&input["policy"])?.as_ref(), &state(&input["state"])?).map_err(abr_err)?; Ok(json!({ "order": order })) })(), "depth" => (|| { @@ -489,7 +515,7 @@ pub(super) fn outcome(c: &Corpus, op: &str, input: &Value) -> Option ids.iter().map(|id| Ok(object_bytes(c, id.as_str().ok_or("an object id")?)?.len() as u64)).collect::>()?, None => input["layers_n"].as_array().ok_or("layers_n")?.iter().map(|x| x.as_u64().ok_or("layers_n")).collect::>()?, }; - let (d, b) = abr::depth(&layers, policy(&input["policy"])?.as_ref(), &state(&input["state"])?); + let (d, b) = abr::depth(&layers, policy(&input["policy"])?.as_ref(), &state(&input["state"])?).map_err(abr_err)?; Ok(json!({ "depth": d, "budget": b.label() })) })(), _ => return None, @@ -1121,16 +1147,76 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { "option_joules_micro": figs, "joule_budget_micro": budget, "segment_ms": 1000 }); let opts = json!([10_000, 100_000, 1_000_000]); let thr = json!({ "id": "throughput", "last_n": 1, "safety_permille": 900 }); - for (name, figs, budget) in [ - ("abr_budget_not_set", json!([10, 50, 500]), Value::Null), - ("abr_budget_not_applied", json!([10, 50, null]), json!(100)), - ("abr_budget_within", json!([10, 50, 100]), json!(100)), - ("abr_budget_lowered", json!([10, 50, 500]), json!(100)), - ("abr_budget_lowered_past_unlabelled", json!([10, null, 500]), json!(100)), - ("abr_budget_floor", json!([200, null, 500]), json!(100)), + // Under a budget only an option with a labelled figure within it is chosen; the + // budget is not applied only when no option has a labelled figure. + for (name, bps, figs, budget) in [ + ("abr_budget_not_set", 100_000_000u64, json!([10, 50, 500]), Value::Null), + ("abr_budget_not_applied", 100_000_000, json!([null, null, null]), json!(100)), + ("abr_budget_within", 100_000_000, json!([10, 50, 100]), json!(100)), + ("abr_budget_lowered", 100_000_000, json!([10, 50, 500]), json!(100)), + ("abr_budget_lowered_past_unlabelled", 100_000_000, json!([10, null, 500]), json!(100)), + ("abr_budget_unlabelled_choice", 100_000_000, json!([10, 50, null]), json!(100)), + ("abr_budget_floor", 100_000_000, json!([200, null, 500]), json!(100)), + ("abr_budget_floor_least_figure", 100_000_000, json!([null, 300, 200]), json!(100)), + ("abr_budget_floor_above_the_choice", 10_000, json!([null, 50, 80]), json!(100)), + ("abr_budget_figures_past_the_options", 100_000_000, json!([null, null, null, 5]), json!(100)), ] { - v.push(case(name, "abr", json!({ "policy": thr, "state": st(0, 100_000_000, opts.clone(), figs, budget) }))); + v.push(case(name, "abr", json!({ "policy": thr, "state": st(0, bps, opts.clone(), figs, budget) }))); } + // Appendix F's bounds: each value within its field's type, at most 256 options; and + // every policy at the extremes of those types, computed exactly. + let edge = json!({ "buffer_ms": u32::MAX, "samples": (0..300u64).map(|i| json!([i, u64::MAX - i, if i % 2 == 0 { 0 } else { 2 }])).collect::>(), + "last": 1, "option_bytes": [1, u64::MAX / 3, u64::MAX - 1, u64::MAX], "option_joules_micro": [u64::MAX, null, 1, u64::MAX - 1], + "joule_budget_micro": null, "segment_ms": 1 }); + let extreme_policies = [ + json!({ "id": "throughput", "last_n": 255, "safety_permille": 65_535 }), + json!({ "id": "buffer_based", "reservoir_ms": 0, "cushion_ms": u32::MAX }), + json!({ "id": "bola_like", "v_milli": u32::MAX, "max_buffer_ms": u32::MAX }), + json!({ "id": "mpc", "horizon": 255, "safety_permille": 65_535 }), + ]; + for p in &extreme_policies { + for seg in [0u64, 1, u64::from(u32::MAX)] { + let mut e = edge.clone(); + e["segment_ms"] = json!(seg); + v.push(case(format!("abr_extreme_{}_{seg}", p["id"].as_str().expect("id")), "abr", json!({ "policy": p, "state": e }))); + } + } + let mut budgeted = edge.clone(); + budgeted["joule_budget_micro"] = json!(u64::MAX - 1); + v.push(case("abr_extreme_budget", "abr", json!({ "policy": extreme_policies[2], "state": budgeted }))); + let mut buffered = edge.clone(); + buffered["buffer_ms"] = json!(0); + buffered["samples"] = json!([[1, 1, 1]]); + v.push(case("abr_extreme_mpc_stalls", "abr", json!({ "policy": extreme_policies[3], "state": buffered }))); + let base = st(0, 100_000, opts.clone(), json!([null, null, null]), Value::Null); + for (name, key, value) in [ + ("abr_out_of_bounds_buffer", "buffer_ms", json!(1u64 << 32)), + ("abr_out_of_bounds_segment", "segment_ms", json!(1u64 << 32)), + ("abr_out_of_bounds_segment_fraction", "segment_ms", json!(1000.5)), + ("abr_out_of_bounds_budget", "joule_budget_micro", json!(18_446_744_073_709_551_616.0)), + ("abr_out_of_bounds_last_negative", "last", json!(-1)), + ("abr_out_of_bounds_options", "option_bytes", json!(vec![1u64; 257])), + ("abr_out_of_bounds_figures", "option_joules_micro", json!(vec![1u64; 257])), + ("abr_out_of_bounds_sample", "samples", json!([[1, 1, 18_446_744_073_709_551_616.0]])), + ] { + let mut st = base.clone(); + st[key] = value; + v.push(refused(name, "abr", json!({ "policy": thr, "state": st }), "abr_state_out_of_bounds")); + } + for (name, p) in [ + ("abr_out_of_bounds_last_n", json!({ "id": "throughput", "last_n": 256, "safety_permille": 900 })), + ("abr_out_of_bounds_safety", json!({ "id": "mpc", "horizon": 3, "safety_permille": 65_536 })), + ("abr_out_of_bounds_v", json!({ "id": "bola_like", "v_milli": 1u64 << 32, "max_buffer_ms": 20_000 })), + ("abr_out_of_bounds_horizon", json!({ "id": "mpc", "horizon": 256, "safety_permille": 900 })), + ] { + v.push(refused(name, "abr", json!({ "policy": p, "state": base }), "abr_state_out_of_bounds")); + } + let deep = json!({ "buffer_ms": 0, "samples": [[1000, 1000, 1000]], "last": null, "option_bytes": [], "option_joules_micro": [u64::MAX, 1], + "joule_budget_micro": 10, "segment_ms": 1000 }); + v.push(refused("depth_out_of_bounds_bytes", "depth", json!({ "layers_n": [u64::MAX, 1], "policy": thr, "state": deep }), "abr_state_out_of_bounds")); + v.push(refused("depth_out_of_bounds_figure", "depth", json!({ "layers_n": [1, 1], "policy": thr, "state": deep }), "abr_state_out_of_bounds")); + v.push(refused("depth_out_of_bounds_layers", "depth", json!({ "layers_n": vec![1u64; 257], "policy": thr, "state": base }), "abr_state_out_of_bounds")); + v.push(case("depth_at_the_bound", "depth", json!({ "layers_n": [u64::MAX - 1, 1], "policy": thr, "state": base }))); // The integer log every utility rests on, at the table's knots, between them, and at // the ends of u64. for (a, b) in [(1u64, 1u64), (2, 1), (3, 2), (17, 16), (31, 16), (33, 32), (1000, 1), (1001, 1000), (65_535, 65_536), (65_537, 65_536), @@ -1184,12 +1270,12 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { /// The selector's order from a case's `abr` member: the policy over the state, on the /// sink's advertised capabilities. -pub(super) fn abr_order(input: &Value, advertises: Vec) -> Result Vec>>, String> { +pub(super) fn abr_order(input: &Value, advertises: Vec) -> Result Vec>>, Stop> { let a = &input["abr"]; if a.is_null() { return Ok(None); } - Ok(Some(abr::selector_order(policy(&a["policy"])?, state(&a["state"])?, move |c| advertises.iter().any(|x| x == c)))) + Ok(Some(abr::selector_order(policy(&a["policy"])?, state(&a["state"])?, move |c| advertises.iter().any(|x| x == c)).map_err(abr_err)?)) } diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs index a8046b346..6fb8973c5 100644 --- a/wai/wai-rs/src/http_binding/abr.rs +++ b/wai/wai-rs/src/http_binding/abr.rs @@ -13,13 +13,52 @@ //! - [`depth`] chooses how many layer objects (§10.4) a client fetches over a base. //! //! An option's energy enters only as a figure a receipt labels with its acquisition -//! class ([`labelled_figure`]); an unlabelled figure is never used, and a budget that -//! has no labelled figure to act on is reported as not applied ([`Budget::NotApplied`]). -//! Nothing here reads a clock: samples carry the caller's measurements. +//! class ([`labelled_figure`]); under a budget only an option with a labelled figure +//! within it is chosen, and a budget with no labelled figure to act on is reported as +//! not applied ([`Budget::NotApplied`]). Nothing here reads a clock: samples carry the +//! caller's measurements. +//! +//! Every input has the bounds Appendix F states ([`check_state`], [`Policy::check`]): +//! within them each quantity is computed exactly, in 128-bit integers and a widened +//! cross-multiplication, never saturated; beyond them a state is refused +//! (`abr_state_out_of_bounds`). use super::head::{Head, Version}; use crate::container::ManifestV2; +/// A state or a policy beyond Appendix F's bounds (`abr_state_out_of_bounds`), with +/// what is out of bounds. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct AbrError(pub &'static str); + +impl AbrError { + pub fn code(&self) -> &'static str { + "abr_state_out_of_bounds" + } +} + +impl std::fmt::Display for AbrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "abr_state_out_of_bounds: {}", self.0) + } +} + +impl std::error::Error for AbrError {} + +/// The most options a state holds, and the most objects (a base and its layers) a depth +/// is chosen over (Appendix F). +pub const MAX_OPTIONS: usize = 256; + +/// Appendix F's bounds on a state beyond those of its fields' types (each `ms` and `u32` +/// field below 2^32, each byte count, figure and budget below 2^64): at most +/// [`MAX_OPTIONS`] options and figures. +pub fn check_state(s: &AbrState) -> Result<(), AbrError> { + if s.option_bytes.len() > MAX_OPTIONS || s.option_joules_micro.len() > MAX_OPTIONS { + return Err(AbrError("more options than 256")); + } + Ok(()) +} + /// One measured request: `bytes` of body received in `ms` milliseconds, finished at /// `at_ms` on the caller's clock. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -55,8 +94,13 @@ pub trait Policy { /// The policy's identifier, as the vectors name it. fn id(&self) -> &'static str; /// The option chosen, before any budget: an index into `option_bytes` (0 when - /// there are none). + /// there are none). Exact for every value of the state's field types. fn choose(&self, s: &AbrState) -> usize; + /// Appendix F's bounds on the policy's parameters beyond their types; none for the + /// reference policies, whose every parameter value is in bounds. + fn check(&self) -> Result<(), AbrError> { + Ok(()) + } } /// The highest option whose rate fits the measured throughput, less a safety margin. @@ -102,7 +146,8 @@ pub struct Mpc { pub enum Budget { /// No budget is held. NotSet, - /// The chosen option has no labelled figure: the budget is not applied. + /// No option has a labelled figure: the budget is not applied, and the choice + /// stands. NotApplied, /// The chosen option's labelled figure is within the budget. Within, @@ -110,7 +155,8 @@ pub enum Budget { /// within the budget. Lowered, /// No option at or below the choice has a labelled figure within the budget: the - /// lowest option. + /// lowest option above it that has one, or, when no option has, the option with the + /// least labelled figure. Floor, } @@ -149,30 +195,46 @@ pub fn ilog2_q16(a: u64, b: u64) -> u64 { } /// The throughput the last `n` samples give, in bits per second: `⌊8000·Σbytes / Σms⌋`, -/// each sample's `ms` taken as at least 1; 0 with no sample. `n` 0 is taken as 1. -pub fn throughput_bps(samples: &[Sample], n: u8) -> u64 { +/// each sample's `ms` taken as at least 1; 0 with no sample. `n` 0 is taken as 1. Exact: +/// at most 255 samples of at most 2^64 − 1 bytes, below 2^85. +pub fn throughput_bps(samples: &[Sample], n: u8) -> u128 { let k = usize::from(n.max(1)).min(samples.len()); let tail = &samples[samples.len() - k..]; let (b, t) = tail.iter().fold((0u128, 0u128), |(b, t), s| (b + u128::from(s.bytes), t + u128::from(s.ms.max(1)))); if t == 0 { return 0; } - u64::try_from(b * 8000 / t).unwrap_or(u64::MAX) + b * 8000 / t } /// An option's rate in bits per second: `⌈8000·bytes / segment_ms⌉`, `segment_ms` taken -/// as at least 1. -pub fn option_bps(bytes: u64, segment_ms: u32) -> u64 { - u64::try_from((u128::from(bytes) * 8000).div_ceil(u128::from(segment_ms.max(1)))).unwrap_or(u64::MAX) +/// as at least 1. Exact: below 2^77. +pub fn option_bps(bytes: u64, segment_ms: u32) -> u128 { + (u128::from(bytes) * 8000).div_ceil(u128::from(segment_ms.max(1))) } /// The highest index whose rate is at most `budget_bps`; 0 when none is. -fn highest_under(s: &AbrState, budget_bps: u64) -> usize { +fn highest_under(s: &AbrState, budget_bps: u128) -> usize { s.option_bytes.iter().enumerate().rev().find(|(_, b)| option_bps(**b, s.segment_ms) <= budget_bps).map_or(0, |(k, _)| k) } -fn permille(x: u64, p: u16) -> u64 { - u64::try_from(u128::from(x) * u128::from(p) / 1000).unwrap_or(u64::MAX) +/// `⌊x·p/1000⌋`, exact for `x` below 2^111. +fn permille(x: u128, p: u16) -> u128 { + x * u128::from(p) / 1000 +} + +/// Whether `a·b > c·d` for `a`, `c` below 2^127 and `b`, `d` below 2^64: the products, +/// below 2^191, compared exactly in two 128-bit halves. +fn product_greater(a: u128, b: u64, c: u128, d: u64) -> bool { + // x·y = (x_hi·2^64 + x_lo)·y, with x_lo·y < 2^128 and x_hi·y < 2^127. + let wide = |x: u128, y: u64| -> (u128, u128) { + let (hi, lo) = (x >> 64, x & u128::from(u64::MAX)); + let low = lo * u128::from(y); + let mid = hi * u128::from(y); + let (sum, carry) = low.overflowing_add(mid << 64); + ((mid >> 64) + u128::from(carry), sum) + }; + wide(a, b) > wide(c, d) } impl Policy for Throughput { @@ -205,8 +267,9 @@ impl Policy for BufferBased { } let lo = option_bps(s.option_bytes[0], s.segment_ms); let hi = option_bps(s.option_bytes[k - 1], s.segment_ms); - let target = u128::from(lo) + u128::from(hi.saturating_sub(lo)) * u128::from(b - r) / u128::from(c); - highest_under(s, u64::try_from(target).unwrap_or(u64::MAX)) + // (hi − lo)·(b − r) is below 2^109. + let target = lo + hi.saturating_sub(lo) * u128::from(b - r) / u128::from(c); + highest_under(s, target) } } @@ -231,13 +294,15 @@ impl Policy for BolaLike { let u = utilities(s); let u_top = i128::from(*u.iter().max().unwrap_or(&0)); let (vn, vd) = ((qmax - 65536).max(0), u_top + 65536); + // V_n·v·(u_k + 65536) is below 2^103, and a score below 2^77 in magnitude. let score: Vec = u.iter().map(|&uk| vn * i128::from(self.v_milli) * (i128::from(uk) + 65536) / (vd * 1000) - q).collect(); - let size = |i: usize| i128::from(s.option_bytes[i].max(1)); + let size = |i: usize| s.option_bytes[i].max(1); let mut best: Option = None; if score.iter().any(|&n| n > 0) { for i in (0..k).filter(|&i| score[i] > 0) { - // score[i]/size(i) > score[b]/size(b), by cross-multiplication. - if best.is_none_or(|b| score[i] * size(b) > score[b] * size(i)) { + // score[i]/size(i) > score[b]/size(b), by cross-multiplication widened + // past 128 bits: a score times a size reaches 2^141. + if best.is_none_or(|b| product_greater(score[i] as u128, size(b), score[b] as u128, size(i))) { best = Some(i); } } @@ -268,7 +333,7 @@ impl Policy for Mpc { } let u = utilities(s); let u_top = i128::from(*u.iter().max().unwrap_or(&0)); - let seg = u64::from(s.segment_ms); + let seg = u128::from(s.segment_ms); let mut best: Option<(i128, usize)> = None; let total = (k as u64).pow(h); let mut seq = vec![0usize; h as usize]; @@ -279,17 +344,19 @@ impl Policy for Mpc { seq[t] = (c % k as u64) as usize; c /= k as u64; } - let (mut b, mut rebuf, mut qoe) = (u64::from(s.buffer_ms), 0u128, 0i128); + // Exact in 128 bits: a download below 2^77 ms, the buffer below 2^35, the + // stall below 2^80, and the stall times the utility bound below 2^103. + let (mut b, mut rebuf, mut qoe) = (u128::from(s.buffer_ms), 0u128, 0i128); let mut prev: Option = s.last.filter(|&l| l < k).map(|l| i128::from(u[l])); for &o in &seq { - let dl = u64::try_from((u128::from(s.option_bytes[o]) * 8000).div_ceil(u128::from(pred))).unwrap_or(u64::MAX); + let dl = (u128::from(s.option_bytes[o]) * 8000).div_ceil(pred); if dl > b { - rebuf += u128::from(dl - b); + rebuf += dl - b; b = 0; } else { b -= dl; } - b = b.saturating_add(seg); + b += seg; let uo = i128::from(u[o]); qoe += 1000 * uo; if let Some(p) = prev { @@ -297,7 +364,7 @@ impl Policy for Mpc { } prev = Some(uo); } - qoe -= i128::try_from(rebuf).unwrap_or(i128::MAX / 2).saturating_mul(u_top + 65536); + qoe -= rebuf as i128 * (u_top + 65536); if best.is_none_or(|(q, _)| qoe > q) { best = Some((qoe, seq[0])); } @@ -313,46 +380,74 @@ pub fn labelled_figure(joules_micro: u64, class: Option<&crate::energy_class::En (class.is_some() && joules_micro > 0 && crate::energy_class::figure_and_class_cohere(joules_micro, class)).then_some(joules_micro) } -/// Apply the state's joule budget to `choice`: unchanged when no budget is held, or the -/// choice has no labelled figure (the budget is not applied); unchanged when its figure -/// is within the budget; otherwise the highest option below it whose labelled figure is -/// within the budget, or the lowest option when none is. +/// Apply the state's joule budget to `choice` (Appendix F): unchanged when no budget is +/// held, or when no option has a labelled figure (the budget is not applied). Otherwise +/// only an option with a labelled figure within the budget is chosen: the choice when it +/// is one; else the highest such option below it; else the lowest such option above +/// it; else, no option being within the budget, the option with the least labelled +/// figure (the lower on a tie). An option is one of `option_bytes` (or the choice, with +/// none); a figure past them names no option. pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) { let Some(b) = s.joule_budget_micro else { return (choice, Budget::NotSet) }; - let fig = |k: usize| s.option_joules_micro.get(k).copied().flatten(); - match fig(choice) { - None => (choice, Budget::NotApplied), - Some(j) if j <= b => (choice, Budget::Within), - Some(_) => match (0..choice).rev().find(|&k| fig(k).is_some_and(|j| j <= b)) { - Some(k) => (k, Budget::Lowered), - None => (0, Budget::Floor), - }, + let n = s.option_bytes.len().max(choice + 1); + let fig = |k: usize| if k < n { s.option_joules_micro.get(k).copied().flatten() } else { None }; + let labelled: Vec<(usize, u64)> = (0..n).filter_map(|k| fig(k).map(|j| (k, j))).collect(); + if labelled.is_empty() { + return (choice, Budget::NotApplied); } + if fig(choice).is_some_and(|j| j <= b) { + return (choice, Budget::Within); + } + if let Some(&(k, _)) = labelled.iter().rev().find(|&&(k, j)| k < choice && j <= b) { + return (k, Budget::Lowered); + } + if let Some(&(k, _)) = labelled.iter().find(|&&(k, j)| k > choice && j <= b) { + return (k, Budget::Floor); + } + let least = labelled.iter().min_by_key(|&&(k, j)| (j, k)).map_or(0, |&(k, _)| k); + (least, Budget::Floor) } -/// The policy's choice under the state's budget. -pub fn decide(p: &dyn Policy, s: &AbrState) -> (usize, Budget) { - clamp_to_budget(s, p.choose(s)) +/// The policy's choice under the state's budget; refused beyond Appendix F's bounds. +pub fn decide(p: &dyn Policy, s: &AbrState) -> Result<(usize, Budget), AbrError> { + p.check()?; + check_state(s)?; + Ok(clamp_to_budget(s, p.choose(s))) } /// How many layers to fetch over a base (§10.4, the counterpart of a MoQ subscriber's /// layer cap): `layers_n[0]` is the base's `envelope_len` and `layers_n[k]` layer `k`'s; /// option `k` is fetching through layer `k`, its bytes the sum through it and its energy /// the sum of the state's per-object labelled figures through it (`None` when any is -/// unlabelled). Returns the depth (0: the base alone) and what the budget did. -pub fn depth(layers_n: &[u64], p: &dyn Policy, s: &AbrState) -> (usize, Budget) { - let mut st = s.clone(); - st.option_bytes = layers_n.iter().scan(0u64, |acc, &n| { - *acc = acc.saturating_add(n); - Some(*acc) - }).collect(); - st.option_joules_micro = (0..layers_n.len()).map(|k| { - (0..=k).try_fold(0u64, |acc, j| s.option_joules_micro.get(j).copied().flatten().map(|f| acc.saturating_add(f))) - }).collect(); - if layers_n.is_empty() { - return (0, clamp_to_budget(&st, 0).1); +/// unlabelled). Returns the depth (0: the base alone) and what the budget did. Refused +/// beyond Appendix F's bounds: more than [`MAX_OPTIONS`] objects, or the bytes or the +/// figure through a depth past 2^64 − 1. +pub fn depth(layers_n: &[u64], p: &dyn Policy, s: &AbrState) -> Result<(usize, Budget), AbrError> { + p.check()?; + check_state(s)?; + if layers_n.len() > MAX_OPTIONS { + return Err(AbrError("more layer objects than 256")); } - decide(p, &st) + let mut st = s.clone(); + let mut acc = 0u64; + st.option_bytes = Vec::with_capacity(layers_n.len()); + for &n in layers_n { + acc = acc.checked_add(n).ok_or(AbrError("the bytes through a depth pass 2^64 - 1"))?; + st.option_bytes.push(acc); + } + st.option_joules_micro = Vec::with_capacity(layers_n.len()); + let mut fig = Some(0u64); + for k in 0..layers_n.len() { + fig = match (fig, s.option_joules_micro.get(k).copied().flatten()) { + (Some(a), Some(f)) => Some(a.checked_add(f).ok_or(AbrError("the figure through a depth passes 2^64 - 1"))?), + _ => None, + }; + st.option_joules_micro.push(fig); + } + if layers_n.is_empty() { + return Ok((0, clamp_to_budget(&st, 0).1)); + } + Ok(clamp_to_budget(&st, p.choose(&st))) } /// SPEC §7.1 step 2's order for one `WAI2`, as a deployer policy: the selectable @@ -362,19 +457,22 @@ pub fn depth(layers_n: &[u64], p: &dyn Policy, s: &AbrState) -> (usize, Budget) /// figure of each entry by its table index; the group is ordered as the policy's choice /// under the budget, then the options below it from the nearest down, then those above /// it from the nearest up. Every other entry follows in table order. A `WAI1` gives -/// `[0]`. -pub fn order_entries(head: &Head, eligible: &dyn Fn(&str) -> bool, p: &dyn Policy, s: &AbrState) -> Vec { +/// `[0]`. Refused beyond Appendix F's bounds, a group of more than [`MAX_OPTIONS`] +/// entries among them. +pub fn order_entries(head: &Head, eligible: &dyn Fn(&str) -> bool, p: &dyn Policy, s: &AbrState) -> Result, AbrError> { + p.check()?; + check_state(s)?; if head.version != Version::Wai2 { - return vec![0]; + return Ok(vec![0]); } match head.manifest_v2() { Ok(m) => order_manifest(head, &m, eligible, p, s), - Err(_) => (0..head.components.len()).collect(), + Err(_) => Ok((0..head.components.len()).collect()), } } /// [`order_entries`] with the manifest already read from `head`. -pub fn order_manifest(head: &Head, m: &ManifestV2, eligible: &dyn Fn(&str) -> bool, p: &dyn Policy, s: &AbrState) -> Vec { +pub fn order_manifest(head: &Head, m: &ManifestV2, eligible: &dyn Fn(&str) -> bool, p: &dyn Policy, s: &AbrState) -> Result, AbrError> { let e = &m.renditions; let len = |i: usize| head.components.get(i).map_or(0, |c| c.len); let mut groups: Vec<(String, Vec)> = Vec::new(); @@ -393,7 +491,7 @@ pub fn order_manifest(head: &Head, m: &ManifestV2, eligible: &dyn Fn(&str) -> bo let mut st = s.clone(); st.option_bytes = g.iter().map(|&i| len(i)).collect(); st.option_joules_micro = g.iter().map(|&i| s.option_joules_micro.get(i).copied().flatten()).collect(); - let (c, _) = decide(p, &st); + let (c, _) = decide(p, &st)?; let c = c.min(g.len() - 1); order.push(g[c]); order.extend(g[..c].iter().rev()); @@ -401,15 +499,19 @@ pub fn order_manifest(head: &Head, m: &ManifestV2, eligible: &dyn Fn(&str) -> bo } let rest: Vec = (0..e.len()).filter(|i| !order.contains(i)).collect(); order.extend(rest); - order + Ok(order) } /// A [`super::session::Selector`] order from a policy and a state: [`order_manifest`] -/// over the head the session verified, on both paths. +/// over the head the session verified, on both paths. Refused when the policy or the +/// state is beyond Appendix F's bounds; a head whose group passes them keeps table +/// order, on which selection runs unchanged. pub fn selector_order( p: Box, s: AbrState, eligible: impl Fn(&str) -> bool + 'static, -) -> Box Vec> { - Box::new(move |h: &Head, m: &ManifestV2| order_manifest(h, m, &eligible, p.as_ref(), &s)) +) -> Result Vec>, AbrError> { + p.check()?; + check_state(&s)?; + Ok(Box::new(move |h: &Head, m: &ManifestV2| order_manifest(h, m, &eligible, p.as_ref(), &s).unwrap_or_else(|_| (0..m.renditions.len()).collect()))) } diff --git a/wai/wai-rs/src/http_binding/tests_abr.rs b/wai/wai-rs/src/http_binding/tests_abr.rs index 64fad70db..401835bda 100644 --- a/wai/wai-rs/src/http_binding/tests_abr.rs +++ b/wai/wai-rs/src/http_binding/tests_abr.rs @@ -127,26 +127,35 @@ fn mpc_shape() { assert!(Mpc { horizon: 5, safety_permille: 900 }.choose(&state(8000, 3_000_000, &many)) < 40); } -/// An unlabelled figure is never used for the budget: a choice with no labelled figure -/// is left as it is and reported as not applied, whatever the options below it state. +/// Under a budget only an option with a labelled figure within it is chosen: the +/// budget is not applied only when no option has a labelled figure; an unlabelled +/// choice does not escape it. #[test] -fn abr_budget_ignores_unlabelled() { +fn abr_budget_holds_labelled_options() { let mut s = state(0, 100_000_000, &OPTIONS); s.joule_budget_micro = Some(100); let p = Throughput { last_n: 1, safety_permille: 900 }; - assert_eq!(decide(&p, &s), (2, Budget::NotApplied)); + let d = |s: &AbrState| decide(&p, s).unwrap(); + assert_eq!(d(&s), (2, Budget::NotApplied)); s.option_joules_micro = vec![Some(10), Some(50), None]; - assert_eq!(decide(&p, &s), (2, Budget::NotApplied)); + assert_eq!(d(&s), (1, Budget::Lowered), "the unlabelled choice is lowered to the highest labelled option within the budget"); s.option_joules_micro = vec![Some(10), Some(50), Some(500)]; - assert_eq!(decide(&p, &s), (1, Budget::Lowered)); + assert_eq!(d(&s), (1, Budget::Lowered)); s.option_joules_micro = vec![Some(10), None, Some(500)]; - assert_eq!(decide(&p, &s), (0, Budget::Lowered), "the unlabelled option is passed over"); + assert_eq!(d(&s), (0, Budget::Lowered), "the unlabelled option is passed over"); s.option_joules_micro = vec![Some(200), None, Some(500)]; - assert_eq!(decide(&p, &s), (0, Budget::Floor)); + assert_eq!(d(&s), (0, Budget::Floor), "none within: the least labelled figure"); + s.option_joules_micro = vec![None, Some(300), Some(200)]; + assert_eq!(d(&s), (2, Budget::Floor), "none within: the least labelled figure, wherever it is"); s.option_joules_micro = vec![Some(10), Some(50), Some(100)]; - assert_eq!(decide(&p, &s), (2, Budget::Within)); + assert_eq!(d(&s), (2, Budget::Within)); + // A choice below every labelled option within the budget is raised to the lowest. + let mut low = state(0, 10_000, &OPTIONS); + low.joule_budget_micro = Some(100); + low.option_joules_micro = vec![None, Some(50), Some(80)]; + assert_eq!(d(&low), (1, Budget::Floor)); s.joule_budget_micro = None; - assert_eq!(decide(&p, &s), (2, Budget::NotSet)); + assert_eq!(d(&s), (2, Budget::NotSet)); // Only a figure with its class is a labelled figure. assert_eq!(labelled_figure(500, None), None); assert_eq!(labelled_figure(500, Some(&EnergyProvenance::ModelBased)), Some(500)); @@ -161,16 +170,59 @@ fn depth_over_layers() { let layers = [1825u64, 12_711, 18_367]; let p = Throughput { last_n: 1, safety_permille: 1000 }; // Through depth 2: 32,903 B a second, 263,224 b/s. - assert_eq!(depth(&layers, &p, &state(0, 263_224, &[])), (2, Budget::NotSet)); - assert_eq!(depth(&layers, &p, &state(0, 263_223, &[])), (1, Budget::NotSet)); - assert_eq!(depth(&layers, &p, &state(0, 14_600, &[])), (0, Budget::NotSet)); + assert_eq!(depth(&layers, &p, &state(0, 263_224, &[])).unwrap(), (2, Budget::NotSet)); + assert_eq!(depth(&layers, &p, &state(0, 263_223, &[])).unwrap(), (1, Budget::NotSet)); + assert_eq!(depth(&layers, &p, &state(0, 14_600, &[])).unwrap(), (0, Budget::NotSet)); let mut s = state(0, 10_000_000, &[]); s.joule_budget_micro = Some(30); s.option_joules_micro = vec![Some(5), Some(10), Some(20)]; - assert_eq!(depth(&layers, &p, &s), (1, Budget::Lowered), "5 + 10 + 20 is past 30"); + assert_eq!(depth(&layers, &p, &s).unwrap(), (1, Budget::Lowered), "5 + 10 + 20 is past 30"); s.option_joules_micro = vec![Some(5), None, Some(20)]; - assert_eq!(depth(&layers, &p, &s), (2, Budget::NotApplied), "an unlabelled layer leaves the sum unknown"); - assert_eq!(depth(&[], &p, &s).0, 0); + assert_eq!(depth(&layers, &p, &s).unwrap(), (0, Budget::Lowered), "an unlabelled layer leaves the sum through it unknown, so the base alone"); + s.option_joules_micro = vec![None, None, None]; + assert_eq!(depth(&layers, &p, &s).unwrap(), (2, Budget::NotApplied)); + assert_eq!(depth(&[], &p, &s).unwrap().0, 0); +} + +/// Appendix F's bounds: a state of more than 256 options, or a depth over more than 256 +/// objects or whose bytes or figure pass 2^64 − 1, is refused; within the bounds every +/// policy chooses at the extremes of its types without overflow. +#[test] +fn abr_bounds_and_extremes() { + let p = Throughput { last_n: 1, safety_permille: 900 }; + let code = |r: Result<(usize, Budget), AbrError>| r.unwrap_err().code(); + assert_eq!(code(decide(&p, &state(0, 1000, &[1; MAX_OPTIONS + 1]))), "abr_state_out_of_bounds"); + decide(&p, &state(0, 1000, &[1; MAX_OPTIONS])).unwrap(); + assert_eq!(code(depth(&[1; MAX_OPTIONS + 1], &p, &state(0, 1000, &[]))), "abr_state_out_of_bounds"); + assert_eq!(code(depth(&[u64::MAX, 1], &p, &state(0, 1000, &[]))), "abr_state_out_of_bounds"); + let mut s = state(0, 1000, &[]); + s.option_joules_micro = vec![Some(u64::MAX), Some(1)]; + assert_eq!(code(depth(&[1, 1], &p, &s)), "abr_state_out_of_bounds"); + // The extremes of every type. + let big = AbrState { + buffer_ms: u32::MAX, + samples: (0..300).map(|i| Sample { at_ms: i, bytes: u64::MAX, ms: if i % 2 == 0 { 0 } else { u64::MAX } }).collect(), + last: Some(1), + option_bytes: vec![1, u64::MAX / 3, u64::MAX - 1, u64::MAX], + option_joules_micro: vec![Some(u64::MAX), None, Some(1), Some(u64::MAX - 1)], + joule_budget_micro: Some(u64::MAX - 1), + segment_ms: 1, + }; + let policies: Vec> = vec![ + Box::new(Throughput { last_n: 255, safety_permille: u16::MAX }), + Box::new(BufferBased { reservoir_ms: 0, cushion_ms: u32::MAX }), + Box::new(BolaLike { v_milli: u32::MAX, max_buffer_ms: u32::MAX }), + Box::new(Mpc { horizon: 255, safety_permille: u16::MAX }), + ]; + for q in &policies { + for seg in [0, 1, u32::MAX] { + let st = AbrState { segment_ms: seg, ..big.clone() }; + let (k, _) = decide(q.as_ref(), &st).unwrap(); + assert!(k < 4, "{}", q.id()); + } + } + assert_eq!(throughput_bps(&big.samples, 255), (255u128 * u128::from(u64::MAX) * 8000) / (128 * u128::from(u64::MAX) + 127)); + assert_eq!(option_bps(u64::MAX, 1), u128::from(u64::MAX) * 8000); } fn appendix_d() -> Vec { @@ -202,20 +254,20 @@ fn order_entries_appendix_d() { let all = |_: &str| true; let p = Throughput { last_n: 1, safety_permille: 900 }; // `high` is 50,000 B a second: 400 kb/s. - assert_eq!(order_entries(&h, &all, &p, &state(0, 100_000, &[])), vec![0, 1, 2]); - assert_eq!(order_entries(&h, &all, &p, &state(0, 10_000_000, &[])), vec![1, 0, 2]); + assert_eq!(order_entries(&h, &all, &p, &state(0, 100_000, &[])).unwrap(), vec![0, 1, 2]); + assert_eq!(order_entries(&h, &all, &p, &state(0, 10_000_000, &[])).unwrap(), vec![1, 0, 2]); let only_vp9 = |c: &str| c == "wai.video.vp9"; - assert_eq!(order_entries(&h, &only_vp9, &p, &state(0, 10_000_000, &[])), vec![0, 1, 2]); + assert_eq!(order_entries(&h, &only_vp9, &p, &state(0, 10_000_000, &[])).unwrap(), vec![0, 1, 2]); let m = head_of(&mixed()); // av1 options by size: small (2), big (0); vp9 alone. - assert_eq!(order_entries(&m, &all, &p, &state(0, 10_000_000, &[])), vec![0, 2, 1]); - assert_eq!(order_entries(&m, &all, &p, &state(0, 10, &[])), vec![2, 0, 1]); + assert_eq!(order_entries(&m, &all, &p, &state(0, 10_000_000, &[])).unwrap(), vec![0, 2, 1]); + assert_eq!(order_entries(&m, &all, &p, &state(0, 10, &[])).unwrap(), vec![2, 0, 1]); } fn fetch_with(env: &[u8], adv: &'static [&'static str], p: Box, s: AbrState, frag: Fragment) -> Result { let proof = HeadProof::build(env).unwrap().to_bytes(); let mut sel = Selector::new(move |c| adv.contains(&c)); - sel.order = Some(selector_order(p, s, move |c| adv.contains(&c))); + sel.order = Some(selector_order(p, s, move |c| adv.contains(&c)).unwrap()); let mut sess = FetchSession::new(content_hash(env), frag, FetchPolicy::default(), sel); drive(&mut sess, |w| { let range = match w { @@ -259,7 +311,7 @@ fn same_pick_under_policies() { for (buffer, bps) in [(0u32, 50_000u64), (6000, 900_000), (20_000, 50_000_000)] { for (k, p) in policies().into_iter().enumerate() { let s = state(buffer, bps, &[]); - let order = order_entries(&h, &|c| adv.contains(&c), p.as_ref(), &s); + let order = order_entries(&h, &|c| adv.contains(&c), p.as_ref(), &s).unwrap(); let want = whole_pick(&env, adv, &order); let got = fetch_with(&env, adv, policies().remove(k), s, Fragment { h: Some(1009), n: Some(env.len() as u64), ..Default::default() }); match (&want, &got) { -- 2.54.0 (Apple Git-157) From e75d491b0badec8306c0f1e7ee18b567671abef0 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 10:27:26 -0400 Subject: [PATCH 28/55] abr: the widened cross-multiplication tested past 128 bits Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/http_binding/abr.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs index 6fb8973c5..1a13ecb7d 100644 --- a/wai/wai-rs/src/http_binding/abr.rs +++ b/wai/wai-rs/src/http_binding/abr.rs @@ -515,3 +515,30 @@ pub fn selector_order( check_state(&s)?; Ok(Box::new(move |h: &Head, m: &ManifestV2| order_manifest(h, m, &eligible, p.as_ref(), &s).unwrap_or_else(|_| (0..m.renditions.len()).collect()))) } + +#[cfg(test)] +mod tests { + use super::*; + + /// The widened cross-multiplication against products small enough for 128 bits, and + /// at the widths the utility-over-buffer rule reaches, where 128 bits wrap. + #[test] + fn product_greater_is_exact_past_128_bits() { + for (a, b, c, d) in [(3u128, 5u64, 4u128, 4u64), (4, 4, 3, 5), (7, 7, 7, 7), (0, 9, 0, 1), (1 << 100, 3, 1 << 101, 1)] { + assert_eq!(product_greater(a, b, c, d), a * u128::from(b) > c * u128::from(d), "{a}·{b} > {c}·{d}"); + } + // 2^76·(2^64 − 1) against (2^76 − 1)·(2^64 − 1) and 2^76·(2^64 − 2): both products + // pass 2^128, where a 128-bit product wraps. + let (big, top) = (1u128 << 76, u64::MAX); + assert!(product_greater(big, top, big - 1, top)); + assert!(product_greater(big, top, big, top - 1)); + assert!(!product_greater(big - 1, top, big, top)); + assert!(!product_greater(big, top, big, top)); + // Just past 2^128 against just below it: a 128-bit product wraps to the smaller. + let (a, b, c, d) = (1u128 << 65, (1u64 << 63) + 1, 1u128 << 64, u64::MAX); + assert!(product_greater(a, b, c, d) && !product_greater(c, d, a, b)); + assert!(a.wrapping_mul(u128::from(b)) < c.wrapping_mul(u128::from(d)), "128 bits would answer wrongly here"); + // The largest operands: (2^127 − 1)·(2^64 − 1). + assert!(product_greater(i128::MAX as u128, top, i128::MAX as u128 - 1, top)); + } +} -- 2.54.0 (Apple Git-157) From db548ab3a708597ba5098f4263e36d4b581318f9 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 10:37:20 -0400 Subject: [PATCH 29/55] http-conformance: a utility-over-buffer vector whose products pass 2^128, and the depth bytes bound alone Co-Authored-By: Claude Opus 5.5 --- wai/http-conformance/cases.json | 3 ++- wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index f83d56b2f..b080086c8 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -685,6 +685,7 @@ {"name":"abr_extreme_mpc_0","op":"abr","input":{"policy":{"id":"mpc","horizon":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":0}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_extreme_mpc_1","op":"abr","input":{"policy":{"id":"mpc","horizon":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_extreme_mpc_4294967295","op":"abr","input":{"policy":{"id":"mpc","horizon":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":4294967295}},"expect":{"choice":2,"budget":"not_set"}}, +{"name":"abr_extreme_bola_like_past_128_bits","op":"abr","input":{"policy":{"id":"bola_like","v_milli":4294967295,"max_buffer_ms":596001515},"state":{"buffer_ms":129,"samples":[],"last":null,"option_bytes":[3997960217448864544,16001797029153315873],"option_joules_micro":[null,null],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_extreme_budget","op":"abr","input":{"policy":{"id":"bola_like","v_milli":4294967295,"max_buffer_ms":4294967295},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":18446744073709551614,"segment_ms":1}},"expect":{"choice":2,"budget":"floor"}}, {"name":"abr_extreme_mpc_stalls","op":"abr","input":{"policy":{"id":"mpc","horizon":255,"safety_permille":65535},"state":{"buffer_ms":0,"samples":[[1,1,1]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_out_of_bounds_buffer","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":4294967296,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, @@ -699,7 +700,7 @@ {"name":"abr_out_of_bounds_safety","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":65536},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, {"name":"abr_out_of_bounds_v","op":"abr","input":{"policy":{"id":"bola_like","v_milli":4294967296,"max_buffer_ms":20000},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, {"name":"abr_out_of_bounds_horizon","op":"abr","input":{"policy":{"id":"mpc","horizon":256,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, -{"name":"depth_out_of_bounds_bytes","op":"depth","input":{"layers_n":[18446744073709551615,1],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[18446744073709551615,1],"joule_budget_micro":10,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, +{"name":"depth_out_of_bounds_bytes","op":"depth","input":{"layers_n":[18446744073709551615,1],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, {"name":"depth_out_of_bounds_figure","op":"depth","input":{"layers_n":[1,1],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1000,1000]],"last":null,"option_bytes":[],"option_joules_micro":[18446744073709551615,1],"joule_budget_micro":10,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, {"name":"depth_out_of_bounds_layers","op":"depth","input":{"layers_n":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, {"name":"depth_at_the_bound","op":"depth","input":{"layers_n":[18446744073709551614,1],"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"depth":0,"budget":"not_set"}}, diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index 3d4fecbb4..0da9b5a19 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -1181,6 +1181,11 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { v.push(case(format!("abr_extreme_{}_{seg}", p["id"].as_str().expect("id")), "abr", json!({ "policy": p, "state": e }))); } } + // Two options whose cross-multiplied scores pass 2^128: a 128-bit product wraps and + // picks the other. + v.push(case("abr_extreme_bola_like_past_128_bits", "abr", json!({ "policy": { "id": "bola_like", "v_milli": u32::MAX, "max_buffer_ms": 596_001_515u32 }, + "state": { "buffer_ms": 129, "samples": [], "last": null, "option_bytes": [3_997_960_217_448_864_544u64, 16_001_797_029_153_315_873u64], + "option_joules_micro": [null, null], "joule_budget_micro": null, "segment_ms": 1 } }))); let mut budgeted = edge.clone(); budgeted["joule_budget_micro"] = json!(u64::MAX - 1); v.push(case("abr_extreme_budget", "abr", json!({ "policy": extreme_policies[2], "state": budgeted }))); @@ -1213,7 +1218,7 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { } let deep = json!({ "buffer_ms": 0, "samples": [[1000, 1000, 1000]], "last": null, "option_bytes": [], "option_joules_micro": [u64::MAX, 1], "joule_budget_micro": 10, "segment_ms": 1000 }); - v.push(refused("depth_out_of_bounds_bytes", "depth", json!({ "layers_n": [u64::MAX, 1], "policy": thr, "state": deep }), "abr_state_out_of_bounds")); + v.push(refused("depth_out_of_bounds_bytes", "depth", json!({ "layers_n": [u64::MAX, 1], "policy": thr, "state": base }), "abr_state_out_of_bounds")); v.push(refused("depth_out_of_bounds_figure", "depth", json!({ "layers_n": [1, 1], "policy": thr, "state": deep }), "abr_state_out_of_bounds")); v.push(refused("depth_out_of_bounds_layers", "depth", json!({ "layers_n": vec![1u64; 257], "policy": thr, "state": base }), "abr_state_out_of_bounds")); v.push(case("depth_at_the_bound", "depth", json!({ "layers_n": [u64::MAX - 1, 1], "policy": thr, "state": base }))); -- 2.54.0 (Apple Git-157) From 10ab331ddbe34a7a247ae782d254ce0ea9f25a0f Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 10:39:19 -0400 Subject: [PATCH 30/55] http-conformance: falsify the budget rule, the bounds and the exact arithmetic of adaptation Co-Authored-By: Claude Opus 5.5 --- .../falsify/abr-options-unbounded.patch | 15 ++++++++++++++ .../falsify/abr-parameter-truncated.patch | 15 ++++++++++++++ .../falsify/bola-narrow-product.patch | 15 ++++++++++++++ .../falsify/budget-floor-zero.patch | 20 +++++++++++++++++++ .../falsify/budget-unlabelled-escapes.patch | 15 ++++++++++++++ .../falsify/budget-unlabelled.patch | 18 ++++++++--------- .../falsify/depth-per-layer.patch | 20 +++++++++---------- .../falsify/depth-sum-saturates.patch | 15 ++++++++++++++ .../falsify/mpc-no-stall.patch | 8 ++++---- 9 files changed, 118 insertions(+), 23 deletions(-) create mode 100644 wai/http-conformance/falsify/abr-options-unbounded.patch create mode 100644 wai/http-conformance/falsify/abr-parameter-truncated.patch create mode 100644 wai/http-conformance/falsify/bola-narrow-product.patch create mode 100644 wai/http-conformance/falsify/budget-floor-zero.patch create mode 100644 wai/http-conformance/falsify/budget-unlabelled-escapes.patch create mode 100644 wai/http-conformance/falsify/depth-sum-saturates.patch diff --git a/wai/http-conformance/falsify/abr-options-unbounded.patch b/wai/http-conformance/falsify/abr-options-unbounded.patch new file mode 100644 index 000000000..35ccb4fda --- /dev/null +++ b/wai/http-conformance/falsify/abr-options-unbounded.patch @@ -0,0 +1,15 @@ +# must fail: abr_out_of_bounds_options abr_out_of_bounds_figures +# options past the bound read (abr::check_state) +diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs +index 6fb8973c..9dace574 100644 +--- a/wai/wai-rs/src/http_binding/abr.rs ++++ b/wai/wai-rs/src/http_binding/abr.rs +@@ -53,7 +53,7 @@ pub const MAX_OPTIONS: usize = 256; + /// field below 2^32, each byte count, figure and budget below 2^64): at most + /// [`MAX_OPTIONS`] options and figures. + pub fn check_state(s: &AbrState) -> Result<(), AbrError> { +- if s.option_bytes.len() > MAX_OPTIONS || s.option_joules_micro.len() > MAX_OPTIONS { ++ if false { + return Err(AbrError("more options than 256")); + } + Ok(()) diff --git a/wai/http-conformance/falsify/abr-parameter-truncated.patch b/wai/http-conformance/falsify/abr-parameter-truncated.patch new file mode 100644 index 000000000..9191812a5 --- /dev/null +++ b/wai/http-conformance/falsify/abr-parameter-truncated.patch @@ -0,0 +1,15 @@ +# must fail: abr_out_of_bounds_last_n abr_out_of_bounds_horizon +# a parameter past its type truncated (the corpus tool's policy reading) +diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +index 3d4fecbb..b61542ec 100644 +--- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs ++++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +@@ -420,7 +420,7 @@ fn bounded_opt(v: &Value, max: u64, what: &str) -> Result, Stop> { + /// and the like, each parameter within its type (Appendix F). + pub(super) fn policy(v: &Value) -> Result, Stop> { + let u = |k: &str, max: u64| bounded(&v[k], max, &format!("policy.{k}")); +- let (u8m, u16m, u32m) = (u64::from(u8::MAX), u64::from(u16::MAX), u64::from(u32::MAX)); ++ let (u8m, u16m, u32m) = (u64::MAX, u64::MAX, u64::MAX); + Ok(match v["id"].as_str() { + Some("throughput") => Box::new(Throughput { last_n: u("last_n", u8m)? as u8, safety_permille: u("safety_permille", u16m)? as u16 }), + Some("buffer_based") => Box::new(BufferBased { reservoir_ms: u("reservoir_ms", u32m)? as u32, cushion_ms: u("cushion_ms", u32m)? as u32 }), diff --git a/wai/http-conformance/falsify/bola-narrow-product.patch b/wai/http-conformance/falsify/bola-narrow-product.patch new file mode 100644 index 000000000..25c2884bb --- /dev/null +++ b/wai/http-conformance/falsify/bola-narrow-product.patch @@ -0,0 +1,15 @@ +# must fail: abr_extreme_bola_like_past_128_bits +# the utility-over-buffer cross-multiplication in 128 bits, wrapping (abr::BolaLike::choose) +diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs +index 6fb8973c..0f72e915 100644 +--- a/wai/wai-rs/src/http_binding/abr.rs ++++ b/wai/wai-rs/src/http_binding/abr.rs +@@ -302,7 +302,7 @@ impl Policy for BolaLike { + for i in (0..k).filter(|&i| score[i] > 0) { + // score[i]/size(i) > score[b]/size(b), by cross-multiplication widened + // past 128 bits: a score times a size reaches 2^141. +- if best.is_none_or(|b| product_greater(score[i] as u128, size(b), score[b] as u128, size(i))) { ++ if best.is_none_or(|b| (score[i] as u128).wrapping_mul(u128::from(size(b))) > (score[b] as u128).wrapping_mul(u128::from(size(i)))) { + best = Some(i); + } + } diff --git a/wai/http-conformance/falsify/budget-floor-zero.patch b/wai/http-conformance/falsify/budget-floor-zero.patch new file mode 100644 index 000000000..93a6217e3 --- /dev/null +++ b/wai/http-conformance/falsify/budget-floor-zero.patch @@ -0,0 +1,20 @@ +# must fail: abr_budget_floor_least_figure abr_budget_floor_above_the_choice +# the floor is option 0, whatever its figure (abr::clamp_to_budget) +diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs +index 6fb8973c..13cbf426 100644 +--- a/wai/wai-rs/src/http_binding/abr.rs ++++ b/wai/wai-rs/src/http_binding/abr.rs +@@ -401,11 +401,8 @@ pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) { + if let Some(&(k, _)) = labelled.iter().rev().find(|&&(k, j)| k < choice && j <= b) { + return (k, Budget::Lowered); + } +- if let Some(&(k, _)) = labelled.iter().find(|&&(k, j)| k > choice && j <= b) { +- return (k, Budget::Floor); +- } +- let least = labelled.iter().min_by_key(|&&(k, j)| (j, k)).map_or(0, |&(k, _)| k); +- (least, Budget::Floor) ++ ++ (0, Budget::Floor) + } + + /// The policy's choice under the state's budget; refused beyond Appendix F's bounds. diff --git a/wai/http-conformance/falsify/budget-unlabelled-escapes.patch b/wai/http-conformance/falsify/budget-unlabelled-escapes.patch new file mode 100644 index 000000000..9b262d3fc --- /dev/null +++ b/wai/http-conformance/falsify/budget-unlabelled-escapes.patch @@ -0,0 +1,15 @@ +# must fail: abr_budget_unlabelled_choice abr_budget_floor_above_the_choice +# an unlabelled choice leaves the budget unapplied (abr::clamp_to_budget) +diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs +index 6fb8973c..1ad66ade 100644 +--- a/wai/wai-rs/src/http_binding/abr.rs ++++ b/wai/wai-rs/src/http_binding/abr.rs +@@ -392,7 +392,7 @@ pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) { + let n = s.option_bytes.len().max(choice + 1); + let fig = |k: usize| if k < n { s.option_joules_micro.get(k).copied().flatten() } else { None }; + let labelled: Vec<(usize, u64)> = (0..n).filter_map(|k| fig(k).map(|j| (k, j))).collect(); +- if labelled.is_empty() { ++ if labelled.is_empty() || fig(choice).is_none() { + return (choice, Budget::NotApplied); + } + if fig(choice).is_some_and(|j| j <= b) { diff --git a/wai/http-conformance/falsify/budget-unlabelled.patch b/wai/http-conformance/falsify/budget-unlabelled.patch index 2706fb8bc..cc6b284bb 100644 --- a/wai/http-conformance/falsify/budget-unlabelled.patch +++ b/wai/http-conformance/falsify/budget-unlabelled.patch @@ -1,15 +1,15 @@ -# must fail: abr_budget_not_applied depth_O10_budget_unlabelled_layer +# must fail: abr_budget_not_applied abr_budget_unlabelled_choice depth_O10_budget_unlabelled_layer # an unlabelled figure taken as zero joules (abr::clamp_to_budget) diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs -index a8046b34..e1c58f55 100644 +index 6fb8973c..5fbd3bc3 100644 --- a/wai/wai-rs/src/http_binding/abr.rs +++ b/wai/wai-rs/src/http_binding/abr.rs -@@ -319,7 +319,7 @@ pub fn labelled_figure(joules_micro: u64, class: Option<&crate::energy_class::En - /// within the budget, or the lowest option when none is. +@@ -390,7 +390,7 @@ pub fn labelled_figure(joules_micro: u64, class: Option<&crate::energy_class::En pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) { let Some(b) = s.joule_budget_micro else { return (choice, Budget::NotSet) }; -- let fig = |k: usize| s.option_joules_micro.get(k).copied().flatten(); -+ let fig = |k: usize| s.option_joules_micro.get(k).copied().flatten().or(Some(0)); - match fig(choice) { - None => (choice, Budget::NotApplied), - Some(j) if j <= b => (choice, Budget::Within), + let n = s.option_bytes.len().max(choice + 1); +- let fig = |k: usize| if k < n { s.option_joules_micro.get(k).copied().flatten() } else { None }; ++ let fig = |k: usize| if k < n { s.option_joules_micro.get(k).copied().flatten().or(Some(0)) } else { None }; + let labelled: Vec<(usize, u64)> = (0..n).filter_map(|k| fig(k).map(|j| (k, j))).collect(); + if labelled.is_empty() { + return (choice, Budget::NotApplied); diff --git a/wai/http-conformance/falsify/depth-per-layer.patch b/wai/http-conformance/falsify/depth-per-layer.patch index 15f0815d2..2673909c1 100644 --- a/wai/http-conformance/falsify/depth-per-layer.patch +++ b/wai/http-conformance/falsify/depth-per-layer.patch @@ -1,15 +1,15 @@ # must fail: depth_O10_throughput_between depth_O11b_bola_like_0 # a depth's bytes taken as its own layer's, not the sum through it (abr::depth) diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs -index a8046b34..1e6c1aa1 100644 +index 6fb8973c..c6472097 100644 --- a/wai/wai-rs/src/http_binding/abr.rs +++ b/wai/wai-rs/src/http_binding/abr.rs -@@ -343,7 +343,7 @@ pub fn decide(p: &dyn Policy, s: &AbrState) -> (usize, Budget) { - pub fn depth(layers_n: &[u64], p: &dyn Policy, s: &AbrState) -> (usize, Budget) { - let mut st = s.clone(); - st.option_bytes = layers_n.iter().scan(0u64, |acc, &n| { -- *acc = acc.saturating_add(n); -+ *acc = n; - Some(*acc) - }).collect(); - st.option_joules_micro = (0..layers_n.len()).map(|k| { +@@ -432,7 +432,7 @@ pub fn depth(layers_n: &[u64], p: &dyn Policy, s: &AbrState) -> Result<(usize, B + let mut acc = 0u64; + st.option_bytes = Vec::with_capacity(layers_n.len()); + for &n in layers_n { +- acc = acc.checked_add(n).ok_or(AbrError("the bytes through a depth pass 2^64 - 1"))?; ++ acc = n; + st.option_bytes.push(acc); + } + st.option_joules_micro = Vec::with_capacity(layers_n.len()); diff --git a/wai/http-conformance/falsify/depth-sum-saturates.patch b/wai/http-conformance/falsify/depth-sum-saturates.patch new file mode 100644 index 000000000..4c59cde59 --- /dev/null +++ b/wai/http-conformance/falsify/depth-sum-saturates.patch @@ -0,0 +1,15 @@ +# must fail: depth_out_of_bounds_bytes +# the bytes through a depth saturate (abr::depth) +diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs +index 6fb8973c..42b3495d 100644 +--- a/wai/wai-rs/src/http_binding/abr.rs ++++ b/wai/wai-rs/src/http_binding/abr.rs +@@ -432,7 +432,7 @@ pub fn depth(layers_n: &[u64], p: &dyn Policy, s: &AbrState) -> Result<(usize, B + let mut acc = 0u64; + st.option_bytes = Vec::with_capacity(layers_n.len()); + for &n in layers_n { +- acc = acc.checked_add(n).ok_or(AbrError("the bytes through a depth pass 2^64 - 1"))?; ++ acc = acc.saturating_add(n); + st.option_bytes.push(acc); + } + st.option_joules_micro = Vec::with_capacity(layers_n.len()); diff --git a/wai/http-conformance/falsify/mpc-no-stall.patch b/wai/http-conformance/falsify/mpc-no-stall.patch index a4d99c517..b5b6883a0 100644 --- a/wai/http-conformance/falsify/mpc-no-stall.patch +++ b/wai/http-conformance/falsify/mpc-no-stall.patch @@ -1,15 +1,15 @@ # must fail: abr_mpc_04 abr_order_O3_mpc_0 depth_O10_mpc_0 # the look-ahead does not count stalls (abr::Mpc::choose) diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs -index a8046b34..3e08bebc 100644 +index 6fb8973c..852404d4 100644 --- a/wai/wai-rs/src/http_binding/abr.rs +++ b/wai/wai-rs/src/http_binding/abr.rs -@@ -297,7 +297,7 @@ impl Policy for Mpc { +@@ -364,7 +364,7 @@ impl Policy for Mpc { } prev = Some(uo); } -- qoe -= i128::try_from(rebuf).unwrap_or(i128::MAX / 2).saturating_mul(u_top + 65536); -+ qoe -= i128::try_from(rebuf).unwrap_or(i128::MAX / 2).saturating_mul(0); +- qoe -= rebuf as i128 * (u_top + 65536); ++ qoe -= rebuf as i128 * 0; if best.is_none_or(|(q, _)| qoe > q) { best = Some((qoe, seq[0])); } -- 2.54.0 (Apple Git-157) From 1a3b7975c9f2312c67d2949097f814b4d6d1b268 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 10:52:24 -0400 Subject: [PATCH 31/55] http-conformance: the adaptation fuzz family reaches the extremes of Appendix F's bounds and just past them Co-Authored-By: Claude Opus 5.5 --- .../src/bin/wai_cli/http_vectors_playlists.rs | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index 0da9b5a19..770b6a94d 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -1539,13 +1539,58 @@ fn abr_case(s: &mut u64) -> Value { }).collect(); let figs: Vec = (0..k + next(s) % 2).map(|_| if next(s) % 3 == 0 { Value::Null } else { json!(next(s) % 10_000) }).collect(); let segment_ms = [0u64, 1, 40, 2000, 10_000][(next(s) % 5) as usize]; - json!({ "policy": p, "state": { + let mut case = json!({ "policy": p, "state": { "buffer_ms": next(s) % 70_000, "samples": samples, "last": if next(s) % 2 == 0 { Value::Null } else { json!(next(s) % 10) }, "option_bytes": opts, "option_joules_micro": figs, "joule_budget_micro": if next(s) % 2 == 0 { Value::Null } else { json!(next(s) % 12_000) }, "segment_ms": segment_ms, - } }) + } }); + // One case in three at the extremes of Appendix F's bounds, or just past them. + if next(s) % 3 == 0 { + let n_opts = case["state"]["option_bytes"].as_array().map_or(0, Vec::len); + let edge = |s: &mut u64, top: u64| -> Value { + match next(s) % 6 { + 0 => json!(top), + 1 => json!(top.saturating_sub(next(s) % 1000)), + 2 => json!(next(s) % (top / 2 + 1)), + 3 => json!(0), + 4 if top < u64::MAX => json!(top + 1), + _ => json!(next(s) >> (next(s) % 64) & top), + } + }; + for _ in 0..1 + next(s) % 4 { + match next(s) % 8 { + 0 => case["state"]["buffer_ms"] = edge(s, u64::from(u32::MAX)), + 1 => case["state"]["segment_ms"] = edge(s, u64::from(u32::MAX)), + 2 => { + let mut v: Vec = (0..n_opts.max(1)).map(|_| (next(s) | (1 << 63)) >> (next(s) % 40)).collect(); + v.sort_unstable(); + case["state"]["option_bytes"] = json!(v); + } + 3 => { + let samples: Vec = (0..1 + next(s) % 5).map(|i| { + let (bytes, ms) = (next(s) >> (next(s) % 8), [0, 1, u64::MAX][(next(s) % 3) as usize]); + json!([i, bytes, ms]) + }).collect(); + case["state"]["samples"] = json!(samples); + } + 4 => case["state"]["joule_budget_micro"] = edge(s, u64::MAX), + 5 => case["state"]["option_joules_micro"] = json!((0..n_opts).map(|_| if next(s) % 3 == 0 { Value::Null } else { edge(s, u64::MAX) }).collect::>()), + 6 => { + let p = &mut case["policy"]; + for (k, top) in [("last_n", 255u64), ("horizon", 255), ("safety_permille", 65_535), ("v_milli", u64::from(u32::MAX)), + ("max_buffer_ms", u64::from(u32::MAX)), ("reservoir_ms", u64::from(u32::MAX)), ("cushion_ms", u64::from(u32::MAX))] { + if !p[k].is_null() { + p[k] = edge(s, top); + } + } + } + _ => case["state"]["last"] = edge(s, u64::MAX), + } + } + } + case } /// A receipt file and its playlist, with one member of the file changed one way. -- 2.54.0 (Apple Git-157) From db79421e7854e01a2ee0fc897d3bf3d8162ce0a8 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 10:56:35 -0400 Subject: [PATCH 32/55] wai: a gap segment carries no parts or layer lines, in the renderer and http_verify.py Co-Authored-By: Claude Opus 5.5 --- wai/extensions/http-delivery.md | 3 ++- wai/http-conformance/cases.json | 4 ++++ wai/http-conformance/falsify/run.sh | 2 +- wai/http-conformance/http_verify.py | 2 ++ wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs | 12 ++++++++++++ wai/wai-rs/src/hls_render.rs | 6 +++++- wai/wai-rs/src/wai_video_manifest.rs | 8 ++++++++ 7 files changed, 34 insertions(+), 3 deletions(-) diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index 316f62769..d0df8eff8 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -1121,7 +1121,8 @@ begins with `#` or holds CR or LF (or `"`, where quoted, the map's among them); program date-time outside ASCII letters, digits and `:.+-`; layer numbers outside 1 to 255 or not increasing; parts with no part target; a part target with no part hold-back of at least twice it; -a preload hint on any but the last extra, or in a playlist that ends; an open segment +a preload hint on any but the last extra, or in a playlist that ends; a gap segment +with parts or layer lines (it offers no object to divide or refine); an open segment in a playlist that ends, with layer lines or a gap of its own, with a discontinuity or date-time before any part, or with neither a part nor a hint; version 0. diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index b080086c8..13092affa 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -374,6 +374,10 @@ {"name":"mpd_rep_id_unicode_space","op":"mpd","input":{"rep_id":"wai 180","playlist":"playlists/o9/wai-180.m3u8","classical":"src/playlists/o9-classical.m3u8","classical_mpd":"src/playlists/o9-classical.mpd","classical_set":"1","set_id":1},"expect":{"error":"playlist_malformed"}}, {"name":"media_playlist_short_O9","op":"media_playlist","input":{"classical":"src/playlists/o9-classical.m3u8","uri_prefix":"","segments":[{"object":"O9a"},{"object":"O9b"}]},"expect":{"error":"adjacency_misaligned"}}, {"name":"media_playlist_parts_beside_no_target","op":"media_playlist","input":{"classical":"src/playlists/o9-classical.m3u8","uri_prefix":"","segments":[{"object":"O9a","parts":[{"object":"O12p0_00","alias":"p0.wai","independent":true,"duration_ms":320},{"object":"O12p0_01","alias":"p1.wai","independent":true,"duration_ms":320}]},{"object":"O9b"},{"object":"O9c"}]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"media_playlist_gap_with_layers","op":"media_playlist","input":{"classical":"src/playlists/o11-classical.m3u8","uri_prefix":"","segments":[{"object":"O11a"},{"object":"O11b","gap":true,"layers":["O11bL1"]}]},"expect":{"error":"playlist_malformed"}}, +{"name":"render_gap_with_parts","op":"render","input":{"segments":[[640,"a.wai",false],[640,"b.wai",false]],"render":{"version":9,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":null,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":true,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"p.wai","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]},"expect":{"error":"playlist_malformed"}}, +{"name":"render_gap_with_layers","op":"render","input":{"segments":[[640,"a.wai",false],[640,"b.wai",false]],"render":{"version":9,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":null,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":true,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"l.wai"}],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]},"expect":{"error":"playlist_malformed"}}, +{"name":"render_gap","op":"render","input":{"segments":[[640,"a.wai",false],[640,"b.wai",false]],"render":{"version":9,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":null,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":true,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]},"expect":{"blake3":"eace1c6cc401981177eb98a6c981c02d13c9ad07f89db07bceeafc5592491695","bytes":218}}, {"name":"media_playlist_layer_not_a_layer","op":"media_playlist","input":{"classical":"src/playlists/o9-classical.m3u8","uri_prefix":"","segments":[{"object":"O9a","layers":["O9b"]},{"object":"O9b"},{"object":"O9c"}]},"expect":{"error":"not_a_layer"}}, {"name":"media_playlist_open_without_classical","op":"media_playlist","input":{"classical":"src/playlists/o9-classical.m3u8","uri_prefix":"","segments":[{"object":"O9a"},{"object":"O9b"},{"object":"O9c"}],"open":{"parts":[{"object":"O12p1_00","alias":"p.wai","independent":true,"duration_ms":320}]}},"expect":{"error":"adjacency_misaligned"}}, {"name":"playlist_parse_playlists_o9_wai_180_m3u8","op":"playlist_parse","input":{"file":"playlists/o9/wai-180.m3u8"},"expect":{"version":7,"target_duration_s":1,"media_sequence":0,"discontinuity_sequence":0,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null,"segments":[[640,false,null,[],[],"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009",false],[640,false,null,[],[],"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004",false],[640,false,null,[],[],"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381",false]],"open":null,"preload_hint":null,"rendered_form":true}}, diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh index ddfef3be4..11f740e57 100755 --- a/wai/http-conformance/falsify/run.sh +++ b/wai/http-conformance/falsify/run.sh @@ -50,7 +50,7 @@ for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done t ilog2-no-interp bola-waits unlabelled-as-zero round-half-down small-order-key small-order-r \ rebind-uri rebind-sequence key-unchecked walk-unbounded reload-walked \ xml-char-unchecked xml-attribute-twice dash-start-ignored dash-timescale-ignored \ - unlabelled-escapes abr-unbounded; do + unlabelled-escapes abr-unbounded gap-carries; do if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then echo "::error::http_verify.py --mutate $m passed"; fail=1 else diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index cc412041a..efc6a8fdd 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -1784,6 +1784,8 @@ def check_render(segs, r, extras): raise bad layers_ok(q["layers"]) layers_ok(e["layers"]) + if i < n and e.get("gap") and (e["parts"] or e["layers"]) and MUTATE != "gap-carries": + raise bad if e["hint"] is not None and (i + 1 != len(extras) or r["end"] or not uri_ok(e["hint"], True)): raise bad if i == n: diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index 770b6a94d..1362e09de 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -911,6 +911,18 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { o9(json!([{ "object": "O9a", "parts": [{ "object": part_id(0, 0), "alias": "p0.wai", "independent": true, "duration_ms": 320 }, { "object": part_id(0, 1), "alias": "p1.wai", "independent": true, "duration_ms": 320 }] }, seg("O9b"), seg("O9c")])), "adjacency_misaligned")); + // A gap offers no object, so nothing refines or divides it (Appendix G). + v.push(refused("media_playlist_gap_with_layers", "media_playlist", json!({ "classical": "src/playlists/o11-classical.m3u8", "uri_prefix": "", + "segments": [seg("O11a"), { "object": "O11b", "gap": true, "layers": ["O11bL1"] }] }), "playlist_malformed")); + let gap_render = |extra: Value| json!({ + "segments": [[640, "a.wai", false], [640, "b.wai", false]], + "render": { "version": 9, "media_sequence": 0, "discontinuity_sequence": 0, "target_duration_s": null, "part_target_ms": 320, "part_hold_back_ms": 960, + "can_block_reload": false, "end_list": true, "playlist_type": null, "independent_segments": false, "map_uri": null }, + "extras": [extra, { "discontinuity": false, "gap": false, "program_date_time": null, "parts": [], "layers": [], "preload_hint": null }] }); + let part = json!({ "duration_ms": 320, "independent": true, "uri": "p.wai", "layers": [] }); + v.push(refused("render_gap_with_parts", "render", gap_render(json!({ "discontinuity": false, "gap": true, "program_date_time": null, "parts": [part], "layers": [], "preload_hint": null })), "playlist_malformed")); + v.push(refused("render_gap_with_layers", "render", gap_render(json!({ "discontinuity": false, "gap": true, "program_date_time": null, "parts": [], "layers": [{ "n": 1, "uri": "l.wai" }], "preload_hint": null })), "playlist_malformed")); + v.push(case("render_gap", "render", gap_render(json!({ "discontinuity": false, "gap": true, "program_date_time": null, "parts": [], "layers": [], "preload_hint": null })))); v.push(refused("media_playlist_layer_not_a_layer", "media_playlist", o9(json!([{ "object": "O9a", "layers": ["O9b"] }, seg("O9b"), seg("O9c")])), "not_a_layer")); v.push(refused("media_playlist_open_without_classical", "media_playlist", json!({ diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs index 7e29b8dd0..ba3f11d4d 100644 --- a/wai/wai-rs/src/hls_render.rs +++ b/wai/wai-rs/src/hls_render.rs @@ -315,7 +315,8 @@ fn check_layers(layers: &[LayerLine], at: &str) -> Result<(), RenderError> { /// twice it (the HLS second edition requires both); /// - every URI is non-empty, does not begin with `#`, and holds no CR or LF, and a /// quoted one (a part's, a layer's, a hint's, the map's) no `"`; -/// - the open segment has no gap; +/// - the open segment has no gap, and a gap segment carries no parts or layer lines +/// (it offers no object there, so nothing refines or divides one); /// - a program date-time is ASCII letters, digits and `:.+-` only; /// - layer numbers are 1 to 255 and strictly increasing after each segment or part; /// - the version is at least 1. @@ -358,6 +359,9 @@ pub fn check_render(segments: &[Segment], r: &HlsRender, extras: &[SegmentExtra] check_layers(&p.layers, &at)?; } check_layers(&e.layers, &at)?; + if i < n && e.gap && (!e.parts.is_empty() || !e.layers.is_empty()) { + return Err(RenderError(format!("{at}: a gap carries parts or layer lines"))); + } if let Some(h) = &e.preload_hint { if i + 1 != extras.len() || r.end_list { return Err(RenderError(format!("{at}: a preload hint is on the last entry of a playlist that does not end"))); diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs index 6315ee926..779b63415 100644 --- a/wai/wai-rs/src/wai_video_manifest.rs +++ b/wai/wai-rs/src/wai_video_manifest.rs @@ -1104,6 +1104,14 @@ mod tests { assert!(seal(segments(), past, vec![]).is_err(), "no canonical form"); let hold = HlsRender { part_target_ms: Some(320), part_hold_back_ms: Some(639), ..live_render(0) }; assert!(seal(vec![], hold, vec![]).is_err()); + // A gap offers no object: no parts and no layer lines on it; a bare gap renders. + let live = HlsRender { part_target_ms: Some(320), part_hold_back_ms: Some(960), ..HlsRender::legacy() }; + let gap = SegmentExtra { gap: true, ..Default::default() }; + seal(segments(), live.clone(), vec![gap.clone(), Default::default(), Default::default()]).unwrap(); + let gap_parts = SegmentExtra { parts: vec![part(0, 0)], ..gap.clone() }; + assert_eq!(seal(segments(), live.clone(), vec![gap_parts, Default::default(), Default::default()]).unwrap_err().code(), "playlist_malformed"); + let gap_layers = SegmentExtra { layers: vec![LayerLine { n: 1, uri: "l.wai".into() }], ..gap }; + assert_eq!(seal(segments(), live, vec![gap_layers, Default::default(), Default::default()]).unwrap_err().code(), "playlist_malformed"); } /// A receipt file is read strictly: an unknown member, a missing one, an upper-case -- 2.54.0 (Apple Git-157) From 438c51f69ef0eb3121779b671625fd1cb6412cbc Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 10:57:18 -0400 Subject: [PATCH 33/55] http-conformance: falsify the gap rule Co-Authored-By: Claude Opus 5.5 --- wai/http-conformance/falsify/gap-carries.patch | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 wai/http-conformance/falsify/gap-carries.patch diff --git a/wai/http-conformance/falsify/gap-carries.patch b/wai/http-conformance/falsify/gap-carries.patch new file mode 100644 index 000000000..b6767dde8 --- /dev/null +++ b/wai/http-conformance/falsify/gap-carries.patch @@ -0,0 +1,15 @@ +# must fail: render_gap_with_parts render_gap_with_layers media_playlist_gap_with_layers +# a gap segment with parts or layer lines rendered (hls_render::check_render) +diff --git a/wai/wai-rs/src/hls_render.rs b/wai/wai-rs/src/hls_render.rs +index ba3f11d4..ba588609 100644 +--- a/wai/wai-rs/src/hls_render.rs ++++ b/wai/wai-rs/src/hls_render.rs +@@ -359,7 +359,7 @@ pub fn check_render(segments: &[Segment], r: &HlsRender, extras: &[SegmentExtra] + check_layers(&p.layers, &at)?; + } + check_layers(&e.layers, &at)?; +- if i < n && e.gap && (!e.parts.is_empty() || !e.layers.is_empty()) { ++ if false && i < n && e.gap && (!e.parts.is_empty() || !e.layers.is_empty()) { + return Err(RenderError(format!("{at}: a gap carries parts or layer lines"))); + } + if let Some(h) = &e.preload_hint { -- 2.54.0 (Apple Git-157) From a8ec3ed08af5197f19720bd6ba816f2816f19077 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:04:29 -0400 Subject: [PATCH 34/55] wai: both playlist receipt readers refuse a member twice and a lone surrogate (playlist_receipt_malformed) ManifestReceipt::from_json refuses a member name repeated in any object, names compared after their escapes are read; http_verify.py refuses the same through object_pairs_hook and refuses an unpaired surrogate as serde_json does. Corpus cases and receipt fuzz edits for each; receipt_diff.py gives no disagreement. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/http-delivery.md | 5 +- wai/http-conformance/cases.json | 8 +++ wai/http-conformance/falsify/run.sh | 2 +- wai/http-conformance/http_verify.py | 25 ++++++- .../src/bin/wai_cli/http_vectors_playlists.rs | 27 ++++++- wai/wai-rs/src/wai_video_manifest.rs | 70 +++++++++++++++++++ 6 files changed, 132 insertions(+), 5 deletions(-) diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index d0df8eff8..a3900a63e 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -1142,7 +1142,10 @@ null —, `independent_segments`, `map_uri`) with `extras` (each `discontinuity` `program_date_time`, `parts` — each `duration_ms`, `independent`, `uri`, `layers` —, `layers` — each `n`, `uri` —, `preload_hint`), the two together or not at all. Hashes are lowercase hexadecimal; a number is an unsigned integer written -as digits alone. +as digits alone. The file is read as I-JSON (RFC 7493): no object holds a member name +twice, at any depth and with names compared after their escapes are read, and no +string holds a `\u` escape that leaves a surrogate unpaired; either is +`playlist_receipt_malformed`, so no two readers can read two receipts from one file. Below, `‖` concatenates, integers are big-endian of the stated width in bytes, and a string is its UTF-8 length in 4 bytes, then its bytes: diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index 13092affa..64de78286 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -486,6 +486,14 @@ {"name":"playlist_receipt_extras_equivalent","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"extras\":[]","\"extras\":[{\"discontinuity\":false,\"gap\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null},{\"discontinuity\":false,\"gap\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null},{\"discontinuity\":false,\"gap\":false,\"program_date_time\":null,\"parts\":[],\"layers\":[],\"preload_hint\":null}]"]]},"expect":{"error":"playlist_receipt_invalid"}}, {"name":"playlist_receipt_kind","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"kind\":\"wai.video.manifest\"","\"kind\":\"wai.video.package\""]]},"expect":{"error":"playlist_receipt_malformed"}}, {"name":"playlist_receipt_unknown_member","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["{\"kind\"","{\"note\":1,\"kind\""]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_duplicate_first","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["{\"kind\"","{\"signer_id\":\"did:example:other\",\"kind\""]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_duplicate_last","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"signer_id\":\"did:example:http-vectors\"","\"signer_id\":\"did:example:http-vectors\",\"signer_id\":\"did:example:other\""]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_duplicate_same_value","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"signer_id\":\"did:example:http-vectors\"","\"signer_id\":\"did:example:http-vectors\",\"signer_id\":\"did:example:http-vectors\""]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_duplicate_in_class","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"geo\":\"\"","\"geo\":\"\",\"geo\":\"\""]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_duplicate_escaped_name","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["{\"kind\"","{\"\\u0073igner_id\":\"did:example:other\",\"kind\""]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_lone_surrogate","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"geo\":\"\"","\"geo\":\"\\ud800\""]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_lone_low_surrogate","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"signer_id\":\"did:example:http-vectors\"","\"signer_id\":\"did:example:http-vectors\\udfff\""]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_surrogate_pair","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"geo\":\"\"","\"geo\":\"\\ud83d\\ude00\""]]},"expect":{"error":"playlist_receipt_invalid"}}, {"name":"playlist_receipt_sig_key_identity","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-key-identity.json","name":false},"expect":{"error":"playlist_receipt_invalid"}}, {"name":"playlist_receipt_sig_key_identity_long","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-key-identity-long.json","name":false},"expect":{"error":"playlist_receipt_invalid"}}, {"name":"playlist_receipt_sig_r_small_order","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-r-small-order.json","name":false},"expect":{"error":"playlist_receipt_invalid"}}, diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh index 11f740e57..e5cfb2941 100755 --- a/wai/http-conformance/falsify/run.sh +++ b/wai/http-conformance/falsify/run.sh @@ -50,7 +50,7 @@ for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done t ilog2-no-interp bola-waits unlabelled-as-zero round-half-down small-order-key small-order-r \ rebind-uri rebind-sequence key-unchecked walk-unbounded reload-walked \ xml-char-unchecked xml-attribute-twice dash-start-ignored dash-timescale-ignored \ - unlabelled-escapes abr-unbounded gap-carries; do + unlabelled-escapes abr-unbounded gap-carries receipt-duplicates receipt-surrogates; do if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then echo "::error::http_verify.py --mutate $m passed"; fail=1 else diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index efc6a8fdd..fb8cb944b 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -2509,12 +2509,33 @@ class NotUnsigned: def read_receipt(text): """The receipt file (§8.6), read strictly; ReceiptMalformed otherwise. A number is an unsigned integer written as digits alone.""" + def pairs(kv): + # A member name twice in one object (I-JSON, RFC 7493 §2.3), names compared as read. + if len({k for k, _ in kv}) != len(kv) and MUTATE != "receipt-duplicates": + raise ReceiptMalformed() + return dict(kv) + try: - o = json.loads(text, parse_int=lambda t: NotUnsigned() if t.startswith("-") else int(t), parse_float=lambda t: NotUnsigned()) - except ValueError: + o = json.loads(text, object_pairs_hook=pairs, parse_int=lambda t: NotUnsigned() if t.startswith("-") else int(t), parse_float=lambda t: NotUnsigned()) + except (ValueError, RecursionError): raise ReceiptMalformed() bad = ReceiptMalformed + def texts(v): + if isinstance(v, str): + yield v + elif isinstance(v, dict): + for k, x in v.items(): + yield k + yield from texts(x) + elif isinstance(v, list): + for x in v: + yield from texts(x) + + # A \u escape that leaves a surrogate unpaired denotes no Unicode text (RFC 8259 §8.2). + if any(0xD800 <= ord(c) <= 0xDFFF for t in texts(o) for c in t) and MUTATE != "receipt-surrogates": + raise bad() + def obj(v, n): if not isinstance(v, dict) or len(v) != n: raise bad() diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index 1362e09de..41050a465 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -1074,6 +1074,20 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { v.push(refused("playlist_receipt_extras_equivalent", "playlist_receipt", base(json!({ "edit_receipt": [["\"extras\":[]", format!("\"extras\":[{default_extra},{default_extra},{default_extra}]")]] })), "playlist_receipt_invalid")); v.push(refused("playlist_receipt_kind", "playlist_receipt", base(json!({ "edit_receipt": [["\"kind\":\"wai.video.manifest\"", "\"kind\":\"wai.video.package\""]] })), "playlist_receipt_malformed")); v.push(refused("playlist_receipt_unknown_member", "playlist_receipt", base(json!({ "edit_receipt": [["{\"kind\"", "{\"note\":1,\"kind\""]] })), "playlist_receipt_malformed")); + // A member twice in one object, however placed or spelled, and a lone surrogate: a + // reader keeping the first and one keeping the last would read two receipts. + for (name, from, to) in [ + ("playlist_receipt_duplicate_first", "{\"kind\"", "{\"signer_id\":\"did:example:other\",\"kind\""), + ("playlist_receipt_duplicate_last", "\"signer_id\":\"did:example:http-vectors\"", "\"signer_id\":\"did:example:http-vectors\",\"signer_id\":\"did:example:other\""), + ("playlist_receipt_duplicate_same_value", "\"signer_id\":\"did:example:http-vectors\"", "\"signer_id\":\"did:example:http-vectors\",\"signer_id\":\"did:example:http-vectors\""), + ("playlist_receipt_duplicate_in_class", "\"geo\":\"\"", "\"geo\":\"\",\"geo\":\"\""), + ("playlist_receipt_duplicate_escaped_name", "{\"kind\"", "{\"\\u0073igner_id\":\"did:example:other\",\"kind\""), + ("playlist_receipt_lone_surrogate", "\"geo\":\"\"", "\"geo\":\"\\ud800\""), + ("playlist_receipt_lone_low_surrogate", "\"signer_id\":\"did:example:http-vectors\"", "\"signer_id\":\"did:example:http-vectors\\udfff\""), + ] { + v.push(refused(name, "playlist_receipt", base(json!({ "edit_receipt": [[from, to]] })), "playlist_receipt_malformed")); + } + v.push(case("playlist_receipt_surrogate_pair", "playlist_receipt", base(json!({ "edit_receipt": [["\"geo\":\"\"", "\"geo\":\"\\ud83d\\ude00\""]] })))); // Ed25519 at its edges (§8.6 step 4, strictly): keys and R a lenient verifier accepts. for (file, _, _, _) in crafted_signatures() { let name = format!("playlist_receipt_{}", file.trim_end_matches(".json").replace('-', "_")); @@ -1617,7 +1631,13 @@ fn receipt_case(c: &Corpus, s: &mut u64) -> Result { if !t.contains(m) || next(s) % 5 == 0 { return Ok(json!({ "playlist": playlist, "receipt": receipt })); } - let to = match next(s) % 8 { + // A member whole (its name and its value), which can be written twice. + let whole = !m.ends_with(':') && !m.ends_with('['); + let escaped_name = || { + let first = m.chars().nth(1).unwrap_or('a'); + format!("\"\\u{:04x}{}", first as u32, &m[2..]) + }; + let to = match next(s) % 11 { 0 => format!("{m}0"), 1 => m.replacen(':', ":\"", 1) + "\"", 2 => m.replacen("\":", "x\":", 1), @@ -1625,6 +1645,11 @@ fn receipt_case(c: &Corpus, s: &mut u64) -> Result { 4 => m.replacen(':', ":-", 1), 5 => m.replacen(':', ":1.0e0,\"z\":", 1), 6 => m.to_uppercase(), + // A member written twice, as it is or under an escaped name. + 8 if whole => format!("{m},{m}"), + 9 if whole => format!("{m},{}", escaped_name()), + // A lone surrogate at the end of a string value. + 10 if m.ends_with('"') => format!("{}\\u{:x}\"", &m[..m.len() - 1], [0xd800u32, 0xdbff, 0xdc00, 0xdfff][(next(s) % 4) as usize]), _ => format!("{m}{m}").replacen("\":", "\":", 1), }; Ok(json!({ "playlist": playlist, "receipt": receipt, "edit_receipt": [[m, to]] })) diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs index 779b63415..b639ad0b7 100644 --- a/wai/wai-rs/src/wai_video_manifest.rs +++ b/wai/wai-rs/src/wai_video_manifest.rs @@ -178,6 +178,51 @@ pub struct ManifestReceipt { pub extras: Vec, } +/// The first member name an object of `text` (valid JSON) holds twice, names compared +/// after their escapes are read. +fn duplicate_member(text: &str) -> Option { + // Each open container: for an object, the names read so far and whether the next + // string is a name; for an array, none. + let mut stack: Vec, bool)>> = Vec::new(); + let b = text.as_bytes(); + let mut i = 0; + while i < b.len() { + match b[i] { + b'{' => stack.push(Some((std::collections::BTreeSet::new(), true))), + b'[' => stack.push(None), + b'}' | b']' => { + stack.pop(); + } + b',' => { + if let Some(Some((_, expect))) = stack.last_mut() { + *expect = true; + } + } + b':' => { + if let Some(Some((_, expect))) = stack.last_mut() { + *expect = false; + } + } + b'"' => { + let start = i; + i += 1; + while i < b.len() && b[i] != b'"' { + i += if b[i] == b'\\' { 2 } else { 1 }; + } + if let Some(Some((names, true))) = stack.last_mut() { + let name: String = serde_json::from_str(&text[start..=i.min(b.len() - 1)]).ok()?; + if !names.insert(name.clone()) { + return Some(name); + } + } + } + _ => {} + } + i += 1; + } + None +} + /// The JSON of a rendering's parameters and extras, as [`ManifestReceipt::render_digest`] /// canonicalises it and the receipt file carries it. pub fn render_json(r: &HlsRender) -> serde_json::Value { @@ -528,7 +573,15 @@ impl ManifestReceipt { /// [`Self::verify`] or [`Self::verify_render`]. pub fn from_json(text: &str) -> Result { use serde_json::Value; + // RFC 8259 text; a `\u` escape that leaves a surrogate unpaired is refused by the + // parser (it denotes no Unicode text). let v: Value = serde_json::from_str(text).map_err(|e| e.to_string())?; + // A member name twice in one object is refused, at any depth and however the + // names are escaped: a reader that keeps the first and one that keeps the last + // would read two receipts from one file (I-JSON, RFC 7493 §2.3). + if let Some(name) = duplicate_member(text) { + return Err(format!("the member {name:?} twice in one object")); + } let o = v.as_object().ok_or("a receipt is a JSON object")?; const KNOWN: &[&str] = &[ "kind", "manifest_hash", "session_class_hash", "class", "segments", "merkle_root", "work_compose_ops", @@ -1133,5 +1186,22 @@ mod tests { assert!(edit(&|o| { o.insert("sig".into(), serde_json::json!(hex(&r.sig).to_uppercase())); }).is_err()); assert!(edit(&|o| { o.remove("extras"); }).is_err()); assert!(edit(&|o| { o["segments"][0]["kind"] = serde_json::json!("Content"); }).is_err()); + // A member twice, at the top, inside the class and inside a segment, first or last, + // and under an escaped spelling of its name; and a lone surrogate. + let text = r.to_json(); + let twice = |from: &str, to: &str| { + assert!(text.contains(from), "{from}"); + ManifestReceipt::from_json(&text.replacen(from, to, 1)) + }; + assert!(twice("{\"kind\"", "{\"signer_id\":\"x\",\"kind\"").is_err()); + assert!(twice("\"sig\":", "\"signer_id\":\"x\",\"sig\":").is_err()); + assert!(twice("\"geo\":\"\"", "\"geo\":\"\",\"geo\":\"x\"").is_err()); + assert!(twice("\"duration_ms\":", "\"duration_ms\":1,\"duration_ms\":").is_err()); + assert!(twice("{\"kind\"", "{\"\\u0073igner_id\":\"x\",\"kind\"").is_err()); + assert!(twice("\"geo\":\"\"", "\"geo\":\"\\ud800\"").is_err()); + // The same names in different objects are not duplicates. + assert!(ManifestReceipt::from_json(&text).is_ok()); + assert_eq!(duplicate_member(r#"{"a":{"a":1},"b":[{"a":1},{"a":2}],"c":"{\"a\":1,\"a\":2}"}"#), None); + assert_eq!(duplicate_member(r#"{"a":1,"\u0061":2}"#), Some("a".into())); } } -- 2.54.0 (Apple Git-157) From 238234a32c1e6738b48988092fae56dce08f9ee2 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:06:33 -0400 Subject: [PATCH 35/55] wai_video_manifest: the duplicate-member test holds escaped quotes and backslashes Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/wai_video_manifest.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs index b639ad0b7..793f34513 100644 --- a/wai/wai-rs/src/wai_video_manifest.rs +++ b/wai/wai-rs/src/wai_video_manifest.rs @@ -1203,5 +1203,9 @@ mod tests { assert!(ManifestReceipt::from_json(&text).is_ok()); assert_eq!(duplicate_member(r#"{"a":{"a":1},"b":[{"a":1},{"a":2}],"c":"{\"a\":1,\"a\":2}"}"#), None); assert_eq!(duplicate_member(r#"{"a":1,"\u0061":2}"#), Some("a".into())); + // A quote escaped inside a name or a value does not end the string. + assert_eq!(duplicate_member(r#"{"a\"b":1,"a\"b":2}"#), Some("a\"b".into())); + assert_eq!(duplicate_member(r#"{"a":"x\",\"a\":1","b":2}"#), None); + assert_eq!(duplicate_member(r#"{"a":"\\","a":2}"#), Some("a".into())); } } -- 2.54.0 (Apple Git-157) From 88374a97012e924b079cc4ebbb1f89c80848aa66 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:06:39 -0400 Subject: [PATCH 36/55] http-conformance: falsify the receipt reader's duplicate-member refusal Co-Authored-By: Claude Opus 5.5 --- .../falsify/receipt-duplicate-names-raw.patch | 15 +++++++++++++++ .../falsify/receipt-duplicates.patch | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 wai/http-conformance/falsify/receipt-duplicate-names-raw.patch create mode 100644 wai/http-conformance/falsify/receipt-duplicates.patch diff --git a/wai/http-conformance/falsify/receipt-duplicate-names-raw.patch b/wai/http-conformance/falsify/receipt-duplicate-names-raw.patch new file mode 100644 index 000000000..27d334c07 --- /dev/null +++ b/wai/http-conformance/falsify/receipt-duplicate-names-raw.patch @@ -0,0 +1,15 @@ +# must fail: playlist_receipt_duplicate_escaped_name +# member names compared as written, not as read (ManifestReceipt::from_json) +diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs +index b639ad0b..7658b047 100644 +--- a/wai/wai-rs/src/wai_video_manifest.rs ++++ b/wai/wai-rs/src/wai_video_manifest.rs +@@ -210,7 +210,7 @@ fn duplicate_member(text: &str) -> Option { + i += if b[i] == b'\\' { 2 } else { 1 }; + } + if let Some(Some((names, true))) = stack.last_mut() { +- let name: String = serde_json::from_str(&text[start..=i.min(b.len() - 1)]).ok()?; ++ let name: String = text[start..=i.min(b.len() - 1)].to_string(); + if !names.insert(name.clone()) { + return Some(name); + } diff --git a/wai/http-conformance/falsify/receipt-duplicates.patch b/wai/http-conformance/falsify/receipt-duplicates.patch new file mode 100644 index 000000000..fcae10b0e --- /dev/null +++ b/wai/http-conformance/falsify/receipt-duplicates.patch @@ -0,0 +1,15 @@ +# must fail: playlist_receipt_duplicate_first playlist_receipt_duplicate_last playlist_receipt_duplicate_in_class playlist_receipt_duplicate_escaped_name +# a receipt file with a member twice read (ManifestReceipt::from_json) +diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs +index b639ad0b..368608bf 100644 +--- a/wai/wai-rs/src/wai_video_manifest.rs ++++ b/wai/wai-rs/src/wai_video_manifest.rs +@@ -579,7 +579,7 @@ impl ManifestReceipt { + // A member name twice in one object is refused, at any depth and however the + // names are escaped: a reader that keeps the first and one that keeps the last + // would read two receipts from one file (I-JSON, RFC 7493 §2.3). +- if let Some(name) = duplicate_member(text) { ++ if let Some(name) = duplicate_member(text).filter(|_| false) { + return Err(format!("the member {name:?} twice in one object")); + } + let o = v.as_object().ok_or("a receipt is a JSON object")?; -- 2.54.0 (Apple Git-157) From fdb3f315851cd223dd19a7b3421aa8957e9a7f3c Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:12:31 -0400 Subject: [PATCH 37/55] wai: the session-data line has one place in the classical master, which classical_unchanged enforces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the first line and the EXT-X-VERSION and EXT-X-INDEPENDENT-SEGMENTS lines that follow it, before any other line (§11.1); a line between an EXT-X-STREAM-INF and its URI, before the version line or at the end is classical_changed, in Rust and http_verify.py. insert_session_data writes it there; corpus cases for each place. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/http-delivery.md | 9 ++- wai/http-conformance/cases.json | 8 +++ wai/http-conformance/falsify/run.sh | 2 +- .../falsify/session-data-first.patch | 15 ----- wai/http-conformance/http_verify.py | 50 +++++++++++------ .../src/bin/wai_cli/http_vectors_playlists.rs | 20 +++++-- wai/wai-rs/src/http_binding/hls.rs | 55 ++++++++++++++----- .../src/http_binding/tests_playlists.rs | 19 ++++++- 8 files changed, 120 insertions(+), 58 deletions(-) delete mode 100644 wai/http-conformance/falsify/session-data-first.patch diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index a3900a63e..86aa180d3 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -676,9 +676,12 @@ receipt stays valid for the objects it did receive ([jwp-receipts](jwp-receipts. tab, LF and CR as character references, and a value holding a character that is not an XML `Char` is not written (`playlist_malformed`). - The published classical multivariant playlist is its original, byte for byte, or the - original with the session-data line inserted after its first line (its own line end, - LF or CR LF). A packager MUST NOT publish one that differs otherwise - (`classical_changed`). + original with the session-data line (its own line end, LF or CR LF) inserted at one + place: after the first line and the `EXT-X-VERSION` and `EXT-X-INDEPENDENT-SEGMENTS` + lines that directly follow it, before any other line. A packager MUST NOT publish one + that differs otherwise (`classical_changed`): the line anywhere else, between an + `EXT-X-STREAM-INF` and its URI among them, is refused, as is an original whose line + before that place does not end. ### 11.2 Alignment diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index 64de78286..f5a151218 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -471,6 +471,14 @@ {"name":"unchanged_other_data_id","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["science.transaction.wai.multivariant","science.transaction.wai.other"]]},"expect":{"error":"classical_changed"}}, {"name":"unchanged_trailing_space","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["VALUE=\"wai.m3u8\"\n","VALUE=\"wai.m3u8\" \n"]]},"expect":{"error":"classical_changed"}}, {"name":"unchanged_first_line","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n",""],["#EXTM3U\n","#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n#EXTM3U\n"]]},"expect":{"error":"classical_changed"}}, +{"name":"unchanged_before_version","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n",""],["#EXTM3U\n","#EXTM3U\n#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n"]]},"expect":{"error":"classical_changed"}}, +{"name":"unchanged_between_variant_and_uri","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n",""],["classical/v0.m3u8","#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\nclassical/v0.m3u8"]]},"expect":{"error":"classical_changed"}}, +{"name":"unchanged_at_the_end","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n",""],["classical/v0.m3u8\n","classical/v0.m3u8\n#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n"]]},"expect":{"error":"classical_changed"}}, +{"name":"unchanged_blank_line_too","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n","#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n\n"]]},"expect":{"error":"classical_changed"}}, +{"name":"unchanged_crlf_line","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n","#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\r\n"]]},"expect":{"unchanged":true}}, +{"name":"unchanged_after_the_first_line","op":"unchanged","input":{"original_text":"#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1\nv.m3u8\n","published_text":"#EXTM3U\n#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n#EXT-X-STREAM-INF:BANDWIDTH=1\nv.m3u8\n"},"expect":{"unchanged":true}}, +{"name":"unchanged_bare_after_the_variant","op":"unchanged","input":{"original_text":"#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1\nv.m3u8\n","published_text":"#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1\n#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\nv.m3u8\n"},"expect":{"error":"classical_changed"}}, +{"name":"unchanged_no_place","op":"unchanged","input":{"original_text":"#EXTM3U\n#EXT-X-VERSION:7","published_text":"#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n"},"expect":{"error":"classical_changed"}}, {"name":"playlist_receipt_playlists_o9_wai_180_m3u8","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json"},"expect":{"signed":"4b4657cea62905884efdfc59c357fd259910c5ddfd6c5314d6ed6c9894c80f13","parent":null}}, {"name":"playlist_receipt_playlists_o10_wai_a_m3u8","op":"playlist_receipt","input":{"playlist":"playlists/o10/wai-a.m3u8","receipt":"playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json"},"expect":{"signed":"73416370f12adade369abf7a0e48aa7963ca1135536a7c7edd08d4dec08b674e","parent":null}}, {"name":"playlist_receipt_playlists_o11_wai_v_m3u8","op":"playlist_receipt","input":{"playlist":"playlists/o11/wai-v.m3u8","receipt":"playlists/o11/mr/044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79.json"},"expect":{"signed":"461a7871f56c0e69017be36a7dce1ca89f4501b3c546280a48b4de0e729398ea","parent":null}}, diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh index e5cfb2941..e8da87849 100755 --- a/wai/http-conformance/falsify/run.sh +++ b/wai/http-conformance/falsify/run.sh @@ -50,7 +50,7 @@ for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done t ilog2-no-interp bola-waits unlabelled-as-zero round-half-down small-order-key small-order-r \ rebind-uri rebind-sequence key-unchecked walk-unbounded reload-walked \ xml-char-unchecked xml-attribute-twice dash-start-ignored dash-timescale-ignored \ - unlabelled-escapes abr-unbounded gap-carries receipt-duplicates receipt-surrogates; do + unlabelled-escapes abr-unbounded gap-carries receipt-duplicates receipt-surrogates session-data-anywhere; do if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then echo "::error::http_verify.py --mutate $m passed"; fail=1 else diff --git a/wai/http-conformance/falsify/session-data-first.patch b/wai/http-conformance/falsify/session-data-first.patch deleted file mode 100644 index 62d1d9e8f..000000000 --- a/wai/http-conformance/falsify/session-data-first.patch +++ /dev/null @@ -1,15 +0,0 @@ -# must fail: unchanged_first_line -# the session-data line allowed before #EXTM3U (hls::classical_unchanged) -diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs -index 2623644b..c0d86962 100644 ---- a/wai/wai-rs/src/http_binding/hls.rs -+++ b/wai/wai-rs/src/http_binding/hls.rs -@@ -688,7 +688,7 @@ pub fn classical_unchanged(original: &str, published: &str) -> Result<(), Playli - return Ok(()); - } - let lines: Vec<&str> = published.split_inclusive('\n').collect(); -- for (i, l) in lines.iter().enumerate().skip(1) { -+ for (i, l) in lines.iter().enumerate() { - let bare = l.strip_suffix('\n').map(|x| x.strip_suffix('\r').unwrap_or(x)).unwrap_or(l); - if !l.ends_with('\n') || !is_session_data_line(bare) { - continue; diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index fb8cb944b..2a87125e7 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -1912,26 +1912,42 @@ def bandwidths(objs): SESSION_DATA = '#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="' +def session_data_position(original): + """§11.1: the offset after the first line and the EXT-X-VERSION and + EXT-X-INDEPENDENT-SEGMENTS lines that directly follow it; None when there is none.""" + at = 0 + lines = original.split("\n") + lines = [l + "\n" for l in lines[:-1]] + ([lines[-1]] if lines[-1] else []) + for i, l in enumerate(lines): + bare = l[:-1] if l.endswith("\n") else l + bare = bare[:-1] if l.endswith("\n") and bare.endswith("\r") else bare + if i > 0 and not (bare.startswith("#EXT-X-VERSION:") or bare == "#EXT-X-INDEPENDENT-SEGMENTS") and MUTATE != "session-data-anywhere": + break + if not l.endswith("\n"): + return None + at += len(l.encode("utf-8")) + if MUTATE == "session-data-anywhere": + break + return at if at > 0 else None + + def classical_unchanged(original, published): if original == published: return - lines = published.split("\n") - lines = [l + "\n" for l in lines[:-1]] + ([lines[-1]] if lines[-1] else []) - for i in range(1, len(lines)): - l = lines[i] - if not l.endswith("\n"): - continue - bare = l[:-1] - if bare.endswith("\r"): - bare = bare[:-1] - if not (bare.startswith(SESSION_DATA) and bare.endswith('"')): - continue - uri = bare[len(SESSION_DATA):-1] - if not uri or any(ch in uri for ch in '"\r\n'): - continue - if "".join(lines[:i] + lines[i + 1:]) == original: - return - raise Refusal("classical_changed") + o, p = original.encode("utf-8"), published.encode("utf-8") + at = session_data_position(original) + if at is None or not (p.startswith(o[:at]) and p.endswith(o[at:]) and len(p) > len(o)): + raise Refusal("classical_changed") + inserted = p[at:len(p) - (len(o) - at)].decode("utf-8") + if not inserted.endswith("\n"): + raise Refusal("classical_changed") + bare = inserted[:-1] + bare = bare[:-1] if bare.endswith("\r") else bare + if "\n" in bare or not (bare.startswith(SESSION_DATA) and bare.endswith('"')): + raise Refusal("classical_changed") + uri = bare[len(SESSION_DATA):-1] + if not uri or any(ch in uri for ch in '"\r\n'): + raise Refusal("classical_changed") def listing_lines(p): diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index 41050a465..aef932c6a 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -17,7 +17,7 @@ use wai::http_binding::codecs::{self, SeqObject}; use wai::http_binding::dash::{wai_mpd, DashTimeline, WaiRepresentation}; use wai::http_binding::head::{parse_head, HeadParse}; use wai::http_binding::hls::{ - bandwidths, check_adjacent, classical_unchanged, listed_cid, parse_media_playlist, session_data_line, wai_media_playlist, wai_multivariant, + bandwidths, check_adjacent, classical_unchanged, listed_cid, parse_media_playlist, wai_media_playlist, wai_multivariant, MediaPlaylist, PlaylistError, VariantLine, WaiLayer, WaiOpen, WaiPart, WaiSegment, }; use wai::http_binding::{content_hash, hex, parse_fragment, unhex32}; @@ -151,11 +151,9 @@ fn receipt_specs() -> Vec<(&'static str, &'static str, Option)> { } /// The published classical master of the player fixture: the original with the -/// session-data line before its first variant. +/// session-data line in its place (§11.1). fn published_master(original: &str) -> Result { - let line = session_data_line("wai.m3u8").map_err(|e| e.to_string())?; - let at = original.find("#EXT-X-STREAM-INF").ok_or("the master has no variant")?; - Ok(format!("{}{line}\n{}", &original[..at], &original[at..])) + wai::http_binding::hls::insert_session_data(original, "wai.m3u8").map_err(|e| e.to_string()) } // -------------------------------------------------------------- evaluation helpers @@ -1049,9 +1047,21 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { ("unchanged_other_data_id", json!([["science.transaction.wai.multivariant", "science.transaction.wai.other"]])), ("unchanged_trailing_space", json!([["VALUE=\"wai.m3u8\"\n", "VALUE=\"wai.m3u8\" \n"]])), ("unchanged_first_line", json!([[sd, ""], ["#EXTM3U\n", format!("{sd}#EXTM3U\n")]])), + ("unchanged_before_version", json!([[sd, ""], ["#EXTM3U\n", format!("#EXTM3U\n{sd}")]])), + ("unchanged_between_variant_and_uri", json!([[sd, ""], ["classical/v0.m3u8", format!("{sd}classical/v0.m3u8")]])), + ("unchanged_at_the_end", json!([[sd, ""], ["classical/v0.m3u8\n", format!("classical/v0.m3u8\n{sd}")]])), + ("unchanged_blank_line_too", json!([[sd, format!("{sd}\n")]])), ] { v.push(refused(name, "unchanged", un(edits), "classical_changed")); } + v.push(case("unchanged_crlf_line", "unchanged", un(json!([[sd, sd.replace('\n', "\r\n")]])))); + // A master with neither EXT-X-VERSION nor EXT-X-INDEPENDENT-SEGMENTS: the line follows + // the first. + let bare = "#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1\nv.m3u8\n"; + v.push(case("unchanged_after_the_first_line", "unchanged", json!({ "original_text": bare, "published_text": format!("#EXTM3U\n{sd}#EXT-X-STREAM-INF:BANDWIDTH=1\nv.m3u8\n") }))); + v.push(refused("unchanged_bare_after_the_variant", "unchanged", json!({ "original_text": bare, "published_text": format!("#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1\n{sd}v.m3u8\n") }), "classical_changed")); + let unterminated = "#EXTM3U\n#EXT-X-VERSION:7"; + v.push(refused("unchanged_no_place", "unchanged", json!({ "original_text": unterminated, "published_text": format!("#EXTM3U\n#EXT-X-VERSION:7\n{sd}") }), "classical_changed")); // Playlist receipts (§8.6): each, and each way one is refused. for (p, _, _) in receipt_specs() { diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs index 265df4e97..107ae8a1e 100644 --- a/wai/wai-rs/src/http_binding/hls.rs +++ b/wai/wai-rs/src/http_binding/hls.rs @@ -804,26 +804,53 @@ fn is_session_data_line(line: &str) -> bool { .is_some_and(|u| session_data_line(u).is_ok()) } +/// §11.1: where the session-data line goes in a classical multivariant playlist, as a +/// byte offset: after its first line and the `EXT-X-VERSION` and +/// `EXT-X-INDEPENDENT-SEGMENTS` lines that directly follow it, before any other line. +/// `None` for an empty playlist, or when the line before that place does not end. +pub fn session_data_position(original: &str) -> Option { + let mut at = 0; + for (i, l) in original.split_inclusive('\n').enumerate() { + let bare = l.strip_suffix('\n').map(|x| x.strip_suffix('\r').unwrap_or(x)).unwrap_or(l); + if i > 0 && !(bare.starts_with("#EXT-X-VERSION:") || bare == "#EXT-X-INDEPENDENT-SEGMENTS") { + break; + } + if !l.ends_with('\n') { + return None; + } + at += l.len(); + } + (at > 0).then_some(at) +} + +/// The classical multivariant playlist as published with the session-data line naming +/// `uri` (§11.1), at [`session_data_position`], ending as the playlist's first line +/// ends. Refused (`playlist_malformed`) when the URI cannot be written or the playlist +/// has no such place. +pub fn insert_session_data(original: &str, uri: &str) -> Result { + let line = session_data_line(uri)?; + let at = session_data_position(original).ok_or_else(|| malformed("the playlist has no place for the session-data line"))?; + let eol = if original[..at].split_inclusive('\n').next().is_some_and(|l| l.ends_with("\r\n")) { "\r\n" } else { "\n" }; + Ok(format!("{}{line}{eol}{}", &original[..at], &original[at..])) +} + /// §11.1: the published classical playlist is its original, byte for byte, or the -/// original with one line added after its first: a session-data line of -/// [`session_data_line`]'s form (its line end the line's own, LF or CR LF). Anything -/// else is `classical_changed`. +/// original with one session-data line of [`session_data_line`]'s form (ending LF or CR +/// LF) inserted at [`session_data_position`]. Anything else — the line anywhere else, +/// between an `EXT-X-STREAM-INF` and its URI among them — is `classical_changed`. pub fn classical_unchanged(original: &str, published: &str) -> Result<(), PlaylistError> { if original == published { return Ok(()); } - let lines: Vec<&str> = published.split_inclusive('\n').collect(); - for (i, l) in lines.iter().enumerate().skip(1) { - let bare = l.strip_suffix('\n').map(|x| x.strip_suffix('\r').unwrap_or(x)).unwrap_or(l); - if !l.ends_with('\n') || !is_session_data_line(bare) { - continue; - } - let without: String = lines.iter().enumerate().filter(|(j, _)| *j != i).map(|(_, x)| *x).collect(); - if without == original { - return Ok(()); - } + let changed = || PlaylistError::ClassicalChanged("the published playlist is not the original with at most the session-data line in its place".into()); + let at = session_data_position(original).ok_or_else(changed)?; + let (head, tail) = original.split_at(at); + let inserted = published.strip_prefix(head).and_then(|r| r.strip_suffix(tail)).ok_or_else(changed)?; + let bare = inserted.strip_suffix('\n').map(|x| x.strip_suffix('\r').unwrap_or(x)).ok_or_else(changed)?; + if bare.contains('\n') || !is_session_data_line(bare) { + return Err(changed()); } - Err(PlaylistError::ClassicalChanged("the published playlist is not the original with at most the session-data line added".into())) + Ok(()) } /// The classical package's segment map beside a WAI media playlist (§11.6): for each diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index fca0fdee6..2fa5cdc24 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -210,20 +210,33 @@ fn hls_port_corrections() { } /// §11.1: the published classical playlist is the original, or the original with the -/// one session-data line added; anything else is `classical_changed`. +/// one session-data line in its one place (after the first line and the version and +/// independence lines that follow it); anything else is `classical_changed`. #[test] fn classical_left_unchanged() { let master = "#EXTM3U\n#EXT-X-VERSION:6\n#EXT-X-STREAM-INF:BANDWIDTH=800000,CODECS=\"avc1.42e01e\"\nv0.m3u8\n"; let line = session_data_line("wai.m3u8").unwrap(); assert_eq!(line, "#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\""); classical_unchanged(master, master).unwrap(); + assert_eq!(session_data_position(master), Some("#EXTM3U\n#EXT-X-VERSION:6\n".len())); for at in [1usize, 2, 3, 4] { let mut lines: Vec = master.lines().map(|l| format!("{l}\n")).collect(); lines.insert(at, format!("{line}\n")); - classical_unchanged(master, &lines.concat()).unwrap(); + let r = classical_unchanged(master, &lines.concat()); + assert_eq!(r.is_ok(), at == 2, "the line at {at}"); } + assert_eq!(insert_session_data(master, "wai.m3u8").unwrap(), master.replacen("6\n", &format!("6\n{line}\n"), 1)); + let both = "#EXTM3U\n#EXT-X-INDEPENDENT-SEGMENTS\n#EXT-X-VERSION:7\n# a note\n#EXT-X-STREAM-INF:BANDWIDTH=1\nv.m3u8\n"; + assert_eq!(session_data_position(both), Some(both.find("# a note").unwrap())); + assert_eq!(session_data_position(""), None); + assert_eq!(session_data_position("#EXTM3U"), None); + assert_eq!(session_data_position("#EXTM3U\n#EXT-X-VERSION:7"), None); let crlf = master.replace('\n', "\r\n"); - classical_unchanged(&crlf, &crlf.replacen("\r\n", &format!("\r\n{line}\r\n"), 1)).unwrap(); + let crlf_published = insert_session_data(&crlf, "wai.m3u8").unwrap(); + assert!(crlf_published.contains(&format!("6\r\n{line}\r\n"))); + classical_unchanged(&crlf, &crlf_published).unwrap(); + classical_unchanged(&crlf, &crlf_published.replacen(&format!("{line}\r\n"), &format!("{line}\n"), 1)).unwrap(); + assert!(classical_unchanged(&crlf, &crlf.replacen("\r\n", &format!("\r\n{line}\r\n"), 1)).is_err(), "before the version line"); let changed = |published: String| classical_unchanged(master, &published).unwrap_err().code(); assert_eq!(changed(format!("{line}\n{master}")), "classical_changed"); assert_eq!(changed(master.replacen("\n", &format!("\n{line}\n{line}\n"), 1)), "classical_changed"); -- 2.54.0 (Apple Git-157) From 25ae35760ff3ef8b4df8a7ad31dc9fe020bc2ab1 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:14:06 -0400 Subject: [PATCH 38/55] hls: the unchanged-master test holds more than the line in its place Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/include/wai.h | 6 ++++++ wai/wai-rs/src/http_binding/tests_playlists.rs | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/wai/wai-rs/include/wai.h b/wai/wai-rs/include/wai.h index 57e905f88..8ebbc7161 100644 --- a/wai/wai-rs/include/wai.h +++ b/wai/wai-rs/include/wai.h @@ -316,6 +316,12 @@ */ #define RESOLUTION_FLOOR_UJ_PPM 288676 +/** + * The most options a state holds, and the most objects (a base and its layers) a depth + * is chosen over (Appendix F). + */ +#define MAX_OPTIONS 256 + /** * The most chaining values a proof may carry. */ diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index 2fa5cdc24..28ee63c80 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -237,6 +237,10 @@ fn classical_left_unchanged() { classical_unchanged(&crlf, &crlf_published).unwrap(); classical_unchanged(&crlf, &crlf_published.replacen(&format!("{line}\r\n"), &format!("{line}\n"), 1)).unwrap(); assert!(classical_unchanged(&crlf, &crlf.replacen("\r\n", &format!("\r\n{line}\r\n"), 1)).is_err(), "before the version line"); + // In its place, the line and anything more. + for more in [format!("{line}\n{line}\n"), format!("{line}\n\n"), format!("{line}\n#EXT-X-INDEPENDENT-SEGMENTS\n")] { + assert!(classical_unchanged(master, &master.replacen("6\n", &format!("6\n{more}"), 1)).is_err(), "{more:?}"); + } let changed = |published: String| classical_unchanged(master, &published).unwrap_err().code(); assert_eq!(changed(format!("{line}\n{master}")), "classical_changed"); assert_eq!(changed(master.replacen("\n", &format!("\n{line}\n{line}\n"), 1)), "classical_changed"); -- 2.54.0 (Apple Git-157) From e8ff6c5380b5e7a74d8197885a25fa48af38095a Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:14:12 -0400 Subject: [PATCH 39/55] http-conformance: falsify the session-data line's place and its being one line Co-Authored-By: Claude Opus 5.5 --- .../falsify/session-data-more-lines.patch | 15 +++++++++++++++ .../falsify/session-data-position.patch | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 wai/http-conformance/falsify/session-data-more-lines.patch create mode 100644 wai/http-conformance/falsify/session-data-position.patch diff --git a/wai/http-conformance/falsify/session-data-more-lines.patch b/wai/http-conformance/falsify/session-data-more-lines.patch new file mode 100644 index 000000000..5597fc08f --- /dev/null +++ b/wai/http-conformance/falsify/session-data-more-lines.patch @@ -0,0 +1,15 @@ +# must fail: unchanged_two_lines unchanged_blank_line_too +# more than the one session-data line inserted (hls::classical_unchanged) +diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs +index 107ae8a1..6a704c25 100644 +--- a/wai/wai-rs/src/http_binding/hls.rs ++++ b/wai/wai-rs/src/http_binding/hls.rs +@@ -847,7 +847,7 @@ pub fn classical_unchanged(original: &str, published: &str) -> Result<(), Playli + let (head, tail) = original.split_at(at); + let inserted = published.strip_prefix(head).and_then(|r| r.strip_suffix(tail)).ok_or_else(changed)?; + let bare = inserted.strip_suffix('\n').map(|x| x.strip_suffix('\r').unwrap_or(x)).ok_or_else(changed)?; +- if bare.contains('\n') || !is_session_data_line(bare) { ++ if !is_session_data_line(bare.lines().next().unwrap_or("")) { + return Err(changed()); + } + Ok(()) diff --git a/wai/http-conformance/falsify/session-data-position.patch b/wai/http-conformance/falsify/session-data-position.patch new file mode 100644 index 000000000..eec1654ae --- /dev/null +++ b/wai/http-conformance/falsify/session-data-position.patch @@ -0,0 +1,15 @@ +# must fail: unchanged_players unchanged_at_the_end +# the session-data line's place taken past every line (hls::session_data_position) +diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs +index 107ae8a1..8f5f5bd9 100644 +--- a/wai/wai-rs/src/http_binding/hls.rs ++++ b/wai/wai-rs/src/http_binding/hls.rs +@@ -812,7 +812,7 @@ pub fn session_data_position(original: &str) -> Option { + let mut at = 0; + for (i, l) in original.split_inclusive('\n').enumerate() { + let bare = l.strip_suffix('\n').map(|x| x.strip_suffix('\r').unwrap_or(x)).unwrap_or(l); +- if i > 0 && !(bare.starts_with("#EXT-X-VERSION:") || bare == "#EXT-X-INDEPENDENT-SEGMENTS") { ++ if false && i > 0 && !(bare.starts_with("#EXT-X-VERSION:") || bare == "#EXT-X-INDEPENDENT-SEGMENTS") { + break; + } + if !l.ends_with('\n') { -- 2.54.0 (Apple Git-157) From 3eafbd7b9d2ef879616a161961ffe5436064eb35 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:14:30 -0400 Subject: [PATCH 40/55] =?UTF-8?q?hls:=20the=20bandwidths=20doc=20states=20?= =?UTF-8?q?what=20a=20segment's=20bytes=20are,=20layers=20included,=20as?= =?UTF-8?q?=20=C2=A711.1=20does?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/http_binding/hls.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs index 107ae8a1e..82cd09e17 100644 --- a/wai/wai-rs/src/http_binding/hls.rs +++ b/wai/wai-rs/src/http_binding/hls.rs @@ -635,12 +635,13 @@ pub fn wai_media_playlist( Ok((segments, r, extras, text)) } -/// The peak and average bit rates of a sequence of objects (§11.1), in bits per second, -/// from each object's `envelope_len` `n` and duration `d` in milliseconds: the peak is -/// the largest `⌈8000·n / d⌉` of any one object, which is never below RFC 8216's peak -/// over any run of segments; the average is `⌈8000·Σn / Σd⌉`. `None` for no objects or -/// a duration of 0. Layer objects are not included: these are the rates of the objects -/// a client must fetch. +/// The peak and average bit rates of a variant (§11.1), in bits per second, from each +/// segment's bytes `n` and duration `d` in milliseconds: the peak is the largest +/// `⌈8000·n / d⌉` of any one segment, which is never below RFC 8216's peak over any run +/// of segments; the average is `⌈8000·Σn / Σd⌉`; either is 2^64 − 1 past it. A +/// segment's bytes are the `envelope_len` of its object and of the layer objects listed +/// with it; the caller passes no gap segment (it counts nothing), and for a live variant +/// with no segment yet, its parts. `None` for no segment or a duration of 0. pub fn bandwidths(objects: &[(u64, u64)]) -> Option<(u64, u64)> { if objects.is_empty() || objects.iter().any(|&(_, d)| d == 0) { return None; -- 2.54.0 (Apple Git-157) From 7ad889ce6a1b321bb7287a6ca76bd603c30dca92 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:21:33 -0400 Subject: [PATCH 41/55] wai: seal-playlist --grant; a grant of no joule ceiling written null and every receipt number at most 2^53 - 1; the origin answers a far-future _HLS_msn with 400; the text states what a playlist receipt does not sign Co-Authored-By: Claude Opus 5.5 --- wai/extensions/http-delivery.md | 23 +++- wai/http-conformance/README.md | 2 +- wai/http-conformance/cases.json | 8 +- .../cases/live-r2-orphan.json | 2 +- ...0aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2.json | 2 +- wai/http-conformance/cases/other-key/r2.json | 2 +- wai/http-conformance/cases/other-key/r3.json | 2 +- ...42529a2bf185da6fd9797c3bab7abcc3f4fd7.json | 2 +- wai/http-conformance/cases/relist/r2.json | 2 +- .../cases/relist/r3-after.json | 2 +- wai/http-conformance/cases/relist/r3.json | 2 +- .../cases/sig-key-base-point.json | 2 +- .../cases/sig-key-identity-long.json | 2 +- .../cases/sig-key-identity.json | 2 +- .../cases/sig-r-non-canonical.json | 2 +- .../cases/sig-r-small-order.json | 2 +- wai/http-conformance/falsify/run.sh | 3 +- wai/http-conformance/http_verify.py | 13 ++- ...ff89d86f93355c2162648dd032ac2263c4271.json | 2 +- ...afe2d76e1cbb92ee9c88b803d0b21d2428900.json | 2 +- ...38e6ff46022b5a7ba48c994338876b6a7c364.json | 2 +- ...b93b7c0b84ff123b99ee2c26505649c68bdf6.json | 2 +- ...63c7518c7e6c1d89d81328a419f8d406c75d9.json | 2 +- ...d02edf6d70de7823de8c1c876b41e202d133e.json | 2 +- ...3fbd4230a4fd626ffccdba54047bf0f6573ae.json | 2 +- ...eb5f2660f80ca6267a73a2ae9cd186df824ed.json | 2 +- ...0860033667f9cea89b45ca1849f0e54fc654d.json | 2 +- ...95532f68da3c5e55365cebdfa8d92dd73a9c5.json | 2 +- ...68efa3ca77abdc07345af2c39724565715f77.json | 2 +- ...8aa7963ca1135536a7c7edd08d4dec08b674e.json | 1 - ...4772159e18cdbffea5352ff72831d5260a1ae.json | 1 + ...e564117bee5bb1b468424361da5ae48a81c79.json | 2 +- ...e1ca89f4501b3c546280a48b4de0e729398ea.json | 2 +- ...7fd259910c5ddfd6c5314d6ed6c9894c80f13.json | 2 +- ...8f756446079c7d95a327e14c85428ffb6c798.json | 2 +- wai/wai-rs/src/bin/wai_cli/http.rs | 44 ++++++-- wai/wai-rs/src/bin/wai_cli/http_playlists.rs | 17 ++- .../src/bin/wai_cli/http_vectors_playlists.rs | 26 ++++- wai/wai-rs/src/wai_video_manifest.rs | 100 ++++++++++++++---- wai/wai-rs/tests/cli_http.rs | 21 ++++ 40 files changed, 245 insertions(+), 70 deletions(-) delete mode 100644 wai/http-conformance/playlists/o10/mr/73416370f12adade369abf7a0e48aa7963ca1135536a7c7edd08d4dec08b674e.json create mode 100644 wai/http-conformance/playlists/o10/mr/7c3b6480fcffa821e3d3d0e93594772159e18cdbffea5352ff72831d5260a1ae.json diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index 86aa180d3..dfaf67623 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -472,8 +472,15 @@ layer lines, a preload hint), rendered as Appendix G states. When both are absen playlist is rendered as before. `manifest_hash` covers the rendered bytes, and the signature also covers a digest of `render` and `extras` themselves (Appendix H), so a rendering has one form. A public playlist is sealed with the entitlement `public`, no -geography, blackout or ad pod, and its media sequence as the window. A multivariant -playlist and a `wai.mpd` are not sealed in this revision. +geography, blackout or ad pod, and its media sequence as the window, under the grant +its publisher holds (`grant`: a grant of no ceilings when it holds none). + +This revision signs media playlists only. A receipt binds neither the multivariant +playlist that names the media playlist nor a `wai.mpd`, and nothing binds +`class.content_id` to the presentation: it is the publisher's label, signed as given, +so a client learns from it only what the signer chose to write. A client that needs the +multivariant playlist's variants, or the content's identity, authenticated holds them +from elsewhere. A client checks a playlist against a receipt file in this order: @@ -733,7 +740,10 @@ Each part is its own object, served under an alias, and listed with its cid: ``` - The alias of a part names those bytes for its lifetime (§5.2). A preload hint names - the alias before the part exists; the origin holds the request until it does. + the alias before the part exists; the origin holds the request until it does. An + origin answers at once with 400 a blocking reload whose `_HLS_msn` is more than two + past the last segment of its current playlist, or that has `_HLS_part` without + `_HLS_msn`, as the HLS second edition requires, rather than holding it. - A client presents a part only after its bytes hash to the cid that a later playlist revision lists for that alias. Issued together, the hint request and a blocking reload (`_HLS_msn`, `_HLS_part`) complete when the part is complete, so the floor is @@ -1144,8 +1154,11 @@ metered, `grant` (`grant_hash`, `capability`, `joule_ceiling_micro`, `funds_ceil null —, `independent_segments`, `map_uri`) with `extras` (each `discontinuity`, `gap`, `program_date_time`, `parts` — each `duration_ms`, `independent`, `uri`, `layers` —, `layers` — each `n`, `uri` —, `preload_hint`), the two together or -not at all. Hashes are lowercase hexadecimal; a number is an unsigned integer written -as digits alone. The file is read as I-JSON (RFC 7493): no object holds a member name +not at all. Hashes are lowercase hexadecimal; a number is an unsigned integer of at +most 2^53 − 1 written as digits alone, and a writer refuses to seal a receipt it cannot +write so. A grant's `joule_ceiling_micro` is `null` when the grant sets no joule ceiling +(it signs as 2^64 − 1), and `funds_ceiling` `null` when it sets no funds ceiling. The +file is read as I-JSON (RFC 7493): no object holds a member name twice, at any depth and with names compared after their escapes are read, and no string holds a `\u` escape that leaves a surrogate unpaired; either is `playlist_receipt_malformed`, so no two readers can read two receipts from one file. diff --git a/wai/http-conformance/README.md b/wai/http-conformance/README.md index 208397e1d..d972f557f 100644 --- a/wai/http-conformance/README.md +++ b/wai/http-conformance/README.md @@ -35,7 +35,7 @@ reference adaptation policies. Two implementations check every case and must agr | `playlists/o9/`, `playlists/o10/`, `playlists/o11/` | WAI media and multivariant playlists (and MPDs) over O9 (and over O9 with its second segment a gap, which `wai-gap.mpd` leaves out), O10 with its layers, and O11, each beside its classical playlist in `src/playlists/`; O9's MPDs are on the timeline of `src/playlists/o9-classical.mpd` (90 kHz, written with `@r`, beside an audio set on its own timescale) | | `live/` | four revisions of a low-latency WAI playlist over O12 beside `src/live/classical-r*.m3u8`, and its multivariant playlist; the parts are listed under aliases `720/..wai` with their cids (the alias files are not in the corpus: the cases serve the objects under them, and `wai http origin --root . --live-edge live/wai-720.m3u8=live/wai-r0.m3u8,live/wai-r1.m3u8,live/wai-r2.m3u8,live/wai-r3.m3u8` replays the edge over HTTP, blocking reloads and hinted parts included) | | `players/` | the player fixture: `classical/` (frozen) is a playable classical presentation, `master.m3u8` its master as published with the session-data line, and the WAI multivariant and media playlists and MPD over O13 | -| `*/mr/.json` | each WAI media playlist's receipt (§8.6, Appendix H), sealed with the corpus's test key (seed 32 bytes of `0x57`, signer `did:example:http-vectors`; it attests nothing); the live revisions are chained | +| `*/mr/.json` | each WAI media playlist's receipt (§8.6, Appendix H), sealed with the corpus's test key (seed 32 bytes of `0x57`, signer `did:example:http-vectors`; it attests nothing) under a grant of no ceilings, O10's under a test grant with both ceilings; the live revisions are chained | | `cases/live-r2-orphan.json` | the third live revision sealed with no parent: a receipt that verifies and does not chain | | `*/mr/.json` | each receipt again under its receipt hash, where a client walking back from a later revision fetches it by its link (§8.6) | | `cases/other-key/` | the third live revision's receipt chained to the second by hash and signed by another key (`r2.json`, and in `mr/` under its receipt hash), and the fourth revision's receipt chained to it (`r3.json`) | diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index f5a151218..71f4d0952 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -480,7 +480,7 @@ {"name":"unchanged_bare_after_the_variant","op":"unchanged","input":{"original_text":"#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1\nv.m3u8\n","published_text":"#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1\n#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\nv.m3u8\n"},"expect":{"error":"classical_changed"}}, {"name":"unchanged_no_place","op":"unchanged","input":{"original_text":"#EXTM3U\n#EXT-X-VERSION:7","published_text":"#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n"},"expect":{"error":"classical_changed"}}, {"name":"playlist_receipt_playlists_o9_wai_180_m3u8","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json"},"expect":{"signed":"4b4657cea62905884efdfc59c357fd259910c5ddfd6c5314d6ed6c9894c80f13","parent":null}}, -{"name":"playlist_receipt_playlists_o10_wai_a_m3u8","op":"playlist_receipt","input":{"playlist":"playlists/o10/wai-a.m3u8","receipt":"playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json"},"expect":{"signed":"73416370f12adade369abf7a0e48aa7963ca1135536a7c7edd08d4dec08b674e","parent":null}}, +{"name":"playlist_receipt_playlists_o10_wai_a_m3u8","op":"playlist_receipt","input":{"playlist":"playlists/o10/wai-a.m3u8","receipt":"playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json"},"expect":{"signed":"7c3b6480fcffa821e3d3d0e93594772159e18cdbffea5352ff72831d5260a1ae","parent":null}}, {"name":"playlist_receipt_playlists_o11_wai_v_m3u8","op":"playlist_receipt","input":{"playlist":"playlists/o11/wai-v.m3u8","receipt":"playlists/o11/mr/044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79.json"},"expect":{"signed":"461a7871f56c0e69017be36a7dce1ca89f4501b3c546280a48b4de0e729398ea","parent":null}}, {"name":"playlist_receipt_live_wai_r0_m3u8","op":"playlist_receipt","input":{"playlist":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json"},"expect":{"signed":"3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364","parent":null}}, {"name":"playlist_receipt_live_wai_r1_m3u8","op":"playlist_receipt","input":{"playlist":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json"},"expect":{"signed":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","parent":"3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364"}}, @@ -502,6 +502,12 @@ {"name":"playlist_receipt_lone_surrogate","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"geo\":\"\"","\"geo\":\"\\ud800\""]]},"expect":{"error":"playlist_receipt_malformed"}}, {"name":"playlist_receipt_lone_low_surrogate","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"signer_id\":\"did:example:http-vectors\"","\"signer_id\":\"did:example:http-vectors\\udfff\""]]},"expect":{"error":"playlist_receipt_malformed"}}, {"name":"playlist_receipt_surrogate_pair","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"geo\":\"\"","\"geo\":\"\\ud83d\\ude00\""]]},"expect":{"error":"playlist_receipt_invalid"}}, +{"name":"playlist_receipt_ceiling_past_2_53","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"joule_ceiling_micro\":null","\"joule_ceiling_micro\":9007199254740992"]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_ceiling_written_out","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"joule_ceiling_micro\":null","\"joule_ceiling_micro\":18446744073709551615"]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_ceiling_number_for_none","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"joule_ceiling_micro\":null","\"joule_ceiling_micro\":9007199254740991"]]},"expect":{"error":"playlist_receipt_invalid"}}, +{"name":"playlist_receipt_window_past_2_53","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json","edit_receipt":[["\"window\":0","\"window\":9007199254740992"]]},"expect":{"error":"playlist_receipt_malformed"}}, +{"name":"playlist_receipt_grant_ceiling_changed","op":"playlist_receipt","input":{"playlist":"playlists/o10/wai-a.m3u8","receipt":"playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json","edit_receipt":[["\"joule_ceiling_micro\":5000000","\"joule_ceiling_micro\":5000001"]]},"expect":{"error":"playlist_receipt_invalid"}}, +{"name":"playlist_receipt_grant_funds_dropped","op":"playlist_receipt","input":{"playlist":"playlists/o10/wai-a.m3u8","receipt":"playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json","edit_receipt":[["\"funds_ceiling\":250","\"funds_ceiling\":null"]]},"expect":{"error":"playlist_receipt_invalid"}}, {"name":"playlist_receipt_sig_key_identity","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-key-identity.json","name":false},"expect":{"error":"playlist_receipt_invalid"}}, {"name":"playlist_receipt_sig_key_identity_long","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-key-identity-long.json","name":false},"expect":{"error":"playlist_receipt_invalid"}}, {"name":"playlist_receipt_sig_r_small_order","op":"playlist_receipt","input":{"playlist":"playlists/o9/wai-180.m3u8","receipt":"cases/sig-r-small-order.json","name":false},"expect":{"error":"playlist_receipt_invalid"}}, diff --git a/wai/http-conformance/cases/live-r2-orphan.json b/wai/http-conformance/cases/live-r2-orphan.json index 81692f146..1470a295c 100644 --- a/wai/http-conformance/cases/live-r2-orphan.json +++ b/wai/http-conformance/cases/live-r2-orphan.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"a6886d729353f79ca98a5520f217544c7f3d5f332b64031a1e2e02945cd5986f48d33efc6912394c3a62f9f7d4b848d63fd3687306ab8b421cf07adf5375ba01","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} +{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"a6886d729353f79ca98a5520f217544c7f3d5f332b64031a1e2e02945cd5986f48d33efc6912394c3a62f9f7d4b848d63fd3687306ab8b421cf07adf5375ba01","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/other-key/mr/90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2.json b/wai/http-conformance/cases/other-key/mr/90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2.json index 6be03d27a..301a52720 100644 --- a/wai/http-conformance/cases/other-key/mr/90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2.json +++ b/wai/http-conformance/cases/other-key/mr/90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"dac31ebca159d283f8341fd90d7fa1cd95bf5eccab657f7952662e67e56afbe3","signer_id":"did:example:http-vectors","sig":"f60bd29bb57eea95c41ee5b5219f2ee034076f500a8c23fef53c5e8de36315bd33867a5d19697a241b07defbe8f88947d20f5eeb119720af8a0416c3cafa2400","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} +{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"dac31ebca159d283f8341fd90d7fa1cd95bf5eccab657f7952662e67e56afbe3","signer_id":"did:example:http-vectors","sig":"f60bd29bb57eea95c41ee5b5219f2ee034076f500a8c23fef53c5e8de36315bd33867a5d19697a241b07defbe8f88947d20f5eeb119720af8a0416c3cafa2400","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/other-key/r2.json b/wai/http-conformance/cases/other-key/r2.json index 6be03d27a..301a52720 100644 --- a/wai/http-conformance/cases/other-key/r2.json +++ b/wai/http-conformance/cases/other-key/r2.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"dac31ebca159d283f8341fd90d7fa1cd95bf5eccab657f7952662e67e56afbe3","signer_id":"did:example:http-vectors","sig":"f60bd29bb57eea95c41ee5b5219f2ee034076f500a8c23fef53c5e8de36315bd33867a5d19697a241b07defbe8f88947d20f5eeb119720af8a0416c3cafa2400","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} +{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"dac31ebca159d283f8341fd90d7fa1cd95bf5eccab657f7952662e67e56afbe3","signer_id":"did:example:http-vectors","sig":"f60bd29bb57eea95c41ee5b5219f2ee034076f500a8c23fef53c5e8de36315bd33867a5d19697a241b07defbe8f88947d20f5eeb119720af8a0416c3cafa2400","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/other-key/r3.json b/wai/http-conformance/cases/other-key/r3.json index f28197f15..be750a341 100644 --- a/wai/http-conformance/cases/other-key/r3.json +++ b/wai/http-conformance/cases/other-key/r3.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"eba7d6f7efd7d3b43d1b44eb82645a2736ad931fe99cc27af3de657bed2d6ee13f079ca53fb5ff8c08404ced7ced0769cbaf43c051ca9b3d217d26dbad3a7707","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} +{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"90cd319c8e785ef9377d032e4330aa6b1b4dbb84ce5ca2d7fcd9a1b866615cb2","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"eba7d6f7efd7d3b43d1b44eb82645a2736ad931fe99cc27af3de657bed2d6ee13f079ca53fb5ff8c08404ced7ced0769cbaf43c051ca9b3d217d26dbad3a7707","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/cases/relist/mr/4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7.json b/wai/http-conformance/cases/relist/mr/4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7.json index 31233213b..dec78301d 100644 --- a/wai/http-conformance/cases/relist/mr/4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7.json +++ b/wai/http-conformance/cases/relist/mr/4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"3d2caa315b4db835b377e56ccb5fd3f78bc039f618bfe766db613481a547a9d7","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"ff79ca7c3a2525d4ba7cb27bfbe5fc353baecd5c322e53a6afd5c9dd68d8d1a9eb9b189d5115e8d07eca7d3e9d0c750811e347f15c24899086e1d6ae4ef12e03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} +{"kind":"wai.video.manifest","manifest_hash":"3d2caa315b4db835b377e56ccb5fd3f78bc039f618bfe766db613481a547a9d7","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"ff79ca7c3a2525d4ba7cb27bfbe5fc353baecd5c322e53a6afd5c9dd68d8d1a9eb9b189d5115e8d07eca7d3e9d0c750811e347f15c24899086e1d6ae4ef12e03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/relist/r2.json b/wai/http-conformance/cases/relist/r2.json index 31233213b..dec78301d 100644 --- a/wai/http-conformance/cases/relist/r2.json +++ b/wai/http-conformance/cases/relist/r2.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"3d2caa315b4db835b377e56ccb5fd3f78bc039f618bfe766db613481a547a9d7","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"ff79ca7c3a2525d4ba7cb27bfbe5fc353baecd5c322e53a6afd5c9dd68d8d1a9eb9b189d5115e8d07eca7d3e9d0c750811e347f15c24899086e1d6ae4ef12e03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} +{"kind":"wai.video.manifest","manifest_hash":"3d2caa315b4db835b377e56ccb5fd3f78bc039f618bfe766db613481a547a9d7","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"ff79ca7c3a2525d4ba7cb27bfbe5fc353baecd5c322e53a6afd5c9dd68d8d1a9eb9b189d5115e8d07eca7d3e9d0c750811e347f15c24899086e1d6ae4ef12e03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/relist/r3-after.json b/wai/http-conformance/cases/relist/r3-after.json index 6a6783022..2ec1d022d 100644 --- a/wai/http-conformance/cases/relist/r3-after.json +++ b/wai/http-conformance/cases/relist/r3-after.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"0f6f640981fe8a69c0631835056547d825d21155ac95abc410baf8a990cea12a70d1c3db6835fec26b8e63ba26f78c2febba35605333c5f915fceb4f0f070b03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} +{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"4fdc8b5cc9867641c9cf38517c942529a2bf185da6fd9797c3bab7abcc3f4fd7","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"0f6f640981fe8a69c0631835056547d825d21155ac95abc410baf8a990cea12a70d1c3db6835fec26b8e63ba26f78c2febba35605333c5f915fceb4f0f070b03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/cases/relist/r3.json b/wai/http-conformance/cases/relist/r3.json index f66cd652e..84d66790f 100644 --- a/wai/http-conformance/cases/relist/r3.json +++ b/wai/http-conformance/cases/relist/r3.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"198e04e4e3aa8c149d7adbcc6c1b5ed0a56956ceb3c8d1f34e42104f318dcbdb","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"c5e68c2958aa68df360e32a66700bdff0c36c01e63d0d6bd80b9f03b2bd66a87","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"b08962976e408f754a89d2cdc1a5ca5f1cae76a186e8a155ff97cbe3f327239113e9beec406d25a73013ec79f87b2ac5d53171b16e0203a4d5c06d29132a2e0c","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} +{"kind":"wai.video.manifest","manifest_hash":"198e04e4e3aa8c149d7adbcc6c1b5ed0a56956ceb3c8d1f34e42104f318dcbdb","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"c5e68c2958aa68df360e32a66700bdff0c36c01e63d0d6bd80b9f03b2bd66a87","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"b08962976e408f754a89d2cdc1a5ca5f1cae76a186e8a155ff97cbe3f327239113e9beec406d25a73013ec79f87b2ac5d53171b16e0203a4d5c06d29132a2e0c","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/cases/sig-key-base-point.json b/wai/http-conformance/cases/sig-key-base-point.json index ea393b61f..066cfbbbf 100644 --- a/wai/http-conformance/cases/sig-key-base-point.json +++ b/wai/http-conformance/cases/sig-key-base-point.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666662f03c04a2605fd41de75215b9a872c6a05423724a0ed9ee29c5c6854cc8a7d05","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666662f03c04a2605fd41de75215b9a872c6a05423724a0ed9ee29c5c6854cc8a7d05","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/cases/sig-key-identity-long.json b/wai/http-conformance/cases/sig-key-identity-long.json index 784c688d7..a7bfb766a 100644 --- a/wai/http-conformance/cases/sig-key-identity-long.json +++ b/wai/http-conformance/cases/sig-key-identity-long.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666660100000000000000000000000000000000000000000000000000000000000000","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666660100000000000000000000000000000000000000000000000000000000000000","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/cases/sig-key-identity.json b/wai/http-conformance/cases/sig-key-identity.json index 0df50a92e..8a0a7457e 100644 --- a/wai/http-conformance/cases/sig-key-identity.json +++ b/wai/http-conformance/cases/sig-key-identity.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"0100000000000000000000000000000000000000000000000000000000000000","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666660100000000000000000000000000000000000000000000000000000000000000","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"0100000000000000000000000000000000000000000000000000000000000000","signer_id":"did:example:http-vectors","sig":"58666666666666666666666666666666666666666666666666666666666666660100000000000000000000000000000000000000000000000000000000000000","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/cases/sig-r-non-canonical.json b/wai/http-conformance/cases/sig-r-non-canonical.json index 86887c419..d034d4da9 100644 --- a/wai/http-conformance/cases/sig-r-non-canonical.json +++ b/wai/http-conformance/cases/sig-r-non-canonical.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f080a137c08411f3c035f03a58f1b5131bcb5887f5082212f2caa4d827d8aed04","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f080a137c08411f3c035f03a58f1b5131bcb5887f5082212f2caa4d827d8aed04","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/cases/sig-r-small-order.json b/wai/http-conformance/cases/sig-r-small-order.json index e4e3d9c2e..fbab2e251 100644 --- a/wai/http-conformance/cases/sig-r-small-order.json +++ b/wai/http-conformance/cases/sig-r-small-order.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"0100000000000000000000000000000000000000000000000000000000000000f57a2a3a17cc1400dba50adbc733921a1b095514a8d91a25909f588c437ac10f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"5866666666666666666666666666666666666666666666666666666666666666","signer_id":"did:example:http-vectors","sig":"0100000000000000000000000000000000000000000000000000000000000000f57a2a3a17cc1400dba50adbc733921a1b095514a8d91a25909f588c437ac10f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh index e8da87849..a047266e8 100755 --- a/wai/http-conformance/falsify/run.sh +++ b/wai/http-conformance/falsify/run.sh @@ -50,7 +50,8 @@ for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done t ilog2-no-interp bola-waits unlabelled-as-zero round-half-down small-order-key small-order-r \ rebind-uri rebind-sequence key-unchecked walk-unbounded reload-walked \ xml-char-unchecked xml-attribute-twice dash-start-ignored dash-timescale-ignored \ - unlabelled-escapes abr-unbounded gap-carries receipt-duplicates receipt-surrogates session-data-anywhere; do + unlabelled-escapes abr-unbounded gap-carries receipt-duplicates receipt-surrogates session-data-anywhere \ + file-numbers-unbounded; do if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then echo "::error::http_verify.py --mutate $m passed"; fail=1 else diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index 2a87125e7..d539f3398 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -2513,6 +2513,9 @@ RECEIPT_KNOWN = {"kind", "manifest_hash", "session_class_hash", "class", "segmen LEGACY_CLASSES = {"HwShunt": 1, "ModelBased": 2, "Estimator": 3} +MAX_FILE_NUMBER = 2 ** 53 - 1 + + class ReceiptMalformed(Exception): pass @@ -2532,7 +2535,9 @@ def read_receipt(text): return dict(kv) try: - o = json.loads(text, object_pairs_hook=pairs, parse_int=lambda t: NotUnsigned() if t.startswith("-") else int(t), parse_float=lambda t: NotUnsigned()) + # A number is an unsigned integer of at most 2^53 - 1, written as digits alone. + o = json.loads(text, object_pairs_hook=pairs, parse_float=lambda t: NotUnsigned(), + parse_int=lambda t: NotUnsigned() if t.startswith("-") or (int(t) > MAX_FILE_NUMBER and MUTATE != "file-numbers-unbounded") else int(t)) except (ValueError, RecursionError): raise ReceiptMalformed() bad = ReceiptMalformed @@ -2595,9 +2600,11 @@ def read_receipt(text): raise bad() segs.append({"index": u64(x.get("index")), "ad": x["kind"] == "ad", "uri": s(x.get("uri")), "ms": u64(x.get("duration_ms")), "ad_id": x["ad_id"]}) g = obj(o["grant"], 4) - if "funds_ceiling" not in g: + if "funds_ceiling" not in g or "joule_ceiling_micro" not in g: raise bad() - grant = {"hash": hx(g.get("grant_hash"), 32), "capability": s(g.get("capability")), "ceiling": u64(g.get("joule_ceiling_micro")), "funds": opt(g["funds_ceiling"], u64)} + # No joule ceiling is written null and signs as 2^64 - 1. + grant = {"hash": hx(g.get("grant_hash"), 32), "capability": s(g.get("capability")), + "ceiling": U64 if g["joule_ceiling_micro"] is None else u64(g["joule_ceiling_micro"]), "funds": opt(g["funds_ceiling"], u64)} label = o.get("energy_provenance") if label is None: if any(o.get(k) is not None for k in ("energy_uncertainty", "energy_counter_filtering", "energy_calibration_ref")): diff --git a/wai/http-conformance/live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json b/wai/http-conformance/live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json index 83d1b3bfe..53e2c5d3b 100644 --- a/wai/http-conformance/live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json +++ b/wai/http-conformance/live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null}],"merkle_root":"00e2bc6cdc748c6ab760c8e867603fd4d9a72d3a58f985bd13810a8d77aedcd3","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"8186f3adb28113d8ad08c923d24df463eb898d7a95b04902d23ca208fc17fe677249dc30f280faef8ec5aeac81abdb6b6fcd577075d9e9346f445c3b8c13fa03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]}],"layers":[],"preload_hint":"720/1201.3.wai"}]} +{"kind":"wai.video.manifest","manifest_hash":"091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null}],"merkle_root":"00e2bc6cdc748c6ab760c8e867603fd4d9a72d3a58f985bd13810a8d77aedcd3","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"8186f3adb28113d8ad08c923d24df463eb898d7a95b04902d23ca208fc17fe677249dc30f280faef8ec5aeac81abdb6b6fcd577075d9e9346f445c3b8c13fa03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]}],"layers":[],"preload_hint":"720/1201.3.wai"}]} diff --git a/wai/http-conformance/live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json b/wai/http-conformance/live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json index d57cdf573..a8e5e05dd 100644 --- a/wai/http-conformance/live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json +++ b/wai/http-conformance/live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"d02f849c0a2476cd732b53039fa56de97d4aedf4f36c49696a0bad2349f8530c31deea9635825a8fc33ef0a16a55e55b15d90a9633e821bcdfed0a5eba7be803","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} +{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"d02f849c0a2476cd732b53039fa56de97d4aedf4f36c49696a0bad2349f8530c31deea9635825a8fc33ef0a16a55e55b15d90a9633e821bcdfed0a5eba7be803","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/live/mr/3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364.json b/wai/http-conformance/live/mr/3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364.json index f9c7b9011..ec69974c1 100644 --- a/wai/http-conformance/live/mr/3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364.json +++ b/wai/http-conformance/live/mr/3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[],"merkle_root":"0000000000000000000000000000000000000000000000000000000000000000","work_compose_ops":0,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"5d213b6e5ce07b2c7391f0d73501c7ed8e91d6b81ab418e6b1eff4169d127f6f943306b5e1dc44844f69505c34156c040686a6e4151b37ddda98a617f2ac7609","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]}],"layers":[],"preload_hint":"720/1200.5.wai"}]} +{"kind":"wai.video.manifest","manifest_hash":"6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[],"merkle_root":"0000000000000000000000000000000000000000000000000000000000000000","work_compose_ops":0,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"5d213b6e5ce07b2c7391f0d73501c7ed8e91d6b81ab418e6b1eff4169d127f6f943306b5e1dc44844f69505c34156c040686a6e4151b37ddda98a617f2ac7609","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]}],"layers":[],"preload_hint":"720/1200.5.wai"}]} diff --git a/wai/http-conformance/live/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json b/wai/http-conformance/live/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json index d57cdf573..a8e5e05dd 100644 --- a/wai/http-conformance/live/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json +++ b/wai/http-conformance/live/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"d02f849c0a2476cd732b53039fa56de97d4aedf4f36c49696a0bad2349f8530c31deea9635825a8fc33ef0a16a55e55b15d90a9633e821bcdfed0a5eba7be803","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} +{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"d02f849c0a2476cd732b53039fa56de97d4aedf4f36c49696a0bad2349f8530c31deea9635825a8fc33ef0a16a55e55b15d90a9633e821bcdfed0a5eba7be803","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json b/wai/http-conformance/live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json index f9c7b9011..ec69974c1 100644 --- a/wai/http-conformance/live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json +++ b/wai/http-conformance/live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[],"merkle_root":"0000000000000000000000000000000000000000000000000000000000000000","work_compose_ops":0,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"5d213b6e5ce07b2c7391f0d73501c7ed8e91d6b81ab418e6b1eff4169d127f6f943306b5e1dc44844f69505c34156c040686a6e4151b37ddda98a617f2ac7609","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]}],"layers":[],"preload_hint":"720/1200.5.wai"}]} +{"kind":"wai.video.manifest","manifest_hash":"6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[],"merkle_root":"0000000000000000000000000000000000000000000000000000000000000000","work_compose_ops":0,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"5d213b6e5ce07b2c7391f0d73501c7ed8e91d6b81ab418e6b1eff4169d127f6f943306b5e1dc44844f69505c34156c040686a6e4151b37ddda98a617f2ac7609","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]}],"layers":[],"preload_hint":"720/1200.5.wai"}]} diff --git a/wai/http-conformance/live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json b/wai/http-conformance/live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json index b62535422..8fe01a022 100644 --- a/wai/http-conformance/live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json +++ b/wai/http-conformance/live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"96ae8d50d493052725debf607391a6a057a94053fdd065799a16a0adbad9dc89379df504d6e055e85588035a3d94153177ca81ae31f25713adbd5de453c7c103","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} +{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"96ae8d50d493052725debf607391a6a057a94053fdd065799a16a0adbad9dc89379df504d6e055e85588035a3d94153177ca81ae31f25713adbd5de453c7c103","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/live/mr/dbc00c5ff81292325d272cbd6b83fbd4230a4fd626ffccdba54047bf0f6573ae.json b/wai/http-conformance/live/mr/dbc00c5ff81292325d272cbd6b83fbd4230a4fd626ffccdba54047bf0f6573ae.json index b62535422..8fe01a022 100644 --- a/wai/http-conformance/live/mr/dbc00c5ff81292325d272cbd6b83fbd4230a4fd626ffccdba54047bf0f6573ae.json +++ b/wai/http-conformance/live/mr/dbc00c5ff81292325d272cbd6b83fbd4230a4fd626ffccdba54047bf0f6573ae.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"96ae8d50d493052725debf607391a6a057a94053fdd065799a16a0adbad9dc89379df504d6e055e85588035a3d94153177ca81ae31f25713adbd5de453c7c103","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} +{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"96ae8d50d493052725debf607391a6a057a94053fdd065799a16a0adbad9dc89379df504d6e055e85588035a3d94153177ca81ae31f25713adbd5de453c7c103","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/live/mr/eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed.json b/wai/http-conformance/live/mr/eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed.json index 83d1b3bfe..53e2c5d3b 100644 --- a/wai/http-conformance/live/mr/eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed.json +++ b/wai/http-conformance/live/mr/eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null}],"merkle_root":"00e2bc6cdc748c6ab760c8e867603fd4d9a72d3a58f985bd13810a8d77aedcd3","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":"3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"8186f3adb28113d8ad08c923d24df463eb898d7a95b04902d23ca208fc17fe677249dc30f280faef8ec5aeac81abdb6b6fcd577075d9e9346f445c3b8c13fa03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]}],"layers":[],"preload_hint":"720/1201.3.wai"}]} +{"kind":"wai.video.manifest","manifest_hash":"091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null}],"merkle_root":"00e2bc6cdc748c6ab760c8e867603fd4d9a72d3a58f985bd13810a8d77aedcd3","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"8186f3adb28113d8ad08c923d24df463eb898d7a95b04902d23ca208fc17fe677249dc30f280faef8ec5aeac81abdb6b6fcd577075d9e9346f445c3b8c13fa03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]}],"layers":[],"preload_hint":"720/1201.3.wai"}]} diff --git a/wai/http-conformance/players/mr/493155241cd03b53886e9bd1a210860033667f9cea89b45ca1849f0e54fc654d.json b/wai/http-conformance/players/mr/493155241cd03b53886e9bd1a210860033667f9cea89b45ca1849f0e54fc654d.json index 7731c6c80..e0575d5c1 100644 --- a/wai/http-conformance/players/mr/493155241cd03b53886e9bd1a210860033667f9cea89b45ca1849f0e54fc654d.json +++ b/wai/http-conformance/players/mr/493155241cd03b53886e9bd1a210860033667f9cea89b45ca1849f0e54fc654d.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"57bbf3d1af7c937c13ac72b729895532f68da3c5e55365cebdfa8d92dd73a9c5","session_class_hash":"2c244c78e6a1548a4563faa232e39f2731ceac2d0594269f393d83ad3e8b9dfc","class":{"content_id":"players/v0","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai#n=21758","duration_ms":1000,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai#n=20986","duration_ms":1000,"ad_id":null},{"index":2,"kind":"content","uri":"../objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai#n=21338","duration_ms":1000,"ad_id":null}],"merkle_root":"c8766cdcc12477f790f429db5da1053c2c016877b1e439dd922bf658723dfe1a","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"b3a180a6c86ce30e5b117efa88c5f56f3639165249b4922e068340920eac40d73f2d0034594ef3372c0b19cf473a20b0bb702b3be60fb85fdaf07cc659c3fb0f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} +{"kind":"wai.video.manifest","manifest_hash":"57bbf3d1af7c937c13ac72b729895532f68da3c5e55365cebdfa8d92dd73a9c5","session_class_hash":"2c244c78e6a1548a4563faa232e39f2731ceac2d0594269f393d83ad3e8b9dfc","class":{"content_id":"players/v0","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai#n=21758","duration_ms":1000,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai#n=20986","duration_ms":1000,"ad_id":null},{"index":2,"kind":"content","uri":"../objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai#n=21338","duration_ms":1000,"ad_id":null}],"merkle_root":"c8766cdcc12477f790f429db5da1053c2c016877b1e439dd922bf658723dfe1a","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"b3a180a6c86ce30e5b117efa88c5f56f3639165249b4922e068340920eac40d73f2d0034594ef3372c0b19cf473a20b0bb702b3be60fb85fdaf07cc659c3fb0f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/players/mr/57bbf3d1af7c937c13ac72b729895532f68da3c5e55365cebdfa8d92dd73a9c5.json b/wai/http-conformance/players/mr/57bbf3d1af7c937c13ac72b729895532f68da3c5e55365cebdfa8d92dd73a9c5.json index 7731c6c80..e0575d5c1 100644 --- a/wai/http-conformance/players/mr/57bbf3d1af7c937c13ac72b729895532f68da3c5e55365cebdfa8d92dd73a9c5.json +++ b/wai/http-conformance/players/mr/57bbf3d1af7c937c13ac72b729895532f68da3c5e55365cebdfa8d92dd73a9c5.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"57bbf3d1af7c937c13ac72b729895532f68da3c5e55365cebdfa8d92dd73a9c5","session_class_hash":"2c244c78e6a1548a4563faa232e39f2731ceac2d0594269f393d83ad3e8b9dfc","class":{"content_id":"players/v0","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai#n=21758","duration_ms":1000,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai#n=20986","duration_ms":1000,"ad_id":null},{"index":2,"kind":"content","uri":"../objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai#n=21338","duration_ms":1000,"ad_id":null}],"merkle_root":"c8766cdcc12477f790f429db5da1053c2c016877b1e439dd922bf658723dfe1a","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"b3a180a6c86ce30e5b117efa88c5f56f3639165249b4922e068340920eac40d73f2d0034594ef3372c0b19cf473a20b0bb702b3be60fb85fdaf07cc659c3fb0f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} +{"kind":"wai.video.manifest","manifest_hash":"57bbf3d1af7c937c13ac72b729895532f68da3c5e55365cebdfa8d92dd73a9c5","session_class_hash":"2c244c78e6a1548a4563faa232e39f2731ceac2d0594269f393d83ad3e8b9dfc","class":{"content_id":"players/v0","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../objects/0e1664a61fc504e04ff37dd2e4a15273791059a2626b78a2d1cd4d99ffbe6f59.wai#n=21758","duration_ms":1000,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/f7aebfa6c378a0b83fa3293be38962773a620b1fabb1d17acfc7f52693ce830a.wai#n=20986","duration_ms":1000,"ad_id":null},{"index":2,"kind":"content","uri":"../objects/761e2e03083d601c1b824bf484ce60a286cbd20e478619944fbb385736c6e543.wai#n=21338","duration_ms":1000,"ad_id":null}],"merkle_root":"c8766cdcc12477f790f429db5da1053c2c016877b1e439dd922bf658723dfe1a","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"b3a180a6c86ce30e5b117efa88c5f56f3639165249b4922e068340920eac40d73f2d0034594ef3372c0b19cf473a20b0bb702b3be60fb85fdaf07cc659c3fb0f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json b/wai/http-conformance/playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json index 4def4364b..2a5175010 100644 --- a/wai/http-conformance/playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json +++ b/wai/http-conformance/playlists/o10/mr/24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77","session_class_hash":"28bd23d88c20332da1bf73dc84748f431ca1459b0f6edf73c2a083da2c48c77d","class":{"content_id":"o10","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825","duration_ms":50,"ad_id":null}],"merkle_root":"30497ebce24b685a2c900556166c46c9954bc45600b2a9ab2832343efad972b7","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"ae378cd7bbaed8c6cf074f89701c138672faca5d570e0b9c05d632bfb98b465a760eaa504572640a037acacd91052100bff94b6cccd8cfe23a8f672af0624a0e","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab.wai#n=12711"},{"n":2,"uri":"../../objects/753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c.wai#n=18367"}],"preload_hint":null}]} +{"kind":"wai.video.manifest","manifest_hash":"24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77","session_class_hash":"28bd23d88c20332da1bf73dc84748f431ca1459b0f6edf73c2a083da2c48c77d","class":{"content_id":"o10","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825","duration_ms":50,"ad_id":null}],"merkle_root":"30497ebce24b685a2c900556166c46c9954bc45600b2a9ab2832343efad972b7","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"174a85e58c0c6af17ef9066a20d8ad7f95f6400373b1f8e3d10dbbfd8c7d163d","capability":"video.manifest","joule_ceiling_micro":5000000,"funds_ceiling":250},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"fdf43d76a5c2c07d0471340843d2969612fa833eaba70cf325e849b94ee5adf784d046247635e6a1d764c0c6ec2849829c994607e52acf1dd336f3293513b402","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab.wai#n=12711"},{"n":2,"uri":"../../objects/753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c.wai#n=18367"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o10/mr/73416370f12adade369abf7a0e48aa7963ca1135536a7c7edd08d4dec08b674e.json b/wai/http-conformance/playlists/o10/mr/73416370f12adade369abf7a0e48aa7963ca1135536a7c7edd08d4dec08b674e.json deleted file mode 100644 index 4def4364b..000000000 --- a/wai/http-conformance/playlists/o10/mr/73416370f12adade369abf7a0e48aa7963ca1135536a7c7edd08d4dec08b674e.json +++ /dev/null @@ -1 +0,0 @@ -{"kind":"wai.video.manifest","manifest_hash":"24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77","session_class_hash":"28bd23d88c20332da1bf73dc84748f431ca1459b0f6edf73c2a083da2c48c77d","class":{"content_id":"o10","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825","duration_ms":50,"ad_id":null}],"merkle_root":"30497ebce24b685a2c900556166c46c9954bc45600b2a9ab2832343efad972b7","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"ae378cd7bbaed8c6cf074f89701c138672faca5d570e0b9c05d632bfb98b465a760eaa504572640a037acacd91052100bff94b6cccd8cfe23a8f672af0624a0e","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab.wai#n=12711"},{"n":2,"uri":"../../objects/753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c.wai#n=18367"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o10/mr/7c3b6480fcffa821e3d3d0e93594772159e18cdbffea5352ff72831d5260a1ae.json b/wai/http-conformance/playlists/o10/mr/7c3b6480fcffa821e3d3d0e93594772159e18cdbffea5352ff72831d5260a1ae.json new file mode 100644 index 000000000..2a5175010 --- /dev/null +++ b/wai/http-conformance/playlists/o10/mr/7c3b6480fcffa821e3d3d0e93594772159e18cdbffea5352ff72831d5260a1ae.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"24db5ea8900112df04426a7e28a68efa3ca77abdc07345af2c39724565715f77","session_class_hash":"28bd23d88c20332da1bf73dc84748f431ca1459b0f6edf73c2a083da2c48c77d","class":{"content_id":"o10","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/1eb764beb4a8a739054341df8b5b7d065e490032b187a93787be15f1478080a8.wai#n=1825","duration_ms":50,"ad_id":null}],"merkle_root":"30497ebce24b685a2c900556166c46c9954bc45600b2a9ab2832343efad972b7","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"174a85e58c0c6af17ef9066a20d8ad7f95f6400373b1f8e3d10dbbfd8c7d163d","capability":"video.manifest","joule_ceiling_micro":5000000,"funds_ceiling":250},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"fdf43d76a5c2c07d0471340843d2969612fa833eaba70cf325e849b94ee5adf784d046247635e6a1d764c0c6ec2849829c994607e52acf1dd336f3293513b402","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/53a48057cb99b88fd235b9165799122df2d1b55a4b84245112a5a5ea948458ab.wai#n=12711"},{"n":2,"uri":"../../objects/753e8ac465d347547c688690eb51615f19118c0a4c17490499fbb31aefd5cd0c.wai#n=18367"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o11/mr/044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79.json b/wai/http-conformance/playlists/o11/mr/044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79.json index bea3f71ef..c312496c9 100644 --- a/wai/http-conformance/playlists/o11/mr/044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79.json +++ b/wai/http-conformance/playlists/o11/mr/044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79","session_class_hash":"7ecee2c182c9807140e70aa35a6abc8888ea610c8497def214626e2cdc30102b","class":{"content_id":"o11","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551","duration_ms":200,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523","duration_ms":200,"ad_id":null}],"merkle_root":"3dda1c1caa7d0ab22afbd28e76e6eacbd57aa35c8cbcda8b7427ad12467a9560","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"3a35c0bcb9a44f27eab67be767d2ed7effa70dc4d7b14b1d56b65c4137dbc0432428207c9996a1c35834e9309398301c93ad7339a1116785cfebd2659589c20d","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai#n=535"}],"preload_hint":null}]} +{"kind":"wai.video.manifest","manifest_hash":"044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79","session_class_hash":"7ecee2c182c9807140e70aa35a6abc8888ea610c8497def214626e2cdc30102b","class":{"content_id":"o11","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551","duration_ms":200,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523","duration_ms":200,"ad_id":null}],"merkle_root":"3dda1c1caa7d0ab22afbd28e76e6eacbd57aa35c8cbcda8b7427ad12467a9560","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"3a35c0bcb9a44f27eab67be767d2ed7effa70dc4d7b14b1d56b65c4137dbc0432428207c9996a1c35834e9309398301c93ad7339a1116785cfebd2659589c20d","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai#n=535"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o11/mr/461a7871f56c0e69017be36a7dce1ca89f4501b3c546280a48b4de0e729398ea.json b/wai/http-conformance/playlists/o11/mr/461a7871f56c0e69017be36a7dce1ca89f4501b3c546280a48b4de0e729398ea.json index bea3f71ef..c312496c9 100644 --- a/wai/http-conformance/playlists/o11/mr/461a7871f56c0e69017be36a7dce1ca89f4501b3c546280a48b4de0e729398ea.json +++ b/wai/http-conformance/playlists/o11/mr/461a7871f56c0e69017be36a7dce1ca89f4501b3c546280a48b4de0e729398ea.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79","session_class_hash":"7ecee2c182c9807140e70aa35a6abc8888ea610c8497def214626e2cdc30102b","class":{"content_id":"o11","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551","duration_ms":200,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523","duration_ms":200,"ad_id":null}],"merkle_root":"3dda1c1caa7d0ab22afbd28e76e6eacbd57aa35c8cbcda8b7427ad12467a9560","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"3a35c0bcb9a44f27eab67be767d2ed7effa70dc4d7b14b1d56b65c4137dbc0432428207c9996a1c35834e9309398301c93ad7339a1116785cfebd2659589c20d","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai#n=535"}],"preload_hint":null}]} +{"kind":"wai.video.manifest","manifest_hash":"044b6597093a22d2ae9cf86603fe564117bee5bb1b468424361da5ae48a81c79","session_class_hash":"7ecee2c182c9807140e70aa35a6abc8888ea610c8497def214626e2cdc30102b","class":{"content_id":"o11","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/8f3a701e2f904ad90663466c2c12cafed3410eeb84ad41db82acdc8f9a10adf7.wai#n=551","duration_ms":200,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/9e4ee50a011204c10f0538b8c89fdd41da51c85d970a6a54d850c55c255d5486.wai#n=523","duration_ms":200,"ad_id":null}],"merkle_root":"3dda1c1caa7d0ab22afbd28e76e6eacbd57aa35c8cbcda8b7427ad12467a9560","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"3a35c0bcb9a44f27eab67be767d2ed7effa70dc4d7b14b1d56b65c4137dbc0432428207c9996a1c35834e9309398301c93ad7339a1116785cfebd2659589c20d","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[{"n":1,"uri":"../../objects/8332309f88f11b520341c80edae6626e5c61ef2b332b12c6fdba4d7c79871504.wai#n=535"}],"preload_hint":null}]} diff --git a/wai/http-conformance/playlists/o9/mr/4b4657cea62905884efdfc59c357fd259910c5ddfd6c5314d6ed6c9894c80f13.json b/wai/http-conformance/playlists/o9/mr/4b4657cea62905884efdfc59c357fd259910c5ddfd6c5314d6ed6c9894c80f13.json index ca7df89fc..fb8d77b1d 100644 --- a/wai/http-conformance/playlists/o9/mr/4b4657cea62905884efdfc59c357fd259910c5ddfd6c5314d6ed6c9894c80f13.json +++ b/wai/http-conformance/playlists/o9/mr/4b4657cea62905884efdfc59c357fd259910c5ddfd6c5314d6ed6c9894c80f13.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"319206c1fef8bab9685bfda23964e51d515afd41f5b893cdd635a959de4a434e400f074ea8605aca48293f6b6b8b1fe7fb4582477f191078cb24c393e22d840f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"319206c1fef8bab9685bfda23964e51d515afd41f5b893cdd635a959de4a434e400f074ea8605aca48293f6b6b8b1fe7fb4582477f191078cb24c393e22d840f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/http-conformance/playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json b/wai/http-conformance/playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json index ca7df89fc..fb8d77b1d 100644 --- a/wai/http-conformance/playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json +++ b/wai/http-conformance/playlists/o9/mr/e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798.json @@ -1 +1 @@ -{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":18446744073709551615,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"319206c1fef8bab9685bfda23964e51d515afd41f5b893cdd635a959de4a434e400f074ea8605aca48293f6b6b8b1fe7fb4582477f191078cb24c393e22d840f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} +{"kind":"wai.video.manifest","manifest_hash":"e6fff6deff390739a13844a07178f756446079c7d95a327e14c85428ffb6c798","session_class_hash":"20acdf15043e64cc0a0642ca48c3719647bd5f03c57d3bcf344d0c1f49466443","class":{"content_id":"o9","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":0},"segments":[{"index":0,"kind":"content","uri":"../../objects/31abc1da2cdf927191f3f447f68508ead30d078690cb7bea2435ae3d1df5f4f3.wai#n=21034&h=1009","duration_ms":640,"ad_id":null},{"index":1,"kind":"content","uri":"../../objects/b7a711ab4b0a0939a561a16b3167039e7fe65c640d73a040ebc5d5cf86e4ecbb.wai#n=18004","duration_ms":640,"ad_id":null},{"index":2,"kind":"content","uri":"../../objects/a8ab2cd0cc277ef7109aacdee6bb55ff713aa0d4dc4c2e3dfe40aab759a90133.wai#n=18381","duration_ms":640,"ad_id":null}],"merkle_root":"323e355c473b98404de2b55855d3abcc843c0e67e07f40ac47fa1b5c30568f86","work_compose_ops":3,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":null,"signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"319206c1fef8bab9685bfda23964e51d515afd41f5b893cdd635a959de4a434e400f074ea8605aca48293f6b6b8b1fe7fb4582477f191078cb24c393e22d840f","render":{"version":7,"media_sequence":0,"discontinuity_sequence":0,"target_duration_s":1,"part_target_ms":null,"part_hold_back_ms":null,"can_block_reload":false,"end_list":true,"playlist_type":"VOD","independent_segments":false,"map_uri":null},"extras":[]} diff --git a/wai/wai-rs/src/bin/wai_cli/http.rs b/wai/wai-rs/src/bin/wai_cli/http.rs index e581cfef2..1b0da2942 100644 --- a/wai/wai-rs/src/bin/wai_cli/http.rs +++ b/wai/wai-rs/src/bin/wai_cli/http.rs @@ -37,7 +37,9 @@ //! [--live-swap ALIAS=OTHER]… //! replay a live edge (§11.4): revision k of the playlist served at PATH is current //! from k·MS after the start (1000 by default); a blocking reload (`_HLS_msn`, -//! `_HLS_part`) is held until a revision holds that segment or part, a part's +//! `_HLS_part`) is held until a revision holds that segment or part (and answered +//! 400 when `_HLS_msn` is more than two past the current revision's last segment, +//! `_HLS_part` comes without it, or either is not a number), a part's //! alias until the revision that first lists it with its cid, which names the //! object served; what no revision lists is answered when the last is current (an //! alias with 404). --live-swap serves OTHER's object under ALIAS (a test aid) @@ -348,18 +350,40 @@ impl LiveEdge { } } - fn serve(&self, path: &str, query: &str, until: Instant) -> Option<(Vec, Vec<(&'static str, String)>)> { + /// The playlist, an alias's object, `None` for what the edge does not serve, or + /// `Err` for a blocking reload the HLS second edition answers with 400: a directive + /// that is not a number, `_HLS_part` without `_HLS_msn`, or an `_HLS_msn` more than two + /// past the last segment of the current revision. + #[allow(clippy::type_complexity)] + fn serve(&self, path: &str, query: &str, until: Instant) -> Option, Vec<(&'static str, String)>), &'static str>> { let rel = path.trim_start_matches('/'); let last = self.revisions.len() - 1; if rel == self.path { - let q = |k: &str| query.split('&').find_map(|kv| kv.strip_prefix(k).and_then(|v| v.strip_prefix('='))).and_then(|v| v.parse::().ok()); - let target = match q("_HLS_msn") { - Some(msn) => (0..self.revisions.len()).find(|&k| self.has(k, msn, q("_HLS_part"))).unwrap_or(last), + let raw = |k: &str| query.split('&').find_map(|kv| kv.strip_prefix(k).and_then(|v| v.strip_prefix('='))); + let num = |k: &str| raw(k).map(|v| v.parse::().ok().filter(|_| !v.is_empty() && v.bytes().all(|b| b.is_ascii_digit()))); + let (msn, part) = (num("_HLS_msn"), num("_HLS_part")); + if matches!(msn, Some(None)) || matches!(part, Some(None)) { + return Some(Err("a blocking-reload directive is not a decimal integer")); + } + let (msn, part) = (msn.flatten(), part.flatten()); + if part.is_some() && msn.is_none() { + return Some(Err("_HLS_part without _HLS_msn")); + } + let target = match msn { + Some(msn) => { + // RFC 8216bis §6.2.5.2: past the last segment of the current playlist + // plus two, the request is refused at once rather than held. + let p = &self.revisions[self.current()].1; + if msn > p.media_sequence.saturating_add(p.segments.len() as u64).saturating_add(1) { + return Some(Err("_HLS_msn is more than two past the last segment")); + } + (0..self.revisions.len()).find(|&k| self.has(k, msn, part)).unwrap_or(last) + } None => 0, }; self.wait_for(target, until); let k = self.current().max(target.min(last)); - return Some((self.revisions[k].0.clone(), origin::playlist_headers("application/vnd.apple.mpegurl"))); + return Some(Ok((self.revisions[k].0.clone(), origin::playlist_headers("application/vnd.apple.mpegurl")))); } let alias = rel.strip_prefix(self.dir.as_str()).filter(|a| a.ends_with(".wai"))?; let looked = self.swap.iter().find(|(a, _)| a == alias).map_or(alias, |(_, b)| b.as_str()); @@ -368,7 +392,7 @@ impl LiveEdge { Some((k, cid)) => { self.wait_for(k, until); let bytes = std::fs::read(self.objects.get(&cid)?).ok()?; - Some((bytes, origin::live_object_headers(&cid, None, self.step.as_secs().max(1) * self.revisions.len() as u64))) + Some(Ok((bytes, origin::live_object_headers(&cid, None, self.step.as_secs().max(1) * self.revisions.len() as u64)))) } None if self.revisions.iter().any(|(b, _)| String::from_utf8_lossy(b).contains(&format!("URI=\"{alias}\""))) => { // Hinted and never listed: held until the edge ends, then not found. @@ -513,7 +537,11 @@ fn serve_one(cfg: &OriginConfig, stream: TcpStream, accepted: Instant) -> std::i if !path_ok(&req.path) { return respond(&s, until, 400, &[], b"bad path\n", req.method == "HEAD"); } - let live = cfg.live_edge.as_ref().and_then(|e| e.serve(&req.path, &req.query, until)); + let live = match cfg.live_edge.as_ref().and_then(|e| e.serve(&req.path, &req.query, until)) { + Some(Err(why)) => return respond(&s, until, 400, &[("Cache-Control", "no-store".to_string())], format!("{why}\n").as_bytes(), req.method == "HEAD"), + Some(Ok(x)) => Some(x), + None => None, + }; let Some((bytes, mut headers)) = live.or_else(|| resource(cfg, &req.path)) else { return respond(&s, until, 404, &[("Cache-Control", "no-store".to_string())], b"not found\n", req.method == "HEAD"); }; diff --git a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs index 620dc76bb..4d11489dc 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs @@ -12,8 +12,11 @@ //! it; with no classical MPD, the classical HLS timeline at timescale 1000 from 0 //! wai http seal-playlist --key //! [--signer-id ID] [--content-id ID] [--parent ] -//! [--out ] -//! seal a WAI playlist in its rendered form; write /mr/.json +//! [--grant ] [--out ] +//! seal a WAI playlist in its rendered form, under the publisher's grant (a JSON +//! object of grant_hash, capability, joule_ceiling_micro and funds_ceiling, each +//! ceiling a number up to 2^53 - 1 or null for none; with no --grant, a grant of +//! no ceilings); write /mr/.json //! and /mr/.json (dir: the playlist's own by default) and //! print its path and receipt hash //! wai http check-playlist --receipt @@ -248,8 +251,16 @@ pub fn seal(args: &[String]) -> Result<(), String> { let key = ed25519_dalek::SigningKey::from_bytes(&seed(opt(args, "--key").ok_or("seal-playlist needs --key")?)?); let parent = opt(args, "--parent").map(|p| ManifestReceipt::from_json(&String::from_utf8_lossy(&read(Path::new(p))?)).map_err(|e| format!("{p}: {e}"))).transpose()?; let stem = Path::new(path).file_name().and_then(|n| n.to_str()).unwrap_or("playlist"); + // The publisher's grant (Appendix H's form), or none: a grant with no ceilings. + let grant = match opt(args, "--grant") { + Some(g) => { + let v: serde_json::Value = serde_json::from_slice(&read(Path::new(g))?).map_err(|e| format!("{g}: {e}"))?; + wai::wai_video_manifest::grant_from_json(&v).map_err(|e| format!("{g}: {e}"))? + } + None => wai::quantum_ops::GrantRef::unbounded("video.manifest"), + }; let r = seal_playlist(&text, &key, opt(args, "--signer-id").unwrap_or("unconfirmed"), opt(args, "--content-id").unwrap_or(stem), - wai::quantum_ops::GrantRef::unbounded("video.manifest"), parent.as_ref()).map_err(|e| format!("{path}: {e}"))?; + grant, parent.as_ref()).map_err(|e| format!("{path}: {e}"))?; let dir = opt(args, "--out").map(PathBuf::from).unwrap_or_else(|| Path::new(path).parent().unwrap_or(Path::new(".")).to_path_buf()); // §8.6: at its manifest hash, where a client checks the playlist, and at its receipt // hash, where a client that skipped this revision fetches it by a later one's link. diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index aef932c6a..59cacb1eb 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -658,7 +658,10 @@ fn seal_all(c: &mut Corpus, out: &mut BTreeMap>) -> Result<(), S let mut sealed: Vec = Vec::new(); for (path, content, parent) in receipt_specs() { let t = String::from_utf8(c.file(path)?).map_err(|_| "not UTF-8")?; - let r = seal_playlist(&t, &key, PLAYLIST_SIGNER, content, GrantRef::unbounded("video.manifest"), parent.map(|k| &sealed[k])) + // O10's playlist is sealed under a publisher's grant with both ceilings; the rest + // under a grant of none, whose joule ceiling the file writes null. + let grant = if path == "playlists/o10/wai-a.m3u8" { publisher_grant() } else { GrantRef::unbounded("video.manifest") }; + let r = seal_playlist(&t, &key, PLAYLIST_SIGNER, content, grant, parent.map(|k| &sealed[k])) .map_err(|e| format!("{path}: {e}"))?; let dir = path.rsplit_once('/').map_or("", |(d, _)| d); publish(c, out, &format!("{dir}/mr"), &r); @@ -728,6 +731,13 @@ fn seal_all(c: &mut Corpus, out: &mut BTreeMap>) -> Result<(), S Ok(()) } +/// The grant the corpus seals O10's playlist under: a test grant, attesting nothing. +#[cfg(feature = "video_manifest")] +pub(super) fn publisher_grant() -> wai::quantum_ops::GrantRef { + wai::quantum_ops::GrantRef { grant_hash: *blake3::hash(b"http-conformance: a publisher's test grant").as_bytes(), capability: "video.manifest".into(), + joule_ceiling_micro: 5_000_000, funds_ceiling: Some(250) } +} + /// A receipt published in `dir` as §8.6 serves it: at `.json` and at /// `.json`. #[cfg(feature = "video_manifest")] @@ -1098,6 +1108,20 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { v.push(refused(name, "playlist_receipt", base(json!({ "edit_receipt": [[from, to]] })), "playlist_receipt_malformed")); } v.push(case("playlist_receipt_surrogate_pair", "playlist_receipt", base(json!({ "edit_receipt": [["\"geo\":\"\"", "\"geo\":\"\\ud83d\\ude00\""]] })))); + // The grant's ceilings (Appendix H): none written null, a number at most 2^53 - 1. + for (name, from, to, code) in [ + ("playlist_receipt_ceiling_past_2_53", "\"joule_ceiling_micro\":null", "\"joule_ceiling_micro\":9007199254740992", "playlist_receipt_malformed"), + ("playlist_receipt_ceiling_written_out", "\"joule_ceiling_micro\":null", "\"joule_ceiling_micro\":18446744073709551615", "playlist_receipt_malformed"), + ("playlist_receipt_ceiling_number_for_none", "\"joule_ceiling_micro\":null", "\"joule_ceiling_micro\":9007199254740991", "playlist_receipt_invalid"), + ("playlist_receipt_window_past_2_53", "\"window\":0", "\"window\":9007199254740992", "playlist_receipt_malformed"), + ] { + v.push(refused(name, "playlist_receipt", base(json!({ "edit_receipt": [[from, to]] })), code)); + } + let o10r = receipt_of(c, "playlists/o10/wai-a.m3u8")?; + v.push(refused("playlist_receipt_grant_ceiling_changed", "playlist_receipt", json!({ "playlist": "playlists/o10/wai-a.m3u8", "receipt": o10r, + "edit_receipt": [["\"joule_ceiling_micro\":5000000", "\"joule_ceiling_micro\":5000001"]] }), "playlist_receipt_invalid")); + v.push(refused("playlist_receipt_grant_funds_dropped", "playlist_receipt", json!({ "playlist": "playlists/o10/wai-a.m3u8", "receipt": o10r, + "edit_receipt": [["\"funds_ceiling\":250", "\"funds_ceiling\":null"]] }), "playlist_receipt_invalid")); // Ed25519 at its edges (§8.6 step 4, strictly): keys and R a lenient verifier accepts. for (file, _, _, _) in crafted_signatures() { let name = format!("playlist_receipt_{}", file.trim_end_matches(".json").replace('-', "_")); diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs index 793f34513..a7574e495 100644 --- a/wai/wai-rs/src/wai_video_manifest.rs +++ b/wai/wai-rs/src/wai_video_manifest.rs @@ -178,6 +178,51 @@ pub struct ManifestReceipt { pub extras: Vec, } +/// The largest number a receipt file writes (Appendix H): 2^53 − 1, so every JSON +/// reader reads each exactly (I-JSON, RFC 7493 §2.2). +pub const MAX_FILE_NUMBER: u64 = (1 << 53) - 1; + +/// Whether every number in `v` is an unsigned integer of at most [`MAX_FILE_NUMBER`]. +fn numbers_within(v: &serde_json::Value) -> bool { + use serde_json::Value; + match v { + Value::Number(n) => n.as_u64().is_some_and(|x| x <= MAX_FILE_NUMBER), + Value::Array(a) => a.iter().all(numbers_within), + Value::Object(o) => o.values().all(numbers_within), + _ => true, + } +} + +/// A grant as a receipt file writes it (Appendix H): `grant_hash`, `capability`, +/// `joule_ceiling_micro` (null for no ceiling, which signs as 2^64 − 1) and +/// `funds_ceiling` (null for none). +pub fn grant_json(g: &GrantRef) -> serde_json::Value { + serde_json::json!({ + "grant_hash": hex(&g.grant_hash), "capability": g.capability, + "joule_ceiling_micro": (g.joule_ceiling_micro != u64::MAX).then_some(g.joule_ceiling_micro), "funds_ceiling": g.funds_ceiling, + }) +} + +/// A grant read from [`grant_json`]'s form: exactly its four members, a ceiling a number +/// of at most 2^53 − 1 or null. +pub fn grant_from_json(v: &serde_json::Value) -> Result { + use serde_json::Value; + let g = v.as_object().filter(|g| g.len() == 4).ok_or("grant is an object of its four members")?; + let ceiling = |k: &str| -> Result, String> { + match g.get(k) { + Some(Value::Null) => Ok(None), + Some(n) => n.as_u64().filter(|&x| x <= MAX_FILE_NUMBER).map(Some).ok_or(format!("grant.{k} is a number of at most 2^53 - 1, or null")), + None => Err(format!("grant.{k}")), + } + }; + Ok(GrantRef { + grant_hash: j_hex32(g, "grant_hash")?, + capability: j_str(g, "capability")?, + joule_ceiling_micro: ceiling("joule_ceiling_micro")?.unwrap_or(u64::MAX), + funds_ceiling: ceiling("funds_ceiling")?, + }) +} + /// The first member name an object of `text` (valid JSON) holds twice, names compared /// after their escapes are read. fn duplicate_member(text: &str) -> Option { @@ -376,6 +421,12 @@ impl ManifestReceipt { if r.render_digest().is_none() { return Err(RenderError("a number past the canonical subset (2^53 - 1)".into())); } + // The file writes every number at most 2^53 − 1 (Appendix H); a joule ceiling of + // 2^64 − 1 (none) is written null. + let v: serde_json::Value = serde_json::from_str(&r.to_json()).map_err(|e| RenderError(e.to_string()))?; + if !numbers_within(&v) { + return Err(RenderError("a number past 2^53 - 1 in the receipt file".into())); + } Ok(r) } @@ -550,10 +601,7 @@ impl ManifestReceipt { o.insert(k.into(), v); } } - o.insert("grant".into(), json!({ - "grant_hash": hex(&self.grant.grant_hash), "capability": self.grant.capability, - "joule_ceiling_micro": self.grant.joule_ceiling_micro, "funds_ceiling": self.grant.funds_ceiling, - })); + o.insert("grant".into(), grant_json(&self.grant)); o.insert("parent_receipt_hash".into(), json!(self.parent_receipt_hash.map(|h| hex(&h)))); o.insert("signer_pubkey".into(), json!(hex(&self.signer_pubkey))); o.insert("signer_id".into(), json!(self.signer_id)); @@ -576,6 +624,10 @@ impl ManifestReceipt { // RFC 8259 text; a `\u` escape that leaves a surrogate unpaired is refused by the // parser (it denotes no Unicode text). let v: Value = serde_json::from_str(text).map_err(|e| e.to_string())?; + // Every number is an unsigned integer of at most 2^53 − 1 (Appendix H). + if !numbers_within(&v) { + return Err("a number that is not an unsigned integer of at most 2^53 - 1".into()); + } // A member name twice in one object is refused, at any depth and however the // names are escaped: a reader that keeps the first and one that keeps the last // would read two receipts from one file (I-JSON, RFC 7493 §2.3). @@ -626,20 +678,7 @@ impl ManifestReceipt { }; segments.push(Segment { index: j_u64(s, "index")?, kind, uri: j_str(s, "uri")?, duration_ms: j_u64(s, "duration_ms")?, ad_id }); } - let grant = o.get("grant").and_then(Value::as_object).ok_or("grant")?; - if grant.len() != 4 { - return Err("grant has other members than its four".into()); - } - let grant = GrantRef { - grant_hash: j_hex32(grant, "grant_hash")?, - capability: j_str(grant, "capability")?, - joule_ceiling_micro: j_u64(grant, "joule_ceiling_micro")?, - funds_ceiling: match grant.get("funds_ceiling") { - Some(Value::Null) => None, - Some(v) => Some(v.as_u64().ok_or("grant.funds_ceiling")?), - None => return Err("grant.funds_ceiling".into()), - }, - }; + let grant = grant_from_json(o.get("grant").ok_or("grant")?)?; let parent_receipt_hash = match o.get("parent_receipt_hash") { Some(Value::Null) => None, Some(_) => Some(j_hex32(o, "parent_receipt_hash")?), @@ -1167,6 +1206,31 @@ mod tests { assert_eq!(seal(segments(), live, vec![gap_layers, Default::default(), Default::default()]).unwrap_err().code(), "playlist_malformed"); } + /// A grant's ceilings in the file: none is written null (and signs as 2^64 − 1), a + /// ceiling a number of at most 2^53 − 1; a receipt that cannot be written so is not + /// sealed, and a file with a number past it is refused. + #[test] + fn grant_ceilings_in_the_file() { + let none = GrantRef::unbounded("video.manifest"); + assert_eq!(grant_json(&none)["joule_ceiling_micro"], serde_json::Value::Null); + assert_eq!(grant_from_json(&grant_json(&none)).unwrap(), none); + let held = GrantRef { grant_hash: [3u8; 32], capability: "video.manifest".into(), joule_ceiling_micro: MAX_FILE_NUMBER, funds_ceiling: Some(7) }; + assert_eq!(grant_from_json(&grant_json(&held)).unwrap(), held); + let mut big = grant_json(&held); + big["joule_ceiling_micro"] = serde_json::json!(MAX_FILE_NUMBER + 1); + assert!(grant_from_json(&big).is_err()); + big["joule_ceiling_micro"] = serde_json::json!(u64::MAX); + assert!(grant_from_json(&big).is_err(), "no ceiling is null, not 2^64 - 1 written out"); + let seal = |g: GrantRef| ManifestReceipt::seal_with(&key(15), "o", public_class(0), segments(), 1, 0, None, g, None, HlsRender::legacy(), vec![]); + let r = seal(none).unwrap(); + assert!(r.to_json().contains("\"joule_ceiling_micro\":null")); + assert_eq!(ManifestReceipt::from_json(&r.to_json()).unwrap(), r); + seal(held.clone()).unwrap(); + assert_eq!(seal(GrantRef { joule_ceiling_micro: MAX_FILE_NUMBER + 1, ..held.clone() }).unwrap_err().code(), "playlist_malformed"); + assert_eq!(seal(GrantRef { funds_ceiling: Some(u64::MAX), ..held }).unwrap_err().code(), "playlist_malformed"); + assert!(ManifestReceipt::from_json(&r.to_json().replace("\"viewers_served\":1", "\"viewers_served\":9007199254740992")).is_err()); + } + /// A receipt file is read strictly: an unknown member, a missing one, an upper-case /// hash, `render` without `extras`, are each refused. #[test] diff --git a/wai/wai-rs/tests/cli_http.rs b/wai/wai-rs/tests/cli_http.rs index 1b704a631..6396b36ea 100644 --- a/wai/wai-rs/tests/cli_http.rs +++ b/wai/wai-rs/tests/cli_http.rs @@ -516,6 +516,20 @@ fn seal_and_check_playlist() { let bad = wai(&["http", "check-playlist", d.join("changed.m3u8").to_str().unwrap(), "--receipt", receipt.to_str().unwrap()]); assert_eq!(bad.status.code(), Some(1)); assert_eq!(String::from_utf8_lossy(&bad.stdout), "refused\tplaylist_receipt_mismatch\n"); + // With no grant, one of no ceilings, written null; under the publisher's grant, its + // ceilings; a ceiling past 2^53 - 1 is not sealed. + assert!(String::from_utf8_lossy(&std::fs::read(&receipt).unwrap()).contains("\"joule_ceiling_micro\":null")); + let grant = d.join("grant.json"); + std::fs::write(&grant, format!("{{\"grant_hash\":\"{}\",\"capability\":\"video.manifest\",\"joule_ceiling_micro\":5000000,\"funds_ceiling\":null}}", "ab".repeat(32))).unwrap(); + let granted = wai(&["http", "seal-playlist", pl.to_str().unwrap(), "--key", &key, "--grant", grant.to_str().unwrap(), "--out", d.join("granted").to_str().unwrap()]); + assert!(granted.status.success(), "{}", String::from_utf8_lossy(&granted.stderr)); + let file = String::from_utf8(std::fs::read(d.join("granted/mr").join(&name)).unwrap()).unwrap(); + assert!(file.contains("\"joule_ceiling_micro\":5000000") && file.contains(&"ab".repeat(32)), "{file}"); + let ok = wai(&["http", "check-playlist", pl.to_str().unwrap(), "--receipt", d.join("granted/mr").join(&name).to_str().unwrap()]); + assert!(ok.status.success(), "{}", String::from_utf8_lossy(&ok.stdout)); + std::fs::write(&grant, format!("{{\"grant_hash\":\"{}\",\"capability\":\"video.manifest\",\"joule_ceiling_micro\":9007199254740992,\"funds_ceiling\":null}}", "ab".repeat(32))).unwrap(); + let past = wai(&["http", "seal-playlist", pl.to_str().unwrap(), "--key", &key, "--grant", grant.to_str().unwrap(), "--out", d.join("past").to_str().unwrap()]); + assert!(!past.status.success()); // Each receipt is also written under its receipt hash. let hash_of = |out: &std::process::Output| String::from_utf8_lossy(&out.stdout).trim_end().rsplit(' ').next().unwrap().to_string(); let r1_hash = hash_of(&run); @@ -574,6 +588,13 @@ fn origin_replays_a_live_edge() { let (h, body) = get(o.port, "/live/wai-720.m3u8", &[]); assert!(h.starts_with("HTTP/1.1 200") && h.contains("Cache-Control: no-cache, no-transform\r\n"), "{h}"); assert!(texts.contains(&body), "the playlist is one of its revisions"); + // A blocking reload more than two segments past the edge, or a part with no segment, + // is refused at once (RFC 8216bis §6.2.5.2), not held. + // (A held reload is answered 200 with a revision; 400 is the refusal.) + for q in ["_HLS_msn=1300", "_HLS_msn=1300&_HLS_part=0", "_HLS_part=1", "_HLS_msn=x", "_HLS_msn=-1"] { + let (h, _) = get(o.port, &format!("/live/wai-720.m3u8?{q}"), &[]); + assert!(h.starts_with("HTTP/1.1 400"), "{q}: {h}"); + } // Part 2 of segment 1201 is first listed by revision 1. let (_, body) = get(o.port, "/live/wai-720.m3u8?_HLS_msn=1201&_HLS_part=2", &[]); let p = wai::http_binding::hls::parse_media_playlist(&String::from_utf8(body.clone()).unwrap()).unwrap(); -- 2.54.0 (Apple Git-157) From 591e75adec226b5a5fb7636ba6fb146a00e55d4f Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:25:08 -0400 Subject: [PATCH 42/55] http-conformance: falsify the grant's null ceiling and the receipt file's number bound Co-Authored-By: Claude Opus 5.5 --- .../falsify/grant-ceiling-written-out.patch | 15 ++++++++++++ .../falsify/receipt-numbers-unbounded.patch | 24 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 wai/http-conformance/falsify/grant-ceiling-written-out.patch create mode 100644 wai/http-conformance/falsify/receipt-numbers-unbounded.patch diff --git a/wai/http-conformance/falsify/grant-ceiling-written-out.patch b/wai/http-conformance/falsify/grant-ceiling-written-out.patch new file mode 100644 index 000000000..5d78400c1 --- /dev/null +++ b/wai/http-conformance/falsify/grant-ceiling-written-out.patch @@ -0,0 +1,15 @@ +# must fail: regeneration +# no joule ceiling written as 2^64 - 1 (wai_video_manifest::grant_json) +diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs +index a7574e49..41275c34 100644 +--- a/wai/wai-rs/src/wai_video_manifest.rs ++++ b/wai/wai-rs/src/wai_video_manifest.rs +@@ -199,7 +199,7 @@ fn numbers_within(v: &serde_json::Value) -> bool { + pub fn grant_json(g: &GrantRef) -> serde_json::Value { + serde_json::json!({ + "grant_hash": hex(&g.grant_hash), "capability": g.capability, +- "joule_ceiling_micro": (g.joule_ceiling_micro != u64::MAX).then_some(g.joule_ceiling_micro), "funds_ceiling": g.funds_ceiling, ++ "joule_ceiling_micro": g.joule_ceiling_micro, "funds_ceiling": g.funds_ceiling, + }) + } + diff --git a/wai/http-conformance/falsify/receipt-numbers-unbounded.patch b/wai/http-conformance/falsify/receipt-numbers-unbounded.patch new file mode 100644 index 000000000..30b0609f6 --- /dev/null +++ b/wai/http-conformance/falsify/receipt-numbers-unbounded.patch @@ -0,0 +1,24 @@ +# must fail: playlist_receipt_ceiling_past_2_53 playlist_receipt_ceiling_written_out playlist_receipt_window_past_2_53 +# numbers past 2^53 - 1 read (ManifestReceipt::from_json) +diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs +index a7574e49..60e021ee 100644 +--- a/wai/wai-rs/src/wai_video_manifest.rs ++++ b/wai/wai-rs/src/wai_video_manifest.rs +@@ -186,7 +186,7 @@ pub const MAX_FILE_NUMBER: u64 = (1 << 53) - 1; + fn numbers_within(v: &serde_json::Value) -> bool { + use serde_json::Value; + match v { +- Value::Number(n) => n.as_u64().is_some_and(|x| x <= MAX_FILE_NUMBER), ++ Value::Number(n) => n.as_u64().is_some(), + Value::Array(a) => a.iter().all(numbers_within), + Value::Object(o) => o.values().all(numbers_within), + _ => true, +@@ -211,7 +211,7 @@ pub fn grant_from_json(v: &serde_json::Value) -> Result { + let ceiling = |k: &str| -> Result, String> { + match g.get(k) { + Some(Value::Null) => Ok(None), +- Some(n) => n.as_u64().filter(|&x| x <= MAX_FILE_NUMBER).map(Some).ok_or(format!("grant.{k} is a number of at most 2^53 - 1, or null")), ++ Some(n) => n.as_u64().map(Some).ok_or(format!("grant.{k} is a number of at most 2^53 - 1, or null")), + None => Err(format!("grant.{k}")), + } + }; -- 2.54.0 (Apple Git-157) From a4e5e42c86574f697c3f2771b15263db89a54c69 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:32:14 -0400 Subject: [PATCH 43/55] wai: the adaptation and receipt-file bounds stay out of the C header; wai.h regenerated, as on main MAX_OPTIONS had reached include/wai.h with an unrelated commit (25ae3576); it and MAX_FILE_NUMBER are cbindgen:ignore, like the walk bound and the MPD reader's limits, and the regenerated header is main's again. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/include/wai.h | 6 ------ wai/wai-rs/src/http_binding/abr.rs | 1 + wai/wai-rs/src/wai_video_manifest.rs | 1 + 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/wai/wai-rs/include/wai.h b/wai/wai-rs/include/wai.h index 2ba709e2d..024e56bbc 100644 --- a/wai/wai-rs/include/wai.h +++ b/wai/wai-rs/include/wai.h @@ -316,12 +316,6 @@ */ #define RESOLUTION_FLOOR_UJ_PPM 288676 -/** - * The most options a state holds, and the most objects (a base and its layers) a depth - * is chosen over (Appendix F). - */ -#define MAX_OPTIONS 256 - /** * The most chaining values a proof may carry. */ diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs index 1a13ecb7d..50361dafd 100644 --- a/wai/wai-rs/src/http_binding/abr.rs +++ b/wai/wai-rs/src/http_binding/abr.rs @@ -47,6 +47,7 @@ impl std::error::Error for AbrError {} /// The most options a state holds, and the most objects (a base and its layers) a depth /// is chosen over (Appendix F). +/// cbindgen:ignore pub const MAX_OPTIONS: usize = 256; /// Appendix F's bounds on a state beyond those of its fields' types (each `ms` and `u32` diff --git a/wai/wai-rs/src/wai_video_manifest.rs b/wai/wai-rs/src/wai_video_manifest.rs index a7574e495..71bf92b43 100644 --- a/wai/wai-rs/src/wai_video_manifest.rs +++ b/wai/wai-rs/src/wai_video_manifest.rs @@ -180,6 +180,7 @@ pub struct ManifestReceipt { /// The largest number a receipt file writes (Appendix H): 2^53 − 1, so every JSON /// reader reads each exactly (I-JSON, RFC 7493 §2.2). +/// cbindgen:ignore pub const MAX_FILE_NUMBER: u64 = (1 << 53) - 1; /// Whether every number in `v` is an unsigned integer of at most [`MAX_FILE_NUMBER`]. -- 2.54.0 (Apple Git-157) From a53c41cd77ef77f83274410f823b4aa456146ea4 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:36:28 -0400 Subject: [PATCH 44/55] CHANGELOG: the playlist and adaptation fixes after review Co-Authored-By: Claude Opus 5.5 --- wai/CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 2f79beaff..ed0fd637f 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -194,6 +194,25 @@ wire format may change and such changes are recorded here. paths (the selector's order now sees the verified head), and layer depth; seeded vectors per policy, the integer log, entry orders and depths, which `http_verify.py` reproduces. +- **Playlists and adaptation, after review** (http-delivery §8.6, §11, Appendices F, H + and I): + - a live client walks back to the head it holds across revisions it skipped: every + receipt is also served at `mr/.json`, the head fetched again is + unchanged, and a walk is bounded (`playlist_receipt_parent_missing`, + `playlist_receipt_walk_exceeded`); no revision lists another cid for an alias or a + media sequence number an earlier one bound (`playlist_listing_rebound`); + - Ed25519 is verified strictly (`wai::sig::verify_strict`: a canonical key of large + order, `S` < L, `R` of large order compared byte for byte), by the playlist receipt + and `http_verify.py`; + - DASH adjacency is checked over an XML subset (Appendix I); `wai.mpd` takes the + classical MPD's period, timescale, `t` and `d` and leaves a gap out; MPD values are + escaped and checked as XML; + - adaptation states have stated bounds (`abr_state_out_of_bounds`) and are computed + exactly within them; under a budget only a labelled option within it is chosen; + - a gap segment carries no parts or layer lines; the session-data line has one place + in the classical master; a receipt file holds no member twice, no lone surrogate + and no number past 2^53 − 1, a grant of no joule ceiling written `null`; + `seal-playlist --grant`; the origin answers a far-future `_HLS_msn` with 400. - **What review found, fixed** (http-delivery §3.1, §4, §7, §7.1, §10, §13, Appendix D): - a layer object (a `WAI1` whose capability is refinement-only) is never dispatched: it is fetched whole at refinement urgency, verified against its cid and handed to -- 2.54.0 (Apple Git-157) From bb166fddb48341784b7569345a4464ba4c319eff Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 11:39:54 -0400 Subject: [PATCH 45/55] wai-http workflow: runs when the shared Ed25519 rule changes Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-http.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/wai-http.yml b/.github/workflows/wai-http.yml index 2efe2c927..45f658044 100644 --- a/.github/workflows/wai-http.yml +++ b/.github/workflows/wai-http.yml @@ -48,6 +48,8 @@ on: # The playlists render through hls_render and are sealed by wai_video_manifest. - 'wai/wai-rs/src/hls_render.rs' - 'wai/wai-rs/src/wai_video_manifest.rs' + # Playlist receipts verify Ed25519 through the shared strict rule. + - 'wai/wai-rs/src/sig.rs' - 'wai/wai-rs/Cargo.toml' - 'wai/http-conformance/**' - 'wai/cache-conformance/**' @@ -73,6 +75,8 @@ on: # The playlists render through hls_render and are sealed by wai_video_manifest. - 'wai/wai-rs/src/hls_render.rs' - 'wai/wai-rs/src/wai_video_manifest.rs' + # Playlist receipts verify Ed25519 through the shared strict rule. + - 'wai/wai-rs/src/sig.rs' - 'wai/wai-rs/Cargo.toml' - 'wai/http-conformance/**' - 'wai/cache-conformance/**' -- 2.54.0 (Apple Git-157) From 81038daec882ec186aa0e74ad8108fee8669a671 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 13:37:12 -0400 Subject: [PATCH 46/55] =?UTF-8?q?wai:=20an=20MPD=20is=20held=20as=20writte?= =?UTF-8?q?n=20=E2=80=94=20timelines=20in=20runs,=20compared=20run=20by=20?= =?UTF-8?q?run,=20at=20most=20256=20per=20document=20=E2=80=94=20and=20an?= =?UTF-8?q?=20element's=20attributes=20read=20in=20linear=20time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dash_read never expands @r: a timeline is its runs (t, d, count), a run's end checked against 2^64 - 1, and §11.2's comparison walks the runs of both timelines, each step passing one. A document holds at most 256 timelines. Attribute names are kept in a set. http_verify.py does the same. DashTimeline::from_mpd expands only up to the caller's own segment count. tests/dash_read_bounds.rs holds the reader to the document's size under a counting allocator (32 timelines of 2^20 segments) and an element of 40000 attributes to linear thread CPU time; http_verify.py's self-test holds both with tracemalloc and thread_time. Appendix I states it. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-conformance.yml | 6 +- wai/extensions/http-delivery.md | 11 +- wai/http-conformance/cases.json | 5 +- wai/http-conformance/http_verify.py | 111 ++++++++++---- wai/wai-rs/src/bin/wai_cli/http_playlists.rs | 6 +- .../src/bin/wai_cli/http_vectors_playlists.rs | 8 +- wai/wai-rs/src/http_binding/dash.rs | 12 +- wai/wai-rs/src/http_binding/dash_read.rs | 122 +++++++++++---- .../src/http_binding/tests_playlists.rs | 43 ++++-- wai/wai-rs/tests/dash_read_bounds.rs | 140 ++++++++++++++++++ 10 files changed, 385 insertions(+), 79 deletions(-) create mode 100644 wai/wai-rs/tests/dash_read_bounds.rs diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index 252b7e38d..b9aec28aa 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -274,10 +274,12 @@ jobs: # and floor, the origin's headers, ranges and If-Range on loopback, and a fetch by # the byte-range procedure whose record states energy as not measured; and `hls`, # `mpd`, `seal-playlist` and `check-playlist`, whose receipts need video_manifest. - # Cargo.toml declares it with required-features = ["http"]. + # Cargo.toml declares it with required-features = ["http"]. tests/dash_read_bounds.rs + # holds Appendix I's reader to the document's size under a counting allocator, and an + # element's attributes to linear CPU time; a binary of its own, one test. - name: The wai http commands working-directory: wai/wai-rs - run: cargo test --no-default-features --features http,video_manifest --test cli_http + run: cargo test --no-default-features --features http,video_manifest --test cli_http --test dash_read_bounds # The machine-readable registry (wai/registry/, wai/vocab/v2.*, the table in # wai/REGISTRY.md) is generated from wai-rs/src/codecs/registry.rs. Regenerate it in # memory and fail on any difference; the message says how to regenerate. One leg: the diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index 66034567a..91829a872 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -1232,5 +1232,12 @@ for a later one. An `AdaptationSet`'s timelines are the `SegmentTimeline` of its `AdaptationSet` and then the `Period`, else 1; 0 is refused. Each `S` has `@d`, a positive integer, and may have `@t` (by default the end of the `S` before it, 0 for the first) and `@r` (by default 0: the segment repeated `@r` more times). `@t`, `@d`, -`@r` and `@timescale` are digits alone; a time past 2^64 − 1, or a timeline of more -than 2^20 segments, is refused. Other elements and attributes are not read. +`@r` and `@timescale` are digits alone; a segment whose end, `@t + @d·(@r + 1)` for the +last of a run, passes 2^64 − 1 is refused, and so is a document of more than 256 +timelines. Other elements and attributes are not read. + +A reader holds a timeline as it is written, one run per `S`, and never expands `@r`: +what it holds is bounded by the document's size, whatever `@r` says. §11.2's comparison +runs run by run: each step passes a WAI run or a classical one, so it is linear in the +runs of the two timelines. The names an element's attributes have are kept in a set, so +an element is read in time linear in its attributes. diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index 71f4d0952..1847a49c9 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -459,7 +459,10 @@ {"name":"dash_adjacency_attribute_twice","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["",""]],"edit_classical":[]},"expect":{"error":"playlist_malformed"}}, {"name":"dash_adjacency_unclosed","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["",""]],"edit_classical":[]},"expect":{"error":"playlist_malformed"}}, {"name":"dash_adjacency_r_negative","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["r=\"2\"","r=\"-1\""]]},"expect":{"error":"playlist_malformed"}}, -{"name":"dash_adjacency_r_past_the_subset","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["r=\"2\"","r=\"1048576\""]]},"expect":{"error":"playlist_malformed"}}, +{"name":"dash_adjacency_classical_r_huge","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["d=\"57600\" r=\"2\"","d=\"57600\" r=\"300000000000000\""]]},"expect":{"adjacent":true}}, +{"name":"dash_adjacency_r_end_past_u64","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["d=\"57600\" r=\"2\"","d=\"57600\" r=\"18446744073709551614\""]]},"expect":{"error":"playlist_malformed"}}, +{"name":"dash_adjacency_wai_r_huge","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["",""]],"edit_classical":[]},"expect":{"error":"adjacency_misaligned"}}, +{"name":"dash_adjacency_timelines_past_the_subset","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["",""]]},"expect":{"error":"playlist_malformed"}}, {"name":"dash_adjacency_start_in_months","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["start=\"PT0S\"","start=\"P0M\""]],"edit_classical":[]},"expect":{"error":"playlist_malformed"}}, {"name":"dash_adjacency_not_an_mpd","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["",""]],"edit_classical":[]},"expect":{"error":"playlist_malformed"}}, diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index d539f3398..2347722e9 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -32,7 +32,7 @@ stops the verifier, never passes. Exit status 0 only when every case gives its committed outcome (or, for diff, every proof verifies, or exactly the flipped ones fail). """ -import hashlib, json, os, random, re, sys +import hashlib, json, os, random, re, sys, time HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) @@ -2067,7 +2067,7 @@ def wai_mpd(t, set_id, union, floor, rep_id, bandwidth, media): # ---------------------------------------------- reading an MPD (Appendix I) -MAX_TIMELINE_SEGMENTS = 1 << 20 +MAX_TIMELINES = 256 MAX_DEPTH = 64 NAME = re.compile(r"[A-Za-z_:][A-Za-z0-9_:.\-]*", re.ASCII) @@ -2165,7 +2165,7 @@ class Xml: def start_tag(self): name = self.name() - attrs = [] + attrs, names = [], set() while True: spaced = self.space() if self.eat("/>"): @@ -2180,8 +2180,10 @@ class Xml: raise self.bad() self.space() v = self.attr_value() - if any(a == k for a, _ in attrs) and MUTATE != "xml-attribute-twice": + seen = any(a == k for a, _ in attrs) if MUTATE == "xml-attributes-scanned" else k in names + if seen and MUTATE != "xml-attribute-twice": raise self.bad() + names.add(k) attrs.append((k, v)) @@ -2296,6 +2298,8 @@ def unsigned(v): def timeline_of(e, inherited): + """A timeline as written: {"timescale", "runs": [(t, d, count)]}, one run per S, never + expanded; None without a SegmentTimeline.""" st = next((c for c in e["children"] if local(c) == "SegmentTimeline"), None) if st is None: return None @@ -2303,22 +2307,22 @@ def timeline_of(e, inherited): timescale = unsigned(ts) if ts is not None else 1 if timescale == 0: raise Refusal("playlist_malformed") - segs, nxt = [], 0 + runs, nxt = [], 0 for sel in (c for c in st["children"] if local(c) == "S"): d = unsigned(xattr(sel, "d")) if d == 0: raise Refusal("playlist_malformed") t = unsigned(xattr(sel, "t")) if xattr(sel, "t") is not None else nxt r = unsigned(xattr(sel, "r")) if xattr(sel, "r") is not None else 0 - if r >= MAX_TIMELINE_SEGMENTS or len(segs) + r + 1 > MAX_TIMELINE_SEGMENTS: + end = t + d * (r + 1) + if end > U64: raise Refusal("playlist_malformed") - for _ in range(r + 1): - segs.append((t, d)) - t += d - if t > U64: - raise Refusal("playlist_malformed") - nxt = t - return {"timescale": timescale, "segments": segs} + if MUTATE == "dash-r-expanded": + runs += [(t + i * d, d, 1) for i in range(r + 1)] + else: + runs.append((t, d, r + 1)) + nxt = end + return {"timescale": timescale, "runs": runs} SEGMENT_INFO = ("SegmentTemplate", "SegmentList") @@ -2329,7 +2333,7 @@ def read_mpd(text): root = read_xml(text) if local(root) != "MPD": raise Refusal("playlist_malformed") - periods = [] + periods, held = [], 0 for i, p in enumerate(c for c in root["children"] if local(c) == "Period"): st = xattr(p, "start") if st is not None: @@ -2354,6 +2358,9 @@ def read_mpd(text): tl = timeline_of(e, [x for x in a_info + p_info if local(x) == local(e)]) if tl is not None: tls.append(tl) + held += len(tls) + if held > MAX_TIMELINES and MUTATE != "dash-timelines-unbounded": + raise Refusal("playlist_malformed") sets.append({"id": xattr(a, "id"), "timelines": tls}) periods.append({"id": xattr(p, "id"), "start_ms": start, "sets": sets}) if not periods: @@ -2362,13 +2369,28 @@ def read_mpd(text): def within(wai, classical): - j = 0 - for t, d in wai: - while j < len(classical) and classical[j][0] != t: - j += 1 - if j == len(classical) or classical[j][1] != d: - return False - j += 1 + """Whether the WAI runs list only segments of the classical runs, each with its t and + d, in order: for each WAI segment, the first classical segment at or after the one + matched before with its t has its d too. Run by run, never expanded.""" + ci, co = 0, 0 + for wt, wd, wn in wai: + t, left = wt, wn + while left > 0: + while True: + if ci == len(classical): + return False + ct, cd, cn = classical[ci] + if t >= ct + co * cd and (t - ct) % cd == 0 and (t - ct) // cd < cn: + co = (t - ct) // cd + break + ci, co = ci + 1, 0 + ct, cd, cn = classical[ci] + if cd != wd: + return False + take = min(left, cn - co) + left, co, t = left - take, co + take, t + take * wd + if co == cn: + ci, co = ci + 1, 0 return True @@ -2387,13 +2409,14 @@ def check_dash_adjacent(wai, classical): if not st["timelines"]: raise mis for tl in st["timelines"]: - if not tl["segments"] or not any((c["timescale"] == tl["timescale"] or MUTATE == "dash-timescale-ignored") and within(tl["segments"], c["segments"]) - for cs in kp["sets"] for c in cs["timelines"]): + if not tl["runs"] or not any((c["timescale"] == tl["timescale"] or MUTATE == "dash-timescale-ignored") and within(tl["runs"], c["runs"]) + for cs in kp["sets"] for c in cs["timelines"]): raise mis -def mpd_timeline(text, period_id, set_id): - """The classical MPD's timeline (§11.1): (period id, start ms, timescale, segments).""" +def mpd_timeline(text, period_id, set_id, max_segments): + """The classical MPD's timeline (§11.1): (period id, start ms, timescale, segments), + expanded only when it lists at most max_segments segments.""" periods = read_mpd(text) p = next((x for x in periods if x["id"] == period_id), None) if period_id is not None else periods[0] if p is None: @@ -2402,7 +2425,9 @@ def mpd_timeline(text, period_id, set_id): if st is None or not st["timelines"]: raise Refusal("playlist_malformed") tl = st["timelines"][0] - return (p["id"], p["start_ms"], tl["timescale"], tl["segments"]) + if sum(n for _, _, n in tl["runs"]) > max_segments: + raise Refusal("playlist_malformed") + return (p["id"], p["start_ms"], tl["timescale"], [(t + i * d, d) for t, d, n in tl["runs"] for i in range(n)]) # ------------------------------------------------ playlist receipts (§8.6) @@ -2966,7 +2991,7 @@ def mpd_case(d, objs, inp): union, floor = union_floor_of(every) peak = bandwidths(segs)[0] if isinstance(inp.get("classical_mpd"), str): - t = mpd_timeline(read_text(d, inp["classical_mpd"]), inp.get("period_id"), inp.get("classical_set")) + t = mpd_timeline(read_text(d, inp["classical_mpd"]), inp.get("period_id"), inp.get("classical_set"), len(p["segments"])) else: c = parse_media_playlist(read_text(d, inp["classical"])) at, segs_t = 0, [] @@ -3229,6 +3254,38 @@ def self_test(): if B3.parent_cv(B3.subtree_cv(data[:l], 0), B3.subtree_cv(data[l:], l), True) != got: print("FAIL self-test: the tree functions over %d bytes" % n) return False + return mpd_bounds_hold() + + +def mpd_bounds_hold(): + """Appendix I's reader holds what a document writes, not what its @r says, and reads + an element's attributes in linear time: 8 timelines of 2^16 segments each in a + kilobyte, read in a footprint bounded by its size (tracemalloc's peak); and one + element of 40000 attributes, read in bounded CPU time of this thread (never wall + time).""" + import tracemalloc + bomb = "%s" % "".join( + '' % i + for i in range(8)) + tracemalloc.start() + try: + periods = read_mpd(bomb) + peak = tracemalloc.get_traced_memory()[1] + finally: + tracemalloc.stop() + if sum(n for tl in periods[0]["sets"][0]["timelines"] for _, _, n in tl["runs"]) != 8 << 16 or peak > 64 * len(bomb) + (1 << 20): + print("FAIL self-test: an MPD of %d bytes held %d bytes" % (len(bomb), peak)) + return False + wide = "" % " ".join('a%d=""' % i for i in range(40000)) + t0 = time.thread_time() + try: + read_mpd(wide) + except Refusal: + pass + spent = time.thread_time() - t0 + if spent > 2.0: + print("FAIL self-test: one element of 40000 attributes read in %.2f s of CPU" % spent) + return False return True diff --git a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs index 4d11489dc..1d123e660 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs @@ -204,7 +204,11 @@ pub fn mpd(args: &[String]) -> Result<(), String> { let classical_mpd = opt(args, "--classical-mpd").map(|p| String::from_utf8(read(Path::new(p))?).map_err(|_| format!("{p}: not UTF-8"))).transpose()?; let hls_timeline = |v: &Variant| DashTimeline::from_hls(&v.classical, opt(args, "--period-id").unwrap_or("p0"), 0).map_err(|e| format!("{}: {e}", v.uri)); let timeline = match &classical_mpd { - Some(text) => DashTimeline::from_mpd(text, opt(args, "--period-id"), opt(args, "--classical-set")).map_err(|e| format!("the classical MPD: {e}"))?, + Some(text) => { + // Expanded no further than the most segments a variant lists. + let most = list.variants.iter().map(|v| v.segments.len()).max().unwrap_or(0); + DashTimeline::from_mpd(text, opt(args, "--period-id"), opt(args, "--classical-set"), most).map_err(|e| format!("the classical MPD: {e}"))? + } None => hls_timeline(first)?, }; let mut reps = Vec::new(); diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index 59cacb1eb..b5dfab810 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -357,7 +357,7 @@ fn render_mpd(c: &Corpus, input: &Value) -> Result { let (union, floor) = union_floor(&all)?; let (peak, _) = bandwidths(&segs).ok_or("no segment")?; let t = match input["classical_mpd"].as_str() { - Some(f) => DashTimeline::from_mpd(&text(c, f)?, input["period_id"].as_str(), input["classical_set"].as_str()).map_err(|e| pl_err(&e))?, + Some(f) => DashTimeline::from_mpd(&text(c, f)?, input["period_id"].as_str(), input["classical_set"].as_str(), p.segments.len()).map_err(|e| pl_err(&e))?, None => { let classical = parse_media_playlist(&text(c, input["classical"].as_str().ok_or("classical")?)?).map_err(|e| pl_err(&e))?; DashTimeline::from_hls(&classical, input["period_id"].as_str().unwrap_or("p0"), 0).map_err(|e| pl_err(&e))? @@ -1037,7 +1037,11 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { ("dash_adjacency_attribute_twice", json!([["", ""]]), none.clone(), bad), ("dash_adjacency_unclosed", json!([["", ""]]), none.clone(), bad), ("dash_adjacency_r_negative", none.clone(), json!([["r=\"2\"", "r=\"-1\""]]), bad), - ("dash_adjacency_r_past_the_subset", none.clone(), json!([["r=\"2\"", "r=\"1048576\""]]), bad), + ("dash_adjacency_classical_r_huge", none.clone(), json!([["d=\"57600\" r=\"2\"", "d=\"57600\" r=\"300000000000000\""]]), None), + ("dash_adjacency_r_end_past_u64", none.clone(), json!([["d=\"57600\" r=\"2\"", "d=\"57600\" r=\"18446744073709551614\""]]), bad), + ("dash_adjacency_wai_r_huge", json!([["", ""]]), none.clone(), mis), + ("dash_adjacency_timelines_past_the_subset", none.clone(), json!([["".repeat(256))]]), bad), ("dash_adjacency_t_past_u64", none.clone(), json!([["", ""]]), bad), ("dash_adjacency_start_in_months", json!([["start=\"PT0S\"", "start=\"P0M\""]]), none.clone(), bad), ("dash_adjacency_not_an_mpd", json!([["", ""]]), none.clone(), bad), diff --git a/wai/wai-rs/src/http_binding/dash.rs b/wai/wai-rs/src/http_binding/dash.rs index 7dd63c36d..6b2448c60 100644 --- a/wai/wai-rs/src/http_binding/dash.rs +++ b/wai/wai-rs/src/http_binding/dash.rs @@ -61,9 +61,11 @@ impl DashTimeline { /// The timeline of a classical MPD (§11.1): the period with `@id` `period_id` (the /// first by default), its adaptation set with `@id` `set_id` (by default the first - /// that has a timeline), and that set's first timeline (Appendix I). Refused - /// (`playlist_malformed`) when the MPD does not read or holds no such timeline. - pub fn from_mpd(text: &str, period_id: Option<&str>, set_id: Option<&str>) -> Result { + /// that has a timeline), and that set's first timeline (Appendix I), expanded to one + /// entry per segment. Refused (`playlist_malformed`) when the MPD does not read, + /// holds no such timeline, or lists more than `max_segments` segments there (the + /// caller's own count, so that no `@r` makes it hold more than it lists). + pub fn from_mpd(text: &str, period_id: Option<&str>, set_id: Option<&str>, max_segments: usize) -> Result { let periods = read_mpd(text)?; let p = match period_id { Some(id) => periods.iter().find(|p| p.id.as_deref() == Some(id)), @@ -74,7 +76,9 @@ impl DashTimeline { None => p.sets.iter().find(|s| !s.timelines.is_empty()), }.ok_or_else(|| PlaylistError::Malformed("no such AdaptationSet in the classical MPD".into()))?; let tl = set.timelines.first().ok_or_else(|| PlaylistError::Malformed("the classical AdaptationSet has no SegmentTimeline".into()))?; - Ok(DashTimeline { period_id: p.id.clone(), period_start_ms: p.start_ms, timescale: tl.timescale, segments: tl.segments.clone() }) + let segments = tl.segments(max_segments) + .ok_or_else(|| PlaylistError::Malformed(format!("the classical timeline lists {} segments, more than {max_segments}", tl.segment_count())))?; + Ok(DashTimeline { period_id: p.id.clone(), period_start_ms: p.start_ms, timescale: tl.timescale, segments }) } /// The timeline's length in milliseconds, and its longest segment in milliseconds. diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs index 79d55df6f..71e36cde2 100644 --- a/wai/wai-rs/src/http_binding/dash_read.rs +++ b/wai/wai-rs/src/http_binding/dash_read.rs @@ -4,6 +4,12 @@ //! `@start`s, and each of its timelines has a classical timeline's timescale and lists //! only that timeline's segments, each with its `t` and `d`, in order. //! +//! A timeline is held as it is written, one run per `S` (`@t`, `@d`, and `@r` + 1 +//! segments), never expanded: what a reader holds is bounded by the document's size, +//! whatever `@r` says, and timelines are compared run by run. A document holds at most +//! [`MAX_TIMELINES`] timelines, so comparing every WAI timeline with every classical one +//! is bounded too. +//! //! The XML read is a subset of XML 1.0, stated in Appendix I, so that every conforming //! reader refuses the same documents: no document type declaration, CDATA section or //! processing instruction past the declaration; ASCII names; the five predefined @@ -11,10 +17,10 @@ use super::hls::{decimal_ms, PlaylistError}; -/// The most segments one timeline expands to (Appendix I); a timeline past it is +/// The most `SegmentTimeline`s a document holds (Appendix I); a document past it is /// refused. /// cbindgen:ignore -pub const MAX_TIMELINE_SEGMENTS: usize = 1 << 20; +pub const MAX_TIMELINES: usize = 256; /// The deepest element nesting read (Appendix I). /// cbindgen:ignore @@ -178,6 +184,8 @@ impl<'a> Reader<'a> { fn start_tag(&mut self) -> Result<(Element, bool), PlaylistError> { let name = self.name()?; let mut attrs: Vec<(String, String)> = Vec::new(); + // The names read so far, to refuse one twice in constant time per attribute. + let mut names = std::collections::HashSet::new(); loop { let spaced = self.space(); if self.eat("/>") { @@ -196,7 +204,7 @@ impl<'a> Reader<'a> { } self.space(); let v = self.attr_value()?; - if attrs.iter().any(|(a, _)| *a == k) { + if !names.insert(k.clone()) { return Err(malformed(format!("the attribute {k} twice"))); } attrs.push((k, v)); @@ -318,11 +326,43 @@ pub fn duration_ms(s: &str) -> Option { any.then_some(ms) } -/// One segment timeline: its timescale and each segment's `t` and `d`, `@r` expanded. -#[derive(Clone, Debug, PartialEq, Eq)] +/// One `S` of a timeline: `count` segments (`@r` + 1, at least 1) of `d` each, the +/// first at `t`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Run { + pub t: u64, + pub d: u64, + pub count: u64, +} + +/// One segment timeline: its timescale and its runs, as written. Every segment's end is +/// at most 2^64 − 1. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Timeline { pub timescale: u64, - pub segments: Vec<(u64, u64)>, + pub runs: Vec, +} + +impl Timeline { + /// How many segments the timeline lists. + pub fn segment_count(&self) -> u128 { + self.runs.iter().map(|r| u128::from(r.count)).sum() + } + + /// Each segment's `t` and `d`, expanded, when there are at most `max`; `None` + /// otherwise, so that no expansion passes what the caller can hold. + pub fn segments(&self, max: usize) -> Option> { + if self.segment_count() > max as u128 { + return None; + } + let mut out = Vec::with_capacity(self.segment_count() as usize); + for r in &self.runs { + for i in 0..r.count { + out.push((r.t + i * r.d, r.d)); + } + } + Some(out) + } } /// An adaptation set's `@id` and the timelines it carries (its own, and each @@ -358,25 +398,24 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result, if timescale == 0 { return Err(malformed("@timescale is 0")); } - let mut segments = Vec::new(); + let mut runs = Vec::new(); let mut next = 0u64; for s in st.children_named("S") { let d = unsigned(s.attr("d").ok_or_else(|| malformed("an S has no @d"))?, "@d")?; if d == 0 { return Err(malformed("an S of no duration")); } - let mut t = s.attr("t").map(|v| unsigned(v, "@t")).transpose()?.unwrap_or(next); + let t = s.attr("t").map(|v| unsigned(v, "@t")).transpose()?.unwrap_or(next); let r = s.attr("r").map(|v| unsigned(v, "@r")).transpose()?.unwrap_or(0); - if r >= MAX_TIMELINE_SEGMENTS as u64 || segments.len() as u64 + r + 1 > MAX_TIMELINE_SEGMENTS as u64 { - return Err(malformed("a timeline past the subset's segments")); + // The run's end, t + d·(r + 1), at most 2^64 − 1: computed, never expanded. + let end = u128::from(t) + u128::from(d) * (u128::from(r) + 1); + if end > u128::from(u64::MAX) { + return Err(malformed("a segment time past 2^64 - 1")); } - for _ in 0..=r { - segments.push((t, d)); - t = t.checked_add(d).ok_or_else(|| malformed("a segment time past 2^64 - 1"))?; - } - next = t; + runs.push(Run { t, d, count: r + 1 }); + next = end as u64; } - Ok(Some(Timeline { timescale, segments })) + Ok(Some(Timeline { timescale, runs })) } const SEGMENT_INFO: [&str; 2] = ["SegmentTemplate", "SegmentList"]; @@ -385,13 +424,19 @@ const SEGMENT_INFO: [&str; 2] = ["SegmentTemplate", "SegmentList"]; /// when absent; a later one without it is refused), and in each `AdaptationSet` its own /// `SegmentTemplate` or `SegmentList` timeline and each `Representation`'s, a timescale /// inherited from the set and the period. Refused (`playlist_malformed`) when it does -/// not read, its root is not `MPD`, it has no period, or a timeline is not one. +/// not read, its root is not `MPD`, it has no period, a timeline is not one, or it holds +/// more than [`MAX_TIMELINES`] timelines. pub fn read_mpd(text: &str) -> Result, PlaylistError> { let root = read_xml(text)?; if root.local() != "MPD" { return Err(malformed("the root element is not MPD")); } let mut periods = Vec::new(); + let mut held = 0usize; + let mut count = |timelines: &Vec| -> Result<(), PlaylistError> { + held += timelines.len(); + if held > MAX_TIMELINES { Err(malformed("more SegmentTimelines than the subset's 256")) } else { Ok(()) } + }; for (i, p) in root.children_named("Period").enumerate() { let start_ms = match p.attr("start") { Some(v) => duration_ms(v).ok_or_else(|| malformed("a Period@start is not a duration of days, hours, minutes and seconds"))?, @@ -413,6 +458,7 @@ pub fn read_mpd(text: &str) -> Result, PlaylistError> { timelines.extend(timeline_of(e, &up)?); } } + count(&timelines)?; sets.push(SetTimelines { id: a.attr("id").map(String::from), timelines }); } periods.push(PeriodTimelines { id: p.attr("id").map(String::from), start_ms, sets }); @@ -424,17 +470,35 @@ pub fn read_mpd(text: &str) -> Result, PlaylistError> { } /// Whether `wai` lists only segments of `classical`, each with its `t` and `d`, in -/// order. -fn within(wai: &[(u64, u64)], classical: &[(u64, u64)]) -> bool { - let mut j = 0; - for &(t, d) in wai { - while j < classical.len() && classical[j].0 != t { - j += 1; +/// order: for each WAI segment in turn, the first classical segment at or after the one +/// matched before with its `t` has its `d` too. Run by run, never expanded: each step +/// consumes a WAI run or a classical one, so the work is linear in the runs. +fn within(wai: &[Run], classical: &[Run]) -> bool { + // The classical cursor: a run, and the segments of it already passed. + let (mut ci, mut co) = (0usize, 0u128); + for w in wai { + let (mut t, mut left) = (u128::from(w.t), u128::from(w.count)); + while left > 0 { + // The first classical segment at or after the cursor whose time is `t`. + loop { + let Some(c) = classical.get(ci) else { return false }; + let (ct, cd) = (u128::from(c.t), u128::from(c.d)); + if t >= ct + co * cd && (t - ct) % cd == 0 && (t - ct) / cd < u128::from(c.count) { + co = (t - ct) / cd; + break; + } + (ci, co) = (ci + 1, 0); + } + let c = &classical[ci]; + if c.d != w.d { + return false; + } + let take = left.min(u128::from(c.count) - co); + (left, co, t) = (left - take, co + take, t + take * u128::from(w.d)); + if co == u128::from(c.count) { + (ci, co) = (ci + 1, 0); + } } - if j == classical.len() || classical[j].1 != d { - return false; - } - j += 1; } true } @@ -461,8 +525,8 @@ pub fn check_dash_adjacent(wai: &str, classical: &str) -> Result<(), PlaylistErr return Err(misaligned(format!("AdaptationSet {:?} has no SegmentTimeline", s.id))); } for tl in &s.timelines { - let aligned = !tl.segments.is_empty() - && kp.sets.iter().flat_map(|c| &c.timelines).any(|c| c.timescale == tl.timescale && within(&tl.segments, &c.segments)); + let aligned = tl.segment_count() > 0 + && kp.sets.iter().flat_map(|c| &c.timelines).any(|c| c.timescale == tl.timescale && within(&tl.runs, &c.runs)); if !aligned { return Err(misaligned(format!("a timeline of AdaptationSet {:?} is not a classical one's", s.id))); } diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index 28ee63c80..81652afc1 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -303,27 +303,33 @@ fn wai_mpd_shape() { } /// The timeline of a classical MPD: its period's id and start, the set named (or the -/// first with a timeline), `@r` expanded, the timescale inherited; and the scaled -/// timeline refuses a time past 2^64 − 1. +/// first with a timeline), held in runs and expanded only up to the caller's count, the +/// timescale inherited; and the scaled timeline refuses a time past 2^64 − 1. #[test] fn dash_timeline_from_the_classical_mpd() { use super::dash::*; let mpd = "\ \ "; - let t = DashTimeline::from_mpd(mpd, None, None).unwrap(); + let t = DashTimeline::from_mpd(mpd, None, None, 3).unwrap(); assert_eq!(t, DashTimeline { period_id: Some("a".into()), period_start_ms: 62_500, timescale: 90_000, segments: vec![(10, 5), (15, 5), (20, 7)] }); - assert_eq!(DashTimeline::from_mpd(mpd, None, Some("9")).unwrap_err().code(), "playlist_malformed"); - assert_eq!(DashTimeline::from_mpd(mpd, Some("b"), None).unwrap_err().code(), "playlist_malformed"); - // An S of no duration, a timeline past 2^20 segments, a time past 2^64 − 1, and a - // later Period without @start are refused; the first Period's start defaults to 0. + assert_eq!(DashTimeline::from_mpd(mpd, None, None, 2).unwrap_err().code(), "playlist_malformed", "more segments than the caller lists"); + assert_eq!(DashTimeline::from_mpd(mpd, None, Some("9"), 3).unwrap_err().code(), "playlist_malformed"); + assert_eq!(DashTimeline::from_mpd(mpd, Some("b"), None, 3).unwrap_err().code(), "playlist_malformed"); + // An S of no duration, a time past 2^64 − 1 (an end, so t + d·(r + 1)), and a later + // Period without @start are refused; the first Period's start defaults to 0. let one = |period: &str, ss: &str| format!("{period}{ss}"); - assert_eq!(DashTimeline::from_mpd(&one("", ""), None, None).unwrap().period_start_ms, 0); - for bad in [one("", ""), one("", ""), one("", ""), - one("", ""), one("", "")] { + assert_eq!(DashTimeline::from_mpd(&one("", ""), None, None, 1).unwrap().period_start_ms, 0); + for bad in [one("", ""), one("", ""), + one("", ""), one("", ""), + one("", "")] { assert_eq!(super::dash_read::read_mpd(&bad).unwrap_err().code(), "playlist_malformed", "{}", &bad[..bad.len().min(120)]); } - assert_eq!(super::dash_read::read_mpd(&one("", "")).unwrap()[0].sets[0].timelines[0].segments.len(), 1 << 20); + // @r is held as written: 2^64 − 1 segments in one run, read and never expanded. + let huge = super::dash_read::read_mpd(&one("", "")).unwrap(); + let tl = &huge[0].sets[0].timelines[0]; + assert_eq!((tl.runs.len(), tl.segment_count()), (1, u128::from(u64::MAX))); + assert_eq!(tl.segments(1 << 20), None); assert_eq!(scaled_timeline(&[(10, 2), (12, 3)], 10, 1000).unwrap(), vec![(0, 2000), (2000, 3000)]); assert_eq!(scaled_timeline(&[(u64::MAX, 1)], 0, 2).unwrap_err().code(), "playlist_malformed"); assert_eq!(scaled_timeline(&[(1, u64::MAX)], 0, 2).unwrap_err().code(), "playlist_malformed"); @@ -373,6 +379,21 @@ fn dash_adjacency() { assert_eq!(code(&wai("id=\"p\" start=\"PT2S\"", 25, "")), Some("adjacency_misaligned")); assert_eq!(code(&wai("id=\"p\" start=\"PT2S\"", 25, "")), Some("adjacency_misaligned")); assert_eq!(code(&wai("id=\"p\" start=\"PT2S\"", 25, "")), Some("playlist_malformed")); + // Run by run: a WAI run that spans two classical runs, a classical run entered + // part-way, and a run past the classical one's end. + let runs = ""; + let in_runs = |ss: &str| check_dash_adjacent(&wai("id=\"p\" start=\"PT2S\"", 25, ss), runs).err().map(|e| e.code()); + assert_eq!(in_runs(""), None, "across the two runs"); + assert_eq!(in_runs(""), None, "entered part-way, then the last"); + assert_eq!(in_runs(""), Some("adjacency_misaligned"), "one past the classical run"); + assert_eq!(in_runs(""), Some("adjacency_misaligned"), "between two classical segments"); + assert_eq!(in_runs(""), Some("playlist_malformed"), "an end past 2^64 - 1"); + assert_eq!(in_runs(""), Some("adjacency_misaligned"), "far past, in one step"); + // At most 256 timelines in a document. + let many = |n: usize| format!("{}", + "".repeat(n)); + assert!(super::dash_read::read_mpd(&many(super::dash_read::MAX_TIMELINES)).is_ok()); + assert_eq!(super::dash_read::read_mpd(&many(super::dash_read::MAX_TIMELINES + 1)).unwrap_err().code(), "playlist_malformed"); assert_eq!(check_dash_adjacent(&wai("id=\"p\" start=\"PT2S\"", 25, ""), "").unwrap_err().code(), "playlist_malformed"); } diff --git a/wai/wai-rs/tests/dash_read_bounds.rs b/wai/wai-rs/tests/dash_read_bounds.rs new file mode 100644 index 000000000..9c034bc8e --- /dev/null +++ b/wai/wai-rs/tests/dash_read_bounds.rs @@ -0,0 +1,140 @@ +//! Reading an MPD (`extensions/http-delivery.md` Appendix I) holds what the document +//! writes, not what its `@r` says, and reads an element's attributes in linear time. +//! Measured with a global allocator that counts the bytes live and their peak, and the +//! calling thread's CPU time. +//! +//! - A few kilobytes of 32 timelines, each one `S` of 2^20 segments, are read and +//! compared with each other within a footprint bounded by the document's size: the +//! runs are held as written and compared run by run, never expanded. +//! - One element of 40000 attributes is read in time linear in its attributes: the +//! names already read are kept in a set, not scanned again for each attribute. +//! +//! Its own test binary with a single test, so that no other test's allocations land in +//! the windows it measures. +#![cfg(feature = "http")] + +use std::alloc::{GlobalAlloc, Layout, System}; +use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; +use std::time::Duration; + +use wai::http_binding::dash_read::{check_dash_adjacent, read_mpd, read_xml}; + +/// The system allocator, counting the bytes live and their peak. +struct Count; +static CUR: AtomicUsize = AtomicUsize::new(0); +static PEAK: AtomicUsize = AtomicUsize::new(0); + +fn grow(n: usize) { + PEAK.fetch_max(CUR.fetch_add(n, Relaxed) + n, Relaxed); +} + +unsafe impl GlobalAlloc for Count { + unsafe fn alloc(&self, l: Layout) -> *mut u8 { + grow(l.size()); + unsafe { System.alloc(l) } + } + unsafe fn alloc_zeroed(&self, l: Layout) -> *mut u8 { + grow(l.size()); + unsafe { System.alloc_zeroed(l) } + } + unsafe fn realloc(&self, p: *mut u8, l: Layout, new: usize) -> *mut u8 { + if new >= l.size() { + grow(new - l.size()); + } else { + CUR.fetch_sub(l.size() - new, Relaxed); + } + unsafe { System.realloc(p, l, new) } + } + unsafe fn dealloc(&self, p: *mut u8, l: Layout) { + CUR.fetch_sub(l.size(), Relaxed); + unsafe { System.dealloc(p, l) } + } +} + +#[global_allocator] +static ALLOC: Count = Count; + +/// The calling thread's CPU time: on a loaded host wall-clock time grows with the load, +/// the thread's own CPU time only with its work. +#[cfg(unix)] +fn now() -> Duration { + #[repr(C)] + struct Timespec { + tv_sec: i64, + tv_nsec: i64, + } + unsafe extern "C" { + fn clock_gettime(clock: i32, tp: *mut Timespec) -> i32; + } + #[cfg(target_vendor = "apple")] + const CLOCK_THREAD_CPUTIME_ID: i32 = 16; + #[cfg(not(target_vendor = "apple"))] + const CLOCK_THREAD_CPUTIME_ID: i32 = 3; + let mut ts = Timespec { tv_sec: 0, tv_nsec: 0 }; + assert_eq!(unsafe { clock_gettime(CLOCK_THREAD_CPUTIME_ID, &mut ts) }, 0, "clock_gettime"); + Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32) +} + +/// Elsewhere, wall-clock time from a fixed origin. +#[cfg(not(unix))] +fn now() -> Duration { + static ORIGIN: std::sync::OnceLock = std::sync::OnceLock::new(); + ORIGIN.get_or_init(std::time::Instant::now).elapsed() +} + +/// The most bytes live at once beyond the start, while `f` runs. +fn peak(f: impl FnOnce() -> T) -> (T, usize) { + let start = CUR.load(Relaxed); + PEAK.store(start, Relaxed); + let out = f(); + (out, PEAK.load(Relaxed) - start) +} + +/// The CPU time of the best of three runs of `f`. +fn cpu(f: impl Fn()) -> Duration { + (0..3).map(|_| { + let t = now(); + f(); + now() - t + }).min().expect("three runs") +} + +/// `n` Representations, each a timeline of one `S` of 2^20 segments. +fn bomb(n: usize) -> String { + let reps: String = (0..n) + .map(|i| format!("")) + .collect(); + format!("{reps}") +} + +/// One element of `n` attributes. +fn wide(n: usize) -> String { + let attrs: String = (0..n).map(|i| format!(" a{i}=\"\"")).collect(); + format!("") +} + +#[test] +fn an_mpd_is_held_as_written_and_read_in_linear_time() { + // 32 timelines of 2^20 segments in about 5 KB: an expanded reader holds 2^25 + // segments (hundreds of megabytes); this one, a run each. + for n in [1usize, 8, 32] { + let text = bomb(n); + let (periods, held) = peak(|| read_mpd(&text).unwrap()); + let tls = &periods[0].sets[0].timelines; + assert_eq!(tls.len(), n); + assert!(tls.iter().all(|t| t.runs.len() == 1 && t.segment_count() == 1 << 20)); + let bound = 64 * text.len() + (1 << 20); + assert!(held <= bound, "{n} timelines in {} bytes held {held} bytes (bound {bound})", text.len()); + // Compared with themselves, each of 32 WAI timelines against 32 classical ones. + let (adjacent, held) = peak(|| check_dash_adjacent(&text, &text)); + adjacent.unwrap(); + assert!(held <= 2 * bound, "comparing {n} timelines held {held} bytes"); + } + // One element of 40000 attributes against one of 10000: linear, so about four times + // the CPU time; a reader that scans the names read so far for each one takes about + // sixteen times. The margin keeps a loaded host from deciding it. + let (short, long) = (wide(10_000), wide(40_000)); + read_xml(&long).unwrap(); + let (t_short, t_long) = (cpu(|| drop(read_xml(&short))), cpu(|| drop(read_xml(&long)))); + assert!(t_long <= t_short * 8 + Duration::from_millis(20), "40000 attributes took {t_long:?}, 10000 took {t_short:?}"); +} -- 2.54.0 (Apple Git-157) From cf4f868a30ae14dbd5018e42ca5d3c66d342aaf8 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 13:40:23 -0400 Subject: [PATCH 47/55] dash_read: the bounds test holds the comparison's CPU time to the runs, not the segments; a WAI segment back within a run is misaligned Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/http_binding/tests_playlists.rs | 1 + wai/wai-rs/tests/dash_read_bounds.rs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index 81652afc1..67b872989 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -387,6 +387,7 @@ fn dash_adjacency() { assert_eq!(in_runs(""), None, "entered part-way, then the last"); assert_eq!(in_runs(""), Some("adjacency_misaligned"), "one past the classical run"); assert_eq!(in_runs(""), Some("adjacency_misaligned"), "between two classical segments"); + assert_eq!(in_runs(""), Some("adjacency_misaligned"), "back within a run"); assert_eq!(in_runs(""), Some("playlist_malformed"), "an end past 2^64 - 1"); assert_eq!(in_runs(""), Some("adjacency_misaligned"), "far past, in one step"); // At most 256 timelines in a document. diff --git a/wai/wai-rs/tests/dash_read_bounds.rs b/wai/wai-rs/tests/dash_read_bounds.rs index 9c034bc8e..3e9f53320 100644 --- a/wai/wai-rs/tests/dash_read_bounds.rs +++ b/wai/wai-rs/tests/dash_read_bounds.rs @@ -6,6 +6,7 @@ //! - A few kilobytes of 32 timelines, each one `S` of 2^20 segments, are read and //! compared with each other within a footprint bounded by the document's size: the //! runs are held as written and compared run by run, never expanded. +//! - Comparing them takes CPU time that does not grow with the segments a run lists. //! - One element of 40000 attributes is read in time linear in its attributes: the //! names already read are kept in a set, not scanned again for each attribute. //! @@ -101,8 +102,14 @@ fn cpu(f: impl Fn()) -> Duration { /// `n` Representations, each a timeline of one `S` of 2^20 segments. fn bomb(n: usize) -> String { + bomb_of(n, 1 << 20) +} + +/// `n` Representations, each a timeline of one `S` of `segments` segments. +fn bomb_of(n: usize, segments: u64) -> String { + let r = segments - 1; let reps: String = (0..n) - .map(|i| format!("")) + .map(|i| format!("")) .collect(); format!("{reps}") } @@ -130,6 +137,12 @@ fn an_mpd_is_held_as_written_and_read_in_linear_time() { adjacent.unwrap(); assert!(held <= 2 * bound, "comparing {n} timelines held {held} bytes"); } + // The comparison is run by run: 32 timelines of 2^20 segments each, compared with + // themselves, take about the CPU time of 32 timelines of 2^10; one that steps a + // segment at a time takes about a thousand times as long. + let (few, many) = (bomb_of(32, 1 << 10), bomb_of(32, 1 << 20)); + let (t_few, t_many) = (cpu(|| check_dash_adjacent(&few, &few).unwrap()), cpu(|| check_dash_adjacent(&many, &many).unwrap())); + assert!(t_many <= t_few * 4 + Duration::from_millis(20), "2^20 segments a timeline took {t_many:?}, 2^10 took {t_few:?}"); // One element of 40000 attributes against one of 10000: linear, so about four times // the CPU time; a reader that scans the names read so far for each one takes about // sixteen times. The margin keeps a loaded host from deciding it. -- 2.54.0 (Apple Git-157) From 265c06f0efa7b59dc3599d896df4c9c2bfca6e61 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 13:51:28 -0400 Subject: [PATCH 48/55] http-conformance: falsify the MPD reader's runs, its timeline bound and run ends, against the new reader; Python mutations for expansion, scanned attribute names and the timeline bound Co-Authored-By: Claude Opus 5.5 --- .../falsify/dash-order-unchecked.patch | 20 +++++++++---------- .../falsify/dash-r-ignored.patch | 15 ++++++++++++++ .../falsify/dash-r-not-expanded.patch | 15 -------------- .../falsify/dash-run-end-unchecked.patch | 15 ++++++++++++++ .../falsify/dash-timeline-unbounded.patch | 15 -------------- .../falsify/dash-timelines-unbounded.patch | 15 ++++++++++++++ .../falsify/dash-timescale-unchecked.patch | 10 +++++----- .../falsify/dash-zero-d.patch | 6 +++--- wai/http-conformance/falsify/run.sh | 1 + .../falsify/xml-attribute-twice.patch | 8 ++++---- 10 files changed, 68 insertions(+), 52 deletions(-) create mode 100644 wai/http-conformance/falsify/dash-r-ignored.patch delete mode 100644 wai/http-conformance/falsify/dash-r-not-expanded.patch create mode 100644 wai/http-conformance/falsify/dash-run-end-unchecked.patch delete mode 100644 wai/http-conformance/falsify/dash-timeline-unbounded.patch create mode 100644 wai/http-conformance/falsify/dash-timelines-unbounded.patch diff --git a/wai/http-conformance/falsify/dash-order-unchecked.patch b/wai/http-conformance/falsify/dash-order-unchecked.patch index c41d2974d..e2f50cee2 100644 --- a/wai/http-conformance/falsify/dash-order-unchecked.patch +++ b/wai/http-conformance/falsify/dash-order-unchecked.patch @@ -1,14 +1,14 @@ # must fail: dash_adjacency_out_of_order -# segments out of order accepted (dash_read::within) +# the classical cursor reset for each WAI run: segments out of order accepted (dash_read::within) diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs -index 79d55df6..4ed32a04 100644 +index 71e36cde..7a416543 100644 --- a/wai/wai-rs/src/http_binding/dash_read.rs +++ b/wai/wai-rs/src/http_binding/dash_read.rs -@@ -428,6 +428,7 @@ pub fn read_mpd(text: &str) -> Result, PlaylistError> { - fn within(wai: &[(u64, u64)], classical: &[(u64, u64)]) -> bool { - let mut j = 0; - for &(t, d) in wai { -+ j = 0; - while j < classical.len() && classical[j].0 != t { - j += 1; - } +@@ -477,6 +477,7 @@ fn within(wai: &[Run], classical: &[Run]) -> bool { + // The classical cursor: a run, and the segments of it already passed. + let (mut ci, mut co) = (0usize, 0u128); + for w in wai { ++ (ci, co) = (0, 0); + let (mut t, mut left) = (u128::from(w.t), u128::from(w.count)); + while left > 0 { + // The first classical segment at or after the cursor whose time is `t`. diff --git a/wai/http-conformance/falsify/dash-r-ignored.patch b/wai/http-conformance/falsify/dash-r-ignored.patch new file mode 100644 index 000000000..be2c4a41e --- /dev/null +++ b/wai/http-conformance/falsify/dash-r-ignored.patch @@ -0,0 +1,15 @@ +# must fail: dash_adjacency_o9 mpd_playlists_o9_wai_mpd +# @r read as no repeat (dash_read::timeline_of) +diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs +index 71e36cde..6611250e 100644 +--- a/wai/wai-rs/src/http_binding/dash_read.rs ++++ b/wai/wai-rs/src/http_binding/dash_read.rs +@@ -412,7 +412,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result, + if end > u128::from(u64::MAX) { + return Err(malformed("a segment time past 2^64 - 1")); + } +- runs.push(Run { t, d, count: r + 1 }); ++ runs.push(Run { t, d, count: 1 }); + next = end as u64; + } + Ok(Some(Timeline { timescale, runs })) diff --git a/wai/http-conformance/falsify/dash-r-not-expanded.patch b/wai/http-conformance/falsify/dash-r-not-expanded.patch deleted file mode 100644 index 971211b0c..000000000 --- a/wai/http-conformance/falsify/dash-r-not-expanded.patch +++ /dev/null @@ -1,15 +0,0 @@ -# must fail: dash_adjacency_o9 mpd_playlists_o9_wai_mpd -# @r not expanded (dash_read::timeline_of) -diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs -index 79d55df6..f725df12 100644 ---- a/wai/wai-rs/src/http_binding/dash_read.rs -+++ b/wai/wai-rs/src/http_binding/dash_read.rs -@@ -370,7 +370,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result, - if r >= MAX_TIMELINE_SEGMENTS as u64 || segments.len() as u64 + r + 1 > MAX_TIMELINE_SEGMENTS as u64 { - return Err(malformed("a timeline past the subset's segments")); - } -- for _ in 0..=r { -+ for _ in 0..=0 { - segments.push((t, d)); - t = t.checked_add(d).ok_or_else(|| malformed("a segment time past 2^64 - 1"))?; - } diff --git a/wai/http-conformance/falsify/dash-run-end-unchecked.patch b/wai/http-conformance/falsify/dash-run-end-unchecked.patch new file mode 100644 index 000000000..457f8fcfb --- /dev/null +++ b/wai/http-conformance/falsify/dash-run-end-unchecked.patch @@ -0,0 +1,15 @@ +# must fail: dash_adjacency_r_end_past_u64 +# a run whose end passes 2^64 - 1 read (dash_read::timeline_of) +diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs +index 71e36cde..5b55d5a1 100644 +--- a/wai/wai-rs/src/http_binding/dash_read.rs ++++ b/wai/wai-rs/src/http_binding/dash_read.rs +@@ -409,7 +409,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result, + let r = s.attr("r").map(|v| unsigned(v, "@r")).transpose()?.unwrap_or(0); + // The run's end, t + d·(r + 1), at most 2^64 − 1: computed, never expanded. + let end = u128::from(t) + u128::from(d) * (u128::from(r) + 1); +- if end > u128::from(u64::MAX) { ++ if false && end > u128::from(u64::MAX) { + return Err(malformed("a segment time past 2^64 - 1")); + } + runs.push(Run { t, d, count: r + 1 }); diff --git a/wai/http-conformance/falsify/dash-timeline-unbounded.patch b/wai/http-conformance/falsify/dash-timeline-unbounded.patch deleted file mode 100644 index 804e1f56f..000000000 --- a/wai/http-conformance/falsify/dash-timeline-unbounded.patch +++ /dev/null @@ -1,15 +0,0 @@ -# must fail: dash_adjacency_r_past_the_subset -# a timeline past the subset's segments read (dash_read::timeline_of) -diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs -index 79d55df6..c80ac01c 100644 ---- a/wai/wai-rs/src/http_binding/dash_read.rs -+++ b/wai/wai-rs/src/http_binding/dash_read.rs -@@ -367,7 +367,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result, - } - let mut t = s.attr("t").map(|v| unsigned(v, "@t")).transpose()?.unwrap_or(next); - let r = s.attr("r").map(|v| unsigned(v, "@r")).transpose()?.unwrap_or(0); -- if r >= MAX_TIMELINE_SEGMENTS as u64 || segments.len() as u64 + r + 1 > MAX_TIMELINE_SEGMENTS as u64 { -+ if r >= u64::MAX / 2 { - return Err(malformed("a timeline past the subset's segments")); - } - for _ in 0..=r { diff --git a/wai/http-conformance/falsify/dash-timelines-unbounded.patch b/wai/http-conformance/falsify/dash-timelines-unbounded.patch new file mode 100644 index 000000000..14c68ca6f --- /dev/null +++ b/wai/http-conformance/falsify/dash-timelines-unbounded.patch @@ -0,0 +1,15 @@ +# must fail: dash_adjacency_timelines_past_the_subset +# a document of more than 256 timelines read (dash_read::read_mpd) +diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs +index 71e36cde..d08ed0fd 100644 +--- a/wai/wai-rs/src/http_binding/dash_read.rs ++++ b/wai/wai-rs/src/http_binding/dash_read.rs +@@ -435,7 +435,7 @@ pub fn read_mpd(text: &str) -> Result, PlaylistError> { + let mut held = 0usize; + let mut count = |timelines: &Vec| -> Result<(), PlaylistError> { + held += timelines.len(); +- if held > MAX_TIMELINES { Err(malformed("more SegmentTimelines than the subset's 256")) } else { Ok(()) } ++ if false && held > MAX_TIMELINES { Err(malformed("more SegmentTimelines than the subset's 256")) } else { Ok(()) } + }; + for (i, p) in root.children_named("Period").enumerate() { + let start_ms = match p.attr("start") { diff --git a/wai/http-conformance/falsify/dash-timescale-unchecked.patch b/wai/http-conformance/falsify/dash-timescale-unchecked.patch index bd25c7ee7..5efb3c3b3 100644 --- a/wai/http-conformance/falsify/dash-timescale-unchecked.patch +++ b/wai/http-conformance/falsify/dash-timescale-unchecked.patch @@ -1,15 +1,15 @@ # must fail: dash_adjacency_timescale dash_adjacency_the_audio_timescale # a timescale not compared (dash_read::check_dash_adjacent) diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs -index 79d55df6..155e1541 100644 +index 71e36cde..6e7a5b83 100644 --- a/wai/wai-rs/src/http_binding/dash_read.rs +++ b/wai/wai-rs/src/http_binding/dash_read.rs -@@ -462,7 +462,7 @@ pub fn check_dash_adjacent(wai: &str, classical: &str) -> Result<(), PlaylistErr +@@ -526,7 +526,7 @@ pub fn check_dash_adjacent(wai: &str, classical: &str) -> Result<(), PlaylistErr } for tl in &s.timelines { - let aligned = !tl.segments.is_empty() -- && kp.sets.iter().flat_map(|c| &c.timelines).any(|c| c.timescale == tl.timescale && within(&tl.segments, &c.segments)); -+ && kp.sets.iter().flat_map(|c| &c.timelines).any(|c| within(&tl.segments, &c.segments)); + let aligned = tl.segment_count() > 0 +- && kp.sets.iter().flat_map(|c| &c.timelines).any(|c| c.timescale == tl.timescale && within(&tl.runs, &c.runs)); ++ && kp.sets.iter().flat_map(|c| &c.timelines).any(|c| within(&tl.runs, &c.runs)); if !aligned { return Err(misaligned(format!("a timeline of AdaptationSet {:?} is not a classical one's", s.id))); } diff --git a/wai/http-conformance/falsify/dash-zero-d.patch b/wai/http-conformance/falsify/dash-zero-d.patch index caea84fb8..048fce9d4 100644 --- a/wai/http-conformance/falsify/dash-zero-d.patch +++ b/wai/http-conformance/falsify/dash-zero-d.patch @@ -1,10 +1,10 @@ # must fail: dash_adjacency_zero_d # an S of no duration read (dash_read::timeline_of) diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs -index 79d55df6..25538221 100644 +index 71e36cde..f4aec871 100644 --- a/wai/wai-rs/src/http_binding/dash_read.rs +++ b/wai/wai-rs/src/http_binding/dash_read.rs -@@ -362,7 +362,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result, +@@ -402,7 +402,7 @@ fn timeline_of(e: &Element, inherited: &[&Element]) -> Result, let mut next = 0u64; for s in st.children_named("S") { let d = unsigned(s.attr("d").ok_or_else(|| malformed("an S has no @d"))?, "@d")?; @@ -12,4 +12,4 @@ index 79d55df6..25538221 100644 + if false { return Err(malformed("an S of no duration")); } - let mut t = s.attr("t").map(|v| unsigned(v, "@t")).transpose()?.unwrap_or(next); + let t = s.attr("t").map(|v| unsigned(v, "@t")).transpose()?.unwrap_or(next); diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh index a047266e8..6b581ef80 100755 --- a/wai/http-conformance/falsify/run.sh +++ b/wai/http-conformance/falsify/run.sh @@ -51,6 +51,7 @@ for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done t rebind-uri rebind-sequence key-unchecked walk-unbounded reload-walked \ xml-char-unchecked xml-attribute-twice dash-start-ignored dash-timescale-ignored \ unlabelled-escapes abr-unbounded gap-carries receipt-duplicates receipt-surrogates session-data-anywhere \ + dash-r-expanded xml-attributes-scanned dash-timelines-unbounded \ file-numbers-unbounded; do if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then echo "::error::http_verify.py --mutate $m passed"; fail=1 diff --git a/wai/http-conformance/falsify/xml-attribute-twice.patch b/wai/http-conformance/falsify/xml-attribute-twice.patch index 743c2fe01..5a92b456f 100644 --- a/wai/http-conformance/falsify/xml-attribute-twice.patch +++ b/wai/http-conformance/falsify/xml-attribute-twice.patch @@ -1,15 +1,15 @@ # must fail: dash_adjacency_attribute_twice # an attribute twice read (dash_read::read_xml) diff --git a/wai/wai-rs/src/http_binding/dash_read.rs b/wai/wai-rs/src/http_binding/dash_read.rs -index 79d55df6..245e70fd 100644 +index 71e36cde..ea32b008 100644 --- a/wai/wai-rs/src/http_binding/dash_read.rs +++ b/wai/wai-rs/src/http_binding/dash_read.rs -@@ -196,7 +196,7 @@ impl<'a> Reader<'a> { +@@ -204,7 +204,7 @@ impl<'a> Reader<'a> { } self.space(); let v = self.attr_value()?; -- if attrs.iter().any(|(a, _)| *a == k) { -+ if false { +- if !names.insert(k.clone()) { ++ if !names.insert(k.clone()) && false { return Err(malformed(format!("the attribute {k} twice"))); } attrs.push((k, v)); -- 2.54.0 (Apple Git-157) From dce785011bc7f2aa28cfffe3eb94e64106333f15 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 13:57:37 -0400 Subject: [PATCH 49/55] wai: a joule budget never raises the policy's choice; with nothing at or below it within the budget, option 0 and the budget unmet Appendix F's Budget paragraph rewritten: the highest option at or below the policy's choice whose labelled figure is within the budget (within, lowered), else option 0 (unmet, replacing floor); not applied only when no option is labelled. Rust and http_verify.py; the reviewer's three rows as vectors (abr_budget_not_raised_*), and a unit test that no choice under a budget is above the policy's. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/http-delivery.md | 17 +++---- wai/http-conformance/cases.json | 35 +++++++------- wai/http-conformance/http_verify.py | 14 +++--- .../src/bin/wai_cli/http_vectors_playlists.rs | 14 ++++-- wai/wai-rs/src/http_binding/abr.rs | 47 ++++++++----------- wai/wai-rs/src/http_binding/tests_abr.rs | 36 +++++++++++--- 6 files changed, 94 insertions(+), 69 deletions(-) diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index 91829a872..0486f2e64 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -1087,14 +1087,15 @@ widths compares those products in 192 bits. in ms times `(max_k u_k + 65536)`. The first choice of the first best sequence is taken. -**Budget.** Without a budget the choice stands (`not_set`). When no option has a -labelled figure, the choice stands and the budget is not applied (`not_applied`): an -unlabelled figure is never used. Otherwise only an option whose labelled figure is -within the budget is chosen, so an unlabelled option never escapes it: the choice when -it is one (`within`); else the highest such option below it (`lowered`); else the lowest -such option above it, or, when no option's figure is within the budget, the option with -the least labelled figure, the lower on a tie (`floor`). A figure past the options names -none. +**Budget.** A budget never raises the policy's choice. Without a budget the choice +stands (`not_set`). When no option has a labelled figure, the choice stands and the +budget is not applied (`not_applied`): an unlabelled figure is never used. Otherwise the +choice is the highest option at or below the policy's whose labelled figure is within +the budget: the policy's own (`within`) or one below it (`lowered`), so an unlabelled +option never escapes the budget. When no option at or below the policy's has a labelled +figure within it, the choice is option 0 and the budget is not met (`unmet`): a client +fetches the least it can, and reports that the budget was not held. A figure past the +options names none. **Entry order.** Within a `WAI2`, the selectable entries whose capability the sink has are grouped by capability in order of first appearance; a group's options are its diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index 1847a49c9..b12aa7083 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -535,7 +535,7 @@ {"name":"abr_throughput_00","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":28203,"samples":[[1000,1259305,570]],"last":2,"option_bytes":[229507,231557,344895],"option_joules_micro":[3913,null,3815],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_throughput_01","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":27557,"samples":[],"last":0,"option_bytes":[48543,69733,178222,189003,255156,340556],"option_joules_micro":[250,4083,4219,null,null,null],"joule_budget_micro":1274,"segment_ms":2000}},"expect":{"choice":0,"budget":"within"}}, {"name":"abr_throughput_02","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":27339,"samples":[],"last":null,"option_bytes":[132854,232413],"option_joules_micro":[null,315],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, -{"name":"abr_throughput_03","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":5325,"samples":[[1000,691100,1108],[2000,1798923,186],[3000,499287,1537],[4000,729119,142]],"last":null,"option_bytes":[47614],"option_joules_micro":[2441],"joule_budget_micro":75,"segment_ms":3840}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_throughput_03","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":5325,"samples":[[1000,691100,1108],[2000,1798923,186],[3000,499287,1537],[4000,729119,142]],"last":null,"option_bytes":[47614],"option_joules_micro":[2441],"joule_budget_micro":75,"segment_ms":3840}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_throughput_04","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":29234,"samples":[[1000,96310,457],[2000,692823,1361],[3000,532395,2859],[4000,1569349,2674],[5000,309913,2047]],"last":2,"option_bytes":[31692,61260,114256,118871,321713],"option_joules_micro":[3464,1013,null,1819,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, {"name":"abr_throughput_05","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":38786,"samples":[[1000,1346651,872],[2000,1898768,937],[3000,1172638,1448]],"last":3,"option_bytes":[96715,141611,162979,171720,184391,300283],"option_joules_micro":[null,1397,3978,58,3341,null],"joule_budget_micro":3328,"segment_ms":1000}},"expect":{"choice":3,"budget":"lowered"}}, {"name":"abr_throughput_06","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":39560,"samples":[],"last":null,"option_bytes":[82831,144942],"option_joules_micro":[null,1121],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, @@ -545,13 +545,13 @@ {"name":"abr_throughput_10","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":6439,"samples":[[1000,1642862,2072],[2000,1724810,2415]],"last":null,"option_bytes":[221303,254054,282799,397258],"option_joules_micro":[2984,4714,951,2746],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":3,"budget":"not_set"}}, {"name":"abr_throughput_11","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":12659,"samples":[[1000,1332812,1319],[2000,939957,2721],[3000,1367969,837],[4000,1599764,1867]],"last":2,"option_bytes":[80403,120456,245927],"option_joules_micro":[3147,null,2885],"joule_budget_micro":5230,"segment_ms":200}},"expect":{"choice":0,"budget":"lowered"}}, {"name":"abr_throughput_12","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":16498,"samples":[[1000,495433,2847],[2000,1138099,1332],[3000,118438,752]],"last":null,"option_bytes":[5136,209878,237512,306265,314855],"option_joules_micro":[2083,1131,2370,3206,2827],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, -{"name":"abr_throughput_13","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":23508,"samples":[],"last":null,"option_bytes":[106126,244473,290603,332362,353222,363306],"option_joules_micro":[null,2545,null,4788,4650,null],"joule_budget_micro":5986,"segment_ms":4000}},"expect":{"choice":1,"budget":"floor"}}, +{"name":"abr_throughput_13","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":23508,"samples":[],"last":null,"option_bytes":[106126,244473,290603,332362,353222,363306],"option_joules_micro":[null,2545,null,4788,4650,null],"joule_budget_micro":5986,"segment_ms":4000}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_throughput_14","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":25907,"samples":[[1000,1865109,2358],[2000,1599056,2350]],"last":null,"option_bytes":[27081,34658],"option_joules_micro":[789,4649],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_throughput_15","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":34250,"samples":[],"last":null,"option_bytes":[35851,202585,220271,236085,248931,345739],"option_joules_micro":[1723,1554,null,199,781,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_throughput_16","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":23347,"samples":[],"last":null,"option_bytes":[200798,293314,359455],"option_joules_micro":[1339,null,2473],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, -{"name":"abr_throughput_17","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":600,"samples":[[1000,1896617,2224],[2000,1558447,1692],[3000,765327,1419]],"last":1,"option_bytes":[163436,186366,272666,275011,340603],"option_joules_micro":[3991,4424,null,3790,3355],"joule_budget_micro":1411,"segment_ms":2000}},"expect":{"choice":4,"budget":"floor"}}, +{"name":"abr_throughput_17","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":600,"samples":[[1000,1896617,2224],[2000,1558447,1692],[3000,765327,1419]],"last":1,"option_bytes":[163436,186366,272666,275011,340603],"option_joules_micro":[3991,4424,null,3790,3355],"joule_budget_micro":1411,"segment_ms":2000}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_throughput_18","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":13891,"samples":[[1000,954290,1322],[2000,1359600,1307],[3000,579442,570],[4000,1316832,1762],[5000,1974497,2440]],"last":null,"option_bytes":[73894,79372,276740],"option_joules_micro":[2593,1518,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":2,"budget":"not_set"}}, -{"name":"abr_throughput_19","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":24789,"samples":[[1000,560355,44],[2000,1168366,2494],[3000,1768747,727],[4000,1437641,603]],"last":null,"option_bytes":[163014,260410],"option_joules_micro":[null,1131],"joule_budget_micro":4590,"segment_ms":200}},"expect":{"choice":1,"budget":"floor"}}, +{"name":"abr_throughput_19","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":24789,"samples":[[1000,560355,44],[2000,1168366,2494],[3000,1768747,727],[4000,1437641,603]],"last":null,"option_bytes":[163014,260410],"option_joules_micro":[null,1131],"joule_budget_micro":4590,"segment_ms":200}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_throughput_20","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":15721,"samples":[[1000,1211483,39],[2000,301375,2007]],"last":0,"option_bytes":[10369,280040],"option_joules_micro":[null,668],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_throughput_21","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":33334,"samples":[[1000,1447610,2507],[2000,1657478,2231],[3000,450998,1363]],"last":null,"option_bytes":[183723,251782,272819,337959,393865,395099],"option_joules_micro":[21,null,1077,4664,303,null],"joule_budget_micro":4277,"segment_ms":3840}},"expect":{"choice":4,"budget":"lowered"}}, {"name":"abr_throughput_22","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":35031,"samples":[[1000,324127,2144],[2000,1092870,1089]],"last":null,"option_bytes":[352081],"option_joules_micro":[1053],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, @@ -561,7 +561,7 @@ {"name":"abr_throughput_26","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":12394,"samples":[[1000,1228723,1636],[2000,1510412,794],[3000,100188,1057]],"last":3,"option_bytes":[10898,111800,122856,238090],"option_joules_micro":[4652,null,null,3096],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_throughput_27","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":28905,"samples":[[1000,694916,82],[2000,112506,981],[3000,1300898,439],[4000,1895031,165]],"last":null,"option_bytes":[256221],"option_joules_micro":[1154],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_throughput_28","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":10426,"samples":[[1000,1429554,3],[2000,119924,2406],[3000,692890,1773],[4000,1863026,2411]],"last":null,"option_bytes":[118910,163595,350359],"option_joules_micro":[null,2770,3426],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":2,"budget":"not_set"}}, -{"name":"abr_throughput_29","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":9070,"samples":[[1000,1012061,1185]],"last":null,"option_bytes":[18854,33308,77115,281011],"option_joules_micro":[1937,1674,2859,1446],"joule_budget_micro":371,"segment_ms":1000}},"expect":{"choice":3,"budget":"floor"}}, +{"name":"abr_throughput_29","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":9070,"samples":[[1000,1012061,1185]],"last":null,"option_bytes":[18854,33308,77115,281011],"option_joules_micro":[1937,1674,2859,1446],"joule_budget_micro":371,"segment_ms":1000}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_throughput_30","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":15557,"samples":[[1000,756365,795],[2000,1757679,1638]],"last":null,"option_bytes":[368004],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_throughput_31","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":26456,"samples":[[1000,1148772,1885],[2000,1378089,1925],[3000,1488056,561],[4000,615024,2153],[5000,203736,1988]],"last":null,"option_bytes":[25456,55188,175334,372052,395592],"option_joules_micro":[2982,null,1043,null,null],"joule_budget_micro":1948,"segment_ms":3840}},"expect":{"choice":2,"budget":"lowered"}}, {"name":"abr_throughput_32","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":13482,"samples":[[1000,353535,461],[2000,100125,2264],[3000,1549597,940]],"last":null,"option_bytes":[71315,160507,171934,384830],"option_joules_micro":[null,4992,null,4405],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":3,"budget":"not_set"}}, @@ -575,11 +575,11 @@ {"name":"abr_buffer_based_00","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":37166,"samples":[[1000,1979410,9]],"last":1,"option_bytes":[210016,308333,345583],"option_joules_micro":[null,2474,null],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_buffer_based_01","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":16121,"samples":[[1000,570802,767]],"last":5,"option_bytes":[204621,284313,289434,306386,318982,351290],"option_joules_micro":[null,1352,null,null,3924,null],"joule_budget_micro":4692,"segment_ms":200}},"expect":{"choice":4,"budget":"lowered"}}, {"name":"abr_buffer_based_02","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":29777,"samples":[[1000,1565417,2877],[2000,1095405,1838],[3000,1213452,1830],[4000,1561082,2035],[5000,514815,2890]],"last":null,"option_bytes":[125752,240457],"option_joules_micro":[null,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":1,"budget":"not_set"}}, -{"name":"abr_buffer_based_03","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":2271,"samples":[[1000,1844685,1561],[2000,1660953,1396],[3000,1046634,1653],[4000,572996,984]],"last":null,"option_bytes":[78556,128536,136533,216821,223315],"option_joules_micro":[null,null,null,563,4718],"joule_budget_micro":4470,"segment_ms":3840}},"expect":{"choice":3,"budget":"floor"}}, +{"name":"abr_buffer_based_03","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":2271,"samples":[[1000,1844685,1561],[2000,1660953,1396],[3000,1046634,1653],[4000,572996,984]],"last":null,"option_bytes":[78556,128536,136533,216821,223315],"option_joules_micro":[null,null,null,563,4718],"joule_budget_micro":4470,"segment_ms":3840}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_buffer_based_04","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":10913,"samples":[[1000,1293814,1998],[2000,1252255,1484],[3000,443443,1964]],"last":null,"option_bytes":[170679],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_buffer_based_05","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":952,"samples":[[1000,787637,1089],[2000,1884870,2269],[3000,1428241,684],[4000,663641,1609],[5000,512905,917]],"last":null,"option_bytes":[27588,215824,225817,302822,314142,359134],"option_joules_micro":[286,2738,439,519,2188,null],"joule_budget_micro":3150,"segment_ms":200}},"expect":{"choice":0,"budget":"within"}}, {"name":"abr_buffer_based_06","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":4978,"samples":[[1000,331389,564],[2000,827587,787],[3000,797362,361],[4000,253656,2761],[5000,772285,657]],"last":0,"option_bytes":[145164],"option_joules_micro":[2801],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, -{"name":"abr_buffer_based_07","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":29335,"samples":[],"last":null,"option_bytes":[70464,132583,186315],"option_joules_micro":[2199,4129,null],"joule_budget_micro":662,"segment_ms":4000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_buffer_based_07","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":29335,"samples":[],"last":null,"option_bytes":[70464,132583,186315],"option_joules_micro":[2199,4129,null],"joule_budget_micro":662,"segment_ms":4000}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_buffer_based_08","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":37749,"samples":[[1000,1709563,19]],"last":null,"option_bytes":[69175,85110,139703,199776,217765],"option_joules_micro":[4958,null,2129,null,2528],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, {"name":"abr_buffer_based_09","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":39078,"samples":[[1000,1551825,1508],[2000,654267,1691],[3000,338056,1616]],"last":null,"option_bytes":[2240,35831,107259,143553,219593],"option_joules_micro":[2163,238,3509,2438,3908],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":4,"budget":"not_set"}}, {"name":"abr_buffer_based_10","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":36561,"samples":[[1000,271282,2798],[2000,430309,868],[3000,46995,1685]],"last":null,"option_bytes":[27471,245810],"option_joules_micro":[1924,102],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, @@ -603,7 +603,7 @@ {"name":"abr_buffer_based_28","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":27041,"samples":[[1000,875003,2914],[2000,475096,1811]],"last":0,"option_bytes":[190765,197897,220330,321218,332550],"option_joules_micro":[1384,628,null,5000,4239],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":4,"budget":"not_set"}}, {"name":"abr_buffer_based_29","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":28564,"samples":[[1000,1903050,1616],[2000,805741,715],[3000,1156735,928]],"last":0,"option_bytes":[132147,391510],"option_joules_micro":[3736,2596],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_buffer_based_30","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":21410,"samples":[[1000,1469834,659]],"last":null,"option_bytes":[283475,308777,311223],"option_joules_micro":[765,2079,4078],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, -{"name":"abr_buffer_based_31","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":23415,"samples":[[1000,442019,2287],[2000,1783989,771],[3000,1466540,996]],"last":null,"option_bytes":[32947,125397,157859,246761,271145],"option_joules_micro":[null,null,2092,1535,4449],"joule_budget_micro":1292,"segment_ms":3840}},"expect":{"choice":3,"budget":"floor"}}, +{"name":"abr_buffer_based_31","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":23415,"samples":[[1000,442019,2287],[2000,1783989,771],[3000,1466540,996]],"last":null,"option_bytes":[32947,125397,157859,246761,271145],"option_joules_micro":[null,null,2092,1535,4449],"joule_budget_micro":1292,"segment_ms":3840}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_buffer_based_32","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":11395,"samples":[[1000,343990,2406],[2000,777840,551]],"last":0,"option_bytes":[270717],"option_joules_micro":[2843],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_buffer_based_33","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":22365,"samples":[[1000,17516,1911],[2000,1837902,2638]],"last":0,"option_bytes":[102200,185038,186610],"option_joules_micro":[2128,4256,1647],"joule_budget_micro":2909,"segment_ms":1000}},"expect":{"choice":2,"budget":"within"}}, {"name":"abr_buffer_based_34","op":"abr","input":{"policy":{"id":"buffer_based","reservoir_ms":5000,"cushion_ms":10000},"state":{"buffer_ms":2245,"samples":[],"last":null,"option_bytes":[137626,366867],"option_joules_micro":[2133,32],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, @@ -619,9 +619,9 @@ {"name":"abr_bola_like_04","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":24993,"samples":[[1000,628261,266],[2000,1311093,2490],[3000,1896231,2508],[4000,1899946,1549],[5000,1473270,521]],"last":3,"option_bytes":[12156,93174,102688,115312,200006],"option_joules_micro":[null,null,null,null,3620],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":4,"budget":"not_set"}}, {"name":"abr_bola_like_05","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":36964,"samples":[[1000,1635778,1330]],"last":0,"option_bytes":[180898,209837,230082,260672],"option_joules_micro":[2254,null,null,3133],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, {"name":"abr_bola_like_06","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":33333,"samples":[[1000,1928993,2256],[2000,1990635,1460],[3000,985446,157],[4000,1448177,1178]],"last":null,"option_bytes":[174914,277374,359614,400433],"option_joules_micro":[3301,2076,null,2638],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":3,"budget":"not_set"}}, -{"name":"abr_bola_like_07","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":14770,"samples":[[1000,1194018,1373],[2000,259042,1066],[3000,1462106,1136]],"last":null,"option_bytes":[243135,292490],"option_joules_micro":[4054,null],"joule_budget_micro":1151,"segment_ms":4000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_bola_like_07","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":14770,"samples":[[1000,1194018,1373],[2000,259042,1066],[3000,1462106,1136]],"last":null,"option_bytes":[243135,292490],"option_joules_micro":[4054,null],"joule_budget_micro":1151,"segment_ms":4000}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_bola_like_08","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":27980,"samples":[[1000,141644,2588],[2000,1022414,964],[3000,388313,362],[4000,900844,2246]],"last":null,"option_bytes":[35175,224251,379021],"option_joules_micro":[3222,3467,1675],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":2,"budget":"not_set"}}, -{"name":"abr_bola_like_09","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":25379,"samples":[[1000,1891490,2917],[2000,1154608,571],[3000,919253,627],[4000,77454,1835]],"last":null,"option_bytes":[33791,62122,140623,207294,208269,277356],"option_joules_micro":[1522,2250,1056,null,3249,4928],"joule_budget_micro":854,"segment_ms":1000}},"expect":{"choice":2,"budget":"floor"}}, +{"name":"abr_bola_like_09","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":25379,"samples":[[1000,1891490,2917],[2000,1154608,571],[3000,919253,627],[4000,77454,1835]],"last":null,"option_bytes":[33791,62122,140623,207294,208269,277356],"option_joules_micro":[1522,2250,1056,null,3249,4928],"joule_budget_micro":854,"segment_ms":1000}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_bola_like_10","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":32840,"samples":[[1000,1081027,2618]],"last":0,"option_bytes":[104494,230609],"option_joules_micro":[1662,62],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_bola_like_11","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":36145,"samples":[[1000,1966685,548]],"last":null,"option_bytes":[53981,203768],"option_joules_micro":[1104,777],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_bola_like_12","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":25512,"samples":[[1000,1037585,2300],[2000,1988690,1445],[3000,258576,2439]],"last":1,"option_bytes":[2816,214121,330531],"option_joules_micro":[3106,4288,2871],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":2,"budget":"not_set"}}, @@ -645,7 +645,7 @@ {"name":"abr_bola_like_30","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":10880,"samples":[[1000,496251,2984],[2000,1410869,227]],"last":5,"option_bytes":[53586,190914,212753,222066,293341,388656],"option_joules_micro":[null,null,2722,1228,2710,null],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":5,"budget":"not_set"}}, {"name":"abr_bola_like_31","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":32168,"samples":[[1000,1586751,2771],[2000,550958,2532],[3000,281722,1964],[4000,1084201,924]],"last":0,"option_bytes":[95722,138681,162936,182448,240430,352697],"option_joules_micro":[203,917,3136,4882,3788,null],"joule_budget_micro":2702,"segment_ms":4000}},"expect":{"choice":1,"budget":"lowered"}}, {"name":"abr_bola_like_32","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":27832,"samples":[],"last":null,"option_bytes":[217137,290770],"option_joules_micro":[null,null],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":1,"budget":"not_set"}}, -{"name":"abr_bola_like_33","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":1133,"samples":[[1000,1883348,310],[2000,1503581,219],[3000,257624,2456]],"last":null,"option_bytes":[376873],"option_joules_micro":[3600],"joule_budget_micro":2937,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, +{"name":"abr_bola_like_33","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":1133,"samples":[[1000,1883348,310],[2000,1503581,219],[3000,257624,2456]],"last":null,"option_bytes":[376873],"option_joules_micro":[3600],"joule_budget_micro":2937,"segment_ms":1000}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_bola_like_34","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":660,"samples":[],"last":2,"option_bytes":[35333,90913,214404,288921,307074,359893],"option_joules_micro":[null,1183,null,3405,4668,1069],"joule_budget_micro":null,"segment_ms":200}},"expect":{"choice":1,"budget":"not_set"}}, {"name":"abr_bola_like_35","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":33019,"samples":[[1000,1196634,2933],[2000,1237240,1474]],"last":0,"option_bytes":[175597],"option_joules_micro":[1574],"joule_budget_micro":5374,"segment_ms":1000}},"expect":{"choice":0,"budget":"within"}}, {"name":"abr_bola_like_36","op":"abr","input":{"policy":{"id":"bola_like","v_milli":930,"max_buffer_ms":20000},"state":{"buffer_ms":9288,"samples":[[1000,834878,1601],[2000,423950,2676],[3000,1358696,1710],[4000,388812,1714]],"last":0,"option_bytes":[312330],"option_joules_micro":[null],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, @@ -681,7 +681,7 @@ {"name":"abr_mpc_26","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":7985,"samples":[[1000,679797,1014],[2000,1388646,1629],[3000,1287532,995],[4000,673199,2771],[5000,1693506,6]],"last":null,"option_bytes":[148612,182814,263724],"option_joules_micro":[1240,3043,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_mpc_27","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":25285,"samples":[],"last":2,"option_bytes":[205132,209606,216912,263414,353879,372690],"option_joules_micro":[3514,1145,1369,4951,null,736],"joule_budget_micro":5625,"segment_ms":1000}},"expect":{"choice":0,"budget":"within"}}, {"name":"abr_mpc_28","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":19124,"samples":[[1000,331098,1453],[2000,536721,2331]],"last":null,"option_bytes":[80660,128098,193362,237934,287501,324214],"option_joules_micro":[null,null,4493,null,3390,null],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":5,"budget":"not_set"}}, -{"name":"abr_mpc_29","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":13960,"samples":[[1000,1446941,305],[2000,1491488,581],[3000,765645,743],[4000,179479,1628],[5000,1937348,312]],"last":null,"option_bytes":[166082,190860],"option_joules_micro":[null,4831],"joule_budget_micro":457,"segment_ms":1000}},"expect":{"choice":1,"budget":"floor"}}, +{"name":"abr_mpc_29","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":13960,"samples":[[1000,1446941,305],[2000,1491488,581],[3000,765645,743],[4000,179479,1628],[5000,1937348,312]],"last":null,"option_bytes":[166082,190860],"option_joules_micro":[null,4831],"joule_budget_micro":457,"segment_ms":1000}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_mpc_30","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":6763,"samples":[],"last":3,"option_bytes":[114132,121182,345843,346021,355368],"option_joules_micro":[3690,null,3581,1578,1526],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_mpc_31","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":12795,"samples":[[1000,904626,2824],[2000,712775,1599],[3000,884425,2063]],"last":null,"option_bytes":[28835,223843,227852,310268],"option_joules_micro":[null,893,3370,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":3,"budget":"not_set"}}, {"name":"abr_mpc_32","op":"abr","input":{"policy":{"id":"mpc","horizon":3,"safety_permille":850},"state":{"buffer_ms":28372,"samples":[],"last":0,"option_bytes":[27801,54184],"option_joules_micro":[1568,null],"joule_budget_micro":null,"segment_ms":4000}},"expect":{"choice":0,"budget":"not_set"}}, @@ -698,9 +698,12 @@ {"name":"abr_budget_lowered","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":1,"budget":"lowered"}}, {"name":"abr_budget_lowered_past_unlabelled","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,null,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":0,"budget":"lowered"}}, {"name":"abr_budget_unlabelled_choice","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[10,50,null],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":1,"budget":"lowered"}}, -{"name":"abr_budget_floor","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[200,null,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":0,"budget":"floor"}}, -{"name":"abr_budget_floor_least_figure","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,300,200],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":2,"budget":"floor"}}, -{"name":"abr_budget_floor_above_the_choice","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1250,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,50,80],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":1,"budget":"floor"}}, +{"name":"abr_budget_unmet","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[200,null,500],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":0,"budget":"unmet"}}, +{"name":"abr_budget_unmet_not_the_least_figure","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,300,200],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":0,"budget":"unmet"}}, +{"name":"abr_budget_not_raised_to_labelled_options","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1250,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,50,80],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":0,"budget":"unmet"}}, +{"name":"abr_budget_not_raised_past_the_budget","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1250,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,7000],"joule_budget_micro":1000,"segment_ms":1000}},"expect":{"choice":0,"budget":"unmet"}}, +{"name":"abr_budget_not_raised_past_the_budget_no_budget","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1250,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,7000],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"choice":0,"budget":"not_set"}}, +{"name":"abr_budget_not_raised_to_one_within","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,1250,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,900,null],"joule_budget_micro":1000,"segment_ms":1000}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_budget_figures_past_the_options","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500000,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null,5],"joule_budget_micro":100,"segment_ms":1000}},"expect":{"choice":2,"budget":"not_applied"}}, {"name":"abr_extreme_throughput_0","op":"abr","input":{"policy":{"id":"throughput","last_n":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":0}},"expect":{"choice":3,"budget":"not_set"}}, {"name":"abr_extreme_throughput_1","op":"abr","input":{"policy":{"id":"throughput","last_n":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":3,"budget":"not_set"}}, @@ -715,7 +718,7 @@ {"name":"abr_extreme_mpc_1","op":"abr","input":{"policy":{"id":"mpc","horizon":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_extreme_mpc_4294967295","op":"abr","input":{"policy":{"id":"mpc","horizon":255,"safety_permille":65535},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":4294967295}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_extreme_bola_like_past_128_bits","op":"abr","input":{"policy":{"id":"bola_like","v_milli":4294967295,"max_buffer_ms":596001515},"state":{"buffer_ms":129,"samples":[],"last":null,"option_bytes":[3997960217448864544,16001797029153315873],"option_joules_micro":[null,null],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":0,"budget":"not_set"}}, -{"name":"abr_extreme_budget","op":"abr","input":{"policy":{"id":"bola_like","v_milli":4294967295,"max_buffer_ms":4294967295},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":18446744073709551614,"segment_ms":1}},"expect":{"choice":2,"budget":"floor"}}, +{"name":"abr_extreme_budget","op":"abr","input":{"policy":{"id":"bola_like","v_milli":4294967295,"max_buffer_ms":4294967295},"state":{"buffer_ms":4294967295,"samples":[[0,18446744073709551615,0],[1,18446744073709551614,2],[2,18446744073709551613,0],[3,18446744073709551612,2],[4,18446744073709551611,0],[5,18446744073709551610,2],[6,18446744073709551609,0],[7,18446744073709551608,2],[8,18446744073709551607,0],[9,18446744073709551606,2],[10,18446744073709551605,0],[11,18446744073709551604,2],[12,18446744073709551603,0],[13,18446744073709551602,2],[14,18446744073709551601,0],[15,18446744073709551600,2],[16,18446744073709551599,0],[17,18446744073709551598,2],[18,18446744073709551597,0],[19,18446744073709551596,2],[20,18446744073709551595,0],[21,18446744073709551594,2],[22,18446744073709551593,0],[23,18446744073709551592,2],[24,18446744073709551591,0],[25,18446744073709551590,2],[26,18446744073709551589,0],[27,18446744073709551588,2],[28,18446744073709551587,0],[29,18446744073709551586,2],[30,18446744073709551585,0],[31,18446744073709551584,2],[32,18446744073709551583,0],[33,18446744073709551582,2],[34,18446744073709551581,0],[35,18446744073709551580,2],[36,18446744073709551579,0],[37,18446744073709551578,2],[38,18446744073709551577,0],[39,18446744073709551576,2],[40,18446744073709551575,0],[41,18446744073709551574,2],[42,18446744073709551573,0],[43,18446744073709551572,2],[44,18446744073709551571,0],[45,18446744073709551570,2],[46,18446744073709551569,0],[47,18446744073709551568,2],[48,18446744073709551567,0],[49,18446744073709551566,2],[50,18446744073709551565,0],[51,18446744073709551564,2],[52,18446744073709551563,0],[53,18446744073709551562,2],[54,18446744073709551561,0],[55,18446744073709551560,2],[56,18446744073709551559,0],[57,18446744073709551558,2],[58,18446744073709551557,0],[59,18446744073709551556,2],[60,18446744073709551555,0],[61,18446744073709551554,2],[62,18446744073709551553,0],[63,18446744073709551552,2],[64,18446744073709551551,0],[65,18446744073709551550,2],[66,18446744073709551549,0],[67,18446744073709551548,2],[68,18446744073709551547,0],[69,18446744073709551546,2],[70,18446744073709551545,0],[71,18446744073709551544,2],[72,18446744073709551543,0],[73,18446744073709551542,2],[74,18446744073709551541,0],[75,18446744073709551540,2],[76,18446744073709551539,0],[77,18446744073709551538,2],[78,18446744073709551537,0],[79,18446744073709551536,2],[80,18446744073709551535,0],[81,18446744073709551534,2],[82,18446744073709551533,0],[83,18446744073709551532,2],[84,18446744073709551531,0],[85,18446744073709551530,2],[86,18446744073709551529,0],[87,18446744073709551528,2],[88,18446744073709551527,0],[89,18446744073709551526,2],[90,18446744073709551525,0],[91,18446744073709551524,2],[92,18446744073709551523,0],[93,18446744073709551522,2],[94,18446744073709551521,0],[95,18446744073709551520,2],[96,18446744073709551519,0],[97,18446744073709551518,2],[98,18446744073709551517,0],[99,18446744073709551516,2],[100,18446744073709551515,0],[101,18446744073709551514,2],[102,18446744073709551513,0],[103,18446744073709551512,2],[104,18446744073709551511,0],[105,18446744073709551510,2],[106,18446744073709551509,0],[107,18446744073709551508,2],[108,18446744073709551507,0],[109,18446744073709551506,2],[110,18446744073709551505,0],[111,18446744073709551504,2],[112,18446744073709551503,0],[113,18446744073709551502,2],[114,18446744073709551501,0],[115,18446744073709551500,2],[116,18446744073709551499,0],[117,18446744073709551498,2],[118,18446744073709551497,0],[119,18446744073709551496,2],[120,18446744073709551495,0],[121,18446744073709551494,2],[122,18446744073709551493,0],[123,18446744073709551492,2],[124,18446744073709551491,0],[125,18446744073709551490,2],[126,18446744073709551489,0],[127,18446744073709551488,2],[128,18446744073709551487,0],[129,18446744073709551486,2],[130,18446744073709551485,0],[131,18446744073709551484,2],[132,18446744073709551483,0],[133,18446744073709551482,2],[134,18446744073709551481,0],[135,18446744073709551480,2],[136,18446744073709551479,0],[137,18446744073709551478,2],[138,18446744073709551477,0],[139,18446744073709551476,2],[140,18446744073709551475,0],[141,18446744073709551474,2],[142,18446744073709551473,0],[143,18446744073709551472,2],[144,18446744073709551471,0],[145,18446744073709551470,2],[146,18446744073709551469,0],[147,18446744073709551468,2],[148,18446744073709551467,0],[149,18446744073709551466,2],[150,18446744073709551465,0],[151,18446744073709551464,2],[152,18446744073709551463,0],[153,18446744073709551462,2],[154,18446744073709551461,0],[155,18446744073709551460,2],[156,18446744073709551459,0],[157,18446744073709551458,2],[158,18446744073709551457,0],[159,18446744073709551456,2],[160,18446744073709551455,0],[161,18446744073709551454,2],[162,18446744073709551453,0],[163,18446744073709551452,2],[164,18446744073709551451,0],[165,18446744073709551450,2],[166,18446744073709551449,0],[167,18446744073709551448,2],[168,18446744073709551447,0],[169,18446744073709551446,2],[170,18446744073709551445,0],[171,18446744073709551444,2],[172,18446744073709551443,0],[173,18446744073709551442,2],[174,18446744073709551441,0],[175,18446744073709551440,2],[176,18446744073709551439,0],[177,18446744073709551438,2],[178,18446744073709551437,0],[179,18446744073709551436,2],[180,18446744073709551435,0],[181,18446744073709551434,2],[182,18446744073709551433,0],[183,18446744073709551432,2],[184,18446744073709551431,0],[185,18446744073709551430,2],[186,18446744073709551429,0],[187,18446744073709551428,2],[188,18446744073709551427,0],[189,18446744073709551426,2],[190,18446744073709551425,0],[191,18446744073709551424,2],[192,18446744073709551423,0],[193,18446744073709551422,2],[194,18446744073709551421,0],[195,18446744073709551420,2],[196,18446744073709551419,0],[197,18446744073709551418,2],[198,18446744073709551417,0],[199,18446744073709551416,2],[200,18446744073709551415,0],[201,18446744073709551414,2],[202,18446744073709551413,0],[203,18446744073709551412,2],[204,18446744073709551411,0],[205,18446744073709551410,2],[206,18446744073709551409,0],[207,18446744073709551408,2],[208,18446744073709551407,0],[209,18446744073709551406,2],[210,18446744073709551405,0],[211,18446744073709551404,2],[212,18446744073709551403,0],[213,18446744073709551402,2],[214,18446744073709551401,0],[215,18446744073709551400,2],[216,18446744073709551399,0],[217,18446744073709551398,2],[218,18446744073709551397,0],[219,18446744073709551396,2],[220,18446744073709551395,0],[221,18446744073709551394,2],[222,18446744073709551393,0],[223,18446744073709551392,2],[224,18446744073709551391,0],[225,18446744073709551390,2],[226,18446744073709551389,0],[227,18446744073709551388,2],[228,18446744073709551387,0],[229,18446744073709551386,2],[230,18446744073709551385,0],[231,18446744073709551384,2],[232,18446744073709551383,0],[233,18446744073709551382,2],[234,18446744073709551381,0],[235,18446744073709551380,2],[236,18446744073709551379,0],[237,18446744073709551378,2],[238,18446744073709551377,0],[239,18446744073709551376,2],[240,18446744073709551375,0],[241,18446744073709551374,2],[242,18446744073709551373,0],[243,18446744073709551372,2],[244,18446744073709551371,0],[245,18446744073709551370,2],[246,18446744073709551369,0],[247,18446744073709551368,2],[248,18446744073709551367,0],[249,18446744073709551366,2],[250,18446744073709551365,0],[251,18446744073709551364,2],[252,18446744073709551363,0],[253,18446744073709551362,2],[254,18446744073709551361,0],[255,18446744073709551360,2],[256,18446744073709551359,0],[257,18446744073709551358,2],[258,18446744073709551357,0],[259,18446744073709551356,2],[260,18446744073709551355,0],[261,18446744073709551354,2],[262,18446744073709551353,0],[263,18446744073709551352,2],[264,18446744073709551351,0],[265,18446744073709551350,2],[266,18446744073709551349,0],[267,18446744073709551348,2],[268,18446744073709551347,0],[269,18446744073709551346,2],[270,18446744073709551345,0],[271,18446744073709551344,2],[272,18446744073709551343,0],[273,18446744073709551342,2],[274,18446744073709551341,0],[275,18446744073709551340,2],[276,18446744073709551339,0],[277,18446744073709551338,2],[278,18446744073709551337,0],[279,18446744073709551336,2],[280,18446744073709551335,0],[281,18446744073709551334,2],[282,18446744073709551333,0],[283,18446744073709551332,2],[284,18446744073709551331,0],[285,18446744073709551330,2],[286,18446744073709551329,0],[287,18446744073709551328,2],[288,18446744073709551327,0],[289,18446744073709551326,2],[290,18446744073709551325,0],[291,18446744073709551324,2],[292,18446744073709551323,0],[293,18446744073709551322,2],[294,18446744073709551321,0],[295,18446744073709551320,2],[296,18446744073709551319,0],[297,18446744073709551318,2],[298,18446744073709551317,0],[299,18446744073709551316,2]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":18446744073709551614,"segment_ms":1}},"expect":{"choice":0,"budget":"unmet"}}, {"name":"abr_extreme_mpc_stalls","op":"abr","input":{"policy":{"id":"mpc","horizon":255,"safety_permille":65535},"state":{"buffer_ms":0,"samples":[[1,1,1]],"last":1,"option_bytes":[1,6148914691236517205,18446744073709551614,18446744073709551615],"option_joules_micro":[18446744073709551615,null,1,18446744073709551614],"joule_budget_micro":null,"segment_ms":1}},"expect":{"choice":0,"budget":"not_set"}}, {"name":"abr_out_of_bounds_buffer","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":4294967296,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":1000}},"expect":{"error":"abr_state_out_of_bounds"}}, {"name":"abr_out_of_bounds_segment","op":"abr","input":{"policy":{"id":"throughput","last_n":1,"safety_permille":900},"state":{"buffer_ms":0,"samples":[[1000,12500,1000]],"last":null,"option_bytes":[10000,100000,1000000],"option_joules_micro":[null,null,null],"joule_budget_micro":null,"segment_ms":4294967296}},"expect":{"error":"abr_state_out_of_bounds"}}, diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index 2347722e9..ee2d40f23 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -1356,8 +1356,9 @@ def choose(p, st): def clamp_to_budget(st, choice): - """Appendix F's budget: only an option with a labelled figure within it is chosen; - not applied only when no option has a labelled figure.""" + """Appendix F's budget: it never raises the choice; the highest option at or below it + whose labelled figure is within it, else option 0 unmet; not applied only when no + option has a labelled figure.""" budget = st["joule_budget_micro"] if budget is None: return choice, "not_set" @@ -1374,10 +1375,11 @@ def clamp_to_budget(st, choice): below = [j for j, f in labelled if j < choice and f <= budget] if below: return below[-1], "lowered" - above = [j for j, f in labelled if j > choice and f <= budget] - if above: - return above[0], "floor" - return min(labelled, key=lambda x: (x[1], x[0]))[0], "floor" + if MUTATE == "budget-raises": + above = [j for j, f in labelled if j > choice and f <= budget] + if above: + return above[0], "unmet" + return 0, "unmet" MAX_OPTIONS = 256 diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index b5dfab810..7654ac7f3 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -1211,8 +1211,9 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { "option_joules_micro": figs, "joule_budget_micro": budget, "segment_ms": 1000 }); let opts = json!([10_000, 100_000, 1_000_000]); let thr = json!({ "id": "throughput", "last_n": 1, "safety_permille": 900 }); - // Under a budget only an option with a labelled figure within it is chosen; the - // budget is not applied only when no option has a labelled figure. + // A budget never raises the policy's choice: the highest option at or below it with + // a labelled figure within the budget, else option 0 unmet; not applied only when no + // option has a labelled figure. At 10 kb/s the throughput policy chooses option 0. for (name, bps, figs, budget) in [ ("abr_budget_not_set", 100_000_000u64, json!([10, 50, 500]), Value::Null), ("abr_budget_not_applied", 100_000_000, json!([null, null, null]), json!(100)), @@ -1220,9 +1221,12 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { ("abr_budget_lowered", 100_000_000, json!([10, 50, 500]), json!(100)), ("abr_budget_lowered_past_unlabelled", 100_000_000, json!([10, null, 500]), json!(100)), ("abr_budget_unlabelled_choice", 100_000_000, json!([10, 50, null]), json!(100)), - ("abr_budget_floor", 100_000_000, json!([200, null, 500]), json!(100)), - ("abr_budget_floor_least_figure", 100_000_000, json!([null, 300, 200]), json!(100)), - ("abr_budget_floor_above_the_choice", 10_000, json!([null, 50, 80]), json!(100)), + ("abr_budget_unmet", 100_000_000, json!([200, null, 500]), json!(100)), + ("abr_budget_unmet_not_the_least_figure", 100_000_000, json!([null, 300, 200]), json!(100)), + ("abr_budget_not_raised_to_labelled_options", 10_000, json!([null, 50, 80]), json!(100)), + ("abr_budget_not_raised_past_the_budget", 10_000, json!([null, null, 7000]), json!(1000)), + ("abr_budget_not_raised_past_the_budget_no_budget", 10_000, json!([null, null, 7000]), Value::Null), + ("abr_budget_not_raised_to_one_within", 10_000, json!([null, 900, null]), json!(1000)), ("abr_budget_figures_past_the_options", 100_000_000, json!([null, null, null, 5]), json!(100)), ] { v.push(case(name, "abr", json!({ "policy": thr, "state": st(0, bps, opts.clone(), figs, budget) }))); diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs index 50361dafd..cc9cd96a4 100644 --- a/wai/wai-rs/src/http_binding/abr.rs +++ b/wai/wai-rs/src/http_binding/abr.rs @@ -13,9 +13,9 @@ //! - [`depth`] chooses how many layer objects (§10.4) a client fetches over a base. //! //! An option's energy enters only as a figure a receipt labels with its acquisition -//! class ([`labelled_figure`]); under a budget only an option with a labelled figure -//! within it is chosen, and a budget with no labelled figure to act on is reported as -//! not applied ([`Budget::NotApplied`]). Nothing here reads a clock: samples carry the +//! class ([`labelled_figure`]); a budget only ever lowers the policy's choice, to an +//! option with a labelled figure within it, and a budget with no labelled figure to act +//! on is reported as not applied ([`Budget::NotApplied`]). Nothing here reads a clock: samples carry the //! caller's measurements. //! //! Every input has the bounds Appendix F states ([`check_state`], [`Policy::check`]): @@ -156,20 +156,20 @@ pub enum Budget { /// within the budget. Lowered, /// No option at or below the choice has a labelled figure within the budget: the - /// lowest option above it that has one, or, when no option has, the option with the - /// least labelled figure. - Floor, + /// lowest option, option 0, and the budget is not met. A budget never raises the + /// choice. + Unmet, } impl Budget { - /// `not_set`, `not_applied`, `within`, `lowered` or `floor`. + /// `not_set`, `not_applied`, `within`, `lowered` or `unmet`. pub fn label(self) -> &'static str { match self { Budget::NotSet => "not_set", Budget::NotApplied => "not_applied", Budget::Within => "within", Budget::Lowered => "lowered", - Budget::Floor => "floor", + Budget::Unmet => "unmet", } } } @@ -381,32 +381,25 @@ pub fn labelled_figure(joules_micro: u64, class: Option<&crate::energy_class::En (class.is_some() && joules_micro > 0 && crate::energy_class::figure_and_class_cohere(joules_micro, class)).then_some(joules_micro) } -/// Apply the state's joule budget to `choice` (Appendix F): unchanged when no budget is -/// held, or when no option has a labelled figure (the budget is not applied). Otherwise -/// only an option with a labelled figure within the budget is chosen: the choice when it -/// is one; else the highest such option below it; else the lowest such option above -/// it; else, no option being within the budget, the option with the least labelled -/// figure (the lower on a tie). An option is one of `option_bytes` (or the choice, with -/// none); a figure past them names no option. +/// Apply the state's joule budget to `choice` (Appendix F). A budget never raises the +/// choice: unchanged when no budget is held (`not_set`), or when no option has a +/// labelled figure (`not_applied`); otherwise the highest option at or below the choice +/// whose labelled figure is within the budget — the choice itself (`within`) or one below +/// it (`lowered`) — and, when there is none, option 0 with the budget unmet (`unmet`). +/// An option is one of `option_bytes` (or the choice, with none); a figure past them +/// names no option. pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) { let Some(b) = s.joule_budget_micro else { return (choice, Budget::NotSet) }; let n = s.option_bytes.len().max(choice + 1); let fig = |k: usize| if k < n { s.option_joules_micro.get(k).copied().flatten() } else { None }; - let labelled: Vec<(usize, u64)> = (0..n).filter_map(|k| fig(k).map(|j| (k, j))).collect(); - if labelled.is_empty() { + if (0..n).all(|k| fig(k).is_none()) { return (choice, Budget::NotApplied); } - if fig(choice).is_some_and(|j| j <= b) { - return (choice, Budget::Within); + match (0..=choice).rev().find(|&k| fig(k).is_some_and(|j| j <= b)) { + Some(k) if k == choice => (k, Budget::Within), + Some(k) => (k, Budget::Lowered), + None => (0, Budget::Unmet), } - if let Some(&(k, _)) = labelled.iter().rev().find(|&&(k, j)| k < choice && j <= b) { - return (k, Budget::Lowered); - } - if let Some(&(k, _)) = labelled.iter().find(|&&(k, j)| k > choice && j <= b) { - return (k, Budget::Floor); - } - let least = labelled.iter().min_by_key(|&&(k, j)| (j, k)).map_or(0, |&(k, _)| k); - (least, Budget::Floor) } /// The policy's choice under the state's budget; refused beyond Appendix F's bounds. diff --git a/wai/wai-rs/src/http_binding/tests_abr.rs b/wai/wai-rs/src/http_binding/tests_abr.rs index 401835bda..582c88207 100644 --- a/wai/wai-rs/src/http_binding/tests_abr.rs +++ b/wai/wai-rs/src/http_binding/tests_abr.rs @@ -127,9 +127,10 @@ fn mpc_shape() { assert!(Mpc { horizon: 5, safety_permille: 900 }.choose(&state(8000, 3_000_000, &many)) < 40); } -/// Under a budget only an option with a labelled figure within it is chosen: the -/// budget is not applied only when no option has a labelled figure; an unlabelled -/// choice does not escape it. +/// A budget only lowers the choice, to the highest option at or below it with a labelled +/// figure within it; with none, option 0 and the budget unmet. It is not applied only +/// when no option has a labelled figure, so an unlabelled choice does not escape it, and +/// it never raises the choice, not to a labelled option within it nor past it. #[test] fn abr_budget_holds_labelled_options() { let mut s = state(0, 100_000_000, &OPTIONS); @@ -144,16 +145,37 @@ fn abr_budget_holds_labelled_options() { s.option_joules_micro = vec![Some(10), None, Some(500)]; assert_eq!(d(&s), (0, Budget::Lowered), "the unlabelled option is passed over"); s.option_joules_micro = vec![Some(200), None, Some(500)]; - assert_eq!(d(&s), (0, Budget::Floor), "none within: the least labelled figure"); + assert_eq!(d(&s), (0, Budget::Unmet), "none within: option 0, the budget unmet"); s.option_joules_micro = vec![None, Some(300), Some(200)]; - assert_eq!(d(&s), (2, Budget::Floor), "none within: the least labelled figure, wherever it is"); + assert_eq!(d(&s), (0, Budget::Unmet), "none within: option 0, not the least figure"); s.option_joules_micro = vec![Some(10), Some(50), Some(100)]; assert_eq!(d(&s), (2, Budget::Within)); - // A choice below every labelled option within the budget is raised to the lowest. + // A choice of option 0 at low throughput is never raised: not to labelled options + // within the budget above it, nor to an option past the budget. let mut low = state(0, 10_000, &OPTIONS); low.joule_budget_micro = Some(100); low.option_joules_micro = vec![None, Some(50), Some(80)]; - assert_eq!(d(&low), (1, Budget::Floor)); + assert_eq!(d(&low), (0, Budget::Unmet)); + low.joule_budget_micro = Some(1000); + low.option_joules_micro = vec![None, None, Some(7000)]; + assert_eq!(d(&low), (0, Budget::Unmet), "not raised to the largest option, past the budget"); + low.option_joules_micro = vec![None, Some(900), None]; + assert_eq!(d(&low), (0, Budget::Unmet), "not raised to the one option within the budget"); + low.joule_budget_micro = None; + assert_eq!(d(&low), (0, Budget::NotSet)); + // Whatever the figures and the budget, the choice under it is never above the + // policy's. + let mut seed = 0x9e37_79b9_7f4a_7c15u64; + let mut next = || { seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); seed >> 33 }; + for _ in 0..2000 { + let mut st = state(0, 1_000 + next() % 20_000_000, &OPTIONS); + st.option_joules_micro = (0..3).map(|_| if next() % 3 == 0 { None } else { Some(next() % 1000) }).collect(); + st.joule_budget_micro = Some(next() % 1000); + let policy = p.choose(&st); + let (k, b) = d(&st); + assert!(k <= policy, "{st:?}: {k} above the policy's {policy}"); + assert!(b != Budget::Unmet || k == 0); + } s.joule_budget_micro = None; assert_eq!(d(&s), (2, Budget::NotSet)); // Only a figure with its class is a labelled figure. -- 2.54.0 (Apple Git-157) From b4fdb61c0bdf9ad94b6b23502aa912784da01c2f Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 14:03:26 -0400 Subject: [PATCH 50/55] http-conformance: falsify the budget's never raising the choice and option 0 when unmet Co-Authored-By: Claude Opus 5.5 --- .../falsify/budget-floor-zero.patch | 20 ------------------- .../falsify/budget-raises.patch | 15 ++++++++++++++ .../falsify/budget-unlabelled-escapes.patch | 12 +++++------ .../falsify/budget-unlabelled.patch | 8 ++++---- .../falsify/budget-unmet-least-figure.patch | 15 ++++++++++++++ wai/http-conformance/falsify/run.sh | 2 +- 6 files changed, 41 insertions(+), 31 deletions(-) delete mode 100644 wai/http-conformance/falsify/budget-floor-zero.patch create mode 100644 wai/http-conformance/falsify/budget-raises.patch create mode 100644 wai/http-conformance/falsify/budget-unmet-least-figure.patch diff --git a/wai/http-conformance/falsify/budget-floor-zero.patch b/wai/http-conformance/falsify/budget-floor-zero.patch deleted file mode 100644 index 93a6217e3..000000000 --- a/wai/http-conformance/falsify/budget-floor-zero.patch +++ /dev/null @@ -1,20 +0,0 @@ -# must fail: abr_budget_floor_least_figure abr_budget_floor_above_the_choice -# the floor is option 0, whatever its figure (abr::clamp_to_budget) -diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs -index 6fb8973c..13cbf426 100644 ---- a/wai/wai-rs/src/http_binding/abr.rs -+++ b/wai/wai-rs/src/http_binding/abr.rs -@@ -401,11 +401,8 @@ pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) { - if let Some(&(k, _)) = labelled.iter().rev().find(|&&(k, j)| k < choice && j <= b) { - return (k, Budget::Lowered); - } -- if let Some(&(k, _)) = labelled.iter().find(|&&(k, j)| k > choice && j <= b) { -- return (k, Budget::Floor); -- } -- let least = labelled.iter().min_by_key(|&&(k, j)| (j, k)).map_or(0, |&(k, _)| k); -- (least, Budget::Floor) -+ -+ (0, Budget::Floor) - } - - /// The policy's choice under the state's budget; refused beyond Appendix F's bounds. diff --git a/wai/http-conformance/falsify/budget-raises.patch b/wai/http-conformance/falsify/budget-raises.patch new file mode 100644 index 000000000..d22286616 --- /dev/null +++ b/wai/http-conformance/falsify/budget-raises.patch @@ -0,0 +1,15 @@ +# must fail: abr_budget_not_raised_to_labelled_options abr_budget_not_raised_to_one_within +# the budget raises the choice to the lowest labelled option above it within the budget (abr::clamp_to_budget) +diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs +index cc9cd96a..a0cb30b9 100644 +--- a/wai/wai-rs/src/http_binding/abr.rs ++++ b/wai/wai-rs/src/http_binding/abr.rs +@@ -398,7 +398,7 @@ pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) { + match (0..=choice).rev().find(|&k| fig(k).is_some_and(|j| j <= b)) { + Some(k) if k == choice => (k, Budget::Within), + Some(k) => (k, Budget::Lowered), +- None => (0, Budget::Unmet), ++ None => ((choice + 1..n).find(|&k| fig(k).is_some_and(|j| j <= b)).unwrap_or(0), Budget::Unmet), + } + } + diff --git a/wai/http-conformance/falsify/budget-unlabelled-escapes.patch b/wai/http-conformance/falsify/budget-unlabelled-escapes.patch index 9b262d3fc..291cb2288 100644 --- a/wai/http-conformance/falsify/budget-unlabelled-escapes.patch +++ b/wai/http-conformance/falsify/budget-unlabelled-escapes.patch @@ -1,15 +1,15 @@ -# must fail: abr_budget_unlabelled_choice abr_budget_floor_above_the_choice +# must fail: abr_budget_unlabelled_choice # an unlabelled choice leaves the budget unapplied (abr::clamp_to_budget) diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs -index 6fb8973c..1ad66ade 100644 +index cc9cd96a..20028eea 100644 --- a/wai/wai-rs/src/http_binding/abr.rs +++ b/wai/wai-rs/src/http_binding/abr.rs @@ -392,7 +392,7 @@ pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) { + let Some(b) = s.joule_budget_micro else { return (choice, Budget::NotSet) }; let n = s.option_bytes.len().max(choice + 1); let fig = |k: usize| if k < n { s.option_joules_micro.get(k).copied().flatten() } else { None }; - let labelled: Vec<(usize, u64)> = (0..n).filter_map(|k| fig(k).map(|j| (k, j))).collect(); -- if labelled.is_empty() { -+ if labelled.is_empty() || fig(choice).is_none() { +- if (0..n).all(|k| fig(k).is_none()) { ++ if (0..n).all(|k| fig(k).is_none()) || fig(choice).is_none() { return (choice, Budget::NotApplied); } - if fig(choice).is_some_and(|j| j <= b) { + match (0..=choice).rev().find(|&k| fig(k).is_some_and(|j| j <= b)) { diff --git a/wai/http-conformance/falsify/budget-unlabelled.patch b/wai/http-conformance/falsify/budget-unlabelled.patch index cc6b284bb..9d44a21b0 100644 --- a/wai/http-conformance/falsify/budget-unlabelled.patch +++ b/wai/http-conformance/falsify/budget-unlabelled.patch @@ -1,15 +1,15 @@ # must fail: abr_budget_not_applied abr_budget_unlabelled_choice depth_O10_budget_unlabelled_layer # an unlabelled figure taken as zero joules (abr::clamp_to_budget) diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs -index 6fb8973c..5fbd3bc3 100644 +index cc9cd96a..18d9cbfe 100644 --- a/wai/wai-rs/src/http_binding/abr.rs +++ b/wai/wai-rs/src/http_binding/abr.rs -@@ -390,7 +390,7 @@ pub fn labelled_figure(joules_micro: u64, class: Option<&crate::energy_class::En +@@ -391,7 +391,7 @@ pub fn labelled_figure(joules_micro: u64, class: Option<&crate::energy_class::En pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) { let Some(b) = s.joule_budget_micro else { return (choice, Budget::NotSet) }; let n = s.option_bytes.len().max(choice + 1); - let fig = |k: usize| if k < n { s.option_joules_micro.get(k).copied().flatten() } else { None }; + let fig = |k: usize| if k < n { s.option_joules_micro.get(k).copied().flatten().or(Some(0)) } else { None }; - let labelled: Vec<(usize, u64)> = (0..n).filter_map(|k| fig(k).map(|j| (k, j))).collect(); - if labelled.is_empty() { + if (0..n).all(|k| fig(k).is_none()) { return (choice, Budget::NotApplied); + } diff --git a/wai/http-conformance/falsify/budget-unmet-least-figure.patch b/wai/http-conformance/falsify/budget-unmet-least-figure.patch new file mode 100644 index 000000000..1c59ec854 --- /dev/null +++ b/wai/http-conformance/falsify/budget-unmet-least-figure.patch @@ -0,0 +1,15 @@ +# must fail: abr_budget_unmet_not_the_least_figure abr_budget_not_raised_past_the_budget +# with nothing within the budget, the option of the least labelled figure (abr::clamp_to_budget) +diff --git a/wai/wai-rs/src/http_binding/abr.rs b/wai/wai-rs/src/http_binding/abr.rs +index cc9cd96a..a2ae9f56 100644 +--- a/wai/wai-rs/src/http_binding/abr.rs ++++ b/wai/wai-rs/src/http_binding/abr.rs +@@ -398,7 +398,7 @@ pub fn clamp_to_budget(s: &AbrState, choice: usize) -> (usize, Budget) { + match (0..=choice).rev().find(|&k| fig(k).is_some_and(|j| j <= b)) { + Some(k) if k == choice => (k, Budget::Within), + Some(k) => (k, Budget::Lowered), +- None => (0, Budget::Unmet), ++ None => ((0..n).filter_map(|k| fig(k).map(|j| (j, k))).min().map_or(0, |(_, k)| k), Budget::Unmet), + } + } + diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh index 6b581ef80..32b401f12 100755 --- a/wai/http-conformance/falsify/run.sh +++ b/wai/http-conformance/falsify/run.sh @@ -51,7 +51,7 @@ for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done t rebind-uri rebind-sequence key-unchecked walk-unbounded reload-walked \ xml-char-unchecked xml-attribute-twice dash-start-ignored dash-timescale-ignored \ unlabelled-escapes abr-unbounded gap-carries receipt-duplicates receipt-surrogates session-data-anywhere \ - dash-r-expanded xml-attributes-scanned dash-timelines-unbounded \ + dash-r-expanded xml-attributes-scanned dash-timelines-unbounded budget-raises \ file-numbers-unbounded; do if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then echo "::error::http_verify.py --mutate $m passed"; fail=1 -- 2.54.0 (Apple Git-157) From b290c11d4b5f6846e6aaef0c2feefa87dcd90364 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 14:18:36 -0400 Subject: [PATCH 51/55] wai: a live chain re-anchors past the walk bound when ahead; bindings keyed by resolved URL, a gap never withdraws a segment, bindings retained for 256 revisions; corpus pins the walk's per-parent checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Past WALK_BOUND, a revision ahead of the head (its media sequence after its last complete segment greater) re-anchors the chain (reanchored): it becomes the head, the chain keeps its bindings, the revisions between are not signed. One not ahead is refused (playlist_receipt_walk_exceeded), so a stale or older revision never moves it back; the head fetched again is unchanged, however often. (§8.6) - Bindings key a line by its URI resolved against the playlist's URL and normalized (RFC 3986 §5.2, §6.2.2; http_binding::uri, and an independent rfc3986_resolve in http_verify.py), so ./720/1201.0.wai is 720/1201.0.wai. (§11.5) - A gap at a sequence number bound to an object is playlist_listing_rebound. - A binding is kept while one of the last 256 revisions lists it (RETENTION); a rebinding older than that is not detected, as §11.5 states. - Corpus: live_edge_reanchored*, live_edge_reanchor_not_backwards, live_edge_parent_substituted, live_edge_parent_invalid, live_relisted_dot_alias, live_dot_alias_same_object, live_gap_withdraws_a_segment, and uri_resolve cases (RFC 3986 §5.4 and alias spellings) with a fuzz family. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/http-delivery.md | 36 +++- wai/http-conformance/README.md | 6 +- wai/http-conformance/cases.json | 67 +++++- .../cases/relist/r2-dot-same.json | 1 + wai/http-conformance/cases/relist/r2-dot.json | 1 + wai/http-conformance/cases/relist/r3-gap.json | 1 + .../cases/relist/wai-r2-dot-same.m3u8 | 36 ++++ .../cases/relist/wai-r2-dot.m3u8 | 36 ++++ .../cases/relist/wai-r3-gap.m3u8 | 13 ++ ...215932108c4da3fde179d6cbaa3b91c296b1c.json | 1 + .../cases/walk-invalid/r3.json | 1 + ...b93b7c0b84ff123b99ee2c26505649c68bdf6.json | 1 + wai/http-conformance/http_verify.py | 150 +++++++++++-- wai/wai-rs/src/bin/wai_cli/http_playlists.rs | 7 +- .../src/bin/wai_cli/http_vectors_playlists.rs | 76 ++++++- wai/wai-rs/src/http_binding/hls.rs | 85 +++++--- wai/wai-rs/src/http_binding/mod.rs | 1 + .../src/http_binding/playlist_receipt.rs | 41 +++- .../src/http_binding/tests_playlists.rs | 98 ++++++++- wai/wai-rs/src/http_binding/uri.rs | 201 ++++++++++++++++++ 20 files changed, 788 insertions(+), 71 deletions(-) create mode 100644 wai/http-conformance/cases/relist/r2-dot-same.json create mode 100644 wai/http-conformance/cases/relist/r2-dot.json create mode 100644 wai/http-conformance/cases/relist/r3-gap.json create mode 100644 wai/http-conformance/cases/relist/wai-r2-dot-same.m3u8 create mode 100644 wai/http-conformance/cases/relist/wai-r2-dot.m3u8 create mode 100644 wai/http-conformance/cases/relist/wai-r3-gap.m3u8 create mode 100644 wai/http-conformance/cases/walk-invalid/mr/532a6470fd1df4eb9eca528cb34215932108c4da3fde179d6cbaa3b91c296b1c.json create mode 100644 wai/http-conformance/cases/walk-invalid/r3.json create mode 100644 wai/http-conformance/cases/walk-substituted/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json create mode 100644 wai/wai-rs/src/http_binding/uri.rs diff --git a/wai/extensions/http-delivery.md b/wai/extensions/http-delivery.md index 0486f2e64..1d778e852 100644 --- a/wai/extensions/http-delivery.md +++ b/wai/extensions/http-delivery.md @@ -504,11 +504,18 @@ fetched from `mr/.json` and checked in order: it is served same key (`playlist_receipt_unchained`). A walk that reaches a receipt with no parent before the head does not chain (`playlist_receipt_unchained`), so a revision older than the head never does. A client fetches at most 64 parents for one revision, and MAY hold -a lower bound; a head further back is refused (`playlist_receipt_walk_exceeded`), and -the revision is held at **listed** (§11.6). Then the walked receipts, oldest first, and -the new head bind what they list (§11.5); one that lists another cid for a URI or a -media sequence number the chain bound is refused (`playlist_listing_rebound`). A -refused revision leaves the chain as it was. +a lower bound. When the head is further back than that, the client re-anchors the chain +at the revision in hand if that revision is ahead of the head (its media sequence number +after its last complete segment is greater than the head's): the revision becomes the +head (`reanchored`), the chain keeps what it bound, and the revisions between the old +head and the new one are not signed: no part they alone list is held at **signed**. A +revision past the bound and not ahead of the head is refused +(`playlist_receipt_walk_exceeded`), so an older or stale revision never moves the chain +back; the head fetched again, however often, changes nothing. Then the walked receipts, +oldest first, and the new head bind what they list (§11.5), a re-anchored head alone; one +that lists another cid for a URL or a media sequence number the chain bound, or a gap +where it bound an object, is refused (`playlist_listing_rebound`). A refused revision +leaves the chain as it was. ## 9. Cache rules @@ -772,14 +779,23 @@ Each part is its own object, served under an alias, and listed with its cid: Every object is immutable from the moment it is listed, so a live playlist becomes on-demand by appending `EXT-X-ENDLIST`; no object is rewritten. -A revision MUST NOT list another cid for a URI an earlier revision listed (an alias, -or any part, segment or layer URI, without its fragment), nor another object at a media -sequence number an earlier revision listed an object at (a gap segment lists no object -at its number). A client keeps both bindings from every revision it accepts, a URI's -cid being the first one listed with it (§5.2), and refuses a revision that lists +A revision MUST NOT list another cid for a URL an earlier revision listed (an alias, or +any part, segment or layer URI), nor another object at a media sequence number an +earlier revision listed an object at, nor a gap there: a listed segment is not +withdrawn (a gap segment lists no object at its number). A URL is the line's URI +resolved against the playlist's URL (RFC 3986 §5.2) and normalized (§6.2.2: the scheme +and host in lower case, percent-encodings' hexadecimal digits in upper case, unreserved +characters decoded, dot segments removed), without its fragment, so two spellings of +one URL are one binding. A client keeps both bindings from the revisions it accepts, a +URL's cid being the first one listed with it (§5.2), and refuses a revision that lists another (`playlist_listing_rebound`), taking none of its listings. A chain of receipts binds the same way over the receipts in it (§8.6). +A client keeps a binding while one of the last 256 revisions it took lists it, and MAY +keep it longer; what it holds is then bounded by what those revisions list, however +long the playlist runs. A rebinding of a URL or a sequence number that none of the last +256 revisions listed is not detected. + ### 11.6 Playlist authentication A WAI playlist SHOULD be authenticated by its receipt (§8.6). A client that verifies it diff --git a/wai/http-conformance/README.md b/wai/http-conformance/README.md index d972f557f..31a54dda6 100644 --- a/wai/http-conformance/README.md +++ b/wai/http-conformance/README.md @@ -39,7 +39,8 @@ reference adaptation policies. Two implementations check every case and must agr | `cases/live-r2-orphan.json` | the third live revision sealed with no parent: a receipt that verifies and does not chain | | `*/mr/.json` | each receipt again under its receipt hash, where a client walking back from a later revision fetches it by its link (§8.6) | | `cases/other-key/` | the third live revision's receipt chained to the second by hash and signed by another key (`r2.json`, and in `mr/` under its receipt hash), and the fourth revision's receipt chained to it (`r3.json`) | -| `cases/relist/` | the third live revision listing the alias of part 0 of segment 1201 with part 1's object (`wai-r2.m3u8`), sealed and chained to the second (`r2.json`, and in `mr/`); the fourth revision's receipt chained to it (`r3-after.json`); and the fourth revision listing segment 1201's object at sequence 1200 (`wai-r3.m3u8`, `r3.json`) | +| `cases/relist/` | the third live revision listing the alias of part 0 of segment 1201 with part 1's object (`wai-r2.m3u8`), sealed and chained to the second (`r2.json`, and in `mr/`); the fourth revision's receipt chained to it (`r3-after.json`); the fourth revision listing segment 1201's object at sequence 1200 (`wai-r3.m3u8`, `r3.json`); the third revision listing that alias as `./720/1201.0.wai`, with part 1's object and with its own (`wai-r2-dot*.m3u8`, `r2-dot*.json`); and the fourth revision withdrawing segment 1200 as a gap (`wai-r3-gap.m3u8`, `r3-gap.json`) | +| `cases/walk-substituted/`, `cases/walk-invalid/` | parents a walk fetches by their links: the second revision's receipt served under the third's receipt hash; and the third revision changed after it was signed, served under its receipt hash, with the fourth revision's receipt chained to it (`r3.json`) | | `cases/sig-*.json` | the O9 playlist's receipt re-signed at the edges of Ed25519 (Appendix H): the identity key, canonical and respelled `y` = p + 1, under `(R, S) = (B, 1)`; under the key B (scalar 1), `R` the identity, canonical and respelled, with `S` = k, so the cofactorless equation holds point for point; and under the key B a signature the strict rule accepts (`sig-key-base-point.json`) | ## Objects @@ -107,7 +108,8 @@ outside this repository, and the AV1 streams were checked to decode. | `adjacency` | a `wai` and a `classical` playlist (files, or `*_text`), optionally edited (`edit_wai`, `edit_classical`: `[from, to]` pairs, each replaced once) | `adjacent`, or `error` | | `unchanged` | an `original` and a `published` classical master (files, or `*_text`), `edit_published` | `unchanged`, or `error` | | `playlist_receipt` | a `playlist`, its `receipt` file and the `name` it is fetched as (the file's own by default), each optionally edited | the receipt hash and its parent, or `error` | -| `live` | `revisions` in the order a client fetched them (each a WAI playlist, its receipt, its classical playlist, `edit_wai`), `fetches` (an `alias` and the `object` served under it, optionally patched), and where parents are fetched from (`parents`, a directory holding `.json`; the first revision's `mr/` by default) and the walk's bound (`walk_bound`, 64 by default) | per revision its adjacency, its listing against what accepted revisions bound, its receipt, what it did to the chain (`true`, `unchanged`, or the reason) and the parents walked; per fetch the accepted revision that first lists the alias, the outcome and what held (`listed`, or `signed` when a receipt in the chain binds the alias to that cid) | +| `live` | `revisions` in the order a client fetched them (each a WAI playlist, its receipt, its classical playlist, `edit_wai`), `fetches` (an `alias` and the `object` served under it, optionally patched), the playlist's `url` (against which its lines resolve; `https://origin.example/live/wai-720.m3u8` by default), where parents are fetched from (`parents`, a directory holding `.json`; the first revision's `mr/` by default) and the walk's bound (`walk_bound`, 64 by default) | per revision its adjacency, its listing against what accepted revisions bound, its receipt, what it did to the chain (`true`, `unchanged`, `reanchored`, or the reason) and the parents walked; per fetch the accepted revision that first lists the alias, the outcome and what held (`listed`, or `signed` when a receipt in the chain binds the alias's URL to that cid) | +| `uri_resolve` | a `base` and a `reference` | the `url` (RFC 3986 §5.2 and §6.2.2, without the fragment) | | `abr` | a `policy` and a `state` (Appendix F) | the `choice` and what the `budget` did, or `error` beyond Appendix F's bounds | | `abr_order` | an object, what the sink `advertises`, a `policy`, a `state` | the entry `order` | | `depth` | layer `objects` (or `layers_n`), a `policy`, a `state` | the `depth` and what the `budget` did, or `error` | diff --git a/wai/http-conformance/cases.json b/wai/http-conformance/cases.json index b12aa7083..1765cf331 100644 --- a/wai/http-conformance/cases.json +++ b/wai/http-conformance/cases.json @@ -467,6 +467,63 @@ {"name":"dash_adjacency_start_in_months","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["start=\"PT0S\"","start=\"P0M\""]],"edit_classical":[]},"expect":{"error":"playlist_malformed"}}, {"name":"dash_adjacency_not_an_mpd","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[["",""]],"edit_classical":[]},"expect":{"error":"playlist_malformed"}}, {"name":"dash_adjacency_zero_d","op":"dash_adjacency","input":{"wai":"playlists/o9/wai.mpd","classical":"src/playlists/o9-classical.mpd","edit_wai":[],"edit_classical":[["d=\"30720\"","d=\"0\""]]},"expect":{"error":"playlist_malformed"}}, +{"name":"uri_resolve_rfc_00","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g:h"},"expect":{"url":"g:h"}}, +{"name":"uri_resolve_rfc_01","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g"},"expect":{"url":"http://a/b/c/g"}}, +{"name":"uri_resolve_rfc_02","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"./g"},"expect":{"url":"http://a/b/c/g"}}, +{"name":"uri_resolve_rfc_03","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g/"},"expect":{"url":"http://a/b/c/g/"}}, +{"name":"uri_resolve_rfc_04","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"/g"},"expect":{"url":"http://a/g"}}, +{"name":"uri_resolve_rfc_05","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"//g"},"expect":{"url":"http://g"}}, +{"name":"uri_resolve_rfc_06","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"?y"},"expect":{"url":"http://a/b/c/d;p?y"}}, +{"name":"uri_resolve_rfc_07","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g?y"},"expect":{"url":"http://a/b/c/g?y"}}, +{"name":"uri_resolve_rfc_08","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"#s"},"expect":{"url":"http://a/b/c/d;p?q"}}, +{"name":"uri_resolve_rfc_09","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g#s"},"expect":{"url":"http://a/b/c/g"}}, +{"name":"uri_resolve_rfc_10","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g?y#s"},"expect":{"url":"http://a/b/c/g?y"}}, +{"name":"uri_resolve_rfc_11","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":";x"},"expect":{"url":"http://a/b/c/;x"}}, +{"name":"uri_resolve_rfc_12","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g;x"},"expect":{"url":"http://a/b/c/g;x"}}, +{"name":"uri_resolve_rfc_13","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g;x?y#s"},"expect":{"url":"http://a/b/c/g;x?y"}}, +{"name":"uri_resolve_rfc_14","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":""},"expect":{"url":"http://a/b/c/d;p?q"}}, +{"name":"uri_resolve_rfc_15","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"."},"expect":{"url":"http://a/b/c/"}}, +{"name":"uri_resolve_rfc_16","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"./"},"expect":{"url":"http://a/b/c/"}}, +{"name":"uri_resolve_rfc_17","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":".."},"expect":{"url":"http://a/b/"}}, +{"name":"uri_resolve_rfc_18","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"../"},"expect":{"url":"http://a/b/"}}, +{"name":"uri_resolve_rfc_19","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"../g"},"expect":{"url":"http://a/b/g"}}, +{"name":"uri_resolve_rfc_20","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"../.."},"expect":{"url":"http://a/"}}, +{"name":"uri_resolve_rfc_21","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"../../"},"expect":{"url":"http://a/"}}, +{"name":"uri_resolve_rfc_22","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"../../g"},"expect":{"url":"http://a/g"}}, +{"name":"uri_resolve_rfc_23","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"../../../g"},"expect":{"url":"http://a/g"}}, +{"name":"uri_resolve_rfc_24","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"../../../../g"},"expect":{"url":"http://a/g"}}, +{"name":"uri_resolve_rfc_25","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"/./g"},"expect":{"url":"http://a/g"}}, +{"name":"uri_resolve_rfc_26","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"/../g"},"expect":{"url":"http://a/g"}}, +{"name":"uri_resolve_rfc_27","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g."},"expect":{"url":"http://a/b/c/g."}}, +{"name":"uri_resolve_rfc_28","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":".g"},"expect":{"url":"http://a/b/c/.g"}}, +{"name":"uri_resolve_rfc_29","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g.."},"expect":{"url":"http://a/b/c/g.."}}, +{"name":"uri_resolve_rfc_30","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"..g"},"expect":{"url":"http://a/b/c/..g"}}, +{"name":"uri_resolve_rfc_31","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"./../g"},"expect":{"url":"http://a/b/g"}}, +{"name":"uri_resolve_rfc_32","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"./g/."},"expect":{"url":"http://a/b/c/g/"}}, +{"name":"uri_resolve_rfc_33","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g/./h"},"expect":{"url":"http://a/b/c/g/h"}}, +{"name":"uri_resolve_rfc_34","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g/../h"},"expect":{"url":"http://a/b/c/h"}}, +{"name":"uri_resolve_rfc_35","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g;x=1/./y"},"expect":{"url":"http://a/b/c/g;x=1/y"}}, +{"name":"uri_resolve_rfc_36","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g;x=1/../y"},"expect":{"url":"http://a/b/c/y"}}, +{"name":"uri_resolve_rfc_37","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g?y/./x"},"expect":{"url":"http://a/b/c/g?y/./x"}}, +{"name":"uri_resolve_rfc_38","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g?y/../x"},"expect":{"url":"http://a/b/c/g?y/../x"}}, +{"name":"uri_resolve_rfc_39","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g#s/./x"},"expect":{"url":"http://a/b/c/g"}}, +{"name":"uri_resolve_rfc_40","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"g#s/../x"},"expect":{"url":"http://a/b/c/g"}}, +{"name":"uri_resolve_rfc_41","op":"uri_resolve","input":{"base":"http://a/b/c/d;p?q","reference":"http:g"},"expect":{"url":"http:g"}}, +{"name":"uri_resolve_alias_00","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"720/1201.0.wai#cid=00"},"expect":{"url":"https://origin.example/live/720/1201.0.wai"}}, +{"name":"uri_resolve_alias_01","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"./720/1201.0.wai"},"expect":{"url":"https://origin.example/live/720/1201.0.wai"}}, +{"name":"uri_resolve_alias_02","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"720/x/../1201.0.wai"},"expect":{"url":"https://origin.example/live/720/1201.0.wai"}}, +{"name":"uri_resolve_alias_03","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"%37%32%30/1201.0.wai"},"expect":{"url":"https://origin.example/live/720/1201.0.wai"}}, +{"name":"uri_resolve_alias_04","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"720/1201%2e0.wai"},"expect":{"url":"https://origin.example/live/720/1201.0.wai"}}, +{"name":"uri_resolve_alias_05","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"720/%2E/1201.0.wai"},"expect":{"url":"https://origin.example/live/720/1201.0.wai"}}, +{"name":"uri_resolve_alias_06","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"720%2F1201.0.wai"},"expect":{"url":"https://origin.example/live/720%2F1201.0.wai"}}, +{"name":"uri_resolve_alias_07","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"HTTPS://ORIGIN.EXAMPLE/live/720/1201.0.wai"},"expect":{"url":"https://origin.example/live/720/1201.0.wai"}}, +{"name":"uri_resolve_alias_08","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"//Origin.Example:443/live/720/1201.0.wai"},"expect":{"url":"https://origin.example:443/live/720/1201.0.wai"}}, +{"name":"uri_resolve_alias_09","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"720/1201.0.wai?x=%7e%41"},"expect":{"url":"https://origin.example/live/720/1201.0.wai?x=~A"}}, +{"name":"uri_resolve_alias_10","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"../../../720/1201.0.wai"},"expect":{"url":"https://origin.example/720/1201.0.wai"}}, +{"name":"uri_resolve_alias_11","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"a:b/c"},"expect":{"url":"a:b/c"}}, +{"name":"uri_resolve_alias_12","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"./a:b"},"expect":{"url":"https://origin.example/live/a:b"}}, +{"name":"uri_resolve_alias_13","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"user@x"},"expect":{"url":"https://origin.example/live/user@x"}}, +{"name":"uri_resolve_alias_14","op":"uri_resolve","input":{"base":"https://origin.example/live/wai-720.m3u8","reference":"//u%41@Host.example/p"},"expect":{"url":"https://uA@host.example/p"}}, {"name":"unchanged_players","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[]},"expect":{"unchanged":true}}, {"name":"unchanged_original_itself","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"src/players/master.m3u8"},"expect":{"unchanged":true}}, {"name":"unchanged_two_lines","op":"unchanged","input":{"original":"src/players/master.m3u8","published":"players/master.m3u8","edit_published":[["#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n","#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n#EXT-X-SESSION-DATA:DATA-ID=\"science.transaction.wai.multivariant\",VALUE=\"wai.m3u8\"\n"]]},"expect":{"error":"classical_changed"}}, @@ -524,7 +581,12 @@ {"name":"live_edge_unchanged_reload","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"unchanged","walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"unchanged","walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":3,"outcome":"accepted","held":"signed"}]}}, {"name":"live_edge_skip","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":1},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":1,"outcome":"accepted","held":"signed"}]}}, {"name":"live_edge_skip_two","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":2}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, -{"name":"live_edge_walk_exceeded","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"walk_bound":1,"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_receipt_walk_exceeded","walked":null}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_edge_reanchored","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"walk_bound":1,"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"reanchored","walked":1}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_edge_reanchored_then_reloaded","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"walk_bound":1,"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"reanchored","walked":1},{"adjacent":true,"listing":true,"receipt":"signed","chained":"unchanged","walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"unchanged","walked":0}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_edge_reanchored_skipping","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8"}],"walk_bound":0,"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"reanchored","walked":0}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":1,"outcome":"accepted","held":"signed"}]}}, +{"name":"live_edge_reanchor_not_backwards","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"}],"walk_bound":0,"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"reanchored","walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_receipt_walk_exceeded","walked":null}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_edge_parent_substituted","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"parents":"cases/walk-substituted/mr","fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"receipt_name_mismatch","walked":null}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_edge_parent_invalid","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"cases/walk-invalid/r3.json","classical":"src/live/classical-r3.m3u8"}],"parents":"cases/walk-invalid/mr","fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_receipt_invalid","walked":null}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, {"name":"live_edge_walk_at_bound","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"walk_bound":2,"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":2}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, {"name":"live_edge_parent_missing","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"parents":"cases/no-receipts","fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_receipt_parent_missing","walked":null}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, {"name":"live_edge_other_key","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"cases/other-key/r2.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_receipt_unchained","walked":null},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":1}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"}]}}, @@ -532,6 +594,9 @@ {"name":"live_relisted_alias","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"cases/relist/wai-r2.m3u8","receipt":"cases/relist/r2.json","classical":"src/live/classical-r2.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"live/mr/b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1201.0.wai","object":"O12p1_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":"playlist_listing_rebound","receipt":null,"chained":null,"walked":null},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":1}],"fetches":[{"alias":"720/1201.0.wai","listed_in":1,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, {"name":"live_relisted_alias_walked","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r3.m3u8","receipt":"cases/relist/r3-after.json","classical":"src/live/classical-r3.m3u8"}],"parents":"cases/relist/mr","fetches":[{"alias":"720/1201.0.wai","object":"O12p1_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":"playlist_listing_rebound","walked":null}],"fetches":[{"alias":"720/1201.0.wai","listed_in":1,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, {"name":"live_resequenced","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8"},{"wai":"cases/relist/wai-r3.m3u8","receipt":"cases/relist/r3.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":"playlist_listing_rebound","receipt":null,"chained":null,"walked":null}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"}]}}, +{"name":"live_relisted_dot_alias","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"cases/relist/wai-r2-dot.m3u8","receipt":"cases/relist/r2-dot.json","classical":"src/live/classical-r2.m3u8"}],"fetches":[{"alias":"720/1201.0.wai","object":"O12p1_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":"playlist_listing_rebound","receipt":null,"chained":null,"walked":null}],"fetches":[{"alias":"720/1201.0.wai","listed_in":1,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":null,"outcome":"not_listed","held":null}]}}, +{"name":"live_dot_alias_same_object","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"cases/relist/wai-r2-dot-same.m3u8","receipt":"cases/relist/r2-dot-same.json","classical":"src/live/classical-r2.m3u8"}],"fetches":[{"alias":"720/1201.0.wai","object":"O12p1_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0}],"fetches":[{"alias":"720/1201.0.wai","listed_in":1,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"}]}}, +{"name":"live_gap_withdraws_a_segment","op":"live","input":{"revisions":[{"wai":"live/wai-r0.m3u8","receipt":"live/mr/6d6933140ea58cce58ea7bd667f63c7518c7e6c1d89d81328a419f8d406c75d9.json","classical":"src/live/classical-r0.m3u8"},{"wai":"live/wai-r1.m3u8","receipt":"live/mr/091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271.json","classical":"src/live/classical-r1.m3u8"},{"wai":"live/wai-r2.m3u8","receipt":"live/mr/3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900.json","classical":"src/live/classical-r2.m3u8"},{"wai":"cases/relist/wai-r3-gap.m3u8","receipt":"cases/relist/r3-gap.json","classical":"src/live/classical-r3.m3u8"}],"fetches":[{"alias":"720/1200.0.wai","object":"O12p0_00"},{"alias":"720/1201.3.wai","object":"O12p1_03"}]},"expect":{"revisions":[{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":true,"receipt":"signed","chained":true,"walked":0},{"adjacent":true,"listing":"playlist_listing_rebound","receipt":null,"chained":null,"walked":null}],"fetches":[{"alias":"720/1200.0.wai","listed_in":0,"outcome":"accepted","held":"signed"},{"alias":"720/1201.3.wai","listed_in":2,"outcome":"accepted","held":"signed"}]}}, {"name":"abr_throughput_00","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":28203,"samples":[[1000,1259305,570]],"last":2,"option_bytes":[229507,231557,344895],"option_joules_micro":[3913,null,3815],"joule_budget_micro":null,"segment_ms":2000}},"expect":{"choice":2,"budget":"not_set"}}, {"name":"abr_throughput_01","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":27557,"samples":[],"last":0,"option_bytes":[48543,69733,178222,189003,255156,340556],"option_joules_micro":[250,4083,4219,null,null,null],"joule_budget_micro":1274,"segment_ms":2000}},"expect":{"choice":0,"budget":"within"}}, {"name":"abr_throughput_02","op":"abr","input":{"policy":{"id":"throughput","last_n":3,"safety_permille":900},"state":{"buffer_ms":27339,"samples":[],"last":null,"option_bytes":[132854,232413],"option_joules_micro":[null,315],"joule_budget_micro":null,"segment_ms":3840}},"expect":{"choice":0,"budget":"not_set"}}, diff --git a/wai/http-conformance/cases/relist/r2-dot-same.json b/wai/http-conformance/cases/relist/r2-dot-same.json new file mode 100644 index 000000000..32bf40b59 --- /dev/null +++ b/wai/http-conformance/cases/relist/r2-dot-same.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"ba060cc75a825f51a94e6e61fba988af8486254134d9452689a9d8340524a085","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"b7bdf3a4ce93f17fcd7e3dd6e10ee519fcee492013c6fd289d6962460c8c28ccc60fc1f561275d1fd4eb5fc93032efbc5fa9ce607a554d48ca64be15fe42f50d","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"./720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/relist/r2-dot.json b/wai/http-conformance/cases/relist/r2-dot.json new file mode 100644 index 000000000..0ba83f269 --- /dev/null +++ b/wai/http-conformance/cases/relist/r2-dot.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"2fb1c914fa5b8fd12fc710a8410fe6d5de290a84eb400aa91bd831fb65db659e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"9248ef288daff80485b0c057b82e90759c2f40f080a375eec628f817aa110a3a83e47b8956c02100c4aeac349894022a1b1104f89b5d94e66aa9fd394cfd3207","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"./720/1201.0.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/relist/r3-gap.json b/wai/http-conformance/cases/relist/r3-gap.json new file mode 100644 index 000000000..024f61bbc --- /dev/null +++ b/wai/http-conformance/cases/relist/r3-gap.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"9738e08a98c5867a031789c26269d72cbfabd153483128811d4feb0e33e4e136","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"3bf9989633f662d7084a06d5698c98bf112c81041b2060627a10d2da4a141e20e58cda96925718090d6bb4800b52dab8745f8ce2a368ce279bb9b88578ff7a0e","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":true,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/cases/relist/wai-r2-dot-same.m3u8 b/wai/http-conformance/cases/relist/wai-r2-dot-same.m3u8 new file mode 100644 index 000000000..2c840b6d0 --- /dev/null +++ b/wai/http-conformance/cases/relist/wai-r2-dot-same.m3u8 @@ -0,0 +1,36 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.320 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014" +#EXTINF:3.840, +../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606 +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="./720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222" +#EXTINF:3.840, +../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118 +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="720/1202.0.wai" diff --git a/wai/http-conformance/cases/relist/wai-r2-dot.m3u8 b/wai/http-conformance/cases/relist/wai-r2-dot.m3u8 new file mode 100644 index 000000000..12a3e67c0 --- /dev/null +++ b/wai/http-conformance/cases/relist/wai-r2-dot.m3u8 @@ -0,0 +1,36 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.320 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014" +#EXTINF:3.840, +../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606 +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="./720/1201.0.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222" +#EXT-X-PART:DURATION=0.320,INDEPENDENT=YES,URI="720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222" +#EXTINF:3.840, +../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118 +#EXT-X-PRELOAD-HINT:TYPE=PART,URI="720/1202.0.wai" diff --git a/wai/http-conformance/cases/relist/wai-r3-gap.m3u8 b/wai/http-conformance/cases/relist/wai-r3-gap.m3u8 new file mode 100644 index 000000000..a55986f51 --- /dev/null +++ b/wai/http-conformance/cases/relist/wai-r3-gap.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:9 +#EXT-X-TARGETDURATION:4 +#EXT-X-PART-INF:PART-TARGET=0.320 +#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=0.960 +#EXT-X-MEDIA-SEQUENCE:1200 +#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z +#EXT-X-GAP +#EXTINF:3.840, +../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606 +#EXTINF:3.840, +../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118 +#EXT-X-ENDLIST diff --git a/wai/http-conformance/cases/walk-invalid/mr/532a6470fd1df4eb9eca528cb34215932108c4da3fde179d6cbaa3b91c296b1c.json b/wai/http-conformance/cases/walk-invalid/mr/532a6470fd1df4eb9eca528cb34215932108c4da3fde179d6cbaa3b91c296b1c.json new file mode 100644 index 000000000..dfb90cbab --- /dev/null +++ b/wai/http-conformance/cases/walk-invalid/mr/532a6470fd1df4eb9eca528cb34215932108c4da3fde179d6cbaa3b91c296b1c.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"3274f7d04d766ab2894b8da756aafe2d76e1cbb92ee9c88b803d0b21d2428900","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":2,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"eab14aa7350c082b1cc52b78c74eb5f2660f80ca6267a73a2ae9cd186df824ed","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"d02f849c0a2476cd732b53039fa56de97d4aedf4f36c49696a0bad2349f8530c31deea9635825a8fc33ef0a16a55e55b15d90a9633e821bcdfed0a5eba7be803","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.3.wai#cid=a8ae3c937be0969d0e9a355e3afedd51eddb754dc3a95ed6fcd9386b2a6372cf&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.4.wai#cid=6f5e3d4204b88e143da63ef4dc22840e65839fdbf47d2a0c03d68695860c0c70&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.5.wai#cid=35f910e1a0a4acfbf8ed201e8ccd430302f395280aa79f9657b3cb16f23ba5b0&n=3146","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.6.wai#cid=cd095db1f4b9560782af8ba700fe5154e7ef541554a0550ea8edb796bde725ce&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.7.wai#cid=173c227ba468e8809e9b7b016264bd86dc1310c7681a732e9d0886373411915f&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.8.wai#cid=db17bd07b023ddcc31b057df9d0da1354b148c3920489e345201a8d3c2c72bed&n=3238","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.9.wai#cid=48c691c6021b6b07ae5a7046dd04eaee0b8ebb7ce3a5b9aca275ec5015d54312&n=3170","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.10.wai#cid=fa7e77738aaf35251d277030fb58d1cd9a3f262ce9b44bbb16248142037bf32d&n=3222","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.11.wai#cid=5f341dcfe19ad23b52d1c879fa2d6f52c60829e89d47cc544dfb0891cac41304&n=3222","layers":[]}],"layers":[],"preload_hint":"720/1202.0.wai"}]} diff --git a/wai/http-conformance/cases/walk-invalid/r3.json b/wai/http-conformance/cases/walk-invalid/r3.json new file mode 100644 index 000000000..659e8293f --- /dev/null +++ b/wai/http-conformance/cases/walk-invalid/r3.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"b6e2d55ea619bd811d877d9098ad02edf6d70de7823de8c1c876b41e202d133e","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null},{"index":1,"kind":"content","uri":"../objects/63c08b05659fd758074ff41a1378a391b7d449b0010a4ddd1894efb60026bb17.wai#n=19118","duration_ms":3840,"ad_id":null}],"merkle_root":"40add72be1d163ade96031789b4d36ca59dfa6100305a6ad2769fd17b096d07d","work_compose_ops":2,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"532a6470fd1df4eb9eca528cb34215932108c4da3fde179d6cbaa3b91c296b1c","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"f4e778d20184fa490c5e0d89da546a9131dbbf9c9f16edc5b0f845f80687103ca6fff7ea16c5d847bb667a26c1fe8adf44b10dcc1b9d54849939ff9f22fd890d","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":true,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[],"layers":[],"preload_hint":null}]} diff --git a/wai/http-conformance/cases/walk-substituted/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json b/wai/http-conformance/cases/walk-substituted/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json new file mode 100644 index 000000000..53e2c5d3b --- /dev/null +++ b/wai/http-conformance/cases/walk-substituted/mr/4ed29942f6a294bf03d422a1b76b93b7c0b84ff123b99ee2c26505649c68bdf6.json @@ -0,0 +1 @@ +{"kind":"wai.video.manifest","manifest_hash":"091740345f60047c283dfbb184cff89d86f93355c2162648dd032ac2263c4271","session_class_hash":"2d9add4fe530faab9a90f8195095117234c8d10472bac66237a1c2e771671759","class":{"content_id":"live/720","entitlement":"public","geo":"","blackouts":[],"ad_pod_id":"","window":1200},"segments":[{"index":0,"kind":"content","uri":"../objects/9de886a61b983bfcb8118ec313551c5af730a176180151756f8071fbb7d5f716.wai#n=16606","duration_ms":3840,"ad_id":null}],"merkle_root":"00e2bc6cdc748c6ab760c8e867603fd4d9a72d3a58f985bd13810a8d77aedcd3","work_compose_ops":1,"viewers_served":1,"joules_micro":0,"grant":{"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","capability":"video.manifest","joule_ceiling_micro":null,"funds_ceiling":null},"parent_receipt_hash":"3ce1b1f8805024633d89312232438e6ff46022b5a7ba48c994338876b6a7c364","signer_pubkey":"c84069fa2216c6edc1d52ca83aa11c267dd5440afa526090ca43ebd0ca86a9cf","signer_id":"did:example:http-vectors","sig":"8186f3adb28113d8ad08c923d24df463eb898d7a95b04902d23ca208fc17fe677249dc30f280faef8ec5aeac81abdb6b6fcd577075d9e9346f445c3b8c13fa03","render":{"version":9,"media_sequence":1200,"discontinuity_sequence":0,"target_duration_s":4,"part_target_ms":320,"part_hold_back_ms":960,"can_block_reload":true,"end_list":false,"playlist_type":null,"independent_segments":false,"map_uri":null},"extras":[{"discontinuity":false,"gap":false,"program_date_time":"2026-10-01T12:00:00.000Z","parts":[{"duration_ms":320,"independent":true,"uri":"720/1200.0.wai#cid=b8637cf58c3a5a0c567115d9065e5ec6d228663a65e50cb0b95a8b0dfb4cc385&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.1.wai#cid=c4336fb0c48737e9e68b8fe5f4bc60aa8314878a602de85cfc9c3c0d33c0e03f&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.2.wai#cid=12f271588cd4463ab7a8c89a25902fac152de57ec5420330b9ba5afd1fe0873a&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.3.wai#cid=00af350c635dd361002588143eaac2171e7b3ba39e8f8354eb3ac3fabd3392d4&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.4.wai#cid=27432e8c8ded7a74a057d0ffa83768d249982ed842d3e8659d0e39f69e92c815&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.5.wai#cid=2f38e98c125601d888c9a719cf0ac74e0846fe09c2d9fb09ed88b54cabc3a778&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.6.wai#cid=0a749256ffcfd6cfb3b396f04cea3d73622b5c230325096eed3585aa5f813008&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.7.wai#cid=dd01430fa65add27f88d5de32bb2100a3491b6202e935a1d4101aae8062d9d6f&n=3018","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.8.wai#cid=7095c2d1f03b11435df79189640f73303b9c1492fa302d1dcbdb26f1527d9ce6&n=3026","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.9.wai#cid=55f3946ced44b2e8384e772b9ffb71f07acbb176d171508cf269d06f0cf69676&n=2962","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.10.wai#cid=f04294c0f3f12e5e6309b39f1c336ef3257c8c755dbeb69314bce44fcc0947ac&n=2994","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1200.11.wai#cid=62853bb69f462471f250a4140c254b7bb0af2cf0691e4e759b89cc320f5f8659&n=3014","layers":[]}],"layers":[],"preload_hint":null},{"discontinuity":false,"gap":false,"program_date_time":null,"parts":[{"duration_ms":320,"independent":true,"uri":"720/1201.0.wai#cid=7ae66a04b663eaf965ee71c445af732893632ab0713cca8139969d3529e8ec8e&n=3190","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.1.wai#cid=ab999eaf1dfdbdee1784b3de6b4f1c7edf311119358fdb59870ad9d51c369c3d&n=3042","layers":[]},{"duration_ms":320,"independent":true,"uri":"720/1201.2.wai#cid=a002266b23a292284444fb753d53a3def273636f4bd7ca182b635326d9f74c7b&n=3122","layers":[]}],"layers":[],"preload_hint":"720/1201.3.wai"}]} diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index ee2d40f23..37a7f3cb3 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -1987,24 +1987,119 @@ def listed_cid(p, uri): return None if line is None else uri_cid(line) +RETENTION = 256 +URI_PARTS = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$", re.S) + + +def remove_dot_segments(path): + """RFC 3986 §5.2.4.""" + inp, out = path, "" + while inp: + if inp.startswith("../"): + inp = inp[3:] + elif inp.startswith("./"): + inp = inp[2:] + elif inp.startswith("/./"): + inp = inp[2:] + elif inp == "/.": + inp = "/" + elif inp.startswith("/../") or inp == "/..": + inp = "/" + inp[4:] if inp.startswith("/../") else "/" + out = out[:out.rfind("/")] if "/" in out else "" + elif inp in (".", ".."): + inp = "" + else: + j = inp.find("/", 1 if inp.startswith("/") else 0) + j = len(inp) if j < 0 else j + out, inp = out + inp[:j], inp[j:] + return out + + +def pct_normal(s): + """RFC 3986 §6.2.2.2: upper-case hexadecimal digits, unreserved characters decoded.""" + def one(m): + c = chr(int(m.group(1), 16)) + return c if re.fullmatch(r"[A-Za-z0-9._~-]", c) else "%" + m.group(1).upper() + return re.sub(r"%([0-9A-Fa-f]{2})", one, s) + + +def rfc3986_resolve(base, ref): + """ref resolved against base (RFC 3986 §5.2.2, §5.2.3) and normalized (§6.2.2), its + fragment dropped.""" + def parts(u): + m = URI_PARTS.match(u) + return m.group(2), m.group(4) if m.group(3) is not None else None, m.group(5), m.group(7) if m.group(6) is not None else None + bs, ba, bp, bq = parts(base) + rs, ra, rp, rq = parts(ref) + if MUTATE == "uri-unresolved": + return ref.split("#", 1)[0] + if rs is not None: + ts, ta, tp, tq = rs, ra, remove_dot_segments(rp), rq + elif ra is not None: + ts, ta, tp, tq = bs, ra, remove_dot_segments(rp), rq + elif rp == "": + ts, ta, tp, tq = bs, ba, bp, rq if rq is not None else bq + elif rp.startswith("/"): + ts, ta, tp, tq = bs, ba, remove_dot_segments(rp), rq + else: + if ba is not None and bp == "": + merged = "/" + rp + else: + merged = bp[:bp.rfind("/") + 1] + rp + ts, ta, tp, tq = bs, ba, remove_dot_segments(merged), rq + out = "" + if ts is not None: + out += ts.lower() + ":" + if ta is not None: + a = pct_normal(ta) + at = a.rfind("@") + out += "//" + (a[:at + 1] + a[at + 1:].lower() if at >= 0 else a.lower()) + out += remove_dot_segments(pct_normal(tp)) + if tq is not None: + out += "?" + pct_normal(tq) + return out + + +def new_bindings(url): + return {"base": url, "uris": {}, "seqs": {}, "rev": 0} + + def bind(b, p): - """§5.2, §11.5: the bindings `b` (each URI and media sequence number to its cid) with - playlist p's added; Refusal when p lists another cid for one already bound.""" - uris, seqs = dict(b[0]), dict(b[1]) + """§5.2, §11.5: the bindings b with playlist p's added as one more revision: each URL + a line names (resolved against the playlist's URL) and each media sequence number to + its cid. Refusal when p lists another cid for one bound, or a gap on a bound sequence + number; then what none of the last RETENTION revisions listed is forgotten.""" + rev = b["rev"] + 1 + uris, seqs = dict(b["uris"]), dict(b["seqs"]) for line in listing_lines(p): cid = uri_cid(line) - if cid is not None and uris.setdefault(line.split("#", 1)[0], cid) != cid and MUTATE != "rebind-uri": - raise Refusal("playlist_listing_rebound") - for i, sg in enumerate(p["segments"]): - cid = uri_cid(sg["uri"]) - if cid is None or sg["extra"]["gap"]: + if cid is None: continue + key = rfc3986_resolve(b["base"], line) + if key in uris and uris[key][0] != cid and MUTATE != "rebind-uri": + raise Refusal("playlist_listing_rebound") + uris[key] = (cid, rev) + for i, sg in enumerate(p["segments"]): n = p["seq"] + i if n > U64: raise Refusal("playlist_malformed") - if seqs.setdefault(n, cid) != cid and MUTATE != "rebind-sequence": + if sg["extra"]["gap"]: + if n in seqs and MUTATE != "gap-withdraws": + raise Refusal("playlist_listing_rebound") + continue + cid = uri_cid(sg["uri"]) + if cid is None: + continue + if n in seqs and seqs[n][0] != cid and MUTATE != "rebind-sequence": raise Refusal("playlist_listing_rebound") - return (uris, seqs) + seqs[n] = (cid, rev) + keep = (lambda last: True) if MUTATE == "bindings-unbounded" else (lambda last: last + RETENTION > rev) + return {"base": b["base"], "uris": {k: v for k, v in uris.items() if keep(v[1])}, "seqs": {k: v for k, v in seqs.items() if keep(v[1])}, "rev": rev} + + +def cid_of(b, uri): + v = b["uris"].get(rfc3986_resolve(b["base"], uri)) + return None if v is None else v[0] def xml_char(c): @@ -2803,13 +2898,14 @@ def check_playlist(playlist, receipt_bytes, name): WALK_BOUND = 64 +LIVE_URL = "https://origin.example/live/wai-720.m3u8" def advance(chain, cur, fetch): """§8.6: take the receipt cur (checked against its playlist) into the chain a client holds, {"head", "bindings", "bound"}; fetch(h) gives the bytes at mr/.json or None. - Returns ("first" | "unchanged" | "chained", walked) and updates chain, or raises - Refusal and leaves it as it was.""" + Returns ("first" | "unchanged" | "chained" | "reanchored", walked) and updates chain, + or raises Refusal and leaves it as it was.""" if chain["head"] is None: b = bind_receipt(chain["bindings"], cur) chain["head"], chain["bindings"] = cur, b @@ -2826,7 +2922,15 @@ def advance(chain, cur, fetch): if link == target: break if len(walked) == chain["bound"] and MUTATE != "walk-unbounded": - raise Refusal("playlist_receipt_walk_exceeded") + # Past the bound: a revision ahead of the head re-anchors the chain, its + # bindings kept; one that is not ahead is refused. + if last_segment(cur) <= last_segment(chain["head"]) and MUTATE != "reanchor-backwards": + raise Refusal("playlist_receipt_walk_exceeded") + if MUTATE == "walk-exceeded-wedges": + raise Refusal("playlist_receipt_walk_exceeded") + chain["bindings"] = bind_receipt(chain["bindings"], cur) + chain["head"] = cur + return "reanchored", len(walked) raw = fetch(link) if raw is None: raise Refusal("playlist_receipt_parent_missing") @@ -2834,9 +2938,9 @@ def advance(chain, cur, fetch): r = read_receipt(raw.decode("utf-8")) except (ReceiptMalformed, UnicodeDecodeError): raise Refusal("playlist_receipt_malformed") - if receipt_hash(r) != link: + if receipt_hash(r) != link and MUTATE != "walk-name-unchecked": raise Refusal("receipt_name_mismatch") - if not receipt_verifies(r): + if not receipt_verifies(r) and MUTATE != "walk-verify-unchecked": raise Refusal("playlist_receipt_invalid") if r["pubkey"] != cur["pubkey"] and MUTATE != "key-unchecked": raise Refusal("playlist_receipt_unchained") @@ -2849,6 +2953,11 @@ def advance(chain, cur, fetch): return "chained", len(walked) +def last_segment(r): + """The media sequence number after the last complete segment r lists.""" + return (r["render"]["seq"] if r["render"] is not None else 0) + len(r["segments"]) + + def bind_receipt(b, r): try: p = parse_media_playlist(rendered(r)) @@ -3037,8 +3146,9 @@ def live_case(d, objs, inp): f = os.path.join(d, parents, h.hex() + ".json") return open(f, "rb").read() if os.path.isfile(f) else None - chain = {"head": None, "bindings": ({}, {}), "bound": inp["walk_bound"] if isinstance(inp.get("walk_bound"), int) else WALK_BOUND} - listed_b = ({}, {}) + url = inp["url"] if isinstance(inp.get("url"), str) else LIVE_URL + chain = {"head": None, "bindings": new_bindings(url), "bound": inp["walk_bound"] if isinstance(inp.get("walk_bound"), int) else WALK_BOUND} + listed_b = new_bindings(url) revs, listed = [], [] for r in revisions: w = edit_text(read_text(d, r["wai"]), r.get("edit_wai")) @@ -3065,7 +3175,7 @@ def live_case(d, objs, inp): continue try: step, walked = advance(chain, rr, fetch) - ch = "unchanged" if step == "unchanged" else True + ch = step if step in ("unchanged", "reanchored") else True except Refusal as e: ch, walked = e.code, None revs.append({"adjacent": adjacent, "listing": True, "receipt": "signed", "chained": ch, "walked": walked}) @@ -3076,7 +3186,7 @@ def live_case(d, objs, inp): if first is None: fetches.append({"alias": f["alias"], "listed_in": None, "outcome": "not_listed", "held": None}) elif content_hash(served) == first[1]: - held = "signed" if chain["bindings"][0].get(f["alias"]) == first[1] else "listed" + held = "signed" if cid_of(chain["bindings"], f["alias"]) == first[1] else "listed" fetches.append({"alias": f["alias"], "listed_in": first[0], "outcome": "accepted", "held": held}) else: fetches.append({"alias": f["alias"], "listed_in": first[0], "outcome": "object_digest_mismatch", "held": None}) @@ -3095,6 +3205,8 @@ def playlist_evaluate(d, objs, op, inp, obj): t = inp["text"] if "text" in inp else read_text(d, inp["file"]) return parsed_case(parse_media_playlist(t), t) inline = lambda k: inp[k + "_text"] if isinstance(inp.get(k + "_text"), str) else read_text(d, inp[k]) + if op == "uri_resolve": + return {"url": rfc3986_resolve(inp["base"], inp["reference"])} if op == "dash_adjacency": check_dash_adjacent(edit_text(inline("wai"), inp.get("edit_wai")), edit_text(inline("classical"), inp.get("edit_classical"))) return {"adjacent": True} diff --git a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs index 1d123e660..ee62960ec 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_playlists.rs @@ -302,10 +302,15 @@ pub fn check(args: &[String]) -> Result { // by their links from the receipt's own directory (§8.6). let head = ManifestReceipt::from_json(&String::from_utf8_lossy(&read(Path::new(p))?)).map_err(|e| format!("{p}: {e}"))?; let dir = Path::new(rpath).parent().unwrap_or(Path::new(".")).to_path_buf(); - let mut chain = LiveChain::new(); + // Lines resolve against the playlist's own path, the same for every revision. + let mut chain = LiveChain::for_playlist(path, wai::http_binding::playlist_receipt::WALK_BOUND); chain.advance(&head, |_| None).map_err(|e| format!("{p}: {e}"))?; match chain.advance(&held, |h| std::fs::read(dir.join(format!("{}.json", hex(h)))).ok()) { Ok(Step::Chained { walked: n }) => walked = Some(n), + Ok(Step::Reanchored { walked: n }) => { + println!("reanchored\t{n} parents walked; the revisions between are not signed"); + walked = Some(n); + } Ok(_) => walked = Some(0), Err(e) => { println!("refused\t{}", e.code()); diff --git a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs index 7654ac7f3..d75da24cc 100644 --- a/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs +++ b/wai/wai-rs/src/bin/wai_cli/http_vectors_playlists.rs @@ -28,6 +28,10 @@ use super::{case, err, patch, read, refused, strs, Case, Corpus, Stop}; /// layer: the corpus's layered sequence (O11). const STAGED_PAIR: &str = "../staged-conformance/cases/live_multi_base"; +/// The URL a live case's client fetches the playlist from (http-delivery §11.5: its lines +/// resolve against it); an example host, serving nothing. +const LIVE_URL: &str = "https://origin.example/live/wai-720.m3u8"; + /// The seed of the corpus's playlist key: a published test key, attesting nothing. pub(super) const PLAYLIST_SEED: [u8; 32] = [0x57; 32]; /// The signer id the corpus's playlist receipts carry. @@ -463,6 +467,10 @@ pub(super) fn outcome(c: &Corpus, op: &str, input: &Value) -> Option (|| { + let (b, r) = (input["base"].as_str().ok_or("base")?, input["reference"].as_str().ok_or("reference")?); + Ok(json!({ "url": wai::http_binding::uri::resolve(b, r) })) + })(), "dash_adjacency" => (|| { let w = edit(inline_or_file(c, input, "wai")?, &input["edit_wai"])?; let k = edit(inline_or_file(c, input, "classical")?, &input["edit_classical"])?; @@ -558,8 +566,10 @@ fn live_outcome(c: &Corpus, input: &Value) -> Result { None => format!("{}/mr", revisions.first().and_then(|r| r["wai"].as_str()).and_then(|w| w.rsplit_once('/')).map_or("", |(d, _)| d)), }; let bound = input["walk_bound"].as_u64().map_or(WALK_BOUND, |b| b as usize); - let mut chain = LiveChain::with_bound(bound); - let mut listed_bindings = Bindings::default(); + // The URL the client fetches every revision from, against which their lines resolve. + let url = input["url"].as_str().unwrap_or(LIVE_URL); + let mut chain = LiveChain::for_playlist(url, bound); + let mut listed_bindings = Bindings::for_playlist(url); let mut revs = Vec::new(); let mut listed: Vec> = Vec::new(); for r in revisions { @@ -586,6 +596,7 @@ fn live_outcome(c: &Corpus, input: &Value) -> Result { Ok(Step::First) => (json!(true), json!(0)), Ok(Step::Chained { walked }) => (json!(true), json!(walked)), Ok(Step::Unchanged) => (json!("unchanged"), json!(0)), + Ok(Step::Reanchored { walked }) => (json!("reanchored"), json!(walked)), Err(e) => (json!(e.code()), Value::Null), }; revs.push(json!({ "adjacent": adjacent, "listing": true, "receipt": "signed", "chained": chained, "walked": walked })); @@ -728,6 +739,30 @@ fn seal_all(c: &mut Corpus, out: &mut BTreeMap>) -> Result<(), S let resequenced = format!("{}{s1}{}", &r3[..at], &r3[at + s0.len()..]); put(c, out, "cases/relist/wai-r3.m3u8", resequenced.clone().into_bytes()); put(c, out, "cases/relist/r3.json", seal(&resequenced, &key, Some(&sealed[5]))?.to_json().into_bytes()); + // Revision 2 relisting part 0 of segment 1201 under another spelling of its alias + // (./720/1201.0.wai, one URL with 720/1201.0.wai): with part 1's object, and with its + // own; each sealed and chained to revision 1. + let dotted = live(c, 2)?.replacen(&p0, &format!("./{}", p1.replacen("1201.1.wai", "1201.0.wai", 1)), 1); + put(c, out, "cases/relist/wai-r2-dot.m3u8", dotted.clone().into_bytes()); + put(c, out, "cases/relist/r2-dot.json", seal(&dotted, &key, Some(&sealed[4]))?.to_json().into_bytes()); + let same = live(c, 2)?.replacen(&p0, &format!("./{p0}"), 1); + put(c, out, "cases/relist/wai-r2-dot-same.m3u8", same.clone().into_bytes()); + put(c, out, "cases/relist/r2-dot-same.json", seal(&same, &key, Some(&sealed[4]))?.to_json().into_bytes()); + // Revision 3 withdrawing segment 1200, bound to its object, as a gap; chained to + // revision 2. + let at = r3.find("#EXTINF:").ok_or("revision 3 has no segment")?; + let withdrawn = format!("{}#EXT-X-GAP\n{}", &r3[..at], &r3[at..]); + put(c, out, "cases/relist/wai-r3-gap.m3u8", withdrawn.clone().into_bytes()); + put(c, out, "cases/relist/r3-gap.json", seal(&withdrawn, &key, Some(&sealed[5]))?.to_json().into_bytes()); + // The walk's per-parent checks: revision 2's link served with revision 1's receipt + // (another receipt under that name), and a revision 3 whose parent is revision 2 + // changed after it was signed (served under its own receipt hash, which it no longer + // verifies under). + put(c, out, &format!("cases/walk-substituted/mr/{}.json", hex(&sealed[5].receipt_hash())), sealed[4].to_json().into_bytes()); + let mut altered = sealed[5].clone(); + altered.viewers_served = 2; + publish_by_hash(c, out, "cases/walk-invalid/mr", &altered); + put(c, out, "cases/walk-invalid/r3.json", seal(&live(c, 3)?, &key, Some(&altered))?.to_json().into_bytes()); Ok(()) } @@ -1050,6 +1085,18 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { v.push(dash(name, ew, ec, code)); } + // URLs (§11.5): RFC 3986 §5.4's examples against its base, and spellings of a live + // alias against a playlist's URL. + for (i, r) in ["g:h", "g", "./g", "g/", "/g", "//g", "?y", "g?y", "#s", "g#s", "g?y#s", ";x", "g;x", "g;x?y#s", "", ".", "./", "..", "../", + "../g", "../..", "../../", "../../g", "../../../g", "../../../../g", "/./g", "/../g", "g.", ".g", "g..", "..g", "./../g", "./g/.", + "g/./h", "g/../h", "g;x=1/./y", "g;x=1/../y", "g?y/./x", "g?y/../x", "g#s/./x", "g#s/../x", "http:g"].iter().enumerate() { + v.push(case(format!("uri_resolve_rfc_{i:02}"), "uri_resolve", json!({ "base": "http://a/b/c/d;p?q", "reference": r }))); + } + for (i, r) in ["720/1201.0.wai#cid=00", "./720/1201.0.wai", "720/x/../1201.0.wai", "%37%32%30/1201.0.wai", "720/1201%2e0.wai", "720/%2E/1201.0.wai", + "720%2F1201.0.wai", "HTTPS://ORIGIN.EXAMPLE/live/720/1201.0.wai", "//Origin.Example:443/live/720/1201.0.wai", "720/1201.0.wai?x=%7e%41", + "../../../720/1201.0.wai", "a:b/c", "./a:b", "user@x", "//u%41@Host.example/p"].iter().enumerate() { + v.push(case(format!("uri_resolve_alias_{i:02}"), "uri_resolve", json!({ "base": LIVE_URL, "reference": r }))); + } // The classical master, published with at most the session-data line (§11.1). let un = |edits: Value| json!({ "original": "src/players/master.m3u8", "published": "players/master.m3u8", "edit_published": edits }); v.push(case("unchanged_players", "unchanged", un(json!([])))); @@ -1177,7 +1224,17 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { v.push(case("live_edge_unchanged_reload", "live", json!({ "revisions": revs_of(&[0, 1, 1, 2, 2, 3])?, "fetches": few }))); v.push(case("live_edge_skip", "live", json!({ "revisions": revs_of(&[0, 2, 3])?, "fetches": few }))); v.push(case("live_edge_skip_two", "live", json!({ "revisions": revs_of(&[0, 3])?, "fetches": few }))); - v.push(case("live_edge_walk_exceeded", "live", json!({ "revisions": revs_of(&[0, 3])?, "walk_bound": 1, "fetches": few }))); + // Past the bound, a revision ahead of the head re-anchors the chain; one that is not + // ahead is refused; a stale reload changes nothing. + v.push(case("live_edge_reanchored", "live", json!({ "revisions": revs_of(&[0, 3])?, "walk_bound": 1, "fetches": few }))); + v.push(case("live_edge_reanchored_then_reloaded", "live", json!({ "revisions": revs_of(&[0, 3, 3, 3])?, "walk_bound": 1, "fetches": few }))); + v.push(case("live_edge_reanchored_skipping", "live", json!({ "revisions": revs_of(&[0, 2])?, "walk_bound": 0, "fetches": few }))); + v.push(case("live_edge_reanchor_not_backwards", "live", json!({ "revisions": revs_of(&[0, 3, 1])?, "walk_bound": 0, "fetches": few }))); + // The walk's checks of each parent: another receipt under the link's name, and one + // that does not verify. + v.push(case("live_edge_parent_substituted", "live", json!({ "revisions": revs_of(&[0, 3])?, "parents": "cases/walk-substituted/mr", "fetches": few }))); + let invalid_parent = vec![rev(0)?, with(rev(3)?, json!({ "receipt": "cases/walk-invalid/r3.json" }))]; + v.push(case("live_edge_parent_invalid", "live", json!({ "revisions": invalid_parent, "parents": "cases/walk-invalid/mr", "fetches": few }))); v.push(case("live_edge_walk_at_bound", "live", json!({ "revisions": revs_of(&[0, 3])?, "walk_bound": 2, "fetches": few }))); v.push(case("live_edge_parent_missing", "live", json!({ "revisions": revs_of(&[0, 3])?, "parents": "cases/no-receipts", "fetches": few }))); // Chained by hash, signed by another key: as the next revision, and as a parent walked. @@ -1195,6 +1252,15 @@ pub(super) fn cases(c: &Corpus) -> Result, String> { v.push(case("live_relisted_alias_walked", "live", json!({ "revisions": relisted_walked, "parents": "cases/relist/mr", "fetches": relist_fetches }))); let resequenced = vec![rev(0)?, rev(1)?, rev(2)?, with(rev(3)?, json!({ "wai": "cases/relist/wai-r3.m3u8", "receipt": "cases/relist/r3.json" }))]; v.push(case("live_resequenced", "live", json!({ "revisions": resequenced, "fetches": few }))); + // An alias is bound by the URL it names, not its spelling: ./720/1201.0.wai with + // another object is refused; with its own, it is the same binding. + let dotted = vec![rev(0)?, rev(1)?, with(rev(2)?, json!({ "wai": "cases/relist/wai-r2-dot.m3u8", "receipt": "cases/relist/r2-dot.json" }))]; + v.push(case("live_relisted_dot_alias", "live", json!({ "revisions": dotted, "fetches": relist_fetches }))); + let same = vec![rev(0)?, rev(1)?, with(rev(2)?, json!({ "wai": "cases/relist/wai-r2-dot-same.m3u8", "receipt": "cases/relist/r2-dot-same.json" }))]; + v.push(case("live_dot_alias_same_object", "live", json!({ "revisions": same, "fetches": relist_fetches }))); + // A listed segment is not withdrawn as a gap. + let withdrawn = vec![rev(0)?, rev(1)?, rev(2)?, with(rev(3)?, json!({ "wai": "cases/relist/wai-r3-gap.m3u8", "receipt": "cases/relist/r3-gap.json" }))]; + v.push(case("live_gap_withdraws_a_segment", "live", json!({ "revisions": withdrawn, "fetches": few }))); // Adaptation (Appendix F): pinned states per policy, seeded; with and without a // budget; and the budget's every outcome. @@ -1711,6 +1777,10 @@ pub(super) fn fuzz_cases(c: &Corpus, s: &mut u64, i: usize) -> Result>().join(if next(s) % 2 == 0 { "/" } else { "" }); + let base = pick(s, &[LIVE_URL, "http://a/b/c/d;p?q", "live/wai.m3u8", "", "HTTP://A.example", "x:/a/b", "//h/p/q"]); + v.push((format!("fuzz_uri_resolve_{i}"), "uri_resolve", json!({ "base": base, "reference": reference }))); let (w, k) = dash_pair(c, s)?; v.push((format!("fuzz_dash_adjacency_{i}"), "dash_adjacency", json!({ "wai_text": w, "classical_text": k }))); Ok(v) diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs index 82cd09e17..1852df359 100644 --- a/wai/wai-rs/src/http_binding/hls.rs +++ b/wai/wai-rs/src/http_binding/hls.rs @@ -912,31 +912,50 @@ fn uri_cid(line: &str) -> Option<(String, [u8; 32])> { Some((base.to_string(), cid)) } -/// What the revisions of a live playlist bound so far (§5.2, §11.5): each URI (without -/// its fragment) to the cid listed with it, and each media sequence number to its -/// segment's cid. A URI or sequence number keeps its cid: an alias names the same bytes -/// for its lifetime, and every object is immutable from the moment it is listed. +/// How many revisions a binding outlives the last one that listed it (§11.5): a client +/// keeps a URI's or a sequence number's cid while one of the last 256 revisions it took +/// lists it. A rebinding of something no revision in that span listed is not detected. +/// cbindgen:ignore +pub const RETENTION: u64 = 256; + +/// What the revisions of a live playlist bound (§5.2, §11.5): each URL a line names (the +/// line's URI resolved against the playlist's URL, without its fragment) to the cid listed +/// with it, and each media sequence number to its segment's cid, each with the revision +/// that last listed it. A URL or sequence number keeps its cid: an alias names the same +/// bytes for its lifetime, and every object is immutable from the moment it is listed. +/// What no revision of the last [`RETENTION`] listed is forgotten. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct Bindings { - uris: std::collections::BTreeMap, - sequences: std::collections::BTreeMap, + base: String, + uris: std::collections::BTreeMap, + sequences: std::collections::BTreeMap, + revision: u64, } impl Bindings { - /// Add `p`'s listings: every part, layer and segment line that names a cid, in the - /// order [`listed_cid`] reads them, and each segment that is not a gap by its media - /// sequence number (a gap offers no object there). Refused - /// (`playlist_listing_rebound`), with nothing added, when a URI or a sequence number - /// is already bound to another cid. + /// Bindings of the playlist at `url`, against which its lines resolve. + pub fn for_playlist(url: &str) -> Bindings { + Bindings { base: url.to_string(), ..Bindings::default() } + } + + /// Add `p`'s listings as one more revision: every part, layer and segment line that + /// names a cid, by the URL it resolves to, in the order [`listed_cid`] reads them, and + /// each segment that is not a gap by its media sequence number. Refused + /// (`playlist_listing_rebound`), with nothing added, when a URL or a sequence number + /// is bound to another cid, or a gap falls on a sequence number bound to an object (a + /// listed segment is not withdrawn). Then what none of the last [`RETENTION`] + /// revisions listed is forgotten. pub fn bind(&mut self, p: &MediaPlaylist) -> Result<(), PlaylistError> { let mut next = self.clone(); + let rev = next.revision + 1; + let base = self.base.clone(); let mut uri = |line: &str| -> Result<(), PlaylistError> { - let Some((base, cid)) = uri_cid(line) else { return Ok(()) }; - match next.uris.get(&base) { - Some(held) if *held != cid => Err(PlaylistError::Rebound(format!("{base} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))), - Some(_) => Ok(()), - None => { - next.uris.insert(base, cid); + let Some((_, cid)) = uri_cid(line) else { return Ok(()) }; + let key = super::uri::resolve(&base, line); + match next.uris.get(&key) { + Some((held, _)) if *held != cid => Err(PlaylistError::Rebound(format!("{key} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))), + _ => { + next.uris.insert(key, (cid, rev)); Ok(()) } } @@ -961,25 +980,43 @@ impl Bindings { extra(o, &mut uri)?; } for (i, s) in p.segments.iter().enumerate() { - let Some((_, cid)) = uri_cid(&s.uri).filter(|_| !s.extra.gap) else { continue }; let n = p.media_sequence.checked_add(i as u64).ok_or_else(|| malformed("the media sequence number passes 2^64"))?; + if s.extra.gap { + if let Some((held, _)) = next.sequences.get(&n) { + return Err(PlaylistError::Rebound(format!("media sequence {n} was listed with {}, and is now a gap", hex(held)))); + } + continue; + } + let Some((_, cid)) = uri_cid(&s.uri) else { continue }; match next.sequences.get(&n) { - Some(held) if *held != cid => { + Some((held, _)) if *held != cid => { return Err(PlaylistError::Rebound(format!("media sequence {n} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))); } - Some(_) => {} - None => { - next.sequences.insert(n, cid); + _ => { + next.sequences.insert(n, (cid, rev)); } } } + next.uris.retain(|_, (_, last)| *last + RETENTION > rev); + next.sequences.retain(|_, (_, last)| *last + RETENTION > rev); + next.revision = rev; *self = next; Ok(()) } - /// The cid bound to `uri` (without its fragment), if any. + /// The cid bound to the URL `uri` resolves to, if any. pub fn cid_of(&self, uri: &str) -> Option<[u8; 32]> { - self.uris.get(uri).copied() + self.uris.get(&super::uri::resolve(&self.base, uri)).map(|(c, _)| *c) + } + + /// How many URLs and sequence numbers are bound. + pub fn len(&self) -> usize { + self.uris.len() + self.sequences.len() + } + + /// Whether nothing is bound. + pub fn is_empty(&self) -> bool { + self.len() == 0 } } diff --git a/wai/wai-rs/src/http_binding/mod.rs b/wai/wai-rs/src/http_binding/mod.rs index af3fbc5e9..975ad5cec 100644 --- a/wai/wai-rs/src/http_binding/mod.rs +++ b/wai/wai-rs/src/http_binding/mod.rs @@ -34,6 +34,7 @@ pub mod playlist; #[cfg(feature = "video_manifest")] pub mod playlist_receipt; pub mod session; +pub mod uri; #[cfg(test)] mod tests; diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs index d5014d158..6fb6141d8 100644 --- a/wai/wai-rs/src/http_binding/playlist_receipt.rs +++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs @@ -126,9 +126,10 @@ pub fn check_playlist(playlist: &[u8], receipt: &[u8], name: Option<&str>) -> Re Ok(r) } -/// How many parent receipts a client fetches for one revision, at most (§8.6): a -/// revision further from the held head than this is refused -/// (`playlist_receipt_walk_exceeded`). A client MAY hold a lower bound. +/// How many parent receipts a client fetches for one revision, at most (§8.6). A +/// revision further from the held head than this re-anchors the chain when it is ahead +/// of the head, and is refused otherwise (`playlist_receipt_walk_exceeded`). A client +/// MAY hold a lower bound. /// cbindgen:ignore pub const WALK_BOUND: usize = 64; @@ -142,6 +143,10 @@ pub enum Step { /// The receipt chains to the head through `walked` parents fetched by their links /// (0: its parent is the head), and becomes the head. Chained { walked: usize }, + /// The head is further back than the bound, and the receipt is ahead of it: the + /// chain re-anchors at the receipt, keeping what it bound. The `walked` parents and + /// any revision between them and the head are not signed (`reanchored`). + Reanchored { walked: usize }, } /// A client's hold on a live playlist's receipts (§8.6, §11.6): the head (the latest @@ -171,6 +176,12 @@ impl LiveChain { LiveChain { head: None, bindings: Bindings::default(), bound } } + /// A chain of the playlist at `url` (against which its receipts' lines resolve), + /// walking at most `bound` parents for one revision. + pub fn for_playlist(url: &str, bound: usize) -> LiveChain { + LiveChain { head: None, bindings: Bindings::for_playlist(url), bound } + } + /// The receipt held as the head. pub fn head(&self) -> Option<&ManifestReceipt> { self.head.as_ref() @@ -188,8 +199,10 @@ impl LiveChain { /// - `cur` under another key than the head (`playlist_receipt_unchained`); /// - the walk from `cur` back by `parent_receipt_hash` reaches a receipt with no /// parent before the head (`playlist_receipt_unchained`); needs more than the - /// bound (`playlist_receipt_walk_exceeded`); needs a parent that is not served - /// (`playlist_receipt_parent_missing`); or meets one that does not read + /// bound when `cur` is not ahead of the head (`playlist_receipt_walk_exceeded`; + /// ahead, the chain re-anchors at `cur`, [`Step::Reanchored`]); needs a parent + /// that is not served (`playlist_receipt_parent_missing`); or meets one that does + /// not read /// (`playlist_receipt_malformed`), whose receipt hash is not the link it was /// fetched by (`receipt_name_mismatch`), that does not verify /// (`playlist_receipt_invalid`) or that is under another key @@ -220,7 +233,17 @@ impl LiveChain { Some(l) => l, }; if walked.len() == self.bound { - return Err(ReceiptError::WalkExceeded); + // §8.6: past the bound, a revision ahead of the head (more segments + // complete) re-anchors the chain, its bindings kept; one that is not + // ahead is refused, so a stale or older revision never rolls it back. + if last_segment(cur) <= last_segment(head) { + return Err(ReceiptError::WalkExceeded); + } + let mut b = self.bindings.clone(); + bind(&mut b, cur)?; + self.bindings = b; + self.head = Some(cur.clone()); + return Ok(Step::Reanchored { walked: walked.len() }); } let bytes = parent(&l).ok_or(ReceiptError::ParentMissing)?; let text = std::str::from_utf8(&bytes).map_err(|_| ReceiptError::Malformed("not UTF-8".into()))?; @@ -247,6 +270,12 @@ impl LiveChain { } } +/// The media sequence number after the last complete segment `r` lists: how far its +/// playlist has advanced. +fn last_segment(r: &ManifestReceipt) -> u128 { + u128::from(r.render.as_ref().map_or(0, |x| x.media_sequence)) + r.segments.len() as u128 +} + /// Bind what `r`'s rendering lists. fn bind(b: &mut Bindings, r: &ManifestReceipt) -> Result<(), ReceiptError> { let p = parse_media_playlist(&r.rendered()).map_err(|e| ReceiptError::Malformed(e.to_string()))?; diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index 67b872989..f26f82ba4 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -521,7 +521,7 @@ mod receipts { let before = held(&at_a); let mut tight = LiveChain::with_bound(0); tight.advance(&a, fetch).unwrap(); - assert_eq!(tight.advance(&c, fetch).unwrap_err(), ReceiptError::WalkExceeded); + assert_eq!(tight.advance(&c, fetch).unwrap_err(), ReceiptError::WalkExceeded, "past the bound, and not ahead of the head"); assert_eq!(at_a.advance(&c, |_| None).unwrap_err(), ReceiptError::ParentMissing); assert_eq!(at_a.advance(&c, |_| Some(a.to_json().into_bytes())).unwrap_err(), ReceiptError::NameMismatch); assert_eq!(at_a.advance(&c, |_| Some(b"{".to_vec())).unwrap_err().code(), "playlist_receipt_malformed"); @@ -547,24 +547,112 @@ mod receipts { let mut listed = Bindings::default(); listed.bind(&parse_media_playlist(&rev(1, open(2))).unwrap()).unwrap(); assert_eq!(listed.bind(&parse_media_playlist(&rev(2, relisted)).unwrap()).unwrap_err().code(), "playlist_listing_rebound"); + // Past the bound, a revision ahead of the head (a segment more) re-anchors the + // chain, keeping what it bound: a rebinding there is still refused; and a revision + // not ahead never re-anchors it backwards. + let parts = |seq: u64, n: u64| (0..n).map(|i| format!("#EXT-X-PART:DURATION=0.5,URI=\"c/{seq}.{i}.m4s\"\n")).collect::(); + let classical_ahead = format!("#EXTM3U\n#EXT-X-VERSION:9\n#EXT-X-TARGETDURATION:2\n#EXT-X-PART-INF:PART-TARGET=0.5\n#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.5\n\ + #EXT-X-MEDIA-SEQUENCE:1200\n#EXT-X-PROGRAM-DATE-TIME:2026-10-01T12:00:00.000Z\n{}#EXTINF:2.00002,\nc/1200.m4s\n{}#EXTINF:2,\nc/1201.m4s\n{}\ + #EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"c/1202.2.m4s\"\n", parts(1200, 4), parts(1201, 4), parts(1202, 2)); + let ll_ahead = parse_media_playlist(&classical_ahead).unwrap(); + let (mut s0, mut s1) = (seg(1, 2000), seg(2, 2000)); + s0.parts = (0..4).map(|i| wpart(1200, i, 500)).collect(); + s1.parts = (0..4).map(|i| wpart(1201, i, 500)).collect(); + let open = WaiOpen { parts: (0..2).map(|i| wpart(1202, i, 500)).collect(), preload_hint: Some("live/1202.2.wai".into()) }; + let t = wai_media_playlist(&ll_ahead, &[s0, s1], Some(&open), "objects/", false).unwrap().3; + let d = seal_playlist(&t, &key(), "k", "live", g(), Some(&c)).unwrap(); + let mut far = LiveChain::with_bound(0); + far.advance(&a, fetch).unwrap(); + assert_eq!(far.advance(&d, fetch).unwrap(), Step::Reanchored { walked: 0 }); + assert_eq!(far.head(), Some(&d)); + assert_eq!(far.bindings().cid_of("live/b/1201.0.wai"), None, "the skipped revisions are not bound"); + assert_eq!(far.bindings().cid_of("live/1200.0.wai"), Some(cid(0x80)), "what the chain bound is kept"); + assert_eq!(far.advance(&d, fetch).unwrap(), Step::Unchanged); + assert_eq!(far.advance(&c, fetch).unwrap_err(), ReceiptError::WalkExceeded, "never backwards"); + // A re-anchoring revision that rebinds what the chain kept is refused. + let mut s0x = seg(1, 2000); + s0x.parts = (0..4).map(|i| wpart(1200, i, 500)).collect(); + s0x.parts[0].cid = cid(0x99); + let (mut s1x, openx) = (seg(2, 2000), WaiOpen { parts: (0..2).map(|i| wpart(1202, i, 500)).collect(), preload_hint: Some("live/1202.2.wai".into()) }); + s1x.parts = (0..4).map(|i| wpart(1201, i, 500)).collect(); + let tx = wai_media_playlist(&ll_ahead, &[s0x, s1x], Some(&openx), "objects/", false).unwrap().3; + let dx = seal_playlist(&tx, &key(), "k", "live", g(), Some(&c)).unwrap(); + let mut farx = LiveChain::with_bound(0); + farx.advance(&a, fetch).unwrap(); + assert_eq!(farx.advance(&dx, fetch).unwrap_err(), ReceiptError::Rebound); // An older revision after a newer one walks back past the start and does not chain. assert_eq!(check_chain(&[a.clone(), c.clone(), b.clone()]).unwrap_err(), (2, ReceiptError::Unchained)); check_chain(&[a.clone(), a.clone(), b.clone(), c.clone(), c]).unwrap(); } /// A media sequence number keeps its segment's cid across revisions; a gap binds - /// none. + /// none, and does not withdraw one already bound. #[test] fn a_sequence_number_keeps_its_cid() { let vod = parse_media_playlist(CLASSICAL_VOD).unwrap(); let text = |segs: &[WaiSegment]| wai_media_playlist(&vod, segs, None, "../objects/", true).unwrap().3; - let mut b = Bindings::default(); - b.bind(&parse_media_playlist(&text(&[seg(1, 640), seg(2, 640), seg(3, 640)])).unwrap()).unwrap(); let mut gap = seg(9, 640); gap.gap = true; - b.clone().bind(&parse_media_playlist(&text(&[seg(1, 640), gap, seg(3, 640)])).unwrap()).unwrap(); + let mut b = Bindings::default(); + b.bind(&parse_media_playlist(&text(&[seg(1, 640), gap.clone(), seg(3, 640)])).unwrap()).unwrap(); + b.bind(&parse_media_playlist(&text(&[seg(1, 640), seg(2, 640), seg(3, 640)])).unwrap()).unwrap(); + assert_eq!(b.clone().bind(&parse_media_playlist(&text(&[seg(1, 640), gap, seg(3, 640)])).unwrap()).unwrap_err().code(), "playlist_listing_rebound", + "a segment listed is not withdrawn as a gap"); assert_eq!(b.bind(&parse_media_playlist(&text(&[seg(1, 640), seg(9, 640), seg(3, 640)])).unwrap()).unwrap_err().code(), "playlist_listing_rebound"); } + + /// An alias is bound by the URL it names: another spelling of it is the same + /// binding, refused with another cid and taken with its own. + #[test] + fn an_alias_is_bound_by_its_url() { + let ll = parse_media_playlist(CLASSICAL_LL).unwrap(); + let rev = |alias: &str, c: u8| { + let mut q = wpart(1201, 0, 500); + q.alias = alias.into(); + q.cid = cid(c); + let open = WaiOpen { parts: vec![q, wpart(1201, 1, 500)], preload_hint: None }; + let mut s0 = seg(1, 2000); + s0.parts = (0..4).map(|i| wpart(1200, i, 500)).collect(); + parse_media_playlist(&wai_media_playlist(&ll, &[s0], Some(&open), "objects/", false).unwrap().3).unwrap() + }; + let mut b = Bindings::for_playlist("https://origin.example/live/wai.m3u8"); + b.bind(&rev("live/1201.0.wai", 0x80)).unwrap(); + for spelling in ["./live/1201.0.wai", "live/x/../1201.0.wai", "/live/live/1201.0.wai", "HTTPS://Origin.Example/live/live/1201.0.wai", "live/1201%2E0.wai"] { + assert_eq!(b.clone().bind(&rev(spelling, 0x99)).unwrap_err().code(), "playlist_listing_rebound", "{spelling}"); + b.clone().bind(&rev(spelling, 0x80)).unwrap(); + } + assert_eq!(b.cid_of("./live/1201.0.wai"), Some(cid(0x80))); + b.bind(&rev("live/1201.0.wai?x", 0x99)).unwrap(); + } + + /// A binding is kept while one of the last RETENTION revisions lists it; a + /// rebinding of what none of them listed is not detected, and what is bound stays + /// bounded however long the playlist runs. + #[test] + fn bindings_are_retained_for_a_span() { + let vod = parse_media_playlist(CLASSICAL_VOD).unwrap(); + let text = |segs: &[WaiSegment]| parse_media_playlist(&wai_media_playlist(&vod, segs, None, "../objects/", true).unwrap().3).unwrap(); + let first = text(&[seg(1, 640), seg(2, 640), seg(3, 640)]); + let other = text(&[seg(4, 640), seg(5, 640), seg(6, 640)]); + let mut b = Bindings::default(); + b.bind(&first).unwrap(); + let held = b.len(); + for _ in 1..RETENTION { + let mut shifted = other.clone(); + shifted.media_sequence = 1000; + b.bind(&shifted).unwrap(); + } + // The last revision of the span: the first one's listings are still held. + let mut rebind = first.clone(); + rebind.segments[0].uri = rebind.segments[1].uri.clone(); + assert_eq!(b.clone().bind(&rebind).unwrap_err().code(), "playlist_listing_rebound"); + let mut shifted = other.clone(); + shifted.media_sequence = 1000; + b.bind(&shifted).unwrap(); + // Past the span: forgotten, so the same rebinding is no longer detected. + b.clone().bind(&rebind).unwrap(); + assert!(b.len() <= 2 * held, "{} held", b.len()); + } } diff --git a/wai/wai-rs/src/http_binding/uri.rs b/wai/wai-rs/src/http_binding/uri.rs new file mode 100644 index 000000000..0f23cf969 --- /dev/null +++ b/wai/wai-rs/src/http_binding/uri.rs @@ -0,0 +1,201 @@ +//! A URI reference resolved against a base and normalized (RFC 3986 §5.2, §6.2.2), so +//! that two spellings of one URL are one key (http-delivery §11.5): what a playlist lists +//! is bound by the URL it names, not by how a line happens to write it. +//! +//! Resolution is §5.2.2's strict algorithm, with §5.2.3's merge and §5.2.4's removal of +//! dot segments. Normalization is §6.2.2's: the scheme and the host in lower case, the +//! hexadecimal digits of a percent-encoding in upper case, a percent-encoded unreserved +//! character decoded, and dot segments removed again from the result. The fragment is +//! dropped: it names nothing the server holds. A base without a scheme is used as it is, +//! so that relative names still resolve relative to each other. + +/// The five components of a reference (RFC 3986 Appendix B), each absent or present +/// (possibly empty). +#[derive(Clone, Debug, Default, PartialEq, Eq)] +struct Parts { + scheme: Option, + authority: Option, + path: String, + query: Option, +} + +/// Split `s` as Appendix B's expression does; the fragment is dropped. +fn split(s: &str) -> Parts { + let s = s.split_once('#').map_or(s, |(a, _)| a); + let mut rest = s; + let mut p = Parts::default(); + if let Some(i) = rest.find([':', '/', '?']) { + if rest.as_bytes()[i] == b':' && i > 0 { + p.scheme = Some(rest[..i].to_string()); + rest = &rest[i + 1..]; + } + } + if let Some(r) = rest.strip_prefix("//") { + let end = r.find(['/', '?']).unwrap_or(r.len()); + p.authority = Some(r[..end].to_string()); + rest = &r[end..]; + } + match rest.split_once('?') { + Some((path, q)) => { + p.path = path.to_string(); + p.query = Some(q.to_string()); + } + None => p.path = rest.to_string(), + } + p +} + +/// §5.2.4: remove `.` and `..` segments. +fn remove_dot_segments(path: &str) -> String { + let mut input = path; + let mut out = String::with_capacity(path.len()); + while !input.is_empty() { + if let Some(r) = input.strip_prefix("../") { + input = r; + } else if let Some(r) = input.strip_prefix("./") { + input = r; + } else if input.starts_with("/./") { + input = &input[2..]; + } else if input == "/." { + input = "/"; + } else if input.starts_with("/../") || input == "/.." { + input = if input == "/.." { "/" } else { &input[3..] }; + match out.rfind('/') { + Some(i) => out.truncate(i), + None => out.clear(), + } + } else if input == "." || input == ".." { + input = ""; + } else { + let start = usize::from(input.starts_with('/')); + let end = input[start..].find('/').map_or(input.len(), |i| i + start); + out.push_str(&input[..end]); + input = &input[end..]; + } + } + out +} + +/// §5.2.3: merge a relative path with the base's. +fn merge(base: &Parts, path: &str) -> String { + if base.authority.is_some() && base.path.is_empty() { + format!("/{path}") + } else { + match base.path.rfind('/') { + Some(i) => format!("{}{path}", &base.path[..=i]), + None => path.to_string(), + } + } +} + +/// §6.2.2.2: hexadecimal digits of a percent-encoding in upper case, and a +/// percent-encoded unreserved character decoded. +fn normalize_percent(s: &str) -> String { + let b = s.as_bytes(); + let mut out = String::with_capacity(s.len()); + let mut i = 0; + while i < b.len() { + if b[i] == b'%' && i + 2 < b.len() && b[i + 1].is_ascii_hexdigit() && b[i + 2].is_ascii_hexdigit() { + let v = u8::from_str_radix(&s[i + 1..i + 3], 16).expect("two hexadecimal digits"); + if v.is_ascii_alphanumeric() || b"-._~".contains(&v) { + out.push(v as char); + } else { + out.push('%'); + out.push_str(&s[i + 1..i + 3].to_ascii_uppercase()); + } + i += 3; + } else { + let c = s[i..].chars().next().expect("a character"); + out.push(c); + i += c.len_utf8(); + } + } + out +} + +/// `reference` resolved against `base` (§5.2.2) and normalized (§6.2.2), without its +/// fragment. +pub fn resolve(base: &str, reference: &str) -> String { + let (b, r) = (split(base), split(reference)); + let t = if r.scheme.is_some() { + Parts { scheme: r.scheme, authority: r.authority, path: remove_dot_segments(&r.path), query: r.query } + } else if r.authority.is_some() { + Parts { scheme: b.scheme, authority: r.authority, path: remove_dot_segments(&r.path), query: r.query } + } else if r.path.is_empty() { + Parts { scheme: b.scheme, authority: b.authority, path: b.path, query: r.query.or(b.query) } + } else if r.path.starts_with('/') { + Parts { scheme: b.scheme, authority: b.authority, path: remove_dot_segments(&r.path), query: r.query } + } else { + let merged = merge(&b, &r.path); + Parts { scheme: b.scheme, authority: b.authority, path: remove_dot_segments(&merged), query: r.query } + }; + let mut out = String::new(); + if let Some(s) = &t.scheme { + out.push_str(&s.to_ascii_lowercase()); + out.push(':'); + } + if let Some(a) = &t.authority { + out.push_str("//"); + // The host (after any userinfo) in lower case. + let a = normalize_percent(a); + match a.rfind('@') { + Some(i) => { + out.push_str(&a[..=i]); + out.push_str(&a[i + 1..].to_ascii_lowercase()); + } + None => out.push_str(&a.to_ascii_lowercase()), + } + } + out.push_str(&remove_dot_segments(&normalize_percent(&t.path))); + if let Some(q) = &t.query { + out.push('?'); + out.push_str(&normalize_percent(q)); + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + /// RFC 3986 §5.4.1 and §5.4.2, every example, against its base. + #[test] + fn the_rfc_examples() { + let base = "http://a/b/c/d;p?q"; + for (r, want) in [ + ("g:h", "g:h"), ("g", "http://a/b/c/g"), ("./g", "http://a/b/c/g"), ("g/", "http://a/b/c/g/"), ("/g", "http://a/g"), + ("//g", "http://g"), ("?y", "http://a/b/c/d;p?y"), ("g?y", "http://a/b/c/g?y"), ("#s", "http://a/b/c/d;p?q"), + ("g#s", "http://a/b/c/g"), ("g?y#s", "http://a/b/c/g?y"), (";x", "http://a/b/c/;x"), ("g;x", "http://a/b/c/g;x"), + ("g;x?y#s", "http://a/b/c/g;x?y"), ("", "http://a/b/c/d;p?q"), (".", "http://a/b/c/"), ("./", "http://a/b/c/"), + ("..", "http://a/b/"), ("../", "http://a/b/"), ("../g", "http://a/b/g"), ("../..", "http://a/"), ("../../", "http://a/"), + ("../../g", "http://a/g"), + ("../../../g", "http://a/g"), ("../../../../g", "http://a/g"), ("/./g", "http://a/g"), ("/../g", "http://a/g"), + ("g.", "http://a/b/c/g."), (".g", "http://a/b/c/.g"), ("g..", "http://a/b/c/g.."), ("..g", "http://a/b/c/..g"), + ("./../g", "http://a/b/g"), ("./g/.", "http://a/b/c/g/"), ("g/./h", "http://a/b/c/g/h"), ("g/../h", "http://a/b/c/h"), + ("g;x=1/./y", "http://a/b/c/g;x=1/y"), ("g;x=1/../y", "http://a/b/c/y"), ("g?y/./x", "http://a/b/c/g?y/./x"), + ("g?y/../x", "http://a/b/c/g?y/../x"), ("g#s/./x", "http://a/b/c/g"), ("g#s/../x", "http://a/b/c/g"), ("http:g", "http:g"), + ] { + assert_eq!(resolve(base, r), want, "{r:?}"); + } + } + + /// Two spellings of one URL are one key; another URL is another. + #[test] + fn spellings_of_one_url() { + let base = "https://Origin.Example/live/wai-720.m3u8"; + let key = resolve(base, "720/1201.0.wai#cid=00"); + assert_eq!(key, "https://origin.example/live/720/1201.0.wai"); + for same in ["./720/1201.0.wai", "../live/720/1201.0.wai", "/live/720/1201.0.wai", "720/./1201.0.wai", "720/x/../1201.0.wai", + "%37%32%30/1201.0.wai", "HTTPS://ORIGIN.EXAMPLE/live/720/1201.0.wai", "//origin.example/live/720/1201.0.wai", + "720/1201%2E0.wai", "720/%2E/1201.0.wai"] { + assert_eq!(resolve(base, same), key, "{same:?}"); + } + for other in ["720/1201.1.wai", "720/1201.0.wai?x", "720/1201%2e0.WAI", "../720/1201.0.wai", "720%2F1201.0.wai"] { + assert_ne!(resolve(base, other), key, "{other:?}"); + } + assert_eq!(resolve(base, "a%2fb%7e"), "https://origin.example/live/a%2Fb~"); + // A base with no scheme: names resolve relative to each other. + assert_eq!(resolve("live/wai-720.m3u8", "./720/a.wai"), resolve("live/wai-720.m3u8", "720/a.wai")); + assert_eq!(resolve("", "./720/a.wai"), "720/a.wai"); + } +} -- 2.54.0 (Apple Git-157) From fa8042e1ce336b97acb6e591b3cf3ca9060d56aa Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 14:22:32 -0400 Subject: [PATCH 52/55] hls: the retention test holds an alias's span apart from any sequence number Co-Authored-By: Claude Opus 5.5 --- .../src/http_binding/tests_playlists.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/wai/wai-rs/src/http_binding/tests_playlists.rs b/wai/wai-rs/src/http_binding/tests_playlists.rs index f26f82ba4..c6e315bb4 100644 --- a/wai/wai-rs/src/http_binding/tests_playlists.rs +++ b/wai/wai-rs/src/http_binding/tests_playlists.rs @@ -652,6 +652,25 @@ mod receipts { // Past the span: forgotten, so the same rebinding is no longer detected. b.clone().bind(&rebind).unwrap(); assert!(b.len() <= 2 * held, "{} held", b.len()); + // The same span for an alias, apart from any sequence number. + let ll = parse_media_playlist(CLASSICAL_LL).unwrap(); + let mut s0 = seg(1, 2000); + s0.parts = (0..4).map(|i| wpart(1200, i, 500)).collect(); + let open = WaiOpen { parts: (0..2).map(|i| wpart(1201, i, 500)).collect(), preload_hint: None }; + let live = parse_media_playlist(&wai_media_playlist(&ll, &[s0], Some(&open), "objects/", false).unwrap().3).unwrap(); + let mut a = Bindings::default(); + a.bind(&live).unwrap(); + let mut relisted = live.clone(); + let line = relisted.open.as_ref().unwrap().parts[0].uri.clone(); + relisted.open.as_mut().unwrap().parts[0].uri = line.replace(&super::super::hex(&cid(0x80)), &super::super::hex(&cid(0x99))); + relisted.segments.clear(); + let unrelated = text(&[seg(4, 640), seg(5, 640), seg(6, 640)]); + for _ in 1..RETENTION { + a.bind(&unrelated).unwrap(); + } + assert_eq!(a.clone().bind(&relisted).unwrap_err().code(), "playlist_listing_rebound", "the alias is still held"); + a.bind(&unrelated).unwrap(); + a.bind(&relisted).unwrap(); } } -- 2.54.0 (Apple Git-157) From c68f6988830c541a3df899f1b3afb4bb0fbcec60 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 14:42:56 -0400 Subject: [PATCH 53/55] http-conformance: falsify re-anchoring, URL-keyed bindings, the gap rule, the walk's per-parent checks and URL resolution; chain and rebinding patches against the new code; Python mutations and a retention self-test Co-Authored-By: Claude Opus 5.5 --- .../falsify/chain-parent-unchecked.patch | 6 ++--- .../falsify/chain-walk-unbounded.patch | 14 +++++------ .../falsify/gap-withdraws.patch | 15 +++++++++++ .../falsify/reanchor-backwards.patch | 15 +++++++++++ .../falsify/rebind-sequence.patch | 14 +++++------ .../falsify/rebind-spelling.patch | 15 +++++++++++ wai/http-conformance/falsify/rebind-uri.patch | 22 ++++++++-------- wai/http-conformance/falsify/run.sh | 1 + .../falsify/uri-dots-kept.patch | 24 ++++++++++++++++++ .../falsify/uri-percent-kept.patch | 15 +++++++++++ .../falsify/walk-exceeded-wedges.patch | 15 +++++++++++ .../falsify/walk-parent-name-unchecked.patch | 15 +++++++++++ .../falsify/walk-parent-unverified.patch | 15 +++++++++++ wai/http-conformance/http_verify.py | 25 +++++++++++++++++++ 14 files changed, 183 insertions(+), 28 deletions(-) create mode 100644 wai/http-conformance/falsify/gap-withdraws.patch create mode 100644 wai/http-conformance/falsify/reanchor-backwards.patch create mode 100644 wai/http-conformance/falsify/rebind-spelling.patch create mode 100644 wai/http-conformance/falsify/uri-dots-kept.patch create mode 100644 wai/http-conformance/falsify/uri-percent-kept.patch create mode 100644 wai/http-conformance/falsify/walk-exceeded-wedges.patch create mode 100644 wai/http-conformance/falsify/walk-parent-name-unchecked.patch create mode 100644 wai/http-conformance/falsify/walk-parent-unverified.patch diff --git a/wai/http-conformance/falsify/chain-parent-unchecked.patch b/wai/http-conformance/falsify/chain-parent-unchecked.patch index 343c519da..c8d1258a0 100644 --- a/wai/http-conformance/falsify/chain-parent-unchecked.patch +++ b/wai/http-conformance/falsify/chain-parent-unchecked.patch @@ -1,10 +1,10 @@ # must fail: live_edge_unchained live_edge_skip live_edge_other_key_walked # a live revision's parent links not walked (playlist_receipt::LiveChain::advance) diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs -index 6666c22a..1df1188a 100644 +index 6fb6141d..db167d4c 100644 --- a/wai/wai-rs/src/http_binding/playlist_receipt.rs +++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs -@@ -214,9 +214,9 @@ impl LiveChain { +@@ -228,9 +228,9 @@ impl LiveChain { let mut link = cur.parent_receipt_hash; loop { let l = match link { @@ -15,4 +15,4 @@ index 6666c22a..1df1188a 100644 + Some(_) => break, }; if walked.len() == self.bound { - return Err(ReceiptError::WalkExceeded); + // §8.6: past the bound, a revision ahead of the head (more segments diff --git a/wai/http-conformance/falsify/chain-walk-unbounded.patch b/wai/http-conformance/falsify/chain-walk-unbounded.patch index f2c7b81a1..36410a759 100644 --- a/wai/http-conformance/falsify/chain-walk-unbounded.patch +++ b/wai/http-conformance/falsify/chain-walk-unbounded.patch @@ -1,15 +1,15 @@ -# must fail: live_edge_walk_exceeded +# must fail: live_edge_reanchored live_edge_reanchor_not_backwards # the walk back to the head not bounded (LiveChain::advance) diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs -index 6666c22a..a63e80f3 100644 +index 6fb6141d..b3b67ab0 100644 --- a/wai/wai-rs/src/http_binding/playlist_receipt.rs +++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs -@@ -218,7 +218,7 @@ impl LiveChain { +@@ -232,7 +232,7 @@ impl LiveChain { Some(l) if l == target => break, Some(l) => l, }; - if walked.len() == self.bound { -+ if false { - return Err(ReceiptError::WalkExceeded); - } - let bytes = parent(&l).ok_or(ReceiptError::ParentMissing)?; ++ if false && walked.len() == self.bound { + // §8.6: past the bound, a revision ahead of the head (more segments + // complete) re-anchors the chain, its bindings kept; one that is not + // ahead is refused, so a stale or older revision never rolls it back. diff --git a/wai/http-conformance/falsify/gap-withdraws.patch b/wai/http-conformance/falsify/gap-withdraws.patch new file mode 100644 index 000000000..bbe7a0e5f --- /dev/null +++ b/wai/http-conformance/falsify/gap-withdraws.patch @@ -0,0 +1,15 @@ +# must fail: live_gap_withdraws_a_segment +# a gap at a sequence number bound to an object accepted (hls::Bindings::bind) +diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs +index 1852df35..e38575cd 100644 +--- a/wai/wai-rs/src/http_binding/hls.rs ++++ b/wai/wai-rs/src/http_binding/hls.rs +@@ -982,7 +982,7 @@ impl Bindings { + for (i, s) in p.segments.iter().enumerate() { + let n = p.media_sequence.checked_add(i as u64).ok_or_else(|| malformed("the media sequence number passes 2^64"))?; + if s.extra.gap { +- if let Some((held, _)) = next.sequences.get(&n) { ++ if let Some((held, _)) = next.sequences.get(&n).filter(|_| false) { + return Err(PlaylistError::Rebound(format!("media sequence {n} was listed with {}, and is now a gap", hex(held)))); + } + continue; diff --git a/wai/http-conformance/falsify/reanchor-backwards.patch b/wai/http-conformance/falsify/reanchor-backwards.patch new file mode 100644 index 000000000..91f3bf32f --- /dev/null +++ b/wai/http-conformance/falsify/reanchor-backwards.patch @@ -0,0 +1,15 @@ +# must fail: live_edge_reanchor_not_backwards +# past the bound, any revision re-anchors, an older one too (LiveChain::advance) +diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs +index 6fb6141d..f71d65eb 100644 +--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs ++++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs +@@ -236,7 +236,7 @@ impl LiveChain { + // §8.6: past the bound, a revision ahead of the head (more segments + // complete) re-anchors the chain, its bindings kept; one that is not + // ahead is refused, so a stale or older revision never rolls it back. +- if last_segment(cur) <= last_segment(head) { ++ if false && last_segment(cur) <= last_segment(head) { + return Err(ReceiptError::WalkExceeded); + } + let mut b = self.bindings.clone(); diff --git a/wai/http-conformance/falsify/rebind-sequence.patch b/wai/http-conformance/falsify/rebind-sequence.patch index 19acd3140..0b1dc3448 100644 --- a/wai/http-conformance/falsify/rebind-sequence.patch +++ b/wai/http-conformance/falsify/rebind-sequence.patch @@ -1,15 +1,15 @@ # must fail: live_resequenced # a media sequence number listed again with another object accepted (hls::Bindings::bind) diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs -index 265df4e9..1eba790b 100644 +index 1852df35..a6e2e383 100644 --- a/wai/wai-rs/src/http_binding/hls.rs +++ b/wai/wai-rs/src/http_binding/hls.rs -@@ -936,7 +936,7 @@ impl Bindings { - let Some((_, cid)) = uri_cid(&s.uri).filter(|_| !s.extra.gap) else { continue }; - let n = p.media_sequence.checked_add(i as u64).ok_or_else(|| malformed("the media sequence number passes 2^64"))?; +@@ -989,7 +989,7 @@ impl Bindings { + } + let Some((_, cid)) = uri_cid(&s.uri) else { continue }; match next.sequences.get(&n) { -- Some(held) if *held != cid => { -+ Some(held) if false && *held != cid => { +- Some((held, _)) if *held != cid => { ++ Some((held, _)) if false && *held != cid => { return Err(PlaylistError::Rebound(format!("media sequence {n} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))); } - Some(_) => {} + _ => { diff --git a/wai/http-conformance/falsify/rebind-spelling.patch b/wai/http-conformance/falsify/rebind-spelling.patch new file mode 100644 index 000000000..c7edcc06c --- /dev/null +++ b/wai/http-conformance/falsify/rebind-spelling.patch @@ -0,0 +1,15 @@ +# must fail: live_relisted_dot_alias +# bindings keyed by a line's spelling, not its resolved URL (hls::Bindings::bind) +diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs +index 1852df35..3f2a43aa 100644 +--- a/wai/wai-rs/src/http_binding/hls.rs ++++ b/wai/wai-rs/src/http_binding/hls.rs +@@ -951,7 +951,7 @@ impl Bindings { + let base = self.base.clone(); + let mut uri = |line: &str| -> Result<(), PlaylistError> { + let Some((_, cid)) = uri_cid(line) else { return Ok(()) }; +- let key = super::uri::resolve(&base, line); ++ let key = super::parse_fragment(line).0; + match next.uris.get(&key) { + Some((held, _)) if *held != cid => Err(PlaylistError::Rebound(format!("{key} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))), + _ => { diff --git a/wai/http-conformance/falsify/rebind-uri.patch b/wai/http-conformance/falsify/rebind-uri.patch index c524e1b9c..9d795519c 100644 --- a/wai/http-conformance/falsify/rebind-uri.patch +++ b/wai/http-conformance/falsify/rebind-uri.patch @@ -1,15 +1,15 @@ # must fail: live_relisted_alias live_relisted_alias_walked -# a URI listed again with another cid accepted (hls::Bindings::bind) +# a URL listed again with another cid accepted (hls::Bindings::bind) diff --git a/wai/wai-rs/src/http_binding/hls.rs b/wai/wai-rs/src/http_binding/hls.rs -index 265df4e9..69811c41 100644 +index 1852df35..867bafe5 100644 --- a/wai/wai-rs/src/http_binding/hls.rs +++ b/wai/wai-rs/src/http_binding/hls.rs -@@ -905,7 +905,7 @@ impl Bindings { - let mut uri = |line: &str| -> Result<(), PlaylistError> { - let Some((base, cid)) = uri_cid(line) else { return Ok(()) }; - match next.uris.get(&base) { -- Some(held) if *held != cid => Err(PlaylistError::Rebound(format!("{base} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))), -+ Some(held) if false && *held != cid => Err(PlaylistError::Rebound(format!("{base} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))), - Some(_) => Ok(()), - None => { - next.uris.insert(base, cid); +@@ -953,7 +953,7 @@ impl Bindings { + let Some((_, cid)) = uri_cid(line) else { return Ok(()) }; + let key = super::uri::resolve(&base, line); + match next.uris.get(&key) { +- Some((held, _)) if *held != cid => Err(PlaylistError::Rebound(format!("{key} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))), ++ Some((held, _)) if false && *held != cid => Err(PlaylistError::Rebound(format!("{key} was listed with {}, and is now listed with {}", hex(held), hex(&cid)))), + _ => { + next.uris.insert(key, (cid, rev)); + Ok(()) diff --git a/wai/http-conformance/falsify/run.sh b/wai/http-conformance/falsify/run.sh index 32b401f12..fd0b18100 100755 --- a/wai/http-conformance/falsify/run.sh +++ b/wai/http-conformance/falsify/run.sh @@ -52,6 +52,7 @@ for m in left-half no-root counter-zero floor-as-codecs blake3-iv base-at-done t xml-char-unchecked xml-attribute-twice dash-start-ignored dash-timescale-ignored \ unlabelled-escapes abr-unbounded gap-carries receipt-duplicates receipt-surrogates session-data-anywhere \ dash-r-expanded xml-attributes-scanned dash-timelines-unbounded budget-raises \ + uri-unresolved gap-withdraws reanchor-backwards walk-exceeded-wedges walk-name-unchecked walk-verify-unchecked bindings-unbounded \ file-numbers-unbounded; do if python3 http_verify.py . --mutate "$m" > "$WORK/py-$m.log" 2>&1; then echo "::error::http_verify.py --mutate $m passed"; fail=1 diff --git a/wai/http-conformance/falsify/uri-dots-kept.patch b/wai/http-conformance/falsify/uri-dots-kept.patch new file mode 100644 index 000000000..41160fcf3 --- /dev/null +++ b/wai/http-conformance/falsify/uri-dots-kept.patch @@ -0,0 +1,24 @@ +# must fail: uri_resolve_rfc_02 uri_resolve_rfc_17 live_relisted_dot_alias +# dot segments kept in a resolved URL (http_binding::uri::resolve) +diff --git a/wai/wai-rs/src/http_binding/uri.rs b/wai/wai-rs/src/http_binding/uri.rs +index 0f23cf96..140ead1a 100644 +--- a/wai/wai-rs/src/http_binding/uri.rs ++++ b/wai/wai-rs/src/http_binding/uri.rs +@@ -127,7 +127,7 @@ pub fn resolve(base: &str, reference: &str) -> String { + Parts { scheme: b.scheme, authority: b.authority, path: remove_dot_segments(&r.path), query: r.query } + } else { + let merged = merge(&b, &r.path); +- Parts { scheme: b.scheme, authority: b.authority, path: remove_dot_segments(&merged), query: r.query } ++ Parts { scheme: b.scheme, authority: b.authority, path: merged, query: r.query } + }; + let mut out = String::new(); + if let Some(s) = &t.scheme { +@@ -146,7 +146,7 @@ pub fn resolve(base: &str, reference: &str) -> String { + None => out.push_str(&a.to_ascii_lowercase()), + } + } +- out.push_str(&remove_dot_segments(&normalize_percent(&t.path))); ++ out.push_str(&normalize_percent(&t.path)); + if let Some(q) = &t.query { + out.push('?'); + out.push_str(&normalize_percent(q)); diff --git a/wai/http-conformance/falsify/uri-percent-kept.patch b/wai/http-conformance/falsify/uri-percent-kept.patch new file mode 100644 index 000000000..e9e50cd59 --- /dev/null +++ b/wai/http-conformance/falsify/uri-percent-kept.patch @@ -0,0 +1,15 @@ +# must fail: uri_resolve_alias_03 +# percent-encoded unreserved characters left encoded (http_binding::uri::resolve) +diff --git a/wai/wai-rs/src/http_binding/uri.rs b/wai/wai-rs/src/http_binding/uri.rs +index 0f23cf96..5f74c61a 100644 +--- a/wai/wai-rs/src/http_binding/uri.rs ++++ b/wai/wai-rs/src/http_binding/uri.rs +@@ -97,7 +97,7 @@ fn normalize_percent(s: &str) -> String { + while i < b.len() { + if b[i] == b'%' && i + 2 < b.len() && b[i + 1].is_ascii_hexdigit() && b[i + 2].is_ascii_hexdigit() { + let v = u8::from_str_radix(&s[i + 1..i + 3], 16).expect("two hexadecimal digits"); +- if v.is_ascii_alphanumeric() || b"-._~".contains(&v) { ++ if false { + out.push(v as char); + } else { + out.push('%'); diff --git a/wai/http-conformance/falsify/walk-exceeded-wedges.patch b/wai/http-conformance/falsify/walk-exceeded-wedges.patch new file mode 100644 index 000000000..b0e3f66d5 --- /dev/null +++ b/wai/http-conformance/falsify/walk-exceeded-wedges.patch @@ -0,0 +1,15 @@ +# must fail: live_edge_reanchored live_edge_reanchored_skipping +# past the bound, every revision refused, so the client is wedged (LiveChain::advance) +diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs +index 6fb6141d..cd1d8dea 100644 +--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs ++++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs +@@ -236,7 +236,7 @@ impl LiveChain { + // §8.6: past the bound, a revision ahead of the head (more segments + // complete) re-anchors the chain, its bindings kept; one that is not + // ahead is refused, so a stale or older revision never rolls it back. +- if last_segment(cur) <= last_segment(head) { ++ if true || last_segment(cur) <= last_segment(head) { + return Err(ReceiptError::WalkExceeded); + } + let mut b = self.bindings.clone(); diff --git a/wai/http-conformance/falsify/walk-parent-name-unchecked.patch b/wai/http-conformance/falsify/walk-parent-name-unchecked.patch new file mode 100644 index 000000000..d613e82da --- /dev/null +++ b/wai/http-conformance/falsify/walk-parent-name-unchecked.patch @@ -0,0 +1,15 @@ +# must fail: live_edge_parent_substituted +# a parent fetched by its link not held to it (LiveChain::advance) +diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs +index 6fb6141d..7d582b26 100644 +--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs ++++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs +@@ -248,7 +248,7 @@ impl LiveChain { + let bytes = parent(&l).ok_or(ReceiptError::ParentMissing)?; + let text = std::str::from_utf8(&bytes).map_err(|_| ReceiptError::Malformed("not UTF-8".into()))?; + let r = ManifestReceipt::from_json(text).map_err(ReceiptError::Malformed)?; +- if r.receipt_hash() != l { ++ if false && r.receipt_hash() != l { + return Err(ReceiptError::NameMismatch); + } + if !r.verify() { diff --git a/wai/http-conformance/falsify/walk-parent-unverified.patch b/wai/http-conformance/falsify/walk-parent-unverified.patch new file mode 100644 index 000000000..542208c79 --- /dev/null +++ b/wai/http-conformance/falsify/walk-parent-unverified.patch @@ -0,0 +1,15 @@ +# must fail: live_edge_parent_invalid +# a parent the walk fetches not verified (LiveChain::advance) +diff --git a/wai/wai-rs/src/http_binding/playlist_receipt.rs b/wai/wai-rs/src/http_binding/playlist_receipt.rs +index 6fb6141d..08eec4e6 100644 +--- a/wai/wai-rs/src/http_binding/playlist_receipt.rs ++++ b/wai/wai-rs/src/http_binding/playlist_receipt.rs +@@ -251,7 +251,7 @@ impl LiveChain { + if r.receipt_hash() != l { + return Err(ReceiptError::NameMismatch); + } +- if !r.verify() { ++ if false && !r.verify() { + return Err(ReceiptError::Invalid); + } + if r.signer_pubkey != cur.signer_pubkey { diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index 37a7f3cb3..1d3a259ef 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -3400,6 +3400,31 @@ def mpd_bounds_hold(): if spent > 2.0: print("FAIL self-test: one element of 40000 attributes read in %.2f s of CPU" % spent) return False + return bindings_retention_holds() + + +def bindings_retention_holds(): + """§11.5: a binding is kept while one of the last RETENTION revisions lists it, then + forgotten, so a rebinding is refused at the span's last revision and not after it.""" + cid = lambda b: bytes([b]) * 32 + first = parse_media_playlist('#EXTM3U\n#EXT-X-TARGETDURATION:1\n#EXT-X-PART-INF:PART-TARGET=0.5\n#EXT-X-PART:DURATION=0.5,URI="p.wai#cid=%s"\n' % cid(1).hex()) + again = parse_media_playlist('#EXTM3U\n#EXT-X-TARGETDURATION:1\n#EXT-X-PART-INF:PART-TARGET=0.5\n#EXT-X-PART:DURATION=0.5,URI="./p.wai#cid=%s"\n' % cid(2).hex()) + other = parse_media_playlist("#EXTM3U\n#EXT-X-TARGETDURATION:1\n#EXTINF:1,\n%s.wai\n" % cid(3).hex()) + b = bind(new_bindings("https://origin.example/live/p.m3u8"), first) + for _ in range(RETENTION - 1): + b = bind(b, other) + try: + bind(b, again) + print("FAIL self-test: a rebinding within the retention was taken") + return False + except Refusal: + pass + b = bind(b, other) + try: + bind(b, again) + except Refusal: + print("FAIL self-test: a binding outlived the retention") + return False return True -- 2.54.0 (Apple Git-157) From ada4f8ff34dfffebfe936f5cba02138832796610 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 14:44:17 -0400 Subject: [PATCH 54/55] CHANGELOG: the third round of playlist and adaptation fixes Co-Authored-By: Claude Opus 5.5 --- wai/CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index ed0fd637f..34b4d7c01 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -213,6 +213,15 @@ wire format may change and such changes are recorded here. in the classical master; a receipt file holds no member twice, no lone surrogate and no number past 2^53 − 1, a grant of no joule ceiling written `null`; `seal-playlist --grant`; the origin answers a far-future `_HLS_msn` with 400. + - an MPD is held as written: timelines in runs, never expanding `@r`, compared run by + run, at most 256 per document, and an element's attributes read in linear time + (Appendix I); + - a budget never raises the policy's choice: with nothing at or below it within the + budget, option 0 and the budget `unmet` (replacing `floor`); + - past the walk bound, a revision ahead of the head re-anchors the chain + (`reanchored`), keeping its bindings, the revisions between unsigned; bindings key a + line by its URL resolved against the playlist's (RFC 3986), a gap never withdraws a + listed segment, and a binding is kept while one of the last 256 revisions lists it. - **What review found, fixed** (http-delivery §3.1, §4, §7, §7.1, §10, §13, Appendix D): - a layer object (a `WAI1` whose capability is refinement-only) is never dispatched: it is fetched whole at refinement urgency, verified against its cid and handed to -- 2.54.0 (Apple Git-157) From 82d5f73eb78dfff22909da92caa0a3a13f18bc7c Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 6 Oct 2026 16:08:51 -0400 Subject: [PATCH 55/55] http_verify.py: the expansion mutation expands only what the self-test's bomb writes, so a mutated run ends in its failures and not in an exhausted host Co-Authored-By: Claude Opus 5.5 --- wai/http-conformance/http_verify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wai/http-conformance/http_verify.py b/wai/http-conformance/http_verify.py index 1d3a259ef..0ed418ed1 100644 --- a/wai/http-conformance/http_verify.py +++ b/wai/http-conformance/http_verify.py @@ -2414,7 +2414,7 @@ def timeline_of(e, inherited): end = t + d * (r + 1) if end > U64: raise Refusal("playlist_malformed") - if MUTATE == "dash-r-expanded": + if MUTATE == "dash-r-expanded" and r < 1 << 20: runs += [(t + i * d, d, 1) for i in range(r + 1)] else: runs.append((t, d, r + 1)) -- 2.54.0 (Apple Git-157)