From eb3fef71183ae5082231824bd3646776d419f767 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 18 Oct 2026 20:19:22 -0400 Subject: [PATCH 1/9] wai: energy-aware content steering, the extension text (L15) wai.live.steering: a signed steering policy (WSP1) and a signed steering decision (WSD1) that chooses each client's pathway from the per-hop records' joules, compared only within a class, a pathway with no figure ranked after every pathway with one, and writes the HLS and DASH steering manifests exactly. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/energy-steering.md | 686 ++++++++++++++++++++++++++++++ 1 file changed, 686 insertions(+) create mode 100644 wai/extensions/energy-steering.md diff --git a/wai/extensions/energy-steering.md b/wai/extensions/energy-steering.md new file mode 100644 index 000000000..f39ee0fa0 --- /dev/null +++ b/wai/extensions/energy-steering.md @@ -0,0 +1,686 @@ +# WAI Extension: Energy-Aware Content Steering + +> Status: Draft. Registers the record `wai.live.steering`: a steering server's choice, for +> one client, of the pathway that delivers a room's stream for the fewest joules within the +> operator's quality and latency limits, decided from the per-hop session records +> ([live-metering](live-metering.md) §3, `WMH1`) that each pathway's hops signed, and signed +> so that anyone holding those records can recompute it. The operator signs a **steering +> policy**, `WSP1`: the pathways, their hops, their limits and the window the evidence is +> read over. For each request the steering server derives each pathway's **evidence**, its +> joules per delivered byte (or per delivered second at the floor tier) with the +> acquisition class of [energy-measurement](energy-measurement.md) §2, applies one integer +> **decision rule**, and signs the **steering decision**, `WSD1`, which names the evidence +> records by digest, the client's report, the server's own inputs and the output. A figure +> is compared only with figures of its class; a pathway whose energy is not known, because a +> hop is unmetered, its classes differ or its evidence is stale, is never ranked above one +> whose energy is, so that not measuring is never rewarded. The output is the steering +> manifest that HLS and DASH players already read, written byte for byte, carrying the +> decision's URI in one key that players ignore. No new transport. The reference +> implementation is the `energy_steering` module of `wai-rs` (feature `energy_steering`); +> the corpus is [`live-conformance/steering/`](../live-conformance/steering/), and its +> second implementation is `live-conformance/energy_steering_verify.py`. +> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. +> +> Texts read for this revision: `draft-pantos-hls-rfc8216bis-22` (§4.4.6.6, the +> `EXT-X-CONTENT-STEERING` tag, and §7, content steering in HLS) and +> `draft-pantos-content-steering-04` (§2 to §7: pathway IDs, the steering manifest, pathway +> cloning, query parameters and client behaviour), Internet-Drafts not past Working Group +> Last Call; ETSI TS 103 998 V1.1.1 (2024-01), content steering for DASH (clauses 5 to 8); +> CTA-5004-B (the CMCD keys `bl` and `mtp`, and the query and header modes); RFC 9651 §4.2; +> RFC 3986 §2; RFC 8259; RFC 8032; FIPS 180-4. A section number of one of those drafts in +> this text is that revision's. + +## 1. Scope + +In scope: the steering policy, its pathways and limits, and who signs it (§3); what a +steering server reads from a client's steering request (§4); a pathway's evidence and its +figure (§5); the decision rule (§6); the steering decision record and who signs it (§7); +the steering manifest, the HLS tag and the DASH element, written exactly, and how the +record rides beside them (§8); verification (§9). + +Out of scope: a transport, and a steering server's network service, which this extension +describes only through its inputs and its output; how a hop measures its energy, which +energy-measurement governs; how an operator chooses its limits; and how a player applies a +steering manifest, which the three texts above fix and this extension does not change. + +Relationship to the core spec: `wai.live.steering` is a **record** ([SPEC](../SPEC.md) §5): +a signed record a WAI tool emits and verifies. It names no payload format and is not +dispatchable. + +## 2. Terms + +- **Room**, **room clock**, **room key**, **hop**, **per-hop session record**, **unmetered + marker**: as in live-rooms §2 and live-metering §2. A record's **digest** is live-metering + §3.5's. +- **Pathway**, **pathway ID**, **pathway clone**, **base pathway**, **steering manifest**, + **steering server**: as in `draft-pantos-content-steering-04` §2. A pathway is an HLS + pathway (rfc8216bis-22 §7.1) and a DASH service location (ETSI TS 103 998 clause 5.1). +- **Content description**: the HLS multivariant playlist or the DASH MPD. +- **Edge hop** of a pathway: a hop whose sessions are clients of the stream; **interior + hop**: a hop of the pathway that feeds other hops. +- **Tier**: a quality level, numbered from 0, the lowest. Tier *i* is the room's layer + class *i* (live-metering §3.3), and the policy states its bitrate. +- **Class**: an acquisition class of energy-measurement §2, by its tag (§2.7): 2 + `ModelBased`, 3 `Estimator`, 4 `OnChipCounter`, 5 `CalibratedInstrument`. +- *u128*, *u64*, *u32*, *u16*, *u8*: little-endian unsigned integers of that many bits. A + digest is SHA-256 (FIPS 180-4), 32 bytes. **ppm**: parts per million. +- Every quantity in this text is an integer and every operation is exact: a product is + formed in full, never rounded or saturated, and a quotient is named with its rounding. + +## 3. The steering policy + +### 3.1 Layout + +The operator signs one policy for a room's steering, `WSP1`: + +| field | size | meaning | +|---|---|---| +| `magic` | 4 | `WSP1` (ASCII) | +| `version` | 1 | 1 in this revision | +| `room` | 32 | the room id whose per-hop records are the evidence | +| `signer` | 32 | the key that signs the policy (§3.5) | +| `period_us` | 8 | u64: *P*, the period on which the pathways' hops cut their records (§5.1) | +| `window_us` | 8 | u64: *W*, the evidence window, a whole number of periods | +| `lag_us` | 8 | u64: *L*, how long after a period ends its records are first required | +| `basis` | 1 | 1: joules per delivered byte; 2: joules per delivered second at or above the floor tier (§5.3) | +| `floor` | 1 | *F*: the floor tier | +| `latency_ceiling_ms` | 4 | u32: the latency ceiling | +| `min_buffer_ms` | 4 | u32: below this buffer a client is not moved (§6.4) | +| `hysteresis_ppm` | 4 | u32: *h*, the margin by which a pathway must beat a client's current one (§6.4) | +| `ttl_s` | 4 | u32: the steering manifest's TTL | +| `min_ttl_s` | 4 | u32: the TTL when the decision holds a client or finds no pathway within the limits | +| `flags` | 1 | bit 0: DASH `queryBeforeStart`; every other bit 0 | +| `initial` | 1 | the index of the initial pathway of the content description | +| `tiers` | 1 + 8 × *n* | a u8 count *n*, then each tier's bitrate in bits per second (u64), tier 0 first | +| `server_uri` | 2 + … | a u16 length, then the steering server's URI, which the content description names | +| `reload_uri` | 2 + … | a u16 length, then the URI the manifest names for the next request (§8.1) | +| `decision_uri` | 2 + … | a u16 length, then the prefix of the URI at which a decision record is served (§8.4) | +| `pathways` | 1 + … | a u8 count, then each pathway entry (§3.2), in the policy's order | +| `sig` | 64 | §3.4 | + +### 3.2 Pathway entries + +| field | size | meaning | +|---|---|---| +| `id` | 1 + … | a u8 length, then the pathway ID | +| `base` | 1 | 255: a pathway of the content description; otherwise the index of the pathway it clones | +| `host` | 1 + … | a u8 length, then the clone's `HOST` (§8.1); length 0 for none | +| `params` | 1 + … | a u8 count, then each of the clone's `PARAMS`: a u8 length and the name, a u8 length and the value | +| `top_tier` | 1 | the highest tier the pathway carries | +| `latency_ms` | 4 | u32: the latency the operator states for the pathway | +| `capacity` | 4 | u32: the most sessions the server steers to it first at once; 2^32 − 1 for no limit | +| `min_share_ppm` | 4 | u32: the share of sessions it is placed first for whatever its evidence (§6.3) | +| `hops` | 1 + 33 × *k* | a u8 count *k*, then each hop: its key (32) and its place (u8: 1 edge, 2 interior) | + +The pathways that are not clones are the content description's: each is the `PATHWAY-ID` of +its HLS variant streams (rfc8216bis-22 §4.4.6.2) and the `@serviceLocation` of its DASH +`BaseURL` elements (ETSI TS 103 998 clause 5.2). A clone is a pathway of its own, with its +own hops: the same content reached through another host. + +### 3.3 Reading and rules + +A reader reads exactly the bytes it is given, in the order of §3.1, as live-events §3.3 +reads a record, with `WSP1` for `WEV1` (`truncated`, `malformed`, `unknown_version`). It +refuses as `clone_unknown` a policy with a clone whose `base` is not the index of a pathway +before it: a clone of a pathway the manifest would not yet define, which +`draft-pantos-content-steering-04` §5 has a client ignore. It refuses as `policy_invalid` a +policy that breaks any of these: + +- `period_us` is above 0, and `window_us` is a multiple of it above 0; +- `basis` is 1 or 2; 1 ≤ `min_ttl_s` ≤ `ttl_s`; `hysteresis_ppm` ≤ 1 000 000; `flags` is 0 + or 1; +- 1 ≤ *n* ≤ 8; the bitrates are above 0 and strictly ascending; `floor` < *n*; +- each URI is 1 to 2048 bytes of RFC 3986's characters (unreserved, reserved and `%`, never + `#`), and each `%` begins a percent-encoding of two hexadecimal digits; +- 1 to 32 pathways; each `id` is 1 to 64 bytes of `A`–`Z`, `a`–`z`, `0`–`9`, `.`, `-` and + `_` (content-steering-04 §2), and no two are equal; +- `initial` is the index of a pathway that is not a clone; +- a pathway that is not a clone has no `host` and no `params`; a clone's `host` is at most + 255 bytes of `A`–`Z`, `a`–`z`, `0`–`9`, `-` and `.`; its `params` have names of 1 to 64 + bytes and values of 0 to 255 bytes, each of RFC 3986's unreserved characters and + percent-encodings, and names in strictly ascending bytewise order (content-steering-04 + §5 appends them in UTF-8 order, which is bytewise for these characters); +- each pathway's `top_tier` < *n*; each has at least one hop and at least one edge hop; each + place is 1 or 2; no hop key appears twice in the policy, in one pathway or in two; +- the sum of the `min_share_ppm` is at most 1 000 000. + +**A hop belongs to one pathway.** A hop that carries two pathways' traffic signs one figure +for both, and no record states how it divides; a share of it would be an allocation, not a +reading. A hop common to every pathway, such as the origin that feeds them all, changes no +comparison and is listed in none. + +### 3.4 Digest and signature + +The **digest** of a policy is SHA-256 over its bytes before `sig`. Its **signature** is +Ed25519 (RFC 8032) by `signer` over the 20 bytes `wai:steering-policy` (ASCII) and 0x01, +followed by the bytes the digest covers, verified strictly as [SPEC](../SPEC.md) §7 +*Signatures* requires: a key that fails step 1 or 2 is `bad_key`, a signature that fails a +later step `bad_signature`. + +### 3.5 Who signs: the steering authority + +A policy and a decision are signed by the room authority, or by a **steering key**: a key +the room's origin's key log lists, not revoked, with the authority `live/steer` in its +`may_sign` (jwp-receipts §7.5). A decision restates figures with their classes, so its +signer's entry, the room authority's included, **MUST** also hold `energy/` followed by the +label of each class among its figures (§7.1), as jwp-receipts §7.5 requires of every signer +that restates a figure with its class. A room that names no origin has no key log, and only +its authority signs. A signer that is neither is `signer_not_authorised`, and a steering key +whose entry is `revoked` is `signer_revoked` (the codes of live-metering §4.3). + +Steering is the operator's act, as metering is, and its key lives in the origin's key log +for the reason live-metering §4.3 gives: a decision moves every client of the room, and a +grant's powers are live-rooms' closed set. + +## 4. The client's report + +A steering request is an HTTP GET of the steering server's URI, or of the last manifest's +`RELOAD-URI`. The server reads three things from it, each **absent** when the request does +not carry it in the form below; a value that does not read is absent, never zero: + +- the client's **current pathway**, *c*: the index of a pathway of the policy; +- its **throughput**, *T*: bits per second; +- its **buffer**, *B*: milliseconds. + +### 4.1 The query + +The query is the request target's bytes after its first `?` and before any `#`. It is cut +at each `&` into parts; a part is cut at its first `=` into a name and a value (a part with +no `=` has an empty value), and both are percent-decoded (RFC 3986 §2.1; `+` is not a +space). A part with a `%` not followed by two hexadecimal digits is skipped. Of the parts +with a name the server reads, the first is read and later ones are ignored. A value is +**unquoted** by removing one `"` at each end when it both begins and ends with one. + +An **integer** here is 1 to 20 ASCII digits whose value is at most 2^64 − 1. + +### 4.2 HLS + +rfc8216bis-22 §7.4 has the client add `_HLS_pathway=""` and +`_HLS_throughput=`, the throughput an integer number of bits per second: + +- *c*: `_HLS_pathway`, unquoted, when it is the `id` of a pathway of the policy; +- *T*: `_HLS_throughput`, when it is an integer. + +### 4.3 DASH + +ETSI TS 103 998 clause 7 step 7 has the client add `_DASH_pathway`, the service locations it +used since its last request, comma-separated within one pair of double quotes, and +`_DASH_throughput`, a comma-separated list of integer bits per second, the *n*-th for the +*n*-th service location: + +- *c*: of `_DASH_pathway`, unquoted and cut at each `,`, the first item that is the `id` of a + pathway of the policy, at position *j* of the list (from 0); +- *T*: of `_DASH_throughput`, cut at each `,`, the item at position *j*, or at 0 when no + pathway is current, when it is an integer. + +The clause's own example writes `_DASH_pathway` without quotes, so both are read. + +### 4.4 CMCD + +A client that reports CMCD (CTA-5004-B) sends its keys as the query parameter `CMCD`, or in +the header shards; CTA-5004-B assigns `bl` (buffer length, milliseconds) and `mtp` +(measured throughput, kilobits per second) to the shard `CMCD-Request`. The server reads +the `CMCD` parameter's value or, when the query has no such part, the `CMCD-Request` +header's value, as an RFC 9651 Dictionary (§4.2.2 of that RFC, the whole grammar, Dates and +Display Strings included); a value that does not parse gives neither key. A key read is: + +- an Integer of 0 or above; or a non-empty Inner List of such Integers, each with any + parameters (CTA-5004-B carries one per object type, its token a parameter): the **least** + of them, the object type whose buffer runs out first, or the slowest measurement; +- anything else, absent. + +Then *B* is `bl`, and when the query gives no *T*, *T* is `mtp` × 1000. + +## 5. A pathway's evidence + +### 5.1 The window + +A pathway's hops cut their per-hop records (live-metering §3.8) on the policy's period: each +record's span runs from one multiple of *P* on the room clock to a later one. At a decision's +instant *t* on the room clock the **window** is [*a*, *b*), with + + b = ⌊(t − L) / P⌋ × P (0 when t < L) + a = b − W (0 when b < W) + +so that the window ends on the last period that has been over for at least *L*: *L* gives a +hop's record of a period time to reach the steering server before the server needs it. A +record is **in the window** when *a* ≤ its `start` and its `end` ≤ *b*. + +### 5.2 Coverage and staleness + +A decision names, for each pathway, the records of its hops it read, grouped by hop in the +pathway's hop order and each hop's in ascending order of `start` (§7.1). A hop is +**covered** when the records named for it **tile** the window: there is at least one, the +first starts at *a*, each starts where the one before it ends, and the last ends at *b*. The +window is then empty for no hop: when *a* = *b* no hop is covered. + +A pathway's evidence is **stale** when one of its hops is not covered: a period the hop has +not signed, or whose record has not reached the server within *L*; a gap; an overlap; a +record that does not reach to *b*. Its energy over the window is then not known, and §6 +ranks it so. Because the window is tiled exactly, every hop of every pathway is read over +the same span, so the figures of two pathways describe the same stretch of room clock; and a +server that leaves out a record makes a pathway stale. It cannot make one look cheaper. + +### 5.3 The figure + +Over the records named for a pathway, its **status** is the first of these that holds: + +1. **stale** (3): a hop is not covered (§5.2); +2. **unmetered** (1): a record's figure is the unmetered marker; +3. **mixed** (2): the records' classes are not all one; +4. **empty** (4): *D* = 0; +5. **figure** (0): otherwise. + +Its **joules** *J* is the sum of the records' `joules`, in microjoules, and its **delivery** +*D* is, over the records of its edge hops: + +- with `basis` 1, the sum of the `bytes` of every track entry of kind 1 (a media track) and + direction 2 (out): the media bytes the pathway delivered; +- with `basis` 2, the sum of `subscriber_us`[*c*] for the layer classes *c* from *F* to 7: + the microseconds of delivery at the floor tier or above. + +A pathway of status figure has the **figure** *J* / *D*, in the records' common class; a +figure is never written as a quotient, and two figures are compared by their products (§6.2). +On `basis` 2 the pathway's every joule is charged to its delivery at the floor tier or above, +so the figure is never less than the joules per such second that any division of the hops' +joules among tiers would give. + +### 5.4 What is not known is not zero + +A pathway of any status other than figure has **no figure**, and its *J* and *D* are stated +as 0 (§7.2). In particular: + +- **An unmetered hop is not a hop of zero joules.** A sum over a pathway's records that + leaves out an unmetered record would state less energy than the pathway spent, and the + less a pathway measured the cheaper it would look. A decision that gives a pathway with an + unmetered record a figure is refused `unmetered_as_zero`. +- **Figures of two classes are not summed.** A `ModelBased` figure added to an + `OnChipCounter` figure is neither, and labelled as either it would present a model as a + reading or a reading as a model (live-metering §4.5). A decision that gives a pathway of + mixed classes a figure is refused `classes_summed`. +- **Stale evidence is not current evidence**, and a pathway whose hops delivered nothing + (empty) has no rate to state. + +## 6. The decision rule + +The rule is a function of the policy; the evidence (§5); the client's report (§4); the +client's **session**, 16 bytes the server minted at the client's first request and carries +in the manifest's `RELOAD-URI` (§8.1); and each pathway's **load**, the number of sessions +the server is steering to it first, its own count. Its output is the order of the pathways, +a TTL and the reason the first was chosen. + +### 6.1 Limits + +A pathway *p* is **within the limits** for the request when all three hold: + +- **latency**: its `latency_ms` ≤ `latency_ceiling_ms`; +- **quality**: its `top_tier` ≥ *F*: it carries the floor tier; +- **capacity**: its `capacity` is 2^32 − 1; or its load < `capacity`; or it is *c* and its + load ≤ `capacity`, since *c*'s load already counts the client. + +### 6.2 The order + +A pathway's **rank** is set by its evidence: 0 for a figure of class 5 +(`CalibratedInstrument`), 1 of class 4 (`OnChipCounter`), 2 of class 2 (`ModelBased`), 3 of +class 3 (`Estimator`), and 4 for no figure. Pathway *p* comes before *q* when, of these, the +first that tells them apart says so: + +1. *p*'s rank is below *q*'s; +2. both have a figure, of one class, and *J**p* × *D**q* < + *J**q* × *D**p*: *p*'s figure is the lower; +3. *p*'s `id` is below *q*'s, bytewise. + +Every two pathways differ in `id`, so the order is total. Figures are compared only within a +class (step 2). Across classes the order is by what backs the figure, the order +energy-measurement §2 gives its classes, and never by the figures: no figure of one class +can be compared with one of another, so the rule cannot tell which of two such pathways +spends less, and it prefers the evidence it can rely on. A pathway with no figure comes after +every pathway with one (step 1), and among pathways with no figure only the `id` orders them +(step 3): being unknown cannot raise a pathway above any pathway whose energy is known, and +nothing about an unknown pathway's energy orders it. + +### 6.3 The draw and the minimum shares + +The client's **draw** is + + d = u32(SHA-256("wai:steering-draw" ‖ 0x01 ‖ policy digest ‖ session)[0..4]) mod 1 000 000 + +the first four bytes read as a u32. It is fixed for the session, so it moves no client from +one decision to the next. The policy's pathways, in its order, divide [0, 1 000 000) from 0 +into **bands**: pathway *i*'s band is [*S**i*, *S**i* + +`min_share_ppm`*i*), with *S**i* the sum of the shares before it. A +session whose draw falls in a pathway's band is placed on that pathway first while it is +within the limits (§6.4 step 4), whatever its evidence. + +The minimum shares are how evidence stays fresh: a pathway no client is steered to delivers +nothing, and its records soon state no delivery, or none at all. A pathway's share of +sessions from its band is its `min_share_ppm`, the same whether its energy is known or not, +so a pathway gains no session by being unknown. + +### 6.4 The first pathway + +The client's **sustainable tier** is, when *T* is known, the highest tier whose bitrate is +at most *T*, or none when tier 0's is above it. The client is **at its floor** when *T* is +known and its sustainable tier is none or at most *F*. + +Let *w* be the first pathway of the order (§6.2) that is within the limits. *w* **beats** a +pathway *x* of its rank when both have a figure and + + J_w × D_x × (1 000 000 + h) < J_x × D_w × 1 000 000 + +that is, *w*'s figure is below *x*'s by more than the margin *h*. The first pathway, and the +**reason** it is first, is given by the first of these steps that applies: + +1. **buffer** (1): *c* is known and within the limits, *B* is known, and *B* < + `min_buffer_ms`: *c*. A switch costs a fresh start on the new pathway, which a short + buffer may not cover. +2. **floor** (2): *c* is known and within the limits, and the client is at its floor: *c*. + Its throughput was measured on *c*, and a client with no margin above the floor is not + moved to a pathway on which it was never measured. +3. **none** (6): no pathway is within the limits: *c* when it is known, otherwise the first + pathway of the order. The client is not moved for a limit no pathway meets + (content-steering-04 §7 step 5 has it stay). +4. **share** (3): the draw falls in the band of a pathway *e* within the limits: *e*. +5. **hold** (4): *c* is known and within the limits, *c* is not *w*, *c* has *w*'s rank, and + either that rank is 4 or *w* does not beat *c*: *c*. This is the **hysteresis**: a client + moves only for a figure lower than its own by more than the margin, so that two figures + within the margin of each other do not swap its pathway from one decision to the next. + Between two pathways with no figure nothing says that either is cheaper, and the client + stays. +6. **rank** (5): *w*. + +Steps 1, 2 and 4 hold a client or place it whatever its pathway's evidence, and so do not +prefer a pathway for being unknown; step 5 holds a client only against a pathway of its own +rank, so a client on a pathway with no figure moves as soon as a pathway with a figure is +within the limits. + +### 6.5 The priority and the TTL + +The **priority** is the first pathway, then the pathways within the limits in the order +(§6.2), then the others in the order, each once. Every pathway is in it, so a client that +penalises its first pathway (content-steering-04 §7 step 3) falls back in the same order. + +The **TTL** is `min_ttl_s` when the reason is buffer, floor or none, so that a client held +or left without a pathway within the limits asks again soon; and `ttl_s` otherwise. + +## 7. The steering decision + +### 7.1 Layout + +For each request the steering server signs a decision, `WSD1`: + +| field | size | meaning | +|---|---|---| +| `magic` | 4 | `WSD1` (ASCII) | +| `version` | 1 | 1 in this revision | +| `room` | 32 | the room id | +| `signer` | 32 | the key that signs the decision (§3.5) | +| `policy` | 32 | the policy's digest | +| `t` | 8 | u64: the decision's instant on the room clock, in microseconds | +| `session` | 16 | the client's session (§6) | +| `draw` | 4 | u32: *d* (§6.3) | +| `protocol` | 1 | 1 HLS (§4.2), 2 DASH (§4.3): which parameters the report was read from | +| `report` | 18 | `present` (u8: bit 0 *c*, bit 1 *T*, bit 2 *B*), `pathway` (u8: *c*), `throughput` (u64: *T*), `buffer` (u64: *B*); an absent value is 0 | +| `pathways` | 1 + … | a u8 count, the policy's, then for each pathway in the policy's order: `load` (u32); `limits` (u8: bit 0 latency, bit 1 quality, bit 2 capacity, each set when it holds, §6.1); `status` (u8, §5.3); `class` (u8: the figure's tag, 0 for no figure); `joules` (u128: *J*); `delivery` (u128: *D*); `evidence`, a u8 count and the digest of each record named (§5.2) | +| `ttl` | 4 | u32: the TTL (§6.5) | +| `reason` | 1 | the first pathway's reason (§6.4): 1 buffer, 2 floor, 3 share, 4 hold, 5 rank, 6 none | +| `priority` | 1 + *m* | a u8 count *m*, then the index of each pathway, the first first (§6.5) | +| `sig` | 64 | §7.3 | + +### 7.2 Reading and rules + +A reader reads the bytes as §3.3 reads a policy, with `WSD1` for `WSP1`, and refuses as +`decision_invalid` a decision that breaks any of these: + +- `protocol` is 1 or 2; `draw` < 1 000 000; `ttl` ≥ 1; `reason` is 1 to 6; +- `present` has no bit above 2; an absent value is 0; a present `pathway` is below the + count of `pathways`; +- each pathway's `limits` has no bit above 2 and its `status` is 0 to 4; with status 0 its + `class` is 2 to 5 and its `joules` and `delivery` are above 0; with any other status its + `class`, `joules` and `delivery` are 0; +- no digest is named twice in the decision; +- `priority` names each pathway once: *m* is the count of `pathways` and its indices are a + permutation of 0 to *m* − 1. + +### 7.3 Digest and signature + +The **digest** of a decision is SHA-256 over its bytes before `sig`; its **signature** is +Ed25519 by `signer` over the 22 bytes `wai:steering-decision` (ASCII) and 0x01, followed by +the bytes the digest covers, verified strictly (SPEC §7 *Signatures*). Who may sign it is +§3.5's rule. + +## 8. The mappings + +### 8.1 The steering manifest + +The output of a decision is one steering manifest (`draft-pantos-content-steering-04` §4, +the DASH Content Steering Manifest of ETSI TS 103 998 clause 6), served with the media type +§11.1 of that draft registers. It is the JSON text (RFC 8259) below, UTF-8, with no white +space outside its strings, its members in this order: + + {"VERSION":1,"TTL":,"RELOAD-URI":"","PATHWAY-PRIORITY":[], + "PATHWAY-CLONES":[],"science.transaction.wai-decision":""} + +(one line, cut here for legibility), where: + +- `` is the decision's `ttl` in decimal digits, with no leading zero; +- `` is the policy's `reload_uri`, then `&` when it holds a `?` and `?` otherwise, + then `wai_session=` and the session as 32 lowercase hexadecimal digits; +- `` is the `id` of each pathway of the `priority`, in its order, each in double + quotes, separated by `,`; +- `"PATHWAY-CLONES":[],` is written only when the policy has a clone. `` is, + for each clone in the policy's order, separated by `,`: + `{"BASE-ID":"","ID":"","URI-REPLACEMENT":{}}`, where + `` is `"HOST":""` when the clone has a host, then (after a `,` when a + host was written) `"PARAMS":{}` when it has params, `` being + `"":""` for each, in the policy's order, separated by `,`; and empty when it + has neither; +- `` is the policy's `decision_uri`, then the decision's digest as 64 lowercase + hexadecimal digits (§8.4). + +§3.3's character rules leave nothing in these strings that JSON escapes, so the text is +fixed byte for byte. `PATHWAY-CLONES` is written for every clone, whether or not it is +first, so that a client can fall back to any pathway of the priority. + +**HLS and DASH are the same bytes.** ETSI TS 103 998 clause 6.2 defines the same keys as +content-steering-04 §4 and §5, and a DASH client ignores the `URI-REPLACEMENT` keys HLS +adds (`PER-VARIANT-URIS`, `PER-RENDITION-URIS`, rfc8216bis-22 §7.3; clause 7 step 13), +which a policy of this revision does not state. A DASH `PATHWAY-PRIORITY` names service +locations, which are the same IDs (§3.2). The two protocols differ in the request (§4) and +in the content description (§8.2, §8.3). + +### 8.2 HLS + +The multivariant playlist carries the policy as one tag (rfc8216bis-22 §4.4.6.6): + + #EXT-X-CONTENT-STEERING:SERVER-URI="",PATHWAY-ID="" + +with the policy's `server_uri` and the `id` of its `initial` pathway. The tag's +`PATHWAY-ID` is the pathway a client applies until its first steering manifest, and +content-steering-04 §4 asks that the first manifest's first pathway agree with it: a client +whose first request carries no report and whose draw is in no band gets the first pathway of +the order, which an operator **SHOULD** make the initial one when it writes the policy. + +### 8.3 DASH + +The MPD carries the policy as one element (ETSI TS 103 998 clause 5.2): + + + +(one line), `queryBeforeStart` `true` when the policy's flag bit 0 is set and `false` +otherwise, and the URI with each `&` written `&`, the only character of §3.3's URIs that +XML text escapes. With `queryBeforeStart` `true` a client asks before it starts (clause 7 +step 5), and its first request carries no `_DASH_` parameters, so its first decision has no +report. + +### 8.4 Carrying the decision + +The decision record is served at the URI the manifest's `science.transaction.wai-decision` +member names: the policy's `decision_uri` followed by the decision's digest in hexadecimal. +A player ignores the member: content-steering-04 §4 lets a manifest hold other keys and has +a client ignore any key it does not recognise, and ETSI TS 103 998 clause 6.1 does the same. +The member is the decision's digest, not its signature over the manifest: the manifest is a +function of the decision and the policy (§8.1), and its member names the decision, so there +is no loop between them. A party that holds the manifest fetches the record, verifies it +(§9), and requires that the manifest be the bytes §8.1 writes from it. A steering server +**SHOULD** keep each decision available for as long as the records it names are. + +## 9. Verification + +### 9.1 Inputs and steps + +A verifier is given the room's descriptor and the head of its authority's key log, or none; +the policy; the decision; the per-hop records; and, optionally, the manifest that was served +with the decision. It runs these steps and stops at the first refusal, which it reports with +the reason and where: + +1. **The room**, at `room`: the descriptor reads and verifies, and the head is held for its + origin and lists its authority (live-rooms §9.1 steps 1 and 2, with that section's + reasons). +2. **The policy** (§3), at `policy`: `truncated`, `malformed`, `unknown_version`, + `clone_unknown`, `policy_invalid`; `wrong_room` when its `room` is not the room id; + `bad_key`, `bad_signature`; its signer (§3.5), `signer_not_authorised`, + `signer_revoked`. +3. **The decision** (§7), at `decision`: `truncated`, `malformed`, `unknown_version`, + `decision_invalid`; `wrong_room`; `record_missing` when its `policy` is not the given + policy's digest; `decision_invalid` when its count of `pathways` is not the policy's, or a + present `pathway` is not a pathway of it; `bad_key`, `bad_signature`; its signer, + `signer_not_authorised`, `signer_revoked`. +4. **Each record it names**, in order, at `evidence` with the pathway's index and the + record's place in its list: + 1. a record whose digest is the one named is given: otherwise `record_missing`; + 2. it reads, its room is the room id and its signature verifies (live-metering §3.6: + `truncated`, `malformed`, `unknown_version`, `energy_invalid`, `record_invalid`, + `wrong_room`, `bad_key`, `bad_signature`); + 3. its hop may sign it (live-metering §5.2: `hop_not_authorised`, `hop_revoked`): a key + the room's key log does not list for its role and class does not sign evidence; + 4. its `hop` is a hop of the pathway it is named under: otherwise + `evidence_not_on_pathway`; + 5. it is in the window of the decision's `t` (§5.1): otherwise `evidence_outside_window`; + 6. it follows the record named before it under the pathway in the order of §5.2, a later + hop of the pathway or the same hop and a later `start`: otherwise `decision_invalid`. +5. **The evidence** (§5), at `pathway` and its index: each pathway's status, class, *J* and + *D* recompute from its records. A pathway stated with a figure whose records hold the + unmetered marker is `unmetered_as_zero`; one stated with a figure whose records are of + more than one class is `classes_summed`; any other difference is `evidence_mismatch`. +6. **The output** (§6), at `output`: the draw, each pathway's `limits`, the priority, the + TTL and the reason recompute from the policy, the evidence, the report, the session and + the loads. When they do not, the decision is `unknown_preferred` if its first pathway has + no figure while the first of the recomputed priority has one, and `decision_mismatch` + otherwise. +7. **The manifest**, at `manifest`, when one is given: it is the bytes §8.1 writes from the + decision and the policy, otherwise `manifest_mismatch`. + +A decision that passes is **accepted**: the priority, the TTL and the reason are what the +rule gives for the evidence the records state, the report and the loads it names. + +### 9.2 What is recomputed and what is stated + +A decision has inputs of three kinds, and a verifier treats them differently: + +- **The evidence** is signed by the hops and verified: its figures cannot be other than the + records give. +- **The report** is the client's, as the server read it (§4). The client does not sign it, + and the decision states it rather than the request, so that a request's other parameters + (tokens, the session) are not published with it. +- **The session and the loads** are the server's own. The decision states them; a verifier + recomputes the rule from them and the draw from the session, and an auditor that holds a + server's decisions checks over them that the loads and the bands' shares are what the + server claims. + +### 9.3 Outcome + +- **refuse**: the reason and where (§9.1). +- **accept**: the decision's digest; the priority by pathway ID; the TTL and the reason; + each pathway's status, class, *J*, *D* and limits; the steering manifest; and the record's + URI. + +## 10. Reasons + +The codes this extension adds, each with where it is raised: + +| reason | raised at | +|---|---| +| `policy_invalid` | §3.3, §9.1 step 2 | +| `clone_unknown` | §3.3, §9.1 step 2 | +| `decision_invalid` | §7.2, §9.1 steps 3 and 4 | +| `evidence_not_on_pathway` | §9.1 step 4 | +| `evidence_outside_window` | §5.1, §9.1 step 4 | +| `evidence_mismatch` | §5.3, §9.1 step 5 | +| `unmetered_as_zero` | §5.4, §9.1 step 5 | +| `unknown_preferred` | §6.2, §9.1 step 6 | +| `decision_mismatch` | §6, §9.1 step 6 | +| `manifest_mismatch` | §8.1, §9.1 step 7 | + +A verifier of this extension also raises `truncated`, `malformed` and `unknown_version` for a +policy, a decision or a record that does not read; `bad_key` and `bad_signature` for their +signatures; `wrong_room` for one of another room; live-metering's `classes_summed`, +`signer_not_authorised`, `signer_revoked`, `record_missing`, `energy_invalid`, +`record_invalid`, `hop_not_authorised` and `hop_revoked`; and live-rooms §9.1's codes for +the room, at `room`. + +## 11. Conformance + +[`live-conformance/steering/`](../live-conformance/steering/) holds the corpus, in one file, +`vectors.json`, written from fixed seeds by `wai_energy_steering_vectors gen`. It sits beside +the metering corpus because its evidence is per-hop records of live-metering, read by that +corpus's second implementation. It holds a room with its key log, policies, the per-hop +records of the pathways' hops, and: + +- **requests**: steering requests over HLS and DASH, with and without CMCD in the query and + in the header, with the report §4 reads from each; +- **vectors**, each a policy, a decision, the records it names and the manifest served with + it, with the outcome §9.3 gives them. **Accepted**: three pathways with figures of one + class; mixed classes, ordered by class; a pathway with an unmetered hop, ranked after those + with figures though its metered hops alone are the cheapest; stale evidence; a client at + its floor, and one with a short buffer, held; hysteresis holding a client and, a period + later, moving it; pathway clones; a session placed by its band; a pathway at its capacity + and one past the latency ceiling; the per-second basis; DASH; and no pathway within the + limits; each with its steering manifest, HLS tag and DASH element byte for byte. + **Refused**: each reason of §10, and the codes it reuses from the extensions it reads, at + least once, each vector a control of one defect. + +A conforming verifier gives every request its report and every vector the outcome, the +reason, the place and the facts the corpus states. `wai_energy_steering_vectors verify` and +the test `tests/energy_steering.rs` check that the file regenerates byte for byte, that +`wai-rs` gives every request and vector its outcome, that every reason of §10 refuses a +vector, and that §10 lists exactly the reference's codes. +`live-conformance/energy_steering_verify.py` is the second implementation, written from +this text: it shares no code with `wai-rs`, verifies signatures with WAI's strict rule written +from RFC 8032 (`claim-conformance/_ed25519.py`), reads the room with `live_rooms_verify.py` +and the per-hop records with `live_metering_verify.py`, parses RFC 9651 itself, recomputes +every report, figure, decision and manifest with Python's integers, gives every request and +vector the same outcome, and requires every mutated copy of the corpus to fail. + +The corpus's energy figures are synthetic, chosen to exercise the rule. It computes which +pathway the rule places first, and claims nothing about the energy any deployment spends or +saves. + +## 12. Security and privacy considerations + +- **The evidence is signed; the choice is checkable.** A steering server cannot state a + figure the records do not give, or a priority the rule does not, without its decision + being refused. What it can do is leave records out, which makes a pathway stale (§5.2) + and so ranks it lower, never higher; and state its own loads and sessions (§9.2), which + only an audit over its decisions checks. +- **Not measuring is never rewarded.** No status other than figure gives a pathway a + figure (§5.4), no pathway with no figure ranks above one with a figure (§6.2), and the + rules that place a client whatever its pathway's evidence (§6.4 steps 1, 2 and 4) give a + pathway with no figure no more than they give one with a figure. An operator that wants a + pathway preferred meters it. +- **Measuring better is rewarded, measuring worse is not penalised below unknown.** Across + classes the order is by what backs the figure; a pathway of the weakest class still ranks + above every pathway with no figure. +- **A hop's figure is its own statement** (energy-measurement §5): the decision makes the + choice recomputable from what the hops signed, not the figures true. A hop that understates + its energy wins traffic, and the remedies are those of live-metering §11: its class says + how the figure was obtained, the key log confines which classes a key may state, and an + upstream hop's record contradicts a downstream one's. +- **Flapping.** The draw is fixed for a session, the window moves one period at a time, and + the margin *h* keeps a client on its pathway against a figure within it (§6.4 step 5). +- **Privacy.** A decision holds no identifier of the client beyond its session, which the + server minted and which names no person; the report is the throughput and buffer the + client sends to every steering server; and the decision does not carry the request, so a + token in its query is not published with it (§9.2). A CMCD report is read for `bl` and + `mtp` alone, and the decision keeps nothing else of it. -- 2.54.0 (Apple Git-157) From d8817912f340d4760ac10ad9632ecbad96b63644 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 18 Oct 2026 20:32:06 -0400 Subject: [PATCH 2/9] wai-rs: energy_steering, the reference module, its corpus generator and tests (L15) Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/Cargo.toml | 19 + .../src/bin/wai_energy_steering_vectors.rs | 55 + wai/wai-rs/src/energy_steering.rs | 1769 +++++++++++++++++ wai/wai-rs/src/energy_steering/corpus.rs | 835 ++++++++ wai/wai-rs/src/energy_steering/tests.rs | 267 +++ wai/wai-rs/src/lib.rs | 9 + wai/wai-rs/tests/energy_steering.rs | 105 + 7 files changed, 3059 insertions(+) create mode 100644 wai/wai-rs/src/bin/wai_energy_steering_vectors.rs create mode 100644 wai/wai-rs/src/energy_steering.rs create mode 100644 wai/wai-rs/src/energy_steering/corpus.rs create mode 100644 wai/wai-rs/src/energy_steering/tests.rs create mode 100644 wai/wai-rs/tests/energy_steering.rs diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 1a8741cfb..3958266e6 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -221,6 +221,15 @@ live_record = ["capture_take"] # composition receipts through their modules and the logs through the telemetry module. SHA-256, # Ed25519 and the key log only; no new dependency. live_metering = ["live_compose", "capture_take", "telemetry"] +# Energy-aware content steering (extensions/energy-steering): `wai.live.steering` — the signed +# steering policy (WSP1) and steering decision (WSD1): each pathway's joules per delivered byte or +# per delivered second at the floor tier from its hops' per-hop records (live-metering's WMH1), with +# their acquisition class, compared only within a class, a pathway with no figure never ranked above +# one with a figure; the integer decision rule with limits, minimum shares and hysteresis; the client's +# report from HLS and DASH steering requests and CMCD (an RFC 9651 parser of its own); and the +# steering manifest, the HLS tag and the DASH element, written exactly. SHA-256, Ed25519 and the key +# log only; no new dependency. +energy_steering = ["live_metering"] # Timed text and live captions (extensions/timed-text): `wai.text.timed` — the cue model, # live caption revisions (`caption` events of wai.live.events with the WTX1 annotation), # and their exact mappings onto WebVTT documents and segments, the IMSC1 Text Profile, @@ -685,6 +694,11 @@ name = "wai_live_metering_vectors" path = "src/bin/wai_live_metering_vectors.rs" required-features = ["live_metering"] +[[bin]] +name = "wai_energy_steering_vectors" +path = "src/bin/wai_energy_steering_vectors.rs" +required-features = ["energy_steering"] + [[bin]] name = "wai_timed_text_vectors" path = "src/bin/wai_timed_text_vectors.rs" @@ -1356,6 +1370,11 @@ name = "live_metering" path = "tests/live_metering.rs" required-features = ["live_metering"] +[[test]] +name = "energy_steering" +path = "tests/energy_steering.rs" +required-features = ["energy_steering"] + [[test]] name = "timed_text" path = "tests/timed_text.rs" diff --git a/wai/wai-rs/src/bin/wai_energy_steering_vectors.rs b/wai/wai-rs/src/bin/wai_energy_steering_vectors.rs new file mode 100644 index 000000000..4dfad5c0c --- /dev/null +++ b/wai/wai-rs/src/bin/wai_energy_steering_vectors.rs @@ -0,0 +1,55 @@ +//! Conformance corpus for energy-aware content steering (`live-conformance/steering/`, +//! extensions/energy-steering.md §11). +//! +//! wai_energy_steering_vectors gen write vectors.json, from fixed seeds +//! wai_energy_steering_vectors verify vectors.json regenerates byte for byte, +//! and every request and vector gets the +//! outcome it states +//! +//! The generator and this verifier are one implementation. The corpus's second +//! implementation is `live-conformance/energy_steering_verify.py`, written from the +//! extension's text and sharing no code with this binary. + +use std::path::Path; +use wai::energy_steering::corpus; + +fn main() { + let a: Vec = std::env::args().collect(); + match (a.get(1).map(String::as_str), a.get(2)) { + (Some("gen"), Some(d)) => { + let p = Path::new(d).join("vectors.json"); + if let Err(e) = std::fs::create_dir_all(d).and_then(|_| std::fs::write(&p, corpus::generate())) { + eprintln!("write {}: {e}", p.display()); + std::process::exit(1); + } + println!("wrote {}", p.display()); + } + (Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))), + _ => { + eprintln!("usage: wai_energy_steering_vectors gen|verify "); + std::process::exit(2); + } + } +} + +fn verify(dir: &Path) -> i32 { + let p = dir.join("vectors.json"); + let doc = match std::fs::read_to_string(&p) { + Ok(s) => s, + Err(e) => { + eprintln!("read {}: {e}", p.display()); + return 1; + } + }; + let mut fail = 0; + let same = doc == corpus::generate(); + println!("{} vectors.json regenerates byte for byte from its seeds", if same { "PASS" } else { "FAIL" }); + fail += (!same) as i32; + let fails = corpus::verify(&doc); + for f in &fails { + println!("FAIL {f}"); + } + println!("{} every request and vector gets the outcome the corpus states", if fails.is_empty() { "PASS" } else { "FAIL" }); + fail += (!fails.is_empty()) as i32; + (fail != 0) as i32 +} diff --git a/wai/wai-rs/src/energy_steering.rs b/wai/wai-rs/src/energy_steering.rs new file mode 100644 index 000000000..2fad2b873 --- /dev/null +++ b/wai/wai-rs/src/energy_steering.rs @@ -0,0 +1,1769 @@ +//! Energy-aware content steering — `wai.live.steering` ([extensions/energy-steering.md]). +//! +//! A steering server's choice, for one client, of the pathway that delivers a room's +//! stream for the fewest joules within the operator's limits, decided from the per-hop +//! session records (`WMH1`, [`crate::live_metering`]) that each pathway's hops signed. The +//! operator signs a **steering policy** (`WSP1`): the pathways, their hops and limits, and +//! the evidence window. For each request the server derives each pathway's evidence, its +//! joules per delivered byte or per delivered second at the floor tier with the records' +//! acquisition class, applies one integer rule, and signs a **steering decision** (`WSD1`) +//! naming the records, the client's report, its own inputs and the output, which it writes +//! as the steering manifest HLS and DASH players read. +//! +//! What this module implements, by section of the extension: +//! +//! - §3: the policy, its rules and its signature; +//! - §4: the client's report, read from a steering request over HLS or DASH and from CMCD, +//! with an RFC 9651 parser of its own ([`sf`]); +//! - §5: a pathway's window, coverage, status and figure; +//! - §6: the decision rule ([`decide`]); +//! - §7: the decision record, its rules and its signature; +//! - §8: the steering manifest, the HLS tag and the DASH element; +//! - §9: verification ([`verify`]). +//! +//! The corpus `live-conformance/steering/` is written by [`corpus::generate`] and checked +//! by [`corpus::verify`] here, and by `live-conformance/energy_steering_verify.py`, which +//! shares no code with this module. +//! +//! [extensions/energy-steering.md]: ../../extensions/energy-steering.md + +use std::cmp::Ordering; +use std::collections::BTreeSet; + +use ed25519_dalek::{Signer, SigningKey}; +use sha2::{Digest, Sha256}; + +use crate::key_log::{EntryStatus, Revision}; +use crate::live_events as events; +use crate::live_metering::{self as metering, HopRecord, HOP_AUTHORITIES}; +use crate::live_room::{self as room, Descriptor}; + +pub mod corpus; + +/// The record this extension registers. +pub const CAPABILITY: &str = "wai.live.steering"; +/// The magic of a steering policy (§3.1). +pub const POLICY_MAGIC: [u8; 4] = *b"WSP1"; +/// The magic of a steering decision (§7.1). +pub const DECISION_MAGIC: [u8; 4] = *b"WSD1"; +/// The version of both records in this revision. +pub const VERSION: u8 = 1; +/// The domain a policy's signature is made over (§3.4). +pub const POLICY_DOMAIN: &[u8] = b"wai:steering-policy\x01"; +/// The domain a decision's signature is made over (§7.3). +pub const DECISION_DOMAIN: &[u8] = b"wai:steering-decision\x01"; +/// The domain of a session's draw (§6.3). +pub const DRAW_DOMAIN: &[u8] = b"wai:steering-draw\x01"; +/// The key-log authority of a steering key (§3.5). +pub const STEER_AUTHORITY: &str = "live/steer"; +/// One million: shares and margins are in parts per million. +pub const PPM: u64 = 1_000_000; +/// A pathway's capacity when it has none (§3.2). +pub const NO_CAPACITY: u32 = u32::MAX; +/// A pathway's `base` when it is a pathway of the content description (§3.2). +pub const NOT_CLONE: u8 = 255; +/// The manifest member that names the decision (§8.4). +pub const MANIFEST_KEY: &str = "science.transaction.wai-decision"; +/// The query parameter `RELOAD-URI` carries the session in (§8.1). +pub const SESSION_PARAM: &str = "wai_session"; +/// The longest URI a policy states (§3.3). +pub const MAX_URI: usize = 2048; +/// The most pathways a policy holds (§3.3). +pub const MAX_PATHWAYS: usize = 32; +/// The most tiers a policy holds (§3.3): the room's media layer classes 0 to 7. +pub const MAX_TIERS: usize = 8; + +/// A hop's place in its pathway (§3.2). +pub mod place { + pub const EDGE: u8 = 1; + pub const INTERIOR: u8 = 2; +} + +/// What a pathway's delivery counts (§5.3). +pub mod basis { + /// Media bytes out of the edge hops. + pub const BYTES: u8 = 1; + /// Microseconds of delivery at the floor tier or above. + pub const SECONDS: u8 = 2; +} + +/// A pathway's status (§5.3). +pub mod status { + pub const FIGURE: u8 = 0; + pub const UNMETERED: u8 = 1; + pub const MIXED: u8 = 2; + pub const STALE: u8 = 3; + pub const EMPTY: u8 = 4; +} + +/// Why the first pathway is first (§6.4). +pub mod why { + pub const BUFFER: u8 = 1; + pub const FLOOR: u8 = 2; + pub const SHARE: u8 = 3; + pub const HOLD: u8 = 4; + pub const RANK: u8 = 5; + pub const NONE: u8 = 6; +} + +/// Which parameters a report was read from (§7.1). +pub mod protocol { + pub const HLS: u8 = 1; + pub const DASH: u8 = 2; +} + +/// The bits of a pathway's `limits` (§7.1), each set when the limit holds (§6.1). +pub mod limit { + pub const LATENCY: u8 = 1; + pub const QUALITY: u8 = 2; + pub const CAPACITY: u8 = 4; + pub const ALL: u8 = 7; +} + +/// The bits of a report's `present` (§7.1). +pub mod present { + pub const PATHWAY: u8 = 1; + pub const THROUGHPUT: u8 = 2; + pub const BUFFER: u8 = 4; +} + +// --------------------------------------------------------------------------------------- +// §10 Reasons +// --------------------------------------------------------------------------------------- + +/// Why a policy or a decision is refused (§10). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Reason { + /// A code of live-rooms §11 for the room. + Room(room::Reason), + /// A code of live-metering §9 (and the codes it reads), reused here. + Metering(metering::Reason), + PolicyInvalid, + CloneUnknown, + DecisionInvalid, + EvidenceNotOnPathway, + EvidenceOutsideWindow, + EvidenceMismatch, + UnmeteredAsZero, + UnknownPreferred, + DecisionMismatch, + ManifestMismatch, +} + +impl From for Reason { + fn from(r: metering::Reason) -> Self { + Reason::Metering(r) + } +} + +const fn ev(r: events::Reason) -> Reason { + Reason::Metering(metering::Reason::Room(room::Reason::Events(r))) +} + +const TRUNCATED: Reason = ev(events::Reason::Truncated); +const MALFORMED: Reason = ev(events::Reason::Malformed); +const UNKNOWN_VERSION: Reason = ev(events::Reason::UnknownVersion); +const WRONG_ROOM: Reason = ev(events::Reason::WrongRoom); +const BAD_KEY: Reason = ev(events::Reason::BadKey); +const BAD_SIGNATURE: Reason = ev(events::Reason::BadSignature); + +impl Reason { + /// The codes this extension adds, in §10's order. + pub const ALL: [Reason; 10] = [ + Reason::PolicyInvalid, + Reason::CloneUnknown, + Reason::DecisionInvalid, + Reason::EvidenceNotOnPathway, + Reason::EvidenceOutsideWindow, + Reason::EvidenceMismatch, + Reason::UnmeteredAsZero, + Reason::UnknownPreferred, + Reason::DecisionMismatch, + Reason::ManifestMismatch, + ]; + + /// The reason's code. + pub fn code(self) -> &'static str { + match self { + Reason::Room(r) => r.code(), + Reason::Metering(r) => r.code(), + Reason::PolicyInvalid => "policy_invalid", + Reason::CloneUnknown => "clone_unknown", + Reason::DecisionInvalid => "decision_invalid", + Reason::EvidenceNotOnPathway => "evidence_not_on_pathway", + Reason::EvidenceOutsideWindow => "evidence_outside_window", + Reason::EvidenceMismatch => "evidence_mismatch", + Reason::UnmeteredAsZero => "unmetered_as_zero", + Reason::UnknownPreferred => "unknown_preferred", + Reason::DecisionMismatch => "decision_mismatch", + Reason::ManifestMismatch => "manifest_mismatch", + } + } +} + +impl std::fmt::Display for Reason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.code()) + } +} + +impl std::error::Error for Reason {} + +/// Where a verification stopped (§9.1). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Place { + Room, + Policy, + Decision, + /// A record named under a pathway: the pathway's index and the record's place in its list. + Evidence(usize, usize), + /// A pathway's evidence, by its index. + Pathway(usize), + Output, + Manifest, +} + +// --------------------------------------------------------------------------------------- +// Reading +// --------------------------------------------------------------------------------------- + +fn sha256(parts: &[&[u8]]) -> [u8; 32] { + let mut h = Sha256::new(); + for p in parts { + h.update(p); + } + h.finalize().into() +} + +pub(crate) fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +/// A cursor; running out is `truncated`. +struct Rd<'a> { + b: &'a [u8], + p: usize, +} + +impl<'a> Rd<'a> { + fn take(&mut self, n: usize) -> Result<&'a [u8], Reason> { + let e = self.p.checked_add(n).filter(|&e| e <= self.b.len()).ok_or(TRUNCATED)?; + let s = &self.b[self.p..e]; + self.p = e; + Ok(s) + } + fn arr(&mut self) -> Result<[u8; N], Reason> { + Ok(self.take(N)?.try_into().expect("N bytes")) + } + fn u8(&mut self) -> Result { + Ok(self.take(1)?[0]) + } + fn u16(&mut self) -> Result { + Ok(u16::from_le_bytes(self.arr()?)) + } + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes(self.arr()?)) + } + fn u64(&mut self) -> Result { + Ok(u64::from_le_bytes(self.arr()?)) + } + fn u128(&mut self) -> Result { + Ok(u128::from_le_bytes(self.arr()?)) + } + fn short(&mut self) -> Result, Reason> { + let n = self.u8()? as usize; + Ok(self.take(n)?.to_vec()) + } + fn long(&mut self) -> Result, Reason> { + let n = self.u16()? as usize; + Ok(self.take(n)?.to_vec()) + } + fn done(&self) -> bool { + self.p == self.b.len() + } +} + +/// live-events §3.3's first two steps, with `magic` for `WEV1`. +fn read_head(b: &[u8], magic: &[u8; 4]) -> Result<(), Reason> { + if b.len() < 4 { + return Err(TRUNCATED); + } + if b[..4] != *magic { + return Err(MALFORMED); + } + if b.len() < 5 { + return Err(TRUNCATED); + } + if b[4] != VERSION { + return Err(UNKNOWN_VERSION); + } + Ok(()) +} + +fn verify_sig(key: &[u8; 32], domain: &[u8], body: &[u8], sig: &[u8; 64]) -> Result<(), Reason> { + let mut m = domain.to_vec(); + m.extend_from_slice(body); + crate::sig::verify_strict(key, &m, sig).map_err(|e| match e { + crate::sig::SigError::BadKey => BAD_KEY, + crate::sig::SigError::BadSignature => BAD_SIGNATURE, + }) +} + +fn sign(key: &SigningKey, domain: &[u8], body: &[u8]) -> [u8; 64] { + let mut m = domain.to_vec(); + m.extend_from_slice(body); + key.sign(&m).to_bytes() +} + +fn put_short(o: &mut Vec, s: &[u8]) { + o.push(u8::try_from(s.len()).expect("at most 255 bytes")); + o.extend_from_slice(s); +} + +fn put_long(o: &mut Vec, s: &[u8]) { + o.extend_from_slice(&u16::try_from(s.len()).expect("at most 65535 bytes").to_le_bytes()); + o.extend_from_slice(s); +} + +// --------------------------------------------------------------------------------------- +// Character rules (§3.3) +// --------------------------------------------------------------------------------------- + +fn is_hex(c: u8) -> bool { + c.is_ascii_hexdigit() +} + +/// Every `%` begins `%` and two hexadecimal digits. +fn percent_ok(s: &[u8]) -> bool { + let mut i = 0; + while i < s.len() { + if s[i] == b'%' { + if i + 2 >= s.len() || !is_hex(s[i + 1]) || !is_hex(s[i + 2]) { + return false; + } + i += 3; + } else { + i += 1; + } + } + true +} + +fn is_unreserved(c: u8) -> bool { + c.is_ascii_alphanumeric() || matches!(c, b'-' | b'.' | b'_' | b'~') +} + +/// RFC 3986's characters, never `#` (§3.3). +fn is_uri_char(c: u8) -> bool { + is_unreserved(c) || b":/?[]@!$&'()*+,;=%".contains(&c) +} + +fn uri_ok(s: &[u8]) -> bool { + !s.is_empty() && s.len() <= MAX_URI && s.iter().all(|&c| is_uri_char(c)) && percent_ok(s) +} + +/// A pathway ID (content-steering-04 §2), at most 64 bytes. +pub fn is_pathway_id(s: &[u8]) -> bool { + !s.is_empty() && s.len() <= 64 && s.iter().all(|&c| c.is_ascii_alphanumeric() || matches!(c, b'.' | b'-' | b'_')) +} + +fn host_ok(s: &[u8]) -> bool { + s.iter().all(|&c| c.is_ascii_alphanumeric() || matches!(c, b'-' | b'.')) +} + +fn param_text_ok(s: &[u8]) -> bool { + s.iter().all(|&c| is_unreserved(c) || c == b'%') && percent_ok(s) +} + +fn text(b: Vec) -> String { + // Only bytes the rules admit reach here, all ASCII; anything else was refused first. + String::from_utf8(b).unwrap_or_default() +} + +// --------------------------------------------------------------------------------------- +// §3 The steering policy +// --------------------------------------------------------------------------------------- + +/// A pathway entry (§3.2). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Pathway { + pub id: String, + /// [`NOT_CLONE`], or the index of the pathway it clones. + pub base: u8, + pub host: String, + pub params: Vec<(String, String)>, + pub top_tier: u8, + pub latency_ms: u32, + pub capacity: u32, + pub min_share_ppm: u32, + /// Each hop's key and its place ([`place`]). + pub hops: Vec<([u8; 32], u8)>, +} + +impl Pathway { + pub fn is_clone(&self) -> bool { + self.base != NOT_CLONE + } + + fn encode(&self, o: &mut Vec) { + put_short(o, self.id.as_bytes()); + o.push(self.base); + put_short(o, self.host.as_bytes()); + o.push(u8::try_from(self.params.len()).expect("at most 255 params")); + for (n, v) in &self.params { + put_short(o, n.as_bytes()); + put_short(o, v.as_bytes()); + } + o.push(self.top_tier); + o.extend_from_slice(&self.latency_ms.to_le_bytes()); + o.extend_from_slice(&self.capacity.to_le_bytes()); + o.extend_from_slice(&self.min_share_ppm.to_le_bytes()); + o.push(u8::try_from(self.hops.len()).expect("at most 255 hops")); + for (k, p) in &self.hops { + o.extend_from_slice(k); + o.push(*p); + } + } +} + +/// A pathway entry as read, its texts as bytes until the rules hold. +struct RawPathway { + id: Vec, + base: u8, + host: Vec, + params: Vec<(Vec, Vec)>, + top_tier: u8, + latency_ms: u32, + capacity: u32, + min_share_ppm: u32, + hops: Vec<([u8; 32], u8)>, +} + +/// A steering policy (§3.1). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Policy { + pub room: [u8; 32], + pub signer: [u8; 32], + pub period_us: u64, + pub window_us: u64, + pub lag_us: u64, + pub basis: u8, + pub floor: u8, + pub latency_ceiling_ms: u32, + pub min_buffer_ms: u32, + pub hysteresis_ppm: u32, + pub ttl_s: u32, + pub min_ttl_s: u32, + pub flags: u8, + pub initial: u8, + /// Each tier's bitrate in bits per second, tier 0 first. + pub tiers: Vec, + pub server_uri: String, + pub reload_uri: String, + pub decision_uri: String, + pub pathways: Vec, + pub sig: [u8; 64], +} + +impl Policy { + /// The policy without `sig` (§3.1). + pub fn body(&self) -> Vec { + let mut o = Vec::new(); + o.extend_from_slice(&POLICY_MAGIC); + o.push(VERSION); + o.extend_from_slice(&self.room); + o.extend_from_slice(&self.signer); + for x in [self.period_us, self.window_us, self.lag_us] { + o.extend_from_slice(&x.to_le_bytes()); + } + o.push(self.basis); + o.push(self.floor); + for x in [self.latency_ceiling_ms, self.min_buffer_ms, self.hysteresis_ppm, self.ttl_s, self.min_ttl_s] { + o.extend_from_slice(&x.to_le_bytes()); + } + o.push(self.flags); + o.push(self.initial); + o.push(u8::try_from(self.tiers.len()).expect("at most 255 tiers")); + for t in &self.tiers { + o.extend_from_slice(&t.to_le_bytes()); + } + put_long(&mut o, self.server_uri.as_bytes()); + put_long(&mut o, self.reload_uri.as_bytes()); + put_long(&mut o, self.decision_uri.as_bytes()); + o.push(u8::try_from(self.pathways.len()).expect("at most 255 pathways")); + for p in &self.pathways { + p.encode(&mut o); + } + o + } + + pub fn to_bytes(&self) -> Vec { + let mut o = self.body(); + o.extend_from_slice(&self.sig); + o + } + + /// The policy's digest (§3.4). + pub fn digest(&self) -> [u8; 32] { + sha256(&[&self.body()]) + } + + /// Signed under `key`, which becomes the signer. + pub fn sign(mut self, key: &SigningKey) -> Policy { + self.signer = key.verifying_key().to_bytes(); + self.sig = sign(key, POLICY_DOMAIN, &self.body()); + self + } + + /// The index of the pathway whose `id` is `id`. + pub fn index_of(&self, id: &[u8]) -> Option { + self.pathways.iter().position(|p| p.id.as_bytes() == id) + } + + /// Read a policy (§3.3): its layout, then `clone_unknown`, then `policy_invalid`. The + /// room and the signature are checked by [`verify`]. + pub fn read(b: &[u8]) -> Result { + read_head(b, &POLICY_MAGIC)?; + let mut r = Rd { b, p: 5 }; + let (room, signer) = (r.arr()?, r.arr()?); + let (period_us, window_us, lag_us) = (r.u64()?, r.u64()?, r.u64()?); + let (basis, floor) = (r.u8()?, r.u8()?); + let (latency_ceiling_ms, min_buffer_ms, hysteresis_ppm, ttl_s, min_ttl_s) = (r.u32()?, r.u32()?, r.u32()?, r.u32()?, r.u32()?); + let (flags, initial) = (r.u8()?, r.u8()?); + let n = r.u8()? as usize; + let mut tiers = Vec::with_capacity(n); + for _ in 0..n { + tiers.push(r.u64()?); + } + let (server_uri, reload_uri, decision_uri) = (r.long()?, r.long()?, r.long()?); + let np = r.u8()? as usize; + let mut raw = Vec::with_capacity(np); + for _ in 0..np { + let id = r.short()?; + let base = r.u8()?; + let host = r.short()?; + let mut params = Vec::new(); + for _ in 0..r.u8()? { + params.push((r.short()?, r.short()?)); + } + let top_tier = r.u8()?; + let (latency_ms, capacity, min_share_ppm) = (r.u32()?, r.u32()?, r.u32()?); + let mut hops = Vec::new(); + for _ in 0..r.u8()? { + hops.push((r.arr()?, r.u8()?)); + } + raw.push(RawPathway { id, base, host, params, top_tier, latency_ms, capacity, min_share_ppm, hops }); + } + let sig = r.arr()?; + if !r.done() { + return Err(MALFORMED); + } + if raw.iter().enumerate().any(|(i, p)| p.base != NOT_CLONE && p.base as usize >= i) { + return Err(Reason::CloneUnknown); + } + let uris_ok = [&server_uri, &reload_uri, &decision_uri].iter().all(|u| uri_ok(u)); + let mut ids = BTreeSet::new(); + let mut keys = BTreeSet::new(); + let mut shares = 0u64; + let mut pathways_ok = true; + for p in &raw { + pathways_ok &= is_pathway_id(&p.id) && ids.insert(p.id.clone()); + pathways_ok &= if p.base == NOT_CLONE { + p.host.is_empty() && p.params.is_empty() + } else { + host_ok(&p.host) + && p.params.iter().all(|(k, v)| (1..=64).contains(&k.len()) && param_text_ok(k) && param_text_ok(v)) + && p.params.windows(2).all(|w| w[0].0 < w[1].0) + }; + pathways_ok &= (p.top_tier as usize) < n; + pathways_ok &= !p.hops.is_empty() && p.hops.iter().any(|h| h.1 == place::EDGE); + pathways_ok &= p.hops.iter().all(|h| matches!(h.1, place::EDGE | place::INTERIOR) && keys.insert(h.0)); + shares += p.min_share_ppm as u64; + } + let rules = period_us > 0 + && window_us > 0 + && window_us.is_multiple_of(period_us) + && matches!(basis, basis::BYTES | basis::SECONDS) + && 1 <= min_ttl_s + && min_ttl_s <= ttl_s + && hysteresis_ppm as u64 <= PPM + && flags <= 1 + && (1..=MAX_TIERS).contains(&n) + && tiers[0] > 0 + && tiers.windows(2).all(|w| w[0] < w[1]) + && (floor as usize) < n + && uris_ok + && (1..=MAX_PATHWAYS).contains(&np) + && pathways_ok + && raw.get(initial as usize).is_some_and(|p| p.base == NOT_CLONE) + && shares <= PPM; + if !rules { + return Err(Reason::PolicyInvalid); + } + let pathways = raw + .into_iter() + .map(|p| Pathway { + id: text(p.id), + base: p.base, + host: text(p.host), + params: p.params.into_iter().map(|(k, v)| (text(k), text(v))).collect(), + top_tier: p.top_tier, + latency_ms: p.latency_ms, + capacity: p.capacity, + min_share_ppm: p.min_share_ppm, + hops: p.hops, + }) + .collect(); + Ok(Policy { + room, + signer, + period_us, + window_us, + lag_us, + basis, + floor, + latency_ceiling_ms, + min_buffer_ms, + hysteresis_ppm, + ttl_s, + min_ttl_s, + flags, + initial, + tiers, + server_uri: text(server_uri), + reload_uri: text(reload_uri), + decision_uri: text(decision_uri), + pathways, + sig, + }) + } + + /// The pathway a hop key belongs to, with the hop's index in it. + pub fn hop(&self, key: &[u8; 32]) -> Option<(usize, usize)> { + self.pathways.iter().enumerate().find_map(|(i, p)| p.hops.iter().position(|h| h.0 == *key).map(|k| (i, k))) + } +} + +// --------------------------------------------------------------------------------------- +// §4 The client's report +// --------------------------------------------------------------------------------------- + +/// The client's report as the server read it (§4, §7.1). +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Report { + pub present: u8, + pub pathway: u8, + pub throughput: u64, + pub buffer: u64, +} + +impl Report { + /// *c*. + pub fn pathway(&self) -> Option { + (self.present & present::PATHWAY != 0).then_some(self.pathway as usize) + } + /// *T*. + pub fn throughput(&self) -> Option { + (self.present & present::THROUGHPUT != 0).then_some(self.throughput) + } + /// *B*. + pub fn buffer(&self) -> Option { + (self.present & present::BUFFER != 0).then_some(self.buffer) + } + + pub fn new(c: Option, t: Option, b: Option) -> Report { + let mut r = Report::default(); + if let Some(c) = c { + r.present |= present::PATHWAY; + r.pathway = u8::try_from(c).expect("a pathway index"); + } + if let Some(t) = t { + r.present |= present::THROUGHPUT; + r.throughput = t; + } + if let Some(b) = b { + r.present |= present::BUFFER; + r.buffer = b; + } + r + } + + fn encode(&self, o: &mut Vec) { + o.push(self.present); + o.push(self.pathway); + o.extend_from_slice(&self.throughput.to_le_bytes()); + o.extend_from_slice(&self.buffer.to_le_bytes()); + } +} + +/// Percent-decode (RFC 3986 §2.1); `None` for a `%` not followed by two hexadecimal digits. +fn percent_decode(s: &[u8]) -> Option> { + let mut o = Vec::with_capacity(s.len()); + let mut i = 0; + while i < s.len() { + if s[i] == b'%' { + let h = s.get(i + 1..i + 3)?; + if !h.iter().all(|&c| is_hex(c)) { + return None; + } + o.push(u8::from_str_radix(std::str::from_utf8(h).ok()?, 16).ok()?); + i += 3; + } else { + o.push(s[i]); + i += 1; + } + } + Some(o) +} + +/// The query's parts (§4.1), decoded, in order; parts that do not decode are skipped. +fn query_parts(target: &[u8]) -> Vec<(Vec, Vec)> { + let Some(q) = target.iter().position(|&c| c == b'?') else { return vec![] }; + let rest = &target[q + 1..]; + let rest = &rest[..rest.iter().position(|&c| c == b'#').unwrap_or(rest.len())]; + let mut out = Vec::new(); + for part in rest.split(|&c| c == b'&') { + let (n, v) = match part.iter().position(|&c| c == b'=') { + Some(e) => (&part[..e], &part[e + 1..]), + None => (part, &part[part.len()..]), + }; + if let (Some(n), Some(v)) = (percent_decode(n), percent_decode(v)) { + out.push((n, v)); + } + } + out +} + +fn first<'a>(parts: &'a [(Vec, Vec)], name: &str) -> Option<&'a [u8]> { + parts.iter().find(|(n, _)| n == name.as_bytes()).map(|(_, v)| v.as_slice()) +} + +fn unquote(v: &[u8]) -> &[u8] { + if v.len() >= 2 && v[0] == b'"' && v[v.len() - 1] == b'"' { &v[1..v.len() - 1] } else { v } +} + +/// An integer of §4.1: 1 to 20 ASCII digits, at most 2^64 − 1. +fn integer(v: &[u8]) -> Option { + if v.is_empty() || v.len() > 20 || !v.iter().all(u8::is_ascii_digit) { + return None; + } + std::str::from_utf8(v).ok()?.parse::().ok() +} + +/// A CMCD key's value read as §4.4 reads it: the least of an Integer or an Inner List of them. +fn cmcd_least(m: &sf::Member) -> Option { + let ints: Vec = match m { + sf::Member::Item(sf::Item { bare: sf::Bare::Integer(x), .. }) => vec![*x], + sf::Member::Inner(items, _) if !items.is_empty() => { + let mut v = Vec::new(); + for it in items { + match it.bare { + sf::Bare::Integer(x) => v.push(x), + _ => return None, + } + } + v + } + _ => return None, + }; + if ints.iter().any(|&x| x < 0) { + return None; + } + ints.into_iter().min().map(|x| x as u64) +} + +/// Read a steering request (§4): its target (path and query) and the value of its +/// `CMCD-Request` header, when it has one. +pub fn read_request(protocol: u8, target: &[u8], cmcd_header: Option<&[u8]>, policy: &Policy) -> Report { + let parts = query_parts(target); + let (mut c, mut t) = (None, None); + match protocol { + protocol::HLS => { + c = first(&parts, "_HLS_pathway").and_then(|v| policy.index_of(unquote(v))); + t = first(&parts, "_HLS_throughput").and_then(integer); + } + _ => { + let mut j = 0; + if let Some(v) = first(&parts, "_DASH_pathway") { + for (k, item) in unquote(v).split(|&x| x == b',').enumerate() { + if let Some(i) = policy.index_of(item) { + c = Some(i); + j = k; + break; + } + } + } + t = first(&parts, "_DASH_throughput").and_then(|v| v.split(|&x| x == b',').nth(j)).and_then(integer); + } + } + let cmcd = match first(&parts, "CMCD") { + Some(v) => Some(v), + None => cmcd_header, + }; + let dict = cmcd.and_then(|v| std::str::from_utf8(v).ok()).and_then(|s| sf::parse_dictionary(s).ok()); + let get = |k: &str| dict.as_ref().and_then(|d| d.iter().find(|(n, _)| n == k)).and_then(|(_, m)| cmcd_least(m)); + let b = get("bl"); + if t.is_none() { + t = get("mtp").and_then(|k| k.checked_mul(1000)); + } + Report::new(c, t, b) +} + +/// RFC 9651 §4.2: Structured Field Values, parsed whole. +pub mod sf { + /// A bare item (RFC 9651 §3.3). + #[derive(Clone, Debug, PartialEq, Eq)] + pub enum Bare { + Integer(i64), + /// A Decimal, as its value × 1000. + Decimal(i64), + String(Vec), + Token(String), + Bytes(Vec), + Boolean(bool), + Date(i64), + Display(String), + } + + /// An item with its parameters. + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct Item { + pub bare: Bare, + pub params: Vec<(String, Bare)>, + } + + /// A dictionary member: an item, or an inner list with its parameters. + #[derive(Clone, Debug, PartialEq, Eq)] + pub enum Member { + Item(Item), + Inner(Vec, Vec<(String, Bare)>), + } + + struct P<'a> { + b: &'a [u8], + i: usize, + } + + type R = Result; + + impl P<'_> { + fn peek(&self) -> Option { + self.b.get(self.i).copied() + } + fn sp(&mut self) { + while self.peek() == Some(b' ') { + self.i += 1; + } + } + fn ows(&mut self) { + while matches!(self.peek(), Some(b' ' | b'\t')) { + self.i += 1; + } + } + fn key(&mut self) -> R { + match self.peek() { + Some(c) if c.is_ascii_lowercase() || c == b'*' => {} + _ => return Err(()), + } + let s = self.i; + while let Some(c) = self.peek() { + if c.is_ascii_lowercase() || c.is_ascii_digit() || matches!(c, b'_' | b'-' | b'.' | b'*') { + self.i += 1; + } else { + break; + } + } + String::from_utf8(self.b[s..self.i].to_vec()).map_err(|_| ()) + } + fn params(&mut self) -> R> { + let mut out: Vec<(String, Bare)> = Vec::new(); + while self.peek() == Some(b';') { + self.i += 1; + self.sp(); + let k = self.key()?; + let v = if self.peek() == Some(b'=') { + self.i += 1; + self.bare()? + } else { + Bare::Boolean(true) + }; + match out.iter_mut().find(|(x, _)| *x == k) { + Some(slot) => slot.1 = v, + None => out.push((k, v)), + } + } + Ok(out) + } + fn item(&mut self) -> R { + let bare = self.bare()?; + let params = self.params()?; + Ok(Item { bare, params }) + } + fn inner(&mut self) -> R { + self.i += 1; // ( + let mut items = Vec::new(); + loop { + self.sp(); + if self.peek() == Some(b')') { + self.i += 1; + let params = self.params()?; + return Ok(Member::Inner(items, params)); + } + items.push(self.item()?); + match self.peek() { + Some(b' ' | b')') => {} + _ => return Err(()), + } + } + } + fn member(&mut self) -> R { + if self.peek() == Some(b'(') { self.inner() } else { Ok(Member::Item(self.item()?)) } + } + fn number(&mut self) -> R { + let neg = if self.peek() == Some(b'-') { + self.i += 1; + true + } else { + false + }; + if !self.peek().is_some_and(|c| c.is_ascii_digit()) { + return Err(()); + } + let s = self.i; + let mut dot = None; + while let Some(c) = self.peek() { + if c.is_ascii_digit() { + self.i += 1; + } else if c == b'.' && dot.is_none() { + if self.i - s > 12 { + return Err(()); + } + dot = Some(self.i); + self.i += 1; + } else { + break; + } + let len = self.i - s; + if dot.is_none() && len > 15 || dot.is_some() && len > 16 { + return Err(()); + } + } + let digits = std::str::from_utf8(&self.b[s..self.i]).map_err(|_| ())?; + let sign = if neg { -1 } else { 1 }; + match dot { + None => Ok(Bare::Integer(sign * digits.parse::().map_err(|_| ())?)), + Some(d) => { + let frac = self.i - d - 1; + if frac == 0 || frac > 3 { + return Err(()); + } + let (int, f) = digits.split_at(d - s); + let f = &f[1..]; + let scaled = int.parse::().map_err(|_| ())? * 1000 + f.parse::().map_err(|_| ())? * 10i64.pow(3 - frac as u32); + Ok(Bare::Decimal(sign * scaled)) + } + } + } + fn bare(&mut self) -> R { + match self.peek().ok_or(())? { + b'-' | b'0'..=b'9' => self.number(), + b'"' => { + self.i += 1; + let mut o = Vec::new(); + loop { + let c = self.peek().ok_or(())?; + self.i += 1; + match c { + b'\\' => { + let n = self.peek().ok_or(())?; + if n != b'"' && n != b'\\' { + return Err(()); + } + self.i += 1; + o.push(n); + } + b'"' => return Ok(Bare::String(o)), + 0x20..=0x7e => o.push(c), + _ => return Err(()), + } + } + } + c if c.is_ascii_alphabetic() || c == b'*' => { + let s = self.i; + self.i += 1; + while let Some(c) = self.peek() { + if c.is_ascii_alphanumeric() || b"!#$%&'*+-.^_`|~:/".contains(&c) { + self.i += 1; + } else { + break; + } + } + Ok(Bare::Token(String::from_utf8(self.b[s..self.i].to_vec()).map_err(|_| ())?)) + } + b':' => { + self.i += 1; + let s = self.i; + loop { + let c = self.peek().ok_or(())?; + if c == b':' { + break; + } + if !(c.is_ascii_alphanumeric() || matches!(c, b'+' | b'/' | b'=')) { + return Err(()); + } + self.i += 1; + } + let v = self.b[s..self.i].to_vec(); + self.i += 1; + Ok(Bare::Bytes(v)) + } + b'?' => { + self.i += 1; + let v = match self.peek() { + Some(b'0') => false, + Some(b'1') => true, + _ => return Err(()), + }; + self.i += 1; + Ok(Bare::Boolean(v)) + } + b'@' => { + self.i += 1; + match self.number()? { + Bare::Integer(x) => Ok(Bare::Date(x)), + _ => Err(()), + } + } + b'%' => { + self.i += 1; + if self.peek() != Some(b'"') { + return Err(()); + } + self.i += 1; + let mut o = Vec::new(); + loop { + let c = self.peek().ok_or(())?; + self.i += 1; + match c { + b'%' => { + let h = self.b.get(self.i..self.i + 2).ok_or(())?; + if !h.iter().all(|&x| x.is_ascii_digit() || (b'a'..=b'f').contains(&x)) { + return Err(()); + } + o.push(u8::from_str_radix(std::str::from_utf8(h).map_err(|_| ())?, 16).map_err(|_| ())?); + self.i += 2; + } + b'"' => return Ok(Bare::Display(String::from_utf8(o).map_err(|_| ())?)), + 0x20..=0x7e => o.push(c), + _ => return Err(()), + } + } + } + _ => Err(()), + } + } + } + + /// Parse a Dictionary field value (RFC 9651 §4.2, §4.2.2). A key given twice keeps its + /// first place and its last value. + pub fn parse_dictionary(s: &str) -> Result, ()> { + let mut p = P { b: s.as_bytes(), i: 0 }; + p.sp(); + let mut out: Vec<(String, Member)> = Vec::new(); + while p.i < p.b.len() { + let k = p.key()?; + let v = if p.peek() == Some(b'=') { + p.i += 1; + p.member()? + } else { + Member::Item(Item { bare: Bare::Boolean(true), params: p.params()? }) + }; + match out.iter_mut().find(|(x, _)| *x == k) { + Some(slot) => slot.1 = v, + None => out.push((k, v)), + } + p.ows(); + if p.i == p.b.len() { + break; + } + if p.peek() != Some(b',') { + return Err(()); + } + p.i += 1; + p.ows(); + if p.i == p.b.len() { + return Err(()); + } + } + Ok(out) + } +} + +// --------------------------------------------------------------------------------------- +// §5 A pathway's evidence +// --------------------------------------------------------------------------------------- + +/// The window at `t` (§5.1): [`a`, `b`). +pub fn window(p: &Policy, t: u64) -> (u64, u64) { + let b = t.checked_sub(p.lag_us).map_or(0, |x| x / p.period_us * p.period_us); + (b.saturating_sub(p.window_us), b) +} + +/// A pathway's evidence (§5.3). +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Evidence { + pub status: u8, + /// The figure's class tag, 0 for no figure. + pub class: u8, + pub joules: u128, + pub delivery: u128, +} + +impl Evidence { + pub fn has_figure(&self) -> bool { + self.status == status::FIGURE + } + + /// The rank of §6.2: 0 to 3 by class, 4 for no figure. + pub fn rank(&self) -> u8 { + if !self.has_figure() { + return 4; + } + match self.class { + 5 => 0, + 4 => 1, + 2 => 2, + _ => 3, + } + } +} + +/// What a pathway's records give before the status is chosen: its joules, its delivery and +/// the sums of its joules by class. +struct Sums { + joules: u128, + delivery: u128, + by_class: [u128; 6], + unmetered: bool, +} + +fn sums(p: &Policy, i: usize, recs: &[HopRecord]) -> Sums { + let path = &p.pathways[i]; + let mut s = Sums { joules: 0, delivery: 0, by_class: [0; 6], unmetered: false }; + for r in recs { + s.joules += r.joules as u128; + let tag = metering::tag_of(r.class.as_ref()); + s.by_class[tag as usize] += r.joules as u128; + s.unmetered |= tag == 0; + let edge = path.hops.iter().any(|h| h.0 == r.hop && h.1 == place::EDGE); + if edge { + s.delivery += match p.basis { + basis::BYTES => r.tracks.iter().filter(|e| e.kind == metering::kind::MEDIA && e.dir == metering::dir::OUT).map(|e| e.bytes as u128).sum::(), + _ => (p.floor as usize..8).map(|c| r.subscriber_us[c] as u128).sum(), + }; + } + } + s +} + +/// Whether a hop's named records tile the window (§5.2). +fn covered(recs: &[&HopRecord], a: u64, b: u64) -> bool { + !recs.is_empty() && recs[0].start == a && recs.windows(2).all(|w| w[1].start == w[0].end) && recs[recs.len() - 1].end == b +} + +/// A pathway's evidence from the records named for it (§5.2, §5.3), which are its hops' +/// records, in the order of §5.2, each in the window. +pub fn evidence(p: &Policy, i: usize, recs: &[HopRecord], t: u64) -> Evidence { + let (a, b) = window(p, t); + let path = &p.pathways[i]; + let stale = path.hops.iter().any(|h| { + let mine: Vec<&HopRecord> = recs.iter().filter(|r| r.hop == h.0).collect(); + !covered(&mine, a, b) + }); + if stale { + return Evidence { status: status::STALE, ..Evidence::default() }; + } + let s = sums(p, i, recs); + if s.unmetered { + return Evidence { status: status::UNMETERED, ..Evidence::default() }; + } + let tags: BTreeSet = recs.iter().map(|r| metering::tag_of(r.class.as_ref())).collect(); + if tags.len() > 1 { + return Evidence { status: status::MIXED, ..Evidence::default() }; + } + if s.delivery == 0 { + return Evidence { status: status::EMPTY, ..Evidence::default() }; + } + Evidence { status: status::FIGURE, class: *tags.iter().next().expect("a hop has a record"), joules: s.joules, delivery: s.delivery } +} + +// --------------------------------------------------------------------------------------- +// Exact products (§2) +// --------------------------------------------------------------------------------------- + +/// The product of `xs`, in full, as little-endian u64 limbs. +fn product(xs: &[u128]) -> Vec { + let mut acc: Vec = vec![1]; + for &x in xs { + let b = [x as u64, (x >> 64) as u64]; + let mut out = vec![0u64; acc.len() + 2]; + for (i, &ai) in acc.iter().enumerate() { + let mut carry = 0u128; + for (j, &bj) in b.iter().enumerate() { + let t = out[i + j] as u128 + ai as u128 * bj as u128 + carry; + out[i + j] = t as u64; + carry = t >> 64; + } + let mut k = i + b.len(); + while carry != 0 { + let t = out[k] as u128 + carry; + out[k] = t as u64; + carry = t >> 64; + k += 1; + } + } + acc = out; + } + acc +} + +fn cmp_limbs(a: &[u64], b: &[u64]) -> Ordering { + let n = a.len().max(b.len()); + for i in (0..n).rev() { + let (x, y) = (a.get(i).copied().unwrap_or(0), b.get(i).copied().unwrap_or(0)); + if x != y { + return x.cmp(&y); + } + } + Ordering::Equal +} + +/// `J_p × D_q` against `J_q × D_p`: `Less` when *p*'s figure is the lower (§6.2 step 2). +fn cmp_figures(p: &Evidence, q: &Evidence) -> Ordering { + cmp_limbs(&product(&[p.joules, q.delivery]), &product(&[q.joules, p.delivery])) +} + +/// Whether *w* beats *x* by more than the margin `h` (§6.4). +fn beats(w: &Evidence, x: &Evidence, h: u32) -> bool { + w.has_figure() && x.has_figure() && cmp_limbs(&product(&[w.joules, x.delivery, PPM as u128 + h as u128]), &product(&[x.joules, w.delivery, PPM as u128])) == Ordering::Less +} + +// --------------------------------------------------------------------------------------- +// §6 The decision rule +// --------------------------------------------------------------------------------------- + +/// The draw of a session (§6.3). +pub fn draw(policy_digest: &[u8; 32], session: &[u8; 16]) -> u32 { + let h = sha256(&[DRAW_DOMAIN, policy_digest, session]); + u32::from_le_bytes(h[..4].try_into().expect("four bytes")) % PPM as u32 +} + +/// The order of §6.2 over every pathway. +pub fn order(p: &Policy, ev: &[Evidence]) -> Vec { + let mut o: Vec = (0..p.pathways.len()).collect(); + o.sort_by(|&x, &y| { + let (ex, ey) = (&ev[x], &ev[y]); + ex.rank() + .cmp(&ey.rank()) + .then_with(|| if ex.has_figure() && ey.has_figure() && ex.class == ey.class { cmp_figures(ex, ey) } else { Ordering::Equal }) + .then_with(|| p.pathways[x].id.as_bytes().cmp(p.pathways[y].id.as_bytes())) + }); + o +} + +/// What the rule gives (§6). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Output { + pub draw: u32, + /// Each pathway's limits ([`limit`]), in the policy's order. + pub limits: Vec, + pub priority: Vec, + pub ttl: u32, + pub reason: u8, +} + +/// The decision rule (§6): the policy, its digest, the session, the report, each +/// pathway's load and evidence. +pub fn decide(p: &Policy, digest: &[u8; 32], session: &[u8; 16], report: &Report, loads: &[u32], ev: &[Evidence]) -> Output { + let n = p.pathways.len(); + let c = report.pathway(); + let limits: Vec = (0..n) + .map(|i| { + let x = &p.pathways[i]; + let mut l = 0; + if x.latency_ms <= p.latency_ceiling_ms { + l |= limit::LATENCY; + } + if x.top_tier >= p.floor { + l |= limit::QUALITY; + } + if x.capacity == NO_CAPACITY || loads[i] < x.capacity || (c == Some(i) && loads[i] <= x.capacity) { + l |= limit::CAPACITY; + } + l + }) + .collect(); + let within = |i: usize| limits[i] == limit::ALL; + let ord = order(p, ev); + let d = draw(digest, session); + let sustainable = report.throughput().map(|t| p.tiers.iter().rposition(|&b| b <= t)); + let at_floor = sustainable.is_some_and(|k| k.is_none_or(|k| k <= p.floor as usize)); + let c_within = c.is_some_and(within); + let w = ord.iter().copied().find(|&i| within(i)); + let band = { + let mut s = 0u64; + let mut hit = None; + for (i, x) in p.pathways.iter().enumerate() { + let e = s + x.min_share_ppm as u64; + if (d as u64) >= s && (d as u64) < e { + hit = Some(i); + } + s = e; + } + hit + }; + let (first, reason) = if c_within && report.buffer().is_some_and(|b| b < p.min_buffer_ms as u64) { + (c.expect("c is known"), why::BUFFER) + } else if c_within && at_floor { + (c.expect("c is known"), why::FLOOR) + } else if w.is_none() { + (c.unwrap_or(ord[0]), why::NONE) + } else if let Some(e) = band.filter(|&e| within(e)) { + (e, why::SHARE) + } else { + let w = w.expect("some pathway is within the limits"); + match c { + Some(c) if c_within && c != w && ev[c].rank() == ev[w].rank() && (ev[w].rank() == 4 || !beats(&ev[w], &ev[c], p.hysteresis_ppm)) => (c, why::HOLD), + _ => (w, why::RANK), + } + }; + let mut priority = vec![first as u8]; + priority.extend(ord.iter().filter(|&&i| i != first && within(i)).map(|&i| i as u8)); + priority.extend(ord.iter().filter(|&&i| i != first && !within(i)).map(|&i| i as u8)); + let ttl = if matches!(reason, why::BUFFER | why::FLOOR | why::NONE) { p.min_ttl_s } else { p.ttl_s }; + Output { draw: d, limits, priority, ttl, reason } +} + +// --------------------------------------------------------------------------------------- +// §7 The steering decision +// --------------------------------------------------------------------------------------- + +/// A pathway's entry in a decision (§7.1). +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PathwayState { + pub load: u32, + pub limits: u8, + pub status: u8, + pub class: u8, + pub joules: u128, + pub delivery: u128, + /// The digests of the records named for it, in the order of §5.2. + pub evidence: Vec<[u8; 32]>, +} + +impl PathwayState { + pub fn stated(&self) -> Evidence { + Evidence { status: self.status, class: self.class, joules: self.joules, delivery: self.delivery } + } +} + +/// A steering decision (§7.1). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Decision { + pub room: [u8; 32], + pub signer: [u8; 32], + pub policy: [u8; 32], + pub t: u64, + pub session: [u8; 16], + pub draw: u32, + pub protocol: u8, + pub report: Report, + pub pathways: Vec, + pub ttl: u32, + pub reason: u8, + pub priority: Vec, + pub sig: [u8; 64], +} + +impl Decision { + /// The decision without `sig` (§7.1). + pub fn body(&self) -> Vec { + let mut o = Vec::new(); + o.extend_from_slice(&DECISION_MAGIC); + o.push(VERSION); + o.extend_from_slice(&self.room); + o.extend_from_slice(&self.signer); + o.extend_from_slice(&self.policy); + o.extend_from_slice(&self.t.to_le_bytes()); + o.extend_from_slice(&self.session); + o.extend_from_slice(&self.draw.to_le_bytes()); + o.push(self.protocol); + self.report.encode(&mut o); + o.push(u8::try_from(self.pathways.len()).expect("at most 255 pathways")); + for s in &self.pathways { + o.extend_from_slice(&s.load.to_le_bytes()); + o.push(s.limits); + o.push(s.status); + o.push(s.class); + o.extend_from_slice(&s.joules.to_le_bytes()); + o.extend_from_slice(&s.delivery.to_le_bytes()); + o.push(u8::try_from(s.evidence.len()).expect("at most 255 records")); + for d in &s.evidence { + o.extend_from_slice(d); + } + } + o.extend_from_slice(&self.ttl.to_le_bytes()); + o.push(self.reason); + o.push(u8::try_from(self.priority.len()).expect("at most 255 pathways")); + o.extend_from_slice(&self.priority); + o + } + + pub fn to_bytes(&self) -> Vec { + let mut o = self.body(); + o.extend_from_slice(&self.sig); + o + } + + /// The decision's digest (§7.3). + pub fn digest(&self) -> [u8; 32] { + sha256(&[&self.body()]) + } + + /// Signed under `key`, which becomes the signer. + pub fn sign(mut self, key: &SigningKey) -> Decision { + self.signer = key.verifying_key().to_bytes(); + self.sig = sign(key, DECISION_DOMAIN, &self.body()); + self + } + + /// Read a decision (§7.2): its layout, then `decision_invalid`. + pub fn read(b: &[u8]) -> Result { + read_head(b, &DECISION_MAGIC)?; + let mut r = Rd { b, p: 5 }; + let (room, signer, policy) = (r.arr()?, r.arr()?, r.arr()?); + let t = r.u64()?; + let session = r.arr()?; + let draw = r.u32()?; + let protocol = r.u8()?; + let report = Report { present: r.u8()?, pathway: r.u8()?, throughput: r.u64()?, buffer: r.u64()? }; + let n = r.u8()? as usize; + let mut pathways = Vec::with_capacity(n); + for _ in 0..n { + let (load, limits, status, class) = (r.u32()?, r.u8()?, r.u8()?, r.u8()?); + let (joules, delivery) = (r.u128()?, r.u128()?); + let mut evidence = Vec::new(); + for _ in 0..r.u8()? { + evidence.push(r.arr()?); + } + pathways.push(PathwayState { load, limits, status, class, joules, delivery, evidence }); + } + let ttl = r.u32()?; + let reason = r.u8()?; + let m = r.u8()? as usize; + let priority = r.take(m)?.to_vec(); + let sig = r.arr()?; + if !r.done() { + return Err(MALFORMED); + } + let rep = &report; + let report_ok = rep.present & !7 == 0 + && (rep.present & present::PATHWAY != 0 || rep.pathway == 0) + && (rep.present & present::THROUGHPUT != 0 || rep.throughput == 0) + && (rep.present & present::BUFFER != 0 || rep.buffer == 0) + && (rep.present & present::PATHWAY == 0 || (rep.pathway as usize) < n); + let states_ok = pathways.iter().all(|s| { + s.limits & !limit::ALL == 0 + && s.status <= status::EMPTY + && if s.status == status::FIGURE { (2..=5).contains(&s.class) && s.joules > 0 && s.delivery > 0 } else { s.class == 0 && s.joules == 0 && s.delivery == 0 } + }); + let mut digests = BTreeSet::new(); + let once = pathways.iter().flat_map(|s| &s.evidence).all(|d| digests.insert(*d)); + let mut seen = vec![false; n]; + let perm = m == n && priority.iter().all(|&i| (i as usize) < n && !std::mem::replace(&mut seen[i as usize], true)); + let rules = matches!(protocol, protocol::HLS | protocol::DASH) && (draw as u64) < PPM && ttl >= 1 && (1..=6).contains(&reason) && report_ok && states_ok && once && perm; + if !rules { + return Err(Reason::DecisionInvalid); + } + Ok(Decision { room, signer, policy, t, session, draw, protocol, report, pathways, ttl, reason, priority, sig }) + } +} + +/// The steering server's step: from the records it holds for each pathway (each list in the +/// order of §5.2, in the window), its loads and the request's report, the signed decision. +#[allow(clippy::too_many_arguments)] +pub fn steer(p: &Policy, t: u64, session: [u8; 16], protocol: u8, report: Report, loads: &[u32], named: &[Vec], key: &SigningKey) -> Decision { + let digest = p.digest(); + let ev: Vec = (0..p.pathways.len()).map(|i| evidence(p, i, &named[i], t)).collect(); + let out = decide(p, &digest, &session, &report, loads, &ev); + let pathways = (0..p.pathways.len()) + .map(|i| PathwayState { + load: loads[i], + limits: out.limits[i], + status: ev[i].status, + class: ev[i].class, + joules: ev[i].joules, + delivery: ev[i].delivery, + evidence: named[i].iter().map(HopRecord::digest).collect(), + }) + .collect(); + Decision { room: p.room, signer: [0; 32], policy: digest, t, session, draw: out.draw, protocol, report, pathways, ttl: out.ttl, reason: out.reason, priority: out.priority, sig: [0; 64] }.sign(key) +} + +// --------------------------------------------------------------------------------------- +// §8 The mappings +// --------------------------------------------------------------------------------------- + +/// The `RELOAD-URI` of a decision (§8.1). +pub fn reload_uri(p: &Policy, session: &[u8; 16]) -> String { + let sep = if p.reload_uri.contains('?') { '&' } else { '?' }; + format!("{}{sep}{SESSION_PARAM}={}", p.reload_uri, hex(session)) +} + +/// The URI a decision is served at (§8.4). +pub fn decision_uri(p: &Policy, d: &Decision) -> String { + format!("{}{}", p.decision_uri, hex(&d.digest())) +} + +/// The steering manifest of a decision (§8.1), the same bytes for HLS and DASH. +pub fn manifest(p: &Policy, d: &Decision) -> String { + let ids: Vec = d.priority.iter().map(|&i| format!("\"{}\"", p.pathways[i as usize].id)).collect(); + let mut o = format!("{{\"VERSION\":1,\"TTL\":{},\"RELOAD-URI\":\"{}\",\"PATHWAY-PRIORITY\":[{}]", d.ttl, reload_uri(p, &d.session), ids.join(",")); + let clones: Vec = p + .pathways + .iter() + .filter(|x| x.is_clone()) + .map(|x| { + let mut rep = Vec::new(); + if !x.host.is_empty() { + rep.push(format!("\"HOST\":\"{}\"", x.host)); + } + if !x.params.is_empty() { + let ps: Vec = x.params.iter().map(|(k, v)| format!("\"{k}\":\"{v}\"")).collect(); + rep.push(format!("\"PARAMS\":{{{}}}", ps.join(","))); + } + format!("{{\"BASE-ID\":\"{}\",\"ID\":\"{}\",\"URI-REPLACEMENT\":{{{}}}}}", p.pathways[x.base as usize].id, x.id, rep.join(",")) + }) + .collect(); + if !clones.is_empty() { + o.push_str(&format!(",\"PATHWAY-CLONES\":[{}]", clones.join(","))); + } + o.push_str(&format!(",\"{MANIFEST_KEY}\":\"{}\"}}", decision_uri(p, d))); + o +} + +/// The multivariant playlist's tag (§8.2). +pub fn hls_tag(p: &Policy) -> String { + format!("#EXT-X-CONTENT-STEERING:SERVER-URI=\"{}\",PATHWAY-ID=\"{}\"", p.server_uri, p.pathways[p.initial as usize].id) +} + +/// The MPD's element (§8.3). +pub fn dash_element(p: &Policy) -> String { + format!( + "{}", + p.pathways[p.initial as usize].id, + if p.flags & 1 != 0 { "true" } else { "false" }, + p.server_uri.replace('&', "&") + ) +} + +// --------------------------------------------------------------------------------------- +// §9 Verification +// --------------------------------------------------------------------------------------- + +/// What a verifier is given (§9.1). +#[derive(Clone, Copy, Debug)] +pub struct Inputs<'a> { + pub descriptor: &'a [u8], + pub head: Option<&'a str>, + pub policy: &'a [u8], + pub decision: &'a [u8], + pub records: &'a [Vec], + pub manifest: Option<&'a str>, +} + +/// An accepted decision (§9.3). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Accepted { + pub digest: [u8; 32], + pub priority: Vec, + pub ttl: u32, + pub reason: u8, + pub evidence: Vec, + pub limits: Vec, + pub manifest: String, + pub uri: String, +} + +/// A verification's outcome (§9.3). +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Outcome { + Refuse { at: Place, reason: Reason }, + Accept(Accepted), +} + +fn refuse(at: Place, reason: Reason) -> Outcome { + Outcome::Refuse { at, reason } +} + +/// The key-log entry of a key, when the room names an origin. +fn entry<'a>(log: Option<&'a Revision>, key: &[u8; 32]) -> Option<&'a crate::key_log::KeyEntry> { + log?.keys.iter().find(|e| e.key == *key) +} + +/// §3.5: the signer of a policy or a decision, with the classes it states. +fn signer_ok(d: &Descriptor, log: Option<&Revision>, signer: &[u8; 32], classes: &[u8]) -> Result<(), Reason> { + if d.origin.is_empty() { + return if *signer == d.authority { Ok(()) } else { Err(metering::Reason::SignerNotAuthorised.into()) }; + } + let e = entry(log, signer); + let listed = *signer == d.authority || e.is_some_and(|e| e.may_sign.iter().any(|a| a == STEER_AUTHORITY)); + let Some(e) = e.filter(|_| listed) else { return Err(metering::Reason::SignerNotAuthorised.into()) }; + if !classes.iter().all(|&t| e.may_sign.iter().any(|a| *a == format!("energy/{}", metering::tag_label(t)))) { + return Err(metering::Reason::SignerNotAuthorised.into()); + } + if e.status == EntryStatus::Revoked { + return Err(metering::Reason::SignerRevoked.into()); + } + Ok(()) +} + +/// live-metering §5.2: the hop may sign a record of its role. +fn hop_ok(d: &Descriptor, log: Option<&Revision>, rec: &HopRecord) -> Result<(), Reason> { + if d.origin.is_empty() { + return if d.is_room_key(&rec.hop) { Ok(()) } else { Err(metering::Reason::HopNotAuthorised.into()) }; + } + let e = entry(log, &rec.hop).ok_or(Reason::Metering(metering::Reason::HopNotAuthorised))?; + let role = HOP_AUTHORITIES[(rec.role - 1) as usize]; + let energy = rec.class.as_ref().map(|c| format!("energy/{}", c.label())); + let holds = |a: &str| e.may_sign.iter().any(|x| x == a); + if !holds(role) || energy.as_deref().is_some_and(|a| !holds(a)) { + return Err(metering::Reason::HopNotAuthorised.into()); + } + if e.status == EntryStatus::Revoked { + return Err(metering::Reason::HopRevoked.into()); + } + Ok(()) +} + +/// §9.1 step 5's reason for a stated evidence that does not recompute. +fn evidence_reason(stated: &Evidence, actual: &Evidence) -> Reason { + match (stated.status, actual.status) { + (status::FIGURE, status::UNMETERED) => Reason::UnmeteredAsZero, + (status::FIGURE, status::MIXED) => Reason::Metering(metering::Reason::ClassesSummed), + _ => Reason::EvidenceMismatch, + } +} + +/// Verify a decision (§9). +pub fn verify(i: &Inputs) -> Outcome { + // 1. The room. + let d = match Descriptor::parse(i.descriptor) { + Ok(d) => d, + Err(r) => return refuse(Place::Room, Reason::Room(r)), + }; + if let Err(r) = room::check_key_log(&d, i.head) { + return refuse(Place::Room, Reason::Room(r)); + } + let id = d.room_id(); + let log = if d.origin.is_empty() { None } else { i.head.and_then(|h| Revision::from_json(h).ok()) }; + // 2. The policy. + let p = match Policy::read(i.policy) { + Ok(p) => p, + Err(r) => return refuse(Place::Policy, r), + }; + if p.room != id { + return refuse(Place::Policy, WRONG_ROOM); + } + if let Err(r) = verify_sig(&p.signer, POLICY_DOMAIN, &i.policy[..i.policy.len() - 64], &p.sig) { + return refuse(Place::Policy, r); + } + if let Err(r) = signer_ok(&d, log.as_ref(), &p.signer, &[]) { + return refuse(Place::Policy, r); + } + // 3. The decision. + let dec = match Decision::read(i.decision) { + Ok(x) => x, + Err(r) => return refuse(Place::Decision, r), + }; + if dec.room != id { + return refuse(Place::Decision, WRONG_ROOM); + } + let digest = p.digest(); + if dec.policy != digest { + return refuse(Place::Decision, metering::Reason::RecordMissing.into()); + } + if dec.pathways.len() != p.pathways.len() { + return refuse(Place::Decision, Reason::DecisionInvalid); + } + if let Err(r) = verify_sig(&dec.signer, DECISION_DOMAIN, &i.decision[..i.decision.len() - 64], &dec.sig) { + return refuse(Place::Decision, r); + } + let classes: BTreeSet = dec.pathways.iter().filter(|s| s.status == status::FIGURE).map(|s| s.class).collect(); + if let Err(r) = signer_ok(&d, log.as_ref(), &dec.signer, &classes.into_iter().collect::>()) { + return refuse(Place::Decision, r); + } + // 4. Each record named. + let (a, b) = window(&p, dec.t); + let mut named: Vec> = vec![vec![]; p.pathways.len()]; + for (pi, s) in dec.pathways.iter().enumerate() { + let mut last: Option<(usize, u64)> = None; + for (k, dg) in s.evidence.iter().enumerate() { + let at = Place::Evidence(pi, k); + let Some(bytes) = i.records.iter().find(|r| r.len() >= 64 && metering::given_digest(r) == *dg) else { + return refuse(at, metering::Reason::RecordMissing.into()); + }; + let rec = match HopRecord::parse(bytes, &id) { + Ok(r) => r, + Err(r) => return refuse(at, r.into()), + }; + if let Err(r) = hop_ok(&d, log.as_ref(), &rec) { + return refuse(at, r); + } + let Some(h) = p.pathways[pi].hops.iter().position(|h| h.0 == rec.hop) else { + return refuse(at, Reason::EvidenceNotOnPathway); + }; + if rec.start < a || rec.end > b { + return refuse(at, Reason::EvidenceOutsideWindow); + } + if last.is_some_and(|l| l >= (h, rec.start)) { + return refuse(at, Reason::DecisionInvalid); + } + last = Some((h, rec.start)); + named[pi].push(rec); + } + } + // 5. The evidence. + let ev: Vec = (0..p.pathways.len()).map(|pi| evidence(&p, pi, &named[pi], dec.t)).collect(); + for (pi, s) in dec.pathways.iter().enumerate() { + if s.stated() != ev[pi] { + return refuse(Place::Pathway(pi), evidence_reason(&s.stated(), &ev[pi])); + } + } + // 6. The output. + let loads: Vec = dec.pathways.iter().map(|s| s.load).collect(); + let out = decide(&p, &digest, &dec.session, &dec.report, &loads, &ev); + let stated_limits: Vec = dec.pathways.iter().map(|s| s.limits).collect(); + if out.draw != dec.draw || out.limits != stated_limits || out.priority != dec.priority || out.ttl != dec.ttl || out.reason != dec.reason { + let unknown_first = !ev[dec.priority[0] as usize].has_figure() && ev[out.priority[0] as usize].has_figure(); + return refuse(Place::Output, if unknown_first { Reason::UnknownPreferred } else { Reason::DecisionMismatch }); + } + // 7. The manifest. + let m = manifest(&p, &dec); + if i.manifest.is_some_and(|x| x != m) { + return refuse(Place::Manifest, Reason::ManifestMismatch); + } + Outcome::Accept(Accepted { + digest: dec.digest(), + priority: dec.priority.iter().map(|&x| p.pathways[x as usize].id.clone()).collect(), + ttl: dec.ttl, + reason: dec.reason, + evidence: ev, + limits: out.limits, + manifest: m, + uri: decision_uri(&p, &dec), + }) +} + +#[cfg(test)] +mod tests; diff --git a/wai/wai-rs/src/energy_steering/corpus.rs b/wai/wai-rs/src/energy_steering/corpus.rs new file mode 100644 index 000000000..ba46e5582 --- /dev/null +++ b/wai/wai-rs/src/energy_steering/corpus.rs @@ -0,0 +1,835 @@ +//! The conformance corpus of extensions/energy-steering.md §11: `live-conformance/steering/vectors.json`. +//! +//! [`generate`] writes it from fixed seeds: every key is SHA-256 of this corpus's label and an +//! index, every session SHA-256 of another label and an index, every time a constant, and +//! Ed25519 signing is deterministic (RFC 8032). The pathways' hops sign their per-hop records +//! (live-metering §3) in **worlds**: one world is one set of figures the hops could have +//! signed, and a vector reads the records of its world. The figures are synthetic, chosen to +//! exercise the rule, and claim nothing about any deployment. Each vector states the outcome +//! it was written to have, and the generator asserts that this module reaches it. +//! +//! [`verify`] checks a corpus file against this module. `live-conformance/energy_steering_verify.py` +//! checks the same file from the extension's text, sharing no code with this module. + +use std::collections::BTreeMap; + +use super::*; +use crate::energy_class::{CounterFiltering, EnergyProvenance, Uncertainty}; +use crate::key_log::{commitment, KeyEntry}; +use crate::live_events::Policy as EventPolicy; +use crate::live_metering::{dir, kind, TrackEntry, CLASSES}; +use crate::live_room::{registered_profile, Caps, Limits, Owner, Recording, Review, Role}; +use serde_json::{json, Map, Value}; + +const S: u64 = 1_000_000; +/// The period the hops cut their records on, and the window. +const PERIOD: u64 = 10 * S; +const WINDOW: u64 = 30 * S; +const LAG: u64 = 2 * S; +/// The room's `t0`: 2026-10-16T00:00:00Z, as the live corpora's. +const T0_US: u64 = 1_792_108_800_000_000; +const ORIGIN: &str = "https://stream.example"; +/// The decisions' instants: a window of [30 s, 60 s), and a period later [40 s, 70 s). +const T1: u64 = 62 * S; +const T2: u64 = 72 * S; + +const AUTHORITY: u8 = 0; +const CLOCK: u8 = 1; +const STEER: u8 = 2; +const STEER_REVOKED: u8 = 3; +const STRANGER: u8 = 4; +const LOG_ROOT: u8 = 5; +const LOG_NEXT: u8 = 6; +const PUBLISHER: u8 = 7; +const A_EDGE: u8 = 10; +const A_CORE: u8 = 11; +const B_EDGE: u8 = 12; +const B_CORE: u8 = 13; +const C_EDGE: u8 = 14; +const C_CORE: u8 = 15; +const D_EDGE: u8 = 16; +const A2_EDGE: u8 = 17; +const UNLISTED_EDGE: u8 = 18; +const REVOKED_EDGE: u8 = 19; + +const PRINCIPALS: [(&str, u8); 18] = [ + ("authority", AUTHORITY), + ("clock", CLOCK), + ("steer", STEER), + ("steer_revoked", STEER_REVOKED), + ("stranger", STRANGER), + ("log_root", LOG_ROOT), + ("log_next", LOG_NEXT), + ("publisher", PUBLISHER), + ("a_edge", A_EDGE), + ("a_core", A_CORE), + ("b_edge", B_EDGE), + ("b_core", B_CORE), + ("c_edge", C_EDGE), + ("c_core", C_CORE), + ("d_edge", D_EDGE), + ("a2_edge", A2_EDGE), + ("unlisted_edge", UNLISTED_EDGE), + ("revoked_edge", REVOKED_EDGE), +]; + +const SERVER_URI: &str = "https://steer.stream.example/v1/room?asset=lesson&tk=a1"; +const RELOAD_URI: &str = "https://steer.stream.example/v1/room?asset=lesson"; +const DECISION_URI: &str = "https://steer.stream.example/v1/decisions/"; + +fn key(i: u8) -> SigningKey { + SigningKey::from_bytes(&sha256(&[b"wai:energy-steering/corpus/key\x00", &[i]])) +} + +fn pk(i: u8) -> [u8; 32] { + key(i).verifying_key().to_bytes() +} + +fn who(k: &[u8; 32]) -> u8 { + PRINCIPALS.iter().find(|(_, i)| pk(*i) == *k).map(|(_, i)| *i).expect("a principal of the corpus") +} + +/// Session `i` of the corpus. +fn session(i: u32) -> [u8; 16] { + sha256(&[b"wai:energy-steering/corpus/session\x00", &i.to_le_bytes()])[..16].try_into().expect("16 bytes") +} + +fn unhex(s: &str) -> Option> { + if !s.len().is_multiple_of(2) { + return None; + } + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(s.get(i..i + 2)?, 16).ok()).collect() +} + +// ---- the room ---- + +fn bits(ks: &[u8]) -> [u8; 32] { + let mut b = [0u8; 32]; + for &k in ks { + b[(k / 8) as usize] |= 1 << (k % 8); + } + b +} + +fn descriptor() -> Descriptor { + use events::kind::{CHAT, REACTION}; + let role = |name: &str, publish: u8, max_layer: u8| Role { name: name.into(), caps: Caps { publish, subscribe: true, max_layer, powers: 0 }, events: bits(&[CHAT, REACTION]) }; + Descriptor { + authority: pk(AUTHORITY), + clock: pk(CLOCK), + t0_us: T0_US, + tick_us: S, + origin: ORIGIN.into(), + limits: Limits { max_present: 8, max_publishers: 2, reval_s: 0, ceiling: EventPolicy::default() }, + roles: vec![role("publisher", 1 | 2, 2), role("audience", 0, 1)], + slots: vec![], + profiles: vec![registered_profile("camera-720").expect("registered")], + review: Review { kinds: [0; 32], deadline_ms: 0, fallback: 0 }, + recording: Recording { mode: 0, retention_days: 0, commit_interval_ms: 0, jitter_ms: 0, regions: vec![] }, + sig: [0; 64], + } + .sign(&key(AUTHORITY)) +} + +const ENERGY: [&str; 4] = ["energy/CalibratedInstrument", "energy/OnChipCounter", "energy/ModelBased", "energy/Estimator"]; + +fn key_log() -> String { + let with = |base: &'static str| -> Vec<&'static str> { std::iter::once(base).chain(ENERGY).collect() }; + let revoked = |id: &str, k: u8, may: &[&str]| KeyEntry { status: EntryStatus::Revoked, compromised_since: Some("2026-10-14T00:00:00Z".into()), ..KeyEntry::active(id, pk(k), may) }; + let mut keys = vec![ + KeyEntry::active("room-authority", pk(AUTHORITY), &[room::KEY_LOG_AUTHORITY]), + KeyEntry::active("steering", pk(STEER), &with(STEER_AUTHORITY)), + revoked("steering-old", STEER_REVOKED, &with(STEER_AUTHORITY)), + ]; + for (id, k) in [("a-edge", A_EDGE), ("a-core", A_CORE), ("b-edge", B_EDGE), ("b-core", B_CORE), ("c-edge", C_EDGE), ("c-core", C_CORE), ("d-edge", D_EDGE), ("a2-edge", A2_EDGE)] { + keys.push(KeyEntry::active(id, pk(k), &with("live/relay"))); + } + keys.push(revoked("c-edge-old", REVOKED_EDGE, &with("live/relay"))); + let revision = Revision { + origin: ORIGIN.into(), + seq: 0, + prev: None, + issued_at: "2026-10-15T00:00:00Z".into(), + root: [0; 32], + next_root: commitment(&pk(LOG_NEXT)), + keys, + revoked_modules: vec![], + grant_max_s: 86_400, + status_max_age_s: 3_600, + sig: [0; 64], + }; + Revision::seal(&key(LOG_ROOT), revision).expect("a well-formed revision").to_json() +} + +// ---- the records ---- + +/// How a hop's figure was acquired in a world. +#[derive(Clone, Copy, PartialEq, Eq)] +enum Meter { + Calibrated, + OnChip, + Model, + Estimate, + Off, +} + +fn class_of(m: Meter) -> Option { + let u = |ppm| Uncertainty::new(ppm, PERIOD).expect("a declaration"); + match m { + Meter::Calibrated => Some(EnergyProvenance::calibrated_instrument(u(5_000), "cal-2026-0042").expect("a class")), + Meter::OnChip => Some(EnergyProvenance::on_chip_counter(u(20_000), CounterFiltering::Off).expect("a class")), + Meter::Model => Some(EnergyProvenance::ModelBased), + Meter::Estimate => Some(EnergyProvenance::Estimator), + Meter::Off => None, + } +} + +/// The worlds the corpus's hops sign records in. +#[derive(Clone, Copy, PartialEq, Eq)] +enum World { + /// Every hop counter-metered. + Base, + /// `edge-a` by calibrated instrument, `edge-b` by counter, `tree` by estimate. + Classes, + /// `edge-b`'s interior hop unmetered. + Unmetered, + /// `edge-a`'s interior hop by model, its edge by counter. + MixedHops, +} + +/// A hop's figure for one period in a world: joules, how acquired, media bytes out, and its +/// subscriber sessions by layer class. +struct Figure { + joules_j: u64, + meter: Meter, + bytes_out: u64, + sessions: &'static [(usize, u64)], +} + +fn figure(world: World, hop: u8, k: u64) -> Figure { + let f = |joules_j, meter, bytes_out, sessions| Figure { joules_j, meter, bytes_out, sessions }; + let base = match hop { + A_EDGE => f(400, Meter::OnChip, 200_000_000, &[(2, 10), (3, 30)][..]), + A_CORE => f(300, Meter::OnChip, 10_000_000, &[][..]), + B_EDGE => f(500, Meter::OnChip, 200_000_000, &[(2, 40)][..]), + B_CORE => f(300, Meter::OnChip, 10_000_000, &[][..]), + C_EDGE | UNLISTED_EDGE | REVOKED_EDGE => f(300, Meter::OnChip, 250_000_000, &[(0, 30), (2, 30)][..]), + // The relay tree's interior hop spends less in the period from 60 s. + C_CORE => f(if k == 6 { 150 } else { 450 }, Meter::OnChip, 10_000_000, &[][..]), + D_EDGE => f(0, Meter::Off, 50_000_000, &[(1, 10)][..]), + A2_EDGE => f(350, Meter::OnChip, 250_000_000, &[(3, 50)][..]), + _ => unreachable!("a hop of the corpus"), + }; + match (world, hop) { + (World::Classes, A_EDGE | A_CORE) => Figure { meter: Meter::Calibrated, ..base }, + (World::Classes, C_EDGE | C_CORE) => Figure { joules_j: 200, meter: Meter::Estimate, ..base }, + (World::Unmetered, B_CORE) => Figure { joules_j: 0, meter: Meter::Off, ..base }, + (World::MixedHops, A_CORE) => Figure { meter: Meter::Model, ..base }, + _ => base, + } +} + +/// The record `hop` signs for period `k` in `world`. +fn record(room_id: &[u8; 32], world: World, hop: u8, k: u64) -> HopRecord { + let fig = figure(world, hop, k); + let track = crate::live_metering::corpus::track(room_id, &Owner::Principal(pk(PUBLISHER)), "video", 2); + let bytes_in = 5_000_000; + let mut subscriber_us = [0u64; CLASSES]; + let mut sessions = 0u32; + for &(c, n) in fig.sessions { + subscriber_us[c] += n * PERIOD; + sessions += n as u32; + } + let class = class_of(fig.meter); + HopRecord { + room: *room_id, + hop: pk(hop), + role: metering::role::RELAY, + start: k * PERIOD, + end: (k + 1) * PERIOD, + joules: if class.is_some() { fig.joules_j * S } else { 0 }, + class, + tracks: vec![ + TrackEntry { kind: kind::MEDIA, track, dir: dir::IN, objects: bytes_in / 1250, bytes: bytes_in, ..TrackEntry::default() }, + TrackEntry { kind: kind::MEDIA, track, dir: dir::OUT, objects: fig.bytes_out / 1250, bytes: fig.bytes_out, ..TrackEntry::default() }, + ], + sessions, + subscriber_us, + events: 0, + grants: 0, + ledger: [0; 32], + sig: [0; 64], + } + .sign(&key(hop)) +} + +/// The records a server read for each pathway at `t` in `world`: each hop's records of the +/// window's periods, in the order of §5.2, leaving out the `(hop, period)` pairs of `skip`. +fn named(p: &Policy, world: World, t: u64, skip: &[(u8, u64)]) -> Vec> { + let (a, b) = window(p, t); + p.pathways + .iter() + .map(|x| { + let mut out = Vec::new(); + for (h, _) in &x.hops { + let hop = who(h); + for k in a / PERIOD..b / PERIOD { + if !skip.contains(&(hop, k)) { + out.push(record(&p.room, world, hop, k)); + } + } + } + out + }) + .collect() +} + +// ---- the policies ---- + +fn pathway(id: &str, hops: &[(u8, u8)], top_tier: u8, latency_ms: u32, capacity: u32, min_share_ppm: u32) -> Pathway { + Pathway { + id: id.into(), + base: NOT_CLONE, + host: String::new(), + params: vec![], + top_tier, + latency_ms, + capacity, + min_share_ppm, + hops: hops.iter().map(|&(k, pl)| (pk(k), pl)).collect(), + } +} + +fn edge_a() -> Pathway { + pathway("edge-a", &[(A_EDGE, place::EDGE), (A_CORE, place::INTERIOR)], 3, 400, NO_CAPACITY, 0) +} + +fn edge_b() -> Pathway { + pathway("edge-b", &[(B_EDGE, place::EDGE), (B_CORE, place::INTERIOR)], 3, 450, NO_CAPACITY, 0) +} + +fn tree(edge: u8) -> Pathway { + pathway("tree", &[(edge, place::EDGE), (C_CORE, place::INTERIOR)], 3, 500, 1_000, 0) +} + +fn base_policy(room_id: [u8; 32], pathways: Vec) -> Policy { + Policy { + room: room_id, + signer: [0; 32], + period_us: PERIOD, + window_us: WINDOW, + lag_us: LAG, + basis: basis::BYTES, + floor: 1, + latency_ceiling_ms: 600, + min_buffer_ms: 4_000, + hysteresis_ppm: 200_000, + ttl_s: 300, + min_ttl_s: 30, + flags: 1, + initial: 0, + tiers: vec![800_000, 1_600_000, 3_200_000, 6_400_000], + server_uri: SERVER_URI.into(), + reload_uri: RELOAD_URI.into(), + decision_uri: DECISION_URI.into(), + pathways, + sig: [0; 64], + } +} + +/// The corpus's policies, by name, as signed bytes. A policy that the rules refuse is signed +/// all the same: §9.1 reads a policy before it checks its signature. +fn policies(room_id: [u8; 32]) -> Vec<(&'static str, Vec)> { + let three = || vec![edge_a(), edge_b(), tree(C_EDGE)]; + let origin = pathway("origin", &[(D_EDGE, place::EDGE)], 1, 300, NO_CAPACITY, 100_000); + let clone = Pathway { + base: 0, + host: "a2.edge.stream.example".into(), + params: vec![("pop".into(), "east-2".into()), ("tk".into(), "x%2Fy".into())], + ..pathway("edge-a2", &[(A2_EDGE, place::EDGE)], 3, 400, NO_CAPACITY, 0) + }; + let signed = |p: Policy| p.sign(&key(STEER)).to_bytes(); + let mut latency = three(); + latency[2].latency_ms = 900; + let mut over = three(); + over[0].min_share_ppm = 600_000; + over[1].min_share_ppm = 500_000; + let mut later = vec![edge_a(), edge_b(), tree(C_EDGE), clone.clone()]; + later[3].base = 3; + vec![ + ("three", signed(base_policy(room_id, three()))), + ("explore", signed(base_policy(room_id, vec![edge_a(), edge_b(), tree(C_EDGE), origin]))), + ("cloned", signed(base_policy(room_id, vec![edge_a(), edge_b(), tree(C_EDGE), clone]))), + ("latency", signed(base_policy(room_id, latency))), + ("seconds", signed(Policy { basis: basis::SECONDS, ..base_policy(room_id, three()) })), + ("strict", signed(Policy { latency_ceiling_ms: 100, ..base_policy(room_id, three()) })), + ("unlisted", signed(base_policy(room_id, vec![edge_a(), edge_b(), tree(UNLISTED_EDGE)]))), + ("revoked", signed(base_policy(room_id, vec![edge_a(), edge_b(), tree(REVOKED_EDGE)]))), + ("shares_over", signed(base_policy(room_id, over))), + ("clone_later", signed(base_policy(room_id, later))), + ("stranger_signed", base_policy(room_id, three()).sign(&key(STRANGER)).to_bytes()), + ] +} + +// ---- the requests ---- + +/// The steering requests of §11, by protocol, target and `CMCD-Request` header. +const REQUESTS: [(&str, u8, &str, Option<&str>); 26] = [ + ("hls_quoted", protocol::HLS, "/v1/room?asset=lesson&_HLS_pathway=%22edge-a%22&_HLS_throughput=4000000", None), + ("hls_unquoted", protocol::HLS, "/v1/room?_HLS_pathway=edge-b&_HLS_throughput=123", None), + ("hls_literal_quotes", protocol::HLS, "/v1/room?_HLS_pathway=\"tree\"", None), + ("hls_unknown_pathway", protocol::HLS, "/v1/room?_HLS_pathway=%22cdn-z%22&_HLS_throughput=5000000", None), + ("hls_throughput_not_integer", protocol::HLS, "/v1/room?_HLS_pathway=edge-a&_HLS_throughput=4e6", None), + ("hls_throughput_past_u64", protocol::HLS, "/v1/room?_HLS_throughput=18446744073709551616", None), + ("hls_throughput_u64_max", protocol::HLS, "/v1/room?_HLS_throughput=18446744073709551615", None), + ("hls_first_part_wins", protocol::HLS, "/v1/room?_HLS_pathway=tree&_HLS_pathway=edge-a", None), + ("hls_bad_percent_skipped", protocol::HLS, "/v1/room?_HLS_pathway=%2&_HLS_throughput=500", None), + ("hls_fragment_cut", protocol::HLS, "/v1/room?_HLS_throughput=9#_HLS_pathway=edge-a", None), + ("hls_no_query", protocol::HLS, "/v1/room", None), + ("hls_dash_parameters_ignored", protocol::HLS, "/v1/room?_DASH_pathway=edge-a&_DASH_throughput=7", None), + ("hls_cmcd_v2_lists", protocol::HLS, "/v1/room?_HLS_pathway=%22tree%22&CMCD=bl%3D%2821300%3Bv%2013000%3Ba%29%2Cmtp%3D%2848000%3Bv%29%2Csid%3D%22abc%22", None), + ("hls_cmcd_v1_integers", protocol::HLS, "/v1/room?CMCD=bl%3D4100%2Cmtp%3D25400", None), + ("hls_cmcd_throughput_parameter_first", protocol::HLS, "/v1/room?_HLS_throughput=3000000&CMCD=mtp%3D25400%2Cbl%3D900", None), + ("hls_cmcd_header", protocol::HLS, "/v1/room?_HLS_pathway=edge-a", Some("bl=(8000;v),br=(3200;v),mtp=(12000;v)")), + ("hls_cmcd_query_over_header", protocol::HLS, "/v1/room?CMCD=bl%3D700", Some("bl=(8000;v),mtp=(12000;v)")), + ("hls_cmcd_unterminated", protocol::HLS, "/v1/room?CMCD=bl%3D%281200", None), + ("hls_cmcd_trailing_comma", protocol::HLS, "/v1/room?CMCD=bl%3D300%2C", None), + ("hls_cmcd_negative", protocol::HLS, "/v1/room?CMCD=bl%3D-5%2Cmtp%3D(-1)", None), + ("hls_cmcd_decimal_in_list", protocol::HLS, "/v1/room?CMCD=bl%3D%281.5%3Bv%29", None), + ("hls_cmcd_display_string_and_date", protocol::HLS, "/v1/room?CMCD=bl%3D300%2Cx%3D%25%22caf%25c3%25a9%22%2Cy%3D%401700000000%2Cz%3D%3AAQID%3A", None), + ("dash_list_second_known", protocol::DASH, "/v1/room?_DASH_pathway=%22cdn-z,edge-b%22&_DASH_throughput=1000,2000", None), + ("dash_unquoted_single", protocol::DASH, "/v1/room?steeringToken=12345&_DASH_pathway=tree&_DASH_throughput=5140000", None), + ("dash_throughput_list_short", protocol::DASH, "/v1/room?_DASH_pathway=%22cdn-z,edge-b%22&_DASH_throughput=1000", None), + ("dash_no_pathway_first_throughput", protocol::DASH, "/v1/room?_DASH_throughput=777,888&_HLS_pathway=edge-a", None), +]; + +fn report_json(p: &Policy, r: &Report) -> Value { + json!({ + "pathway": r.pathway().map(|i| Value::from(p.pathways[i].id.clone())).unwrap_or(Value::Null), + "throughput": r.throughput().map(Value::from).unwrap_or(Value::Null), + "buffer": r.buffer().map(Value::from).unwrap_or(Value::Null), + }) +} + +fn protocol_label(x: u8) -> &'static str { + if x == protocol::HLS { "hls" } else { "dash" } +} + +// ---- the vectors ---- + +enum Intent { + Accept(u8), + Refuse(&'static str), +} + +struct Vector { + name: &'static str, + about: &'static str, + policy: &'static str, + decision: Vec, + /// The digests of the records the verifier is given. + records: Vec<[u8; 32]>, + manifest: Option, + request: Option<(u8, String, Option)>, + intent: Intent, +} + +struct Builder { + room_id: [u8; 32], + policies: Vec<(&'static str, Vec)>, + pool: BTreeMap<[u8; 32], Vec>, +} + +impl Builder { + fn policy(&self, name: &str) -> Policy { + let b = &self.policies.iter().find(|(n, _)| *n == name).expect("a policy").1; + Policy::read(b).expect("a policy that reads") + } + + fn add(&mut self, named: &[Vec]) -> Vec<[u8; 32]> { + let mut out = Vec::new(); + for r in named.iter().flatten() { + self.pool.insert(r.digest(), r.to_bytes()); + out.push(r.digest()); + } + out + } + + /// A decision the reference steering server signs, with the records it names. + #[allow(clippy::too_many_arguments)] + fn decide(&mut self, policy: &str, world: World, t: u64, sess: u32, proto: u8, report: Report, loads: &[u32], skip: &[(u8, u64)]) -> (Decision, Vec<[u8; 32]>, Vec>) { + let p = self.policy(policy); + let named = named(&p, world, t, skip); + let given = self.add(&named); + (steer(&p, t, session(sess), proto, report, loads, &named, &key(STEER)), given, named) + } +} + +const LOADS: [u32; 4] = [120, 80, 300, 40]; + +fn accept(name: &'static str, about: &'static str, policy: &'static str, d: &Decision, records: Vec<[u8; 32]>, b: &Builder, request: Option<(u8, String, Option)>, why_: u8) -> Vector { + let m = manifest(&b.policy(policy), d); + Vector { name, about, policy, decision: d.to_bytes(), records, manifest: Some(m), request, intent: Intent::Accept(why_) } +} + +fn refused(name: &'static str, about: &'static str, policy: &'static str, decision: Vec, records: Vec<[u8; 32]>, manifest: Option, code: &'static str) -> Vector { + Vector { name, about, policy, decision, records, manifest, request: None, intent: Intent::Refuse(code) } +} + +fn resign(d: Decision, who_: u8) -> Vec { + d.sign(&key(who_)).to_bytes() +} + +fn vectors(b: &mut Builder) -> Vec { + let mut v = Vec::new(); + let none = Report::default(); + let three = b.policy("three"); + let req = |proto: u8, target: &str, header: Option<&str>| (proto, target.to_string(), header.map(String::from)); + let read = |p: &Policy, r: &(u8, String, Option)| read_request(r.0, r.1.as_bytes(), r.2.as_deref().map(str::as_bytes), p); + + // ---- accepted ---- + let (d3, g3, n3) = b.decide("three", World::Base, T1, 0, protocol::HLS, none, &LOADS[..3], &[]); + v.push(accept("three_metered", "three pathways counter-metered: the relay tree's figure is the lowest, and it is first", "three", &d3, g3.clone(), b, None, why::RANK)); + let (d, g, _) = b.decide("three", World::Classes, T1, 0, protocol::HLS, none, &LOADS[..3], &[]); + v.push(accept("mixed_classes", "one class per pathway, three classes: ordered by what backs each figure, the calibrated pathway first and the estimated one last though its figure is the lowest", "three", &d, g, b, None, why::RANK)); + let (du, gu, nu) = b.decide("three", World::Unmetered, T1, 0, protocol::HLS, none, &LOADS[..3], &[]); + v.push(accept("one_unmetered", "edge-b's interior hop is unmetered: edge-b has no figure and comes last, though its metered hop alone is the cheapest of all", "three", &du, gu.clone(), b, None, why::RANK)); + let (dm, gm, nm) = b.decide("three", World::MixedHops, T1, 0, protocol::HLS, none, &LOADS[..3], &[]); + v.push(accept("mixed_hops", "edge-a's hops are of two classes: edge-a has no figure and comes after both pathways with one", "three", &dm, gm.clone(), b, None, why::RANK)); + let (ds, gs, _) = b.decide("three", World::Base, T1, 0, protocol::HLS, none, &LOADS[..3], &[(C_CORE, 5)]); + v.push(accept("stale", "the relay tree's interior hop has no record of its last period: its evidence is stale, and the cheapest pathway of three_metered comes last", "three", &ds, gs.clone(), b, None, why::RANK)); + let r = req(protocol::HLS, "/v1/room?asset=lesson&_HLS_pathway=%22edge-b%22&_HLS_throughput=1500000", None); + let (d, g, _) = b.decide("three", World::Base, T1, 0, protocol::HLS, read(&three, &r), &LOADS[..3], &[]); + v.push(accept("floor_client", "a client on edge-b whose throughput sustains only tier 0, at or below the floor: held on edge-b, with the short TTL", "three", &d, g, b, Some(r), why::FLOOR)); + let r = req(protocol::HLS, "/v1/room?_HLS_pathway=%22edge-b%22&_HLS_throughput=8000000&CMCD=bl%3D%281200%3Bv%201800%3Ba%29", None); + let (d, g, _) = b.decide("three", World::Base, T1, 0, protocol::HLS, read(&three, &r), &LOADS[..3], &[]); + v.push(accept("buffer_guard", "a client on edge-b with 1.2 s of video buffered, below the policy's 4 s: held", "three", &d, g, b, Some(r), why::BUFFER)); + let r = req(protocol::HLS, "/v1/room?_HLS_pathway=%22edge-a%22&_HLS_throughput=8000000&CMCD=bl%3D20000", None); + let (d, g, _) = b.decide("three", World::Base, T1, 0, protocol::HLS, read(&three, &r), &LOADS[..3], &[]); + v.push(accept("hysteresis_hold", "a client on edge-a: the relay tree's figure is lower by less than the 20% margin, so the client stays", "three", &d, g, b, Some(r.clone()), why::HOLD)); + let (d, g, _) = b.decide("three", World::Base, T2, 0, protocol::HLS, read(&three, &r), &LOADS[..3], &[]); + v.push(accept("hysteresis_switch", "the same client a period later: the relay tree's newest period brings its figure below edge-a's by more than the margin, and the client moves", "three", &d, g, b, Some(r), why::RANK)); + let (dc, gc, _) = b.decide("cloned", World::Base, T1, 0, protocol::HLS, none, &LOADS, &[]); + v.push(accept("clones", "a clone of edge-a through another host, with its own edge hop: first, and every clone written in PATHWAY-CLONES", "cloned", &dc, gc.clone(), b, None, why::RANK)); + let explore = b.policy("explore"); + let digest = explore.digest(); + let inside = (0u32..).find(|&i| draw(&digest, &session(i)) < 100_000).expect("a session in the band"); + let outside = (0u32..).find(|&i| draw(&digest, &session(i)) >= 100_000).expect("a session outside the band"); + let (d, g, _) = b.decide("explore", World::Base, T1, inside, protocol::HLS, none, &LOADS, &[]); + v.push(accept("share", "the origin path is unmetered and holds a 10% minimum share: a session whose draw is in its band is placed on it first", "explore", &d, g, b, None, why::SHARE)); + let (d, g, _) = b.decide("explore", World::Base, T1, outside, protocol::HLS, none, &LOADS, &[]); + v.push(accept("share_outside", "a session whose draw is outside the origin path's band: the origin path has no figure and comes last", "explore", &d, g, b, None, why::RANK)); + let (d, g, _) = b.decide("three", World::Base, T1, 0, protocol::HLS, none, &[120, 80, 1_000], &[]); + v.push(accept("capacity", "the relay tree is at its capacity of 1000 sessions: edge-a is first and the relay tree last", "three", &d, g, b, None, why::RANK)); + let (d, g, _) = b.decide("latency", World::Base, T1, 0, protocol::HLS, none, &LOADS[..3], &[]); + v.push(accept("latency", "the relay tree's latency is past the ceiling: it comes after the pathways within the limits", "latency", &d, g, b, None, why::RANK)); + let (d, g, _) = b.decide("seconds", World::Base, T1, 0, protocol::HLS, none, &LOADS[..3], &[]); + v.push(accept("per_second", "the per-second basis: the relay tree delivers the most bytes but half its sessions below the floor tier, and comes last", "seconds", &d, g, b, None, why::RANK)); + let r = req(protocol::DASH, "/v1/room?asset=lesson&wai_session=00&_DASH_pathway=%22edge-b,edge-a%22&_DASH_throughput=9000000,7000000", Some("bl=(25000;v 30000;a)")); + let (d, g, _) = b.decide("three", World::Base, T1, 1, protocol::DASH, read(&three, &r), &LOADS[..3], &[]); + v.push(accept("dash", "DASH: the client used edge-b then edge-a; edge-b is current, and the relay tree beats it by more than the margin", "three", &d, g, b, Some(r), why::RANK)); + let r = req(protocol::HLS, "/v1/room?_HLS_pathway=edge-a&_HLS_throughput=8000000", None); + let strict = b.policy("strict"); + let (d, g, _) = b.decide("strict", World::Base, T1, 0, protocol::HLS, read(&strict, &r), &LOADS[..3], &[]); + v.push(accept("none_within", "a latency ceiling no pathway meets: the client stays on edge-a, with the short TTL", "strict", &d, g, b, Some(r), why::NONE)); + + // ---- refused ---- + let m3 = manifest(&three, &d3); + v.push(refused("policy_invalid", "the policy's minimum shares sum past one million", "shares_over", d3.to_bytes(), g3.clone(), None, "policy_invalid")); + v.push(refused("clone_unknown", "a clone whose base is itself, a pathway not before it", "clone_later", d3.to_bytes(), g3.clone(), None, "clone_unknown")); + v.push(refused("policy_signer_not_authorised", "a policy signed by a key the key log does not list", "stranger_signed", d3.to_bytes(), g3.clone(), None, "signer_not_authorised")); + v.push(refused("decision_policy_missing", "a decision under another policy than the one given", "latency", d3.to_bytes(), g3.clone(), None, "record_missing")); + let mut sig = d3.to_bytes(); + let last = sig.len() - 1; + sig[last] ^= 0x01; + v.push(refused("decision_bad_signature", "the decision's signature with one bit flipped", "three", sig, g3.clone(), None, "bad_signature")); + v.push(refused("decision_signer_not_authorised", "a decision signed by a key the key log does not list", "three", resign(d3.clone(), STRANGER), g3.clone(), None, "signer_not_authorised")); + v.push(refused("decision_signer_revoked", "a decision signed by a revoked steering key", "three", resign(d3.clone(), STEER_REVOKED), g3.clone(), None, "signer_revoked")); + let mut x = d3.clone(); + x.priority = vec![2, 2, 0]; + v.push(refused("decision_invalid", "a priority that names a pathway twice", "three", resign(x, STEER), g3.clone(), None, "decision_invalid")); + let mut x = d3.clone(); + x.pathways[0].evidence.reverse(); + v.push(refused("evidence_out_of_order", "edge-a's records named in reverse order", "three", resign(x, STEER), g3.clone(), None, "decision_invalid")); + let missing = n3[2][4].digest(); + v.push(refused("record_missing", "a record the decision names is not given", "three", d3.to_bytes(), g3.iter().copied().filter(|g| *g != missing).collect(), None, "record_missing")); + let (d, g, _) = b.decide("unlisted", World::Base, T1, 0, protocol::HLS, none, &LOADS[..3], &[]); + v.push(refused("hop_not_authorised", "evidence signed by a hop key the key log does not list", "unlisted", d.to_bytes(), g, None, "hop_not_authorised")); + let (d, g, _) = b.decide("revoked", World::Base, T1, 0, protocol::HLS, none, &LOADS[..3], &[]); + v.push(refused("hop_revoked", "evidence signed by a hop key the key log revoked", "revoked", d.to_bytes(), g, None, "hop_revoked")); + let mut x = d3.clone(); + let moved = x.pathways[1].evidence.remove(0); + x.pathways[0].evidence.push(moved); + v.push(refused("evidence_not_on_pathway", "edge-b's edge record named under edge-a", "three", resign(x, STEER), g3.clone(), None, "evidence_not_on_pathway")); + let p3 = three.clone(); + let late = record(&p3.room, World::Base, A_EDGE, 6); + b.pool.insert(late.digest(), late.to_bytes()); + let mut x = d3.clone(); + x.pathways[0].evidence.insert(3, late.digest()); + let mut gl = g3.clone(); + gl.push(late.digest()); + v.push(refused("evidence_outside_window", "a record of the period from 60 s named at 62 s, whose window ends at 60 s", "three", resign(x, STEER), gl, None, "evidence_outside_window")); + let mut x = d3.clone(); + x.pathways[2].delivery += 1; + v.push(refused("evidence_mismatch", "the relay tree's delivery stated one byte more than its records give", "three", resign(x, STEER), g3.clone(), None, "evidence_mismatch")); + let mut x = du.clone(); + let metered: u128 = nu[1].iter().map(|r| r.joules as u128).sum(); + let delivered: u128 = nu[1].iter().filter(|r| r.hop == pk(B_EDGE)).flat_map(|r| &r.tracks).filter(|e| e.dir == dir::OUT).map(|e| e.bytes as u128).sum(); + x.pathways[1] = PathwayState { status: status::FIGURE, class: 4, joules: metered, delivery: delivered, ..x.pathways[1].clone() }; + v.push(refused("unmetered_as_zero", "edge-b stated with a figure that counts its unmetered hop as zero joules", "three", resign(x, STEER), gu, None, "unmetered_as_zero")); + let mut x = dm.clone(); + let all: u128 = nm[0].iter().map(|r| r.joules as u128).sum(); + let delivered: u128 = nm[0].iter().filter(|r| r.hop == pk(A_EDGE)).flat_map(|r| &r.tracks).filter(|e| e.dir == dir::OUT).map(|e| e.bytes as u128).sum(); + x.pathways[0] = PathwayState { status: status::FIGURE, class: 4, joules: all, delivery: delivered, ..x.pathways[0].clone() }; + v.push(refused("classes_summed", "edge-a stated with one figure that sums a counter reading and a model's output", "three", resign(x, STEER), gm, None, "classes_summed")); + let mut x = ds.clone(); + x.priority = vec![2, 0, 1]; + v.push(refused("unknown_preferred", "the stale relay tree placed first", "three", resign(x, STEER), gs, None, "unknown_preferred")); + let mut x = d3.clone(); + x.priority = vec![2, 1, 0]; + v.push(refused("decision_mismatch", "the second and third pathways swapped", "three", resign(x, STEER), g3.clone(), None, "decision_mismatch")); + let mut x = d3.clone(); + x.ttl += 1; + v.push(refused("ttl_mismatch", "a TTL one second longer than the rule gives", "three", resign(x, STEER), g3.clone(), None, "decision_mismatch")); + let extra = m3.replacen("\"PATHWAY-PRIORITY\":[", "\"PATHWAY-PRIORITY\":[\"cdn-z\",", 1); + v.push(refused("manifest_mismatch", "a manifest with a pathway the policy does not have", "three", d3.to_bytes(), g3.clone(), Some(extra), "manifest_mismatch")); + let cloned = b.policy("cloned"); + let bad_base = manifest(&cloned, &dc).replacen("\"BASE-ID\":\"edge-a\"", "\"BASE-ID\":\"cdn-z\"", 1); + v.push(refused("manifest_clone_unknown", "a manifest whose clone names an unknown base pathway", "cloned", dc.to_bytes(), gc, Some(bad_base), "manifest_mismatch")); + v +} + +// ---- writing ---- + +fn status_label(s: u8) -> &'static str { + ["figure", "unmetered", "mixed", "stale", "empty"][s as usize] +} + +fn why_label(w: u8) -> &'static str { + ["", "buffer", "floor", "share", "hold", "rank", "none"][w as usize] +} + +fn at_json(at: &Place) -> Value { + match at { + Place::Room => json!(["room"]), + Place::Policy => json!(["policy"]), + Place::Decision => json!(["decision"]), + Place::Evidence(p, k) => json!(["evidence", p, k]), + Place::Pathway(p) => json!(["pathway", p]), + Place::Output => json!(["output"]), + Place::Manifest => json!(["manifest"]), + } +} + +fn outcome_json(policy: Option<&Policy>, o: &Outcome) -> Map { + let mut m = Map::new(); + match o { + Outcome::Refuse { at, reason } => { + m.insert("outcome".into(), "refuse".into()); + m.insert("reason".into(), reason.code().into()); + m.insert("at".into(), at_json(at)); + } + Outcome::Accept(a) => { + let p = policy.expect("an accepted decision's policy reads"); + m.insert("outcome".into(), "accept".into()); + m.insert("digest".into(), hex(&a.digest).into()); + m.insert("priority".into(), a.priority.clone().into()); + m.insert("ttl".into(), a.ttl.into()); + m.insert("why".into(), why_label(a.reason).into()); + let ps: Vec = p + .pathways + .iter() + .zip(&a.evidence) + .zip(&a.limits) + .map(|((x, e), l)| { + json!({ + "id": x.id, + "status": status_label(e.status), + "class": if e.has_figure() { Value::from(metering::tag_label(e.class)) } else { Value::Null }, + "joules": e.joules.to_string(), + "delivery": e.delivery.to_string(), + "limits": l, + }) + }) + .collect(); + m.insert("pathways".into(), ps.into()); + m.insert("steering_manifest".into(), a.manifest.clone().into()); + m.insert("uri".into(), a.uri.clone().into()); + } + } + m +} + +fn inputs_json(policy: Option<&Policy>, decision: &[u8]) -> Value { + let (Some(p), Ok(d)) = (policy, Decision::read(decision)) else { return Value::Null }; + if d.pathways.len() != p.pathways.len() { + return Value::Null; + } + json!({ + "t": d.t, + "session": hex(&d.session), + "protocol": protocol_label(d.protocol), + "report": report_json(p, &d.report), + "loads": d.pathways.iter().map(|s| s.load).collect::>(), + }) +} + +fn request_json(r: &Option<(u8, String, Option)>) -> Value { + match r { + None => Value::Null, + Some((proto, target, header)) => json!({ "protocol": protocol_label(*proto), "target": target, "cmcd_header": header }), + } +} + +fn inputs<'a>(desc: &'a [u8], head: &'a str, policy: &'a [u8], v: &'a VectorView, records: &'a [Vec]) -> Inputs<'a> { + Inputs { descriptor: desc, head: Some(head), policy, decision: &v.decision, records, manifest: v.manifest.as_deref() } +} + +/// What a vector gives a verifier, read back from the file or as written. +struct VectorView { + decision: Vec, + manifest: Option, +} + +fn reached(intent: &Intent, o: &Outcome) -> bool { + match (intent, o) { + (Intent::Accept(w), Outcome::Accept(a)) => a.reason == *w, + (Intent::Refuse(code), Outcome::Refuse { reason, .. }) => reason.code() == *code, + _ => false, + } +} + +/// Write the corpus. +pub fn generate() -> String { + let d = descriptor(); + let room_id = d.room_id(); + let desc = d.to_bytes(); + let head = key_log(); + let mut b = Builder { room_id, policies: policies(room_id), pool: BTreeMap::new() }; + let vs = vectors(&mut b); + let mut out = Map::new(); + out.insert("corpus".into(), CAPABILITY.into()); + out.insert("extension".into(), "extensions/energy-steering.md".into()); + out.insert("principals".into(), PRINCIPALS.iter().map(|(n, i)| (n.to_string(), Value::from(hex(&pk(*i))))).collect::>().into()); + out.insert("room".into(), json!({ "id": hex(&b.room_id), "descriptor": hex(&desc), "key_log_head": head })); + let mut ps = Map::new(); + for (n, bytes) in &b.policies { + let read = Policy::read(bytes).ok(); + ps.insert( + n.to_string(), + json!({ + "policy": hex(bytes), + "hls_tag": read.as_ref().map(hls_tag), + "dash_element": read.as_ref().map(dash_element), + }), + ); + } + out.insert("policies".into(), ps.into()); + let three = b.policy("three"); + let reqs: Vec = REQUESTS + .iter() + .map(|(name, proto, target, header)| { + let r = read_request(*proto, target.as_bytes(), header.map(str::as_bytes), &three); + json!({ "name": name, "protocol": protocol_label(*proto), "policy": "three", "target": target, "cmcd_header": header, "report": report_json(&three, &r) }) + }) + .collect(); + out.insert("requests".into(), reqs.into()); + let mut jv = Vec::new(); + for v in &vs { + let pbytes = &b.policies.iter().find(|(n, _)| *n == v.policy).expect("a policy").1; + let policy = Policy::read(pbytes).ok(); + let records: Vec> = v.records.iter().map(|g| b.pool[g].clone()).collect(); + let view = VectorView { decision: v.decision.clone(), manifest: v.manifest.clone() }; + let o = super::verify(&inputs(&desc, &head, pbytes, &view, &records)); + assert!(reached(&v.intent, &o), "vector {} reaches {:?}", v.name, o); + if let (Some(r), Some(p), Ok(dec)) = (&v.request, &policy, Decision::read(&v.decision)) { + assert_eq!(read_request(r.0, r.1.as_bytes(), r.2.as_deref().map(str::as_bytes), p), dec.report, "vector {}: the request gives the decision's report", v.name); + } + let mut m = Map::new(); + m.insert("name".into(), v.name.into()); + m.insert("about".into(), v.about.into()); + m.insert("policy".into(), v.policy.into()); + m.insert("request".into(), request_json(&v.request)); + m.insert("inputs".into(), inputs_json(policy.as_ref(), &v.decision)); + m.insert("decision".into(), hex(&v.decision).into()); + m.insert("records".into(), v.records.iter().map(|g| Value::from(hex(g))).collect::>().into()); + m.insert("manifest".into(), v.manifest.clone().map(Value::from).unwrap_or(Value::Null)); + m.extend(outcome_json(policy.as_ref(), &o)); + jv.push(Value::Object(m)); + } + out.insert("records".into(), b.pool.iter().map(|(g, r)| (hex(g), Value::from(hex(r)))).collect::>().into()); + out.insert("vectors".into(), jv.into()); + serde_json::to_string_pretty(&Value::Object(out)).expect("JSON") + "\n" +} + +fn bytes(v: &Value) -> Option> { + unhex(v.as_str()?) +} + +/// Check a corpus file against this module: every request gets the report, and every vector +/// the outcome, reason, place and facts the file states. Returns what differs. +pub fn verify(doc: &str) -> Vec { + let mut fails = Vec::new(); + let d: Value = match serde_json::from_str(doc) { + Ok(d) => d, + Err(e) => return vec![format!("vectors.json: {e}")], + }; + let (Some(desc), Some(head)) = (bytes(&d["room"]["descriptor"]), d["room"]["key_log_head"].as_str()) else { + return vec!["vectors.json: no room".into()]; + }; + let Some(pols) = d["policies"].as_object() else { return vec!["vectors.json: no policies".into()] }; + let pool: BTreeMap> = d["records"].as_object().into_iter().flatten().filter_map(|(k, v)| Some((k.clone(), bytes(v)?))).collect(); + let policy_bytes = |n: &str| pols.get(n).and_then(|p| bytes(&p["policy"])); + for (n, p) in pols { + let read = bytes(&p["policy"]).and_then(|b| Policy::read(&b).ok()); + let want = |k: &str, f: fn(&Policy) -> String| read.as_ref().map(|r| Value::from(f(r))).unwrap_or(Value::Null) == p[k]; + if !want("hls_tag", hls_tag) || !want("dash_element", dash_element) { + fails.push(format!("policy {n}: its HLS tag or DASH element differs")); + } + } + for r in d["requests"].as_array().into_iter().flatten() { + let name = r["name"].as_str().unwrap_or("?"); + let Some(p) = r["policy"].as_str().and_then(policy_bytes).and_then(|b| Policy::read(&b).ok()) else { + fails.push(format!("request {name}: no policy")); + continue; + }; + let proto = if r["protocol"] == "hls" { protocol::HLS } else { protocol::DASH }; + let got = read_request(proto, r["target"].as_str().unwrap_or("").as_bytes(), r["cmcd_header"].as_str().map(str::as_bytes), &p); + if report_json(&p, &got) != r["report"] { + fails.push(format!("request {name}: the report is {}, the corpus states {}", report_json(&p, &got), r["report"])); + } + } + for v in d["vectors"].as_array().into_iter().flatten() { + let name = v["name"].as_str().unwrap_or("?"); + let (Some(pb), Some(decision)) = (v["policy"].as_str().and_then(policy_bytes), bytes(&v["decision"])) else { + fails.push(format!("{name}: the vector does not read")); + continue; + }; + let Some(records) = v["records"].as_array().and_then(|a| a.iter().map(|g| g.as_str().and_then(|g| pool.get(g)).cloned()).collect::>>()) else { + fails.push(format!("{name}: a record it lists is not in the corpus")); + continue; + }; + let policy = Policy::read(&pb).ok(); + let view = VectorView { decision: decision.clone(), manifest: v["manifest"].as_str().map(String::from) }; + let o = verify_inputs(&desc, head, &pb, &view, &records); + let mut got = outcome_json(policy.as_ref(), &o); + got.insert("inputs".into(), inputs_json(policy.as_ref(), &decision)); + for (k, want) in &got { + if v.get(k) != Some(want) { + fails.push(format!("{name}: {k} is {want}, the corpus states {}", v.get(k).unwrap_or(&Value::Null))); + } + } + if let (Some(r), Some(p), Ok(dec)) = (v["request"].as_object(), &policy, Decision::read(&decision)) { + let proto = if r["protocol"] == "hls" { protocol::HLS } else { protocol::DASH }; + let got = read_request(proto, r["target"].as_str().unwrap_or("").as_bytes(), r["cmcd_header"].as_str().map(str::as_bytes), p); + if got != dec.report { + fails.push(format!("{name}: its request does not give the decision's report")); + } + } + } + fails +} + +fn verify_inputs(desc: &[u8], head: &str, policy: &[u8], v: &VectorView, records: &[Vec]) -> Outcome { + super::verify(&inputs(desc, head, policy, v, records)) +} diff --git a/wai/wai-rs/src/energy_steering/tests.rs b/wai/wai-rs/src/energy_steering/tests.rs new file mode 100644 index 000000000..964aef056 --- /dev/null +++ b/wai/wai-rs/src/energy_steering/tests.rs @@ -0,0 +1,267 @@ +//! Unit tests of the steering module: the records round-trip and are refused by their rules, +//! the request reader and its RFC 9651 parser, the exact products at their limits, the margin +//! at its boundary, and the rule's guarantee that a pathway with no figure is never placed +//! first by its rank while a pathway with a figure is within the limits. + +use super::*; + +fn k(i: u8) -> SigningKey { + SigningKey::from_bytes(&sha256(&[b"wai:energy-steering/test/key\x00", &[i]])) +} + +fn pathway(id: &str, hop: u8) -> Pathway { + Pathway { + id: id.into(), + base: NOT_CLONE, + host: String::new(), + params: vec![], + top_tier: 2, + latency_ms: 100, + capacity: NO_CAPACITY, + min_share_ppm: 0, + hops: vec![(k(hop).verifying_key().to_bytes(), place::EDGE)], + } +} + +fn policy(pathways: Vec) -> Policy { + Policy { + room: [7; 32], + signer: [0; 32], + period_us: 10, + window_us: 30, + lag_us: 2, + basis: basis::BYTES, + floor: 1, + latency_ceiling_ms: 500, + min_buffer_ms: 1000, + hysteresis_ppm: 100_000, + ttl_s: 300, + min_ttl_s: 30, + flags: 0, + initial: 0, + tiers: vec![1_000, 2_000, 4_000], + server_uri: "https://s.example/steer?a=1&b=2".into(), + reload_uri: "https://s.example/steer".into(), + decision_uri: "https://s.example/d/".into(), + pathways, + sig: [0; 64], + } + .sign(&k(0)) +} + +fn two() -> Policy { + policy(vec![pathway("a", 1), pathway("b", 2)]) +} + +fn fig(class: u8, joules: u128, delivery: u128) -> Evidence { + Evidence { status: status::FIGURE, class, joules, delivery } +} + +const NO_FIGURE: Evidence = Evidence { status: status::STALE, class: 0, joules: 0, delivery: 0 }; + +#[test] +fn a_policy_round_trips_and_its_rules_refuse() { + let p = two(); + let b = p.to_bytes(); + assert_eq!(Policy::read(&b).unwrap(), p); + let reread = |f: &dyn Fn(&mut Policy)| { + let mut q = two(); + f(&mut q); + Policy::read(&q.to_bytes()) + }; + assert_eq!(reread(&|q| q.window_us = 25), Err(Reason::PolicyInvalid), "a window that is not whole periods"); + assert_eq!(reread(&|q| q.pathways[1].id = "a".into()), Err(Reason::PolicyInvalid), "two pathways of one id"); + assert_eq!(reread(&|q| q.pathways[1].hops = q.pathways[0].hops.clone()), Err(Reason::PolicyInvalid), "a hop in two pathways"); + assert_eq!(reread(&|q| q.reload_uri = "https://s.example/#x".into()), Err(Reason::PolicyInvalid), "a fragment"); + assert_eq!(reread(&|q| q.reload_uri = "https://s.example/%4".into()), Err(Reason::PolicyInvalid), "a broken percent-encoding"); + assert_eq!(reread(&|q| q.pathways[0].hops[0].1 = place::INTERIOR), Err(Reason::PolicyInvalid), "no edge hop"); + assert_eq!(reread(&|q| q.pathways[1].base = 1), Err(Reason::CloneUnknown), "a clone of itself"); + assert_eq!(reread(&|q| q.pathways[0].base = 1), Err(Reason::CloneUnknown), "a clone of a later pathway"); + assert_eq!(reread(&|q| q.pathways[1].host = "h.example".into()), Err(Reason::PolicyInvalid), "a host on a pathway that is no clone"); + assert!(reread(&|q| { + q.pathways[1].base = 0; + q.pathways[1].host = "h.example".into(); + q.pathways[1].params = vec![("x".into(), "%2F".into())]; + }) + .is_ok()); + let mut short = b.clone(); + short.truncate(b.len() - 1); + assert_eq!(Policy::read(&short), Err(TRUNCATED)); + let mut long = b.clone(); + long.push(0); + assert_eq!(Policy::read(&long), Err(MALFORMED)); +} + +#[test] +fn the_window_ends_on_the_last_period_past_the_lag() { + let p = two(); + assert_eq!(window(&p, 0), (0, 0)); + assert_eq!(window(&p, 11), (0, 0)); + assert_eq!(window(&p, 12), (0, 10)); + assert_eq!(window(&p, 41), (0, 30)); + assert_eq!(window(&p, 52), (20, 50)); +} + +#[test] +fn the_request_reader_takes_what_each_protocol_sends() { + let p = two(); + let hls = |t: &str| read_request(protocol::HLS, t.as_bytes(), None, &p); + let dash = |t: &str| read_request(protocol::DASH, t.as_bytes(), None, &p); + assert_eq!(hls("/s?_HLS_pathway=%22b%22&_HLS_throughput=4000"), Report::new(Some(1), Some(4000), None)); + assert_eq!(hls("/s?_HLS_pathway=b&_HLS_pathway=a"), Report::new(Some(1), None, None)); + assert_eq!(hls("/s?_HLS_pathway=%22z%22&_HLS_throughput=-1"), Report::default()); + assert_eq!(hls("/s?_HLS_throughput=4%3"), Report::default()); + assert_eq!(dash("/s?_DASH_pathway=%22z,b%22&_DASH_throughput=5,6"), Report::new(Some(1), Some(6), None)); + assert_eq!(dash("/s?_DASH_pathway=a&_DASH_throughput=5,6"), Report::new(Some(0), Some(5), None)); + let cmcd = read_request(protocol::HLS, b"/s?CMCD=bl%3D%2821300%3Bv%2013000%3Ba%29%2Cmtp%3D48000", None, &p); + assert_eq!(cmcd, Report::new(None, Some(48_000_000), Some(13_000))); + let header = read_request(protocol::HLS, b"/s", Some(b"bl=(900;v),mtp=(2;v 3;a)"), &p); + assert_eq!(header, Report::new(None, Some(2_000), Some(900))); +} + +#[test] +fn the_structured_field_parser_reads_rfc_9651_dictionaries() { + use sf::{parse_dictionary as d, Bare, Member}; + let ok = d("a=1, b=?0, c=\"x\\\"y\", d=tok/en:1, e=:AQID:, f=-1.25;p=@12, g=%\"caf%c3%a9\", h, i=(1 2;q);r").unwrap(); + assert_eq!(ok.len(), 9); + assert_eq!(ok[0].1, Member::Item(sf::Item { bare: Bare::Integer(1), params: vec![] })); + assert_eq!(ok[5].1, Member::Item(sf::Item { bare: Bare::Decimal(-1250), params: vec![("p".into(), Bare::Date(12))] })); + assert_eq!(ok[6].1, Member::Item(sf::Item { bare: Bare::Display("café".into()), params: vec![] })); + assert_eq!(ok[7].1, Member::Item(sf::Item { bare: Bare::Boolean(true), params: vec![] })); + assert!(matches!(&ok[8].1, Member::Inner(items, ps) if items.len() == 2 && ps.len() == 1)); + assert_eq!(d("").unwrap(), vec![]); + assert_eq!(d("a=1,a=2").unwrap().len(), 1, "a key given twice keeps one place"); + for bad in ["a=1,", "A=1", "a=(1", "a=1 b=2", "a=1234567890123456", "a=1.2345", "a=1.", "a=\"x", "a=?2", "a=%\"\u{e9}\"", "a=%\"%C3\"", "a=@1.5", "a=:a*b:"] { + assert!(d(bad).is_err(), "{bad} parses"); + } +} + +#[test] +fn products_are_exact_at_their_limits() { + let m = u128::MAX; + let a = fig(4, m, m - 1); + let b = fig(4, m - 1, m); + // J_a × D_b = m × m; J_b × D_a = (m − 1)²: a's figure is the higher. + assert_eq!(cmp_figures(&a, &b), Ordering::Greater); + assert_eq!(cmp_figures(&b, &a), Ordering::Less); + assert_eq!(cmp_figures(&a, &a), Ordering::Equal); + assert_eq!(product(&[m, m, PPM as u128]).len(), 6); +} + +#[test] +fn the_margin_is_strict() { + // w's figure is exactly 10% below x's: it does not beat x by more than 10%. + let x = fig(4, 110, 100); + let w = fig(4, 100, 100); + assert!(!beats(&w, &x, 100_000)); + assert!(beats(&w, &x, 99_999)); + assert!(!beats(&w, &NO_FIGURE, 0)); +} + +#[test] +fn the_order_compares_figures_only_within_a_class() { + let p = policy(vec![pathway("a", 1), pathway("b", 2), pathway("c", 3), pathway("d", 4)]); + // "a" is the cheapest but estimated, "b" counter-read, "c" calibrated, "d" unknown. + let ev = [fig(3, 1, 100), fig(4, 50, 100), fig(5, 90, 100), NO_FIGURE]; + assert_eq!(order(&p, &ev), vec![2, 1, 0, 3]); + let ev = [fig(4, 3, 1), fig(4, 6, 2), NO_FIGURE, NO_FIGURE]; + assert_eq!(order(&p, &ev), vec![0, 1, 2, 3], "equal figures fall to the ids"); +} + +/// Over every evidence of four pathways drawn from a small set, with and without a current +/// pathway: when some pathway with a figure is within the limits, the rule never places a +/// pathway with no figure first by its rank or by holding a client on it. +#[test] +fn a_pathway_with_no_figure_is_never_preferred_for_it() { + let p = policy(vec![pathway("a", 1), pathway("b", 2), pathway("c", 3), pathway("d", 4)]); + let set = [NO_FIGURE, fig(4, 1, 100), fig(4, 9, 100), fig(2, 1, 1000), Evidence { status: status::UNMETERED, ..NO_FIGURE }]; + let digest = p.digest(); + let session = [0u8; 16]; + let mut seen = 0; + for i in 0..set.len().pow(4) { + let ev: Vec = (0..4).map(|j| set[i / set.len().pow(j) % set.len()]).collect(); + for c in [None, Some(0), Some(3)] { + let out = decide(&p, &digest, &session, &Report::new(c, Some(10_000), Some(5_000)), &[0; 4], &ev); + let any_figure = ev.iter().any(Evidence::has_figure); + if any_figure && matches!(out.reason, why::RANK | why::HOLD) { + assert!(ev[out.priority[0] as usize].has_figure(), "{ev:?} with c {c:?} gives {out:?}"); + seen += 1; + } + // Every pathway is within the limits here, so after the first the priority is the + // order: every pathway with a figure before every pathway with none. + let tail: Vec = out.priority[1..].iter().map(|&x| ev[x as usize].has_figure()).collect(); + assert!(tail.windows(2).all(|w| w[0] >= w[1]), "a pathway with a figure after one with none: {ev:?} gives {out:?}"); + } + } + assert!(seen > 0); +} + +#[test] +fn a_decision_round_trips_and_its_rules_refuse() { + let p = two(); + let d = Decision { + room: p.room, + signer: [0; 32], + policy: p.digest(), + t: 52, + session: [9; 16], + draw: draw(&p.digest(), &[9; 16]), + protocol: protocol::HLS, + report: Report::new(Some(1), Some(5_000), None), + pathways: vec![ + PathwayState { load: 1, limits: limit::ALL, status: status::FIGURE, class: 4, joules: 10, delivery: 20, evidence: vec![[1; 32]] }, + PathwayState { load: 2, limits: limit::ALL, status: status::STALE, class: 0, joules: 0, delivery: 0, evidence: vec![] }, + ], + ttl: 300, + reason: why::RANK, + priority: vec![0, 1], + sig: [0; 64], + } + .sign(&k(0)); + assert_eq!(Decision::read(&d.to_bytes()).unwrap(), d); + let reread = |f: &dyn Fn(&mut Decision)| { + let mut x = d.clone(); + f(&mut x); + Decision::read(&x.to_bytes()) + }; + assert_eq!(reread(&|x| x.priority = vec![0, 0]), Err(Reason::DecisionInvalid)); + assert_eq!(reread(&|x| x.pathways[1].joules = 5), Err(Reason::DecisionInvalid), "a figure on a stale pathway"); + assert_eq!(reread(&|x| x.pathways[0].class = 0), Err(Reason::DecisionInvalid), "a figure with no class"); + assert_eq!(reread(&|x| x.pathways[1].evidence = vec![[1; 32]]), Err(Reason::DecisionInvalid), "a record named twice"); + assert_eq!(reread(&|x| x.report.throughput = 0), Ok(Decision { report: Report { throughput: 0, ..d.report }, ..d.clone() })); + assert_eq!(reread(&|x| x.report.present &= !present::THROUGHPUT), Err(Reason::DecisionInvalid), "an absent value that is not 0"); + assert_eq!(reread(&|x| x.reason = 7), Err(Reason::DecisionInvalid)); +} + +#[test] +fn the_manifest_and_the_content_descriptions_are_fixed_bytes() { + let mut p = two(); + p.pathways[1].base = 0; + p.pathways[1].host = "h.example".into(); + p.pathways[1].params = vec![("k".into(), "v".into()), ("t".into(), "%2F".into())]; + let d = Decision { + room: p.room, + signer: [0; 32], + policy: p.digest(), + t: 0, + session: [0xab; 16], + draw: 0, + protocol: protocol::DASH, + report: Report::default(), + pathways: vec![PathwayState::default(), PathwayState::default()], + ttl: 30, + reason: why::NONE, + priority: vec![1, 0], + sig: [0; 64], + }; + let m = manifest(&p, &d); + let want = format!( + "{{\"VERSION\":1,\"TTL\":30,\"RELOAD-URI\":\"https://s.example/steer?wai_session={}\",\"PATHWAY-PRIORITY\":[\"b\",\"a\"],\"PATHWAY-CLONES\":[{{\"BASE-ID\":\"a\",\"ID\":\"b\",\"URI-REPLACEMENT\":{{\"HOST\":\"h.example\",\"PARAMS\":{{\"k\":\"v\",\"t\":\"%2F\"}}}}}}],\"science.transaction.wai-decision\":\"https://s.example/d/{}\"}}", + "ab".repeat(16), + hex(&d.digest()) + ); + assert_eq!(m, want); + assert!(serde_json::from_str::(&m).is_ok()); + assert_eq!(hls_tag(&p), "#EXT-X-CONTENT-STEERING:SERVER-URI=\"https://s.example/steer?a=1&b=2\",PATHWAY-ID=\"a\""); + assert_eq!(dash_element(&p), "https://s.example/steer?a=1&b=2"); +} diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 3676fadc2..2b3c5805e 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -442,6 +442,15 @@ pub mod live_record; /// cbindgen:ignore #[cfg(feature = "live_metering")] pub mod live_metering; +// Energy-aware content steering (extensions/energy-steering.md): `wai.live.steering` — a signed +// steering policy (WSP1) and a signed steering decision (WSD1) that chooses each client's pathway +// from the joules its hops' per-hop records state, compared only within an acquisition class, a +// pathway with no figure never ranked above one with a figure, and writes the HLS and DASH steering +// manifests exactly. Rust-only, like the other live modules: its constants must not become macros in +// include/wai.h. +/// cbindgen:ignore +#[cfg(feature = "energy_steering")] +pub mod energy_steering; // Timed text and live captions (extensions/timed-text.md): `wai.text.timed` — the cue model // both WebVTT and the IMSC1 Text Profile express, live caption revisions as `caption` // events of `wai.live.events` with the WTX1 annotation, and the exact mappings onto WebVTT diff --git a/wai/wai-rs/tests/energy_steering.rs b/wai/wai-rs/tests/energy_steering.rs new file mode 100644 index 000000000..8e2e29613 --- /dev/null +++ b/wai/wai-rs/tests/energy_steering.rs @@ -0,0 +1,105 @@ +//! `wai.live.steering` (extensions/energy-steering.md): the committed corpus +//! `live-conformance/steering/vectors.json` regenerates byte for byte from its fixed seeds, +//! every request and vector in it gets the outcome it states, every reason the extension +//! adds refuses a vector, the accepted vectors are the cases §11 names with the reasons they +//! were written for, and the extension's §10 lists exactly the reasons the reference reports. +#![cfg(feature = "energy_steering")] + +use std::collections::BTreeSet; +use std::path::PathBuf; +use wai::energy_steering::{corpus, Reason}; + +fn wai_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") +} + +fn read(p: &str) -> String { + std::fs::read_to_string(wai_dir().join(p)).unwrap_or_else(|e| panic!("{p}: {e}")) +} + +fn committed() -> String { + read("live-conformance/steering/vectors.json") +} + +#[test] +fn the_committed_corpus_regenerates_from_its_seeds() { + assert!(committed() == corpus::generate(), "live-conformance/steering/vectors.json differs from `wai_energy_steering_vectors gen`"); +} + +#[test] +fn every_request_and_vector_gets_its_stated_outcome() { + let fails = corpus::verify(&committed()); + assert!(fails.is_empty(), "{}", fails.join("\n")); +} + +/// Every reason this extension adds refuses a vector; the accepted vectors are §11's, each +/// first for the reason it was written for; a pathway with no figure is last wherever a +/// figure decides; and HLS and DASH content descriptions are written for every policy that +/// reads. +#[test] +fn the_corpus_covers_every_reason_and_case() { + let doc: serde_json::Value = serde_json::from_str(&committed()).unwrap(); + let vectors = doc["vectors"].as_array().unwrap(); + let seen: BTreeSet<&str> = vectors.iter().filter_map(|v| v["reason"].as_str()).collect(); + for r in Reason::ALL { + assert!(seen.contains(r.code()), "nothing in the corpus is refused {r}"); + } + for r in ["classes_summed", "hop_not_authorised", "hop_revoked", "record_missing", "signer_not_authorised", "signer_revoked", "bad_signature"] { + assert!(seen.contains(r), "nothing in the corpus is refused {r}"); + } + let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}")); + let first = |n: &str| get(n)["priority"][0].as_str().unwrap().to_string(); + let last = |n: &str| get(n)["priority"].as_array().unwrap().last().unwrap().as_str().unwrap().to_string(); + for (n, why) in [ + ("three_metered", "rank"), + ("mixed_classes", "rank"), + ("one_unmetered", "rank"), + ("mixed_hops", "rank"), + ("stale", "rank"), + ("floor_client", "floor"), + ("buffer_guard", "buffer"), + ("hysteresis_hold", "hold"), + ("hysteresis_switch", "rank"), + ("clones", "rank"), + ("share", "share"), + ("share_outside", "rank"), + ("capacity", "rank"), + ("latency", "rank"), + ("per_second", "rank"), + ("dash", "rank"), + ("none_within", "none"), + ] { + assert_eq!(get(n)["outcome"], "accept", "{n}"); + assert_eq!(get(n)["why"], why, "{n}"); + } + assert_eq!(first("three_metered"), "tree"); + assert_eq!(get("mixed_classes")["priority"], serde_json::json!(["edge-a", "edge-b", "tree"]), "ordered by class, not by figure"); + for (n, unknown) in [("one_unmetered", "edge-b"), ("mixed_hops", "edge-a"), ("stale", "tree"), ("share_outside", "origin")] { + assert_eq!(last(n), unknown, "{n}: the pathway with no figure is last"); + } + assert_eq!(first("share"), "origin", "a minimum share places the unmetered origin path whatever its evidence"); + assert_eq!((first("hysteresis_hold"), first("hysteresis_switch")), ("edge-a".into(), "tree".into())); + assert_eq!(first("clones"), "edge-a2"); + assert!(get("clones")["steering_manifest"].as_str().unwrap().contains("\"PATHWAY-CLONES\":[{\"BASE-ID\":\"edge-a\",\"ID\":\"edge-a2\"")); + assert_eq!(get("per_second")["priority"], serde_json::json!(["edge-a", "edge-b", "tree"])); + assert_eq!(get("dash")["inputs"]["protocol"], "dash"); + for n in ["floor_client", "buffer_guard", "none_within"] { + assert_eq!(get(n)["ttl"], 30, "{n}: the short TTL"); + } + for (n, p) in doc["policies"].as_object().unwrap() { + let reads = !["shares_over", "clone_later"].contains(&n.as_str()); + assert_eq!(p["hls_tag"].is_string(), reads, "{n}"); + assert_eq!(p["dash_element"].is_string(), reads, "{n}"); + } +} + +/// §10 of the extension lists exactly the codes `Reason::ALL` has, each once, in order. +#[test] +fn the_extension_lists_the_reasons() { + let text = read("extensions/energy-steering.md"); + let at = text.find("\n## 10. Reasons").expect("a §10"); + let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); + let listed: Vec = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect(); + let want: Vec = Reason::ALL.iter().map(|r| r.code().to_string()).collect(); + assert_eq!(listed, want, "§10's table and Reason::ALL differ"); +} -- 2.54.0 (Apple Git-157) From 1166f270d33ca25c30a4d265100dd706c60faeae Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 18 Oct 2026 20:34:24 -0400 Subject: [PATCH 3/9] wai: the steering corpus, live-conformance/steering/ (L15) Co-Authored-By: Claude Opus 5.5 --- wai/live-conformance/README.md | 9 +- wai/live-conformance/steering/README.md | 82 + wai/live-conformance/steering/vectors.json | 2912 ++++++++++++++++++++ wai/wai-rs/src/energy_steering.rs | 24 +- wai/wai-rs/src/energy_steering/tests.rs | 6 +- 5 files changed, 3011 insertions(+), 22 deletions(-) create mode 100644 wai/live-conformance/steering/README.md create mode 100644 wai/live-conformance/steering/vectors.json diff --git a/wai/live-conformance/README.md b/wai/live-conformance/README.md index 0fb0c2898..b9d82f77b 100644 --- a/wai/live-conformance/README.md +++ b/wai/live-conformance/README.md @@ -12,14 +12,16 @@ and the independent verifier of each, beside it. | [`recordings/`](recordings/) | [live-recording](../extensions/live-recording.md) (`wai.live.recording`) | `live_record_verify.py` | | [`captions/`](captions/) | [timed-text](../extensions/timed-text.md) (`wai.text.timed`) | `timed_text_verify.py` | | [`metering/`](metering/) | [live-metering](../extensions/live-metering.md) (`wai.live.metering`) | `live_metering_verify.py` | +| [`steering/`](steering/) | [energy-steering](../extensions/energy-steering.md) (`wai.live.steering`) | `energy_steering_verify.py` | **Provenance.** Generated here: `wai_live_events_vectors gen` writes `events/`, `wai_live_room_vectors gen` writes `rooms/`, `wai_capture_take_vectors gen` writes `takes/`, `wai_live_compose_vectors gen` writes `compose/`, `wai_live_record_vectors gen` writes `recordings/`, `wai_timed_text_vectors gen` writes `captions/` and -`wai_live_metering_vectors gen` writes `metering/`, each from records authored in code, from -fixed seeds (`events/README.md`, `rooms/README.md`, `takes/README.md`, `compose/README.md`, -`recordings/README.md`, `captions/README.md`, `metering/README.md`). One recorded record: +`wai_live_metering_vectors gen` writes `metering/` and `wai_energy_steering_vectors gen` writes +`steering/`, each from records authored in code, from fixed seeds (`events/README.md`, +`rooms/README.md`, `takes/README.md`, `compose/README.md`, `recordings/README.md`, +`captions/README.md`, `metering/README.md`, `steering/README.md`). One recorded record: `metering/relay-record.wmh` and its ledger, which the reference relay signed in its test. Each verifier is written from its extension's text, shares no code with `wai-rs`, and is @@ -33,4 +35,5 @@ python3 live_compose_verify.py compose python3 live_record_verify.py recordings python3 timed_text_verify.py captions python3 live_metering_verify.py metering +python3 energy_steering_verify.py steering ``` diff --git a/wai/live-conformance/steering/README.md b/wai/live-conformance/steering/README.md new file mode 100644 index 000000000..00b8f8654 --- /dev/null +++ b/wai/live-conformance/steering/README.md @@ -0,0 +1,82 @@ +# live-conformance/steering — energy-aware content steering + +The corpus of [`extensions/energy-steering.md`](../../extensions/energy-steering.md) §11: a +room and its key log, steering policies (`WSP1`), the per-hop session records (`WMH1`, +[live-metering](../../extensions/live-metering.md) §3) of the pathways' hops, steering +requests with the report each gives, and vectors, each a steering decision (`WSD1`) with the +records and the manifest given with it, and the outcome a verifier gives it. + +It sits beside [`metering/`](../metering/) because its evidence is live-metering's per-hop +records, read by that corpus's second implementation, `live_metering_verify.py`. + +**Steering equivalence.** For each request, a conforming reader given the policy, the +protocol, the `target` and the `cmcd_header` reaches the `report` it states (§4). For each +vector, a conforming verifier given the room (`descriptor` and `key_log_head`), the vector's +`policy` (by name), its `decision`, the records it lists (by digest, from `records`) and its +`manifest` (or none, when `null`) reaches the outcome the vector states (§9.3): + +- `refuse`: the `reason` and where, `at`: `["room"]`, `["policy"]`, `["decision"]`, + `["evidence", p, k]` for the record `k` named under pathway `p`, `["pathway", p]`, + `["output"]` or `["manifest"]`; +- `accept`: the decision's `digest`; the `priority` by pathway ID; the `ttl`; `why` the first + pathway is first; each pathway's `status`, `class`, `joules` and `delivery` (decimal + strings) and `limits` (the bits of §7.1); the `steering_manifest`; and the record's `uri`. + +Every vector also states the decision's `inputs` as it reads (its instant, session, protocol, +report and loads), and a vector whose report came from a request states the `request`, which +gives that report. Each policy that reads states its `hls_tag` and `dash_element` (§8.2, +§8.3). A verifier that reaches another outcome, reason, place, fact or byte does not +conform. + +**Provenance.** Generated here: `wai_energy_steering_vectors gen` writes `vectors.json` from +figures written in code, from fixed seeds: each key is SHA-256 of the corpus's fixed label and +the principal's index, each session SHA-256 of another fixed label and an index, every time is +a constant (the room's `t0` is 2026-10-16T00:00:00Z; the hops cut their records on a 10 s +period, and the decisions fall at 62 s and 72 s of room clock), and Ed25519 signing is +deterministic (RFC 8032), so the file is the same bytes on every machine. The per-hop records' +joule figures, bytes and subscriber time are synthetic constants, chosen to exercise the rule, +and were measured by nothing: the corpus computes which pathway the rule places first and +claims nothing about the energy any deployment spends or saves. The key-log head is sealed by +the reference's key log under a fixed root; the origin and the URIs are `.example` names (RFC +2606). The generator asserts that each vector reaches the outcome it was written to have +before it writes it. + +## Layout + +One file, `vectors.json`: + +- `principals`: the keys, by name (informative). +- `room`: its `id`, `descriptor` (`WRD1`, hexadecimal) and `key_log_head` (a key-log revision, + JSON text), which lists the room authority, the steering key `live/steer` with every energy + authority, a revoked steering key, and each hop with `live/relay` and every energy + authority, one of them revoked. One hop key, `unlisted_edge`, is in no entry. +- `policies`: by name, each `policy` (hexadecimal) with its `hls_tag` and `dash_element`, or + `null` for a policy the rules refuse. `three` has three pathways (`edge-a`, `edge-b` and the + relay tree `tree`, each an edge hop and an interior hop); `explore` adds the unmetered + `origin` path with a 10% minimum share; `cloned` adds `edge-a2`, a clone of `edge-a` + through another host with its own edge hop; `latency` puts the tree past the latency + ceiling, `seconds` uses the per-second basis, `strict` has a ceiling no pathway meets; + `unlisted` and `revoked` give the tree an edge hop the key log does not list or has revoked; + `shares_over`, `clone_later` and `stranger_signed` are refused. +- `records`: every per-hop record a vector lists, by its digest. The hops sign them in four + worlds: every hop counter-metered; one class per pathway (calibrated, counter, estimate); + `edge-b`'s interior hop unmetered; `edge-a`'s hops of two classes. +- `requests`: steering requests over HLS and DASH, with CMCD in the query and in the + `CMCD-Request` header, and malformed values the reader leaves absent. +- `vectors`: accepted, three pathways with figures of one class, mixed classes, an unmetered + hop, hops of two classes, stale evidence, a client at its floor, a short buffer, hysteresis + holding and a period later moving a client, clones, a session placed by its band and one + outside it, capacity, latency, the per-second basis, DASH, and no pathway within the limits; + refused, each reason of §10 and the codes it reuses (`classes_summed`, `hop_not_authorised`, + `hop_revoked`, `record_missing`, `signer_not_authorised`, `signer_revoked`, + `bad_signature`), each a control of one defect. + +## Regenerate and verify + +``` +cd wai/wai-rs +cargo run --no-default-features --features energy_steering --bin wai_energy_steering_vectors -- gen ../live-conformance/steering +cargo run --no-default-features --features energy_steering --bin wai_energy_steering_vectors -- verify ../live-conformance/steering +cargo test --no-default-features --features energy_steering --test energy_steering +python3 -I ../live-conformance/energy_steering_verify.py ../live-conformance/steering +``` diff --git a/wai/live-conformance/steering/vectors.json b/wai/live-conformance/steering/vectors.json new file mode 100644 index 000000000..0b6d7d879 --- /dev/null +++ b/wai/live-conformance/steering/vectors.json @@ -0,0 +1,2912 @@ +{ + "corpus": "wai.live.steering", + "extension": "extensions/energy-steering.md", + "principals": { + "authority": "7307f14e3005abac4f7fc91f14a15ee7e9d20864ef882380a6de2ba9de7bd7f0", + "clock": "e33b32ebca8857e14e4c262fee594bb7b3acac026e14a8a702b55ab15413a157", + "steer": "633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9", + "steer_revoked": "3fb2eb59810fde4638e6825f59e131a9dae94e1cb46ef488c40e6de5d187261b", + "stranger": "ee356e26d8df9c5c2e2b5b20ff87fe4c1f68bf43461430de6d2cad3b45e3c11e", + "log_root": "4cfe80320c86a685e81038300e896f92844e2d7b2bbf4410a02b45b4c65574c5", + "log_next": "b38123fcc98a507b069ef7200288ab969b2b25cc0c2b4e143297e299812e7fe4", + "publisher": "d66a91cadf7d69b1da5ac18c66cac0fd045d1407ef0bc0887304e532fdb83182", + "a_edge": "d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4", + "a_core": "0e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e744450", + "b_edge": "aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338", + "b_core": "1794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5", + "c_edge": "c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa10", + "c_core": "891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d878", + "d_edge": "3f2b8f6e1cd82356f6b65ee907e884182b327b26eafe350e60b9d55ebd2d4823", + "a2_edge": "00b78ba673f0a16a84264bc59333d9e3836eb1dd75aab27b76f247c9a8c30a5f", + "unlisted_edge": "780811ac754ae7b149721027583c2cba205c2f9e6c6b259fdc5d604a8e5c6a1d", + "revoked_edge": "870c467cdd9860be693d7c74575abcdbe97d33b3ff2011ae46fc020a3269c0ed" + }, + "room": { + "id": "c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3", + "descriptor": "57524431017307f14e3005abac4f7fc91f14a15ee7e9d20864ef882380a6de2ba9de7bd7f0e33b32ebca8857e14e4c262fee594bb7b3acac026e14a8a702b55ab15413a15700406cd8e95d060040420f0000000000160068747470733a2f2f73747265616d2e6578616d706c65080000000200000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f000000000002097075626c69736865720301020018000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100180000000000000000000000000000000000000000000000000000000000000000010a63616d6572612d37323002020d617630312e302e30354d2e30380b617663312e363430303146010002800268011e0096000000f40100000005d0021e00f4010000dc05000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ad0ea0cc2cc0d8c4366443a1b801af21d3bf7943e8985ecc7e767479fb78970c3b2008725360c6dd0d242d5913214d4ac6ca7cd62bec6695ab05dc0f50bd6b0c", + "key_log_head": "{\"grant_max_s\":86400,\"issued_at\":\"2026-10-15T00:00:00Z\",\"keys\":[{\"compromised_since\":null,\"id\":\"room-authority\",\"key\":\"7307f14e3005abac4f7fc91f14a15ee7e9d20864ef882380a6de2ba9de7bd7f0\",\"may_sign\":[\"live/room\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"steering\",\"key\":\"633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9\",\"may_sign\":[\"live/steer\",\"energy/CalibratedInstrument\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":\"2026-10-14T00:00:00Z\",\"id\":\"steering-old\",\"key\":\"3fb2eb59810fde4638e6825f59e131a9dae94e1cb46ef488c40e6de5d187261b\",\"may_sign\":[\"live/steer\",\"energy/CalibratedInstrument\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"revoked\",\"until\":null},{\"compromised_since\":null,\"id\":\"a-edge\",\"key\":\"d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4\",\"may_sign\":[\"live/relay\",\"energy/CalibratedInstrument\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"a-core\",\"key\":\"0e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e744450\",\"may_sign\":[\"live/relay\",\"energy/CalibratedInstrument\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"b-edge\",\"key\":\"aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338\",\"may_sign\":[\"live/relay\",\"energy/CalibratedInstrument\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"b-core\",\"key\":\"1794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5\",\"may_sign\":[\"live/relay\",\"energy/CalibratedInstrument\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"c-edge\",\"key\":\"c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa10\",\"may_sign\":[\"live/relay\",\"energy/CalibratedInstrument\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"c-core\",\"key\":\"891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d878\",\"may_sign\":[\"live/relay\",\"energy/CalibratedInstrument\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"d-edge\",\"key\":\"3f2b8f6e1cd82356f6b65ee907e884182b327b26eafe350e60b9d55ebd2d4823\",\"may_sign\":[\"live/relay\",\"energy/CalibratedInstrument\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"a2-edge\",\"key\":\"00b78ba673f0a16a84264bc59333d9e3836eb1dd75aab27b76f247c9a8c30a5f\",\"may_sign\":[\"live/relay\",\"energy/CalibratedInstrument\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":\"2026-10-14T00:00:00Z\",\"id\":\"c-edge-old\",\"key\":\"870c467cdd9860be693d7c74575abcdbe97d33b3ff2011ae46fc020a3269c0ed\",\"may_sign\":[\"live/relay\",\"energy/CalibratedInstrument\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"revoked\",\"until\":null}],\"kind\":\"wai-key-set\",\"next_root\":\"385b73207ef2635e45c2406b402d83acfda84a57dd566e1aacab60139e791be2\",\"origin\":\"https://stream.example\",\"prev\":null,\"revoked_modules\":[],\"root\":\"4cfe80320c86a685e81038300e896f92844e2d7b2bbf4410a02b45b4c65574c5\",\"seq\":0,\"sig\":\"4b42d15b7d633094c5f0eee74c4eac8370b2c03937bef2c9833ca6e04ccfdb2c9bc412772f58083bd7da2daf1229477b139c92d92ead75105b60e98e1d66c50b\",\"status_max_age_s\":3600}" + }, + "policies": { + "three": { + "policy": "5753503101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9809698000000000080c3c9010000000080841e0000000000010158020000a00f0000400d03002c0100001e00000001000400350c0000000000006a18000000000000d430000000000000a8610000000000370068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e26746b3d6131310068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e2a0068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f6465636973696f6e732f0306656467652d61ff00000390010000ffffffff0000000002d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500206656467652d62ff000003c2010000ffffffff0000000002aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338011794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5020474726565ff000003f4010000e80300000000000002c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa1001891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d878023baf66d185523b41bca87a90338882013d75d44fe6b6c0ff3f3f2b467c4a7739a148d1527535dfd47c053359c62f26a36c96c8b4dc68337e7cf2e559d877a602", + "hls_tag": "#EXT-X-CONTENT-STEERING:SERVER-URI=\"https://steer.stream.example/v1/room?asset=lesson&tk=a1\",PATHWAY-ID=\"edge-a\"", + "dash_element": "https://steer.stream.example/v1/room?asset=lesson&tk=a1" + }, + "explore": { + "policy": "5753503101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9809698000000000080c3c9010000000080841e0000000000010158020000a00f0000400d03002c0100001e00000001000400350c0000000000006a18000000000000d430000000000000a8610000000000370068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e26746b3d6131310068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e2a0068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f6465636973696f6e732f0406656467652d61ff00000390010000ffffffff0000000002d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500206656467652d62ff000003c2010000ffffffff0000000002aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338011794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5020474726565ff000003f4010000e80300000000000002c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa1001891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d87802066f726967696eff0000012c010000ffffffffa0860100013f2b8f6e1cd82356f6b65ee907e884182b327b26eafe350e60b9d55ebd2d482301735906d8e529105b9954bbb22eb9264aadc03e649b5e7f62fa7365c2371c1dc1fba353174c7eb3817c7ced4a0073b3e4737d845f7df63fe5a0d2002f9f7cc80e", + "hls_tag": "#EXT-X-CONTENT-STEERING:SERVER-URI=\"https://steer.stream.example/v1/room?asset=lesson&tk=a1\",PATHWAY-ID=\"edge-a\"", + "dash_element": "https://steer.stream.example/v1/room?asset=lesson&tk=a1" + }, + "cloned": { + "policy": "5753503101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9809698000000000080c3c9010000000080841e0000000000010158020000a00f0000400d03002c0100001e00000001000400350c0000000000006a18000000000000d430000000000000a8610000000000370068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e26746b3d6131310068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e2a0068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f6465636973696f6e732f0406656467652d61ff00000390010000ffffffff0000000002d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500206656467652d62ff000003c2010000ffffffff0000000002aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338011794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5020474726565ff000003f4010000e80300000000000002c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa1001891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d8780207656467652d6132001661322e656467652e73747265616d2e6578616d706c650203706f7006656173742d3202746b0578253246790390010000ffffffff000000000100b78ba673f0a16a84264bc59333d9e3836eb1dd75aab27b76f247c9a8c30a5f01d566954f95bc379ddc9c6edbfffa2931e9741281e8eb8d705a358bbb9931bbfb0c2b1627f90a7f4c080896a2b3c06013d3c2f005ed7e89227f917d7f5800fb07", + "hls_tag": "#EXT-X-CONTENT-STEERING:SERVER-URI=\"https://steer.stream.example/v1/room?asset=lesson&tk=a1\",PATHWAY-ID=\"edge-a\"", + "dash_element": "https://steer.stream.example/v1/room?asset=lesson&tk=a1" + }, + "latency": { + "policy": "5753503101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9809698000000000080c3c9010000000080841e0000000000010158020000a00f0000400d03002c0100001e00000001000400350c0000000000006a18000000000000d430000000000000a8610000000000370068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e26746b3d6131310068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e2a0068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f6465636973696f6e732f0306656467652d61ff00000390010000ffffffff0000000002d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500206656467652d62ff000003c2010000ffffffff0000000002aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338011794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5020474726565ff00000384030000e80300000000000002c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa1001891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d8780252ea8ef4e3df7ac0249177a1342a0f9ebbcab9ec7192239ecd768c753e224247b025999dbc8d25bf18e16f409df6090cf50f77edc457667c9e33e7e194632403", + "hls_tag": "#EXT-X-CONTENT-STEERING:SERVER-URI=\"https://steer.stream.example/v1/room?asset=lesson&tk=a1\",PATHWAY-ID=\"edge-a\"", + "dash_element": "https://steer.stream.example/v1/room?asset=lesson&tk=a1" + }, + "seconds": { + "policy": "5753503101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9809698000000000080c3c9010000000080841e0000000000020158020000a00f0000400d03002c0100001e00000001000400350c0000000000006a18000000000000d430000000000000a8610000000000370068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e26746b3d6131310068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e2a0068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f6465636973696f6e732f0306656467652d61ff00000390010000ffffffff0000000002d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500206656467652d62ff000003c2010000ffffffff0000000002aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338011794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5020474726565ff000003f4010000e80300000000000002c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa1001891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d8780299f644f9ab8df4b7b253148b4355fef763d86469d38df227707e9466a6b5efce90a985b2b7ba3b4fed51b388c63b139b907f5ea5975ace298e0e5aab9c063901", + "hls_tag": "#EXT-X-CONTENT-STEERING:SERVER-URI=\"https://steer.stream.example/v1/room?asset=lesson&tk=a1\",PATHWAY-ID=\"edge-a\"", + "dash_element": "https://steer.stream.example/v1/room?asset=lesson&tk=a1" + }, + "strict": { + "policy": "5753503101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9809698000000000080c3c9010000000080841e0000000000010164000000a00f0000400d03002c0100001e00000001000400350c0000000000006a18000000000000d430000000000000a8610000000000370068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e26746b3d6131310068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e2a0068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f6465636973696f6e732f0306656467652d61ff00000390010000ffffffff0000000002d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500206656467652d62ff000003c2010000ffffffff0000000002aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338011794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5020474726565ff000003f4010000e80300000000000002c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa1001891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d878028563327a90f6ff5720b45b35b03e82813a247ff72f0bef3ebf5ba79a8bad4f7d9e9d450fc0d289097bb7c52c55ef041afa5831fa76741f97c564f8a3ae476d00", + "hls_tag": "#EXT-X-CONTENT-STEERING:SERVER-URI=\"https://steer.stream.example/v1/room?asset=lesson&tk=a1\",PATHWAY-ID=\"edge-a\"", + "dash_element": "https://steer.stream.example/v1/room?asset=lesson&tk=a1" + }, + "unlisted": { + "policy": "5753503101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9809698000000000080c3c9010000000080841e0000000000010158020000a00f0000400d03002c0100001e00000001000400350c0000000000006a18000000000000d430000000000000a8610000000000370068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e26746b3d6131310068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e2a0068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f6465636973696f6e732f0306656467652d61ff00000390010000ffffffff0000000002d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500206656467652d62ff000003c2010000ffffffff0000000002aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338011794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5020474726565ff000003f4010000e80300000000000002780811ac754ae7b149721027583c2cba205c2f9e6c6b259fdc5d604a8e5c6a1d01891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d8780281cd0390a2a44f7e8306fad239e448d3864c79ccbba9d9b3db538806efd18b7ca41afd9aa8582fdff3132ea32e818e164711c8ae52680abbb6dce821242a0909", + "hls_tag": "#EXT-X-CONTENT-STEERING:SERVER-URI=\"https://steer.stream.example/v1/room?asset=lesson&tk=a1\",PATHWAY-ID=\"edge-a\"", + "dash_element": "https://steer.stream.example/v1/room?asset=lesson&tk=a1" + }, + "revoked": { + "policy": "5753503101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9809698000000000080c3c9010000000080841e0000000000010158020000a00f0000400d03002c0100001e00000001000400350c0000000000006a18000000000000d430000000000000a8610000000000370068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e26746b3d6131310068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e2a0068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f6465636973696f6e732f0306656467652d61ff00000390010000ffffffff0000000002d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500206656467652d62ff000003c2010000ffffffff0000000002aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338011794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5020474726565ff000003f4010000e80300000000000002870c467cdd9860be693d7c74575abcdbe97d33b3ff2011ae46fc020a3269c0ed01891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d87802dae88a5ab60c755eb516c78dd3a515deee4537e2923b41e7cd9395ff6b4d468bb1109acb0ac0b346423db5f227b0695549066cb4e77cc37e3169a43448f4540e", + "hls_tag": "#EXT-X-CONTENT-STEERING:SERVER-URI=\"https://steer.stream.example/v1/room?asset=lesson&tk=a1\",PATHWAY-ID=\"edge-a\"", + "dash_element": "https://steer.stream.example/v1/room?asset=lesson&tk=a1" + }, + "shares_over": { + "policy": "5753503101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9809698000000000080c3c9010000000080841e0000000000010158020000a00f0000400d03002c0100001e00000001000400350c0000000000006a18000000000000d430000000000000a8610000000000370068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e26746b3d6131310068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e2a0068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f6465636973696f6e732f0306656467652d61ff00000390010000ffffffffc027090002d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500206656467652d62ff000003c2010000ffffffff20a1070002aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338011794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5020474726565ff000003f4010000e80300000000000002c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa1001891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d87802d6ebe9dae080bfdb522a972675ac327b689fe42f88dfc2879145d58ec873c6de2a90dfee0806fb81f6b8464174f794d039364f4acf73bc56341846bd53ae8608", + "hls_tag": null, + "dash_element": null + }, + "clone_later": { + "policy": "5753503101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9809698000000000080c3c9010000000080841e0000000000010158020000a00f0000400d03002c0100001e00000001000400350c0000000000006a18000000000000d430000000000000a8610000000000370068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e26746b3d6131310068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e2a0068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f6465636973696f6e732f0406656467652d61ff00000390010000ffffffff0000000002d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500206656467652d62ff000003c2010000ffffffff0000000002aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338011794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5020474726565ff000003f4010000e80300000000000002c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa1001891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d8780207656467652d6132031661322e656467652e73747265616d2e6578616d706c650203706f7006656173742d3202746b0578253246790390010000ffffffff000000000100b78ba673f0a16a84264bc59333d9e3836eb1dd75aab27b76f247c9a8c30a5f015561804c8cc2ae394c9601acf5f5d0c2614c94f4c963d119794f88b3186869768b7931ce2551f5ff373384aa97c242b3192bce2f69d0fda02f68f735d4880303", + "hls_tag": null, + "dash_element": null + }, + "stranger_signed": { + "policy": "5753503101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3ee356e26d8df9c5c2e2b5b20ff87fe4c1f68bf43461430de6d2cad3b45e3c11e809698000000000080c3c9010000000080841e0000000000010158020000a00f0000400d03002c0100001e00000001000400350c0000000000006a18000000000000d430000000000000a8610000000000370068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e26746b3d6131310068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f726f6f6d3f61737365743d6c6573736f6e2a0068747470733a2f2f73746565722e73747265616d2e6578616d706c652f76312f6465636973696f6e732f0306656467652d61ff00000390010000ffffffff0000000002d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500206656467652d62ff000003c2010000ffffffff0000000002aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338011794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5020474726565ff000003f4010000e80300000000000002c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa1001891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d87802c50fb83a6cf1d93f751a65bc05ea2d55d00ac5b3ff4beba1c9c4fec4b7bdc1b37ac1aa38657322eb14e063ca301626279df1fb1a515e716cd776d8b6d1db8602", + "hls_tag": "#EXT-X-CONTENT-STEERING:SERVER-URI=\"https://steer.stream.example/v1/room?asset=lesson&tk=a1\",PATHWAY-ID=\"edge-a\"", + "dash_element": "https://steer.stream.example/v1/room?asset=lesson&tk=a1" + } + }, + "requests": [ + { + "name": "hls_quoted", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?asset=lesson&_HLS_pathway=%22edge-a%22&_HLS_throughput=4000000", + "cmcd_header": null, + "report": { + "pathway": "edge-a", + "throughput": 4000000, + "buffer": null + } + }, + { + "name": "hls_unquoted", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_pathway=edge-b&_HLS_throughput=123", + "cmcd_header": null, + "report": { + "pathway": "edge-b", + "throughput": 123, + "buffer": null + } + }, + { + "name": "hls_literal_quotes", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_pathway=\"tree\"", + "cmcd_header": null, + "report": { + "pathway": "tree", + "throughput": null, + "buffer": null + } + }, + { + "name": "hls_unknown_pathway", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_pathway=%22cdn-z%22&_HLS_throughput=5000000", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": 5000000, + "buffer": null + } + }, + { + "name": "hls_throughput_not_integer", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_pathway=edge-a&_HLS_throughput=4e6", + "cmcd_header": null, + "report": { + "pathway": "edge-a", + "throughput": null, + "buffer": null + } + }, + { + "name": "hls_throughput_past_u64", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_throughput=18446744073709551616", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": null, + "buffer": null + } + }, + { + "name": "hls_throughput_u64_max", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_throughput=18446744073709551615", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": 18446744073709551615, + "buffer": null + } + }, + { + "name": "hls_first_part_wins", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_pathway=tree&_HLS_pathway=edge-a", + "cmcd_header": null, + "report": { + "pathway": "tree", + "throughput": null, + "buffer": null + } + }, + { + "name": "hls_bad_percent_skipped", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_pathway=%2&_HLS_throughput=500", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": 500, + "buffer": null + } + }, + { + "name": "hls_fragment_cut", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_throughput=9#_HLS_pathway=edge-a", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": 9, + "buffer": null + } + }, + { + "name": "hls_no_query", + "protocol": "hls", + "policy": "three", + "target": "/v1/room", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": null, + "buffer": null + } + }, + { + "name": "hls_dash_parameters_ignored", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_DASH_pathway=edge-a&_DASH_throughput=7", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": null, + "buffer": null + } + }, + { + "name": "hls_cmcd_v2_lists", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_pathway=%22tree%22&CMCD=bl%3D%2821300%3Bv%2013000%3Ba%29%2Cmtp%3D%2848000%3Bv%29%2Csid%3D%22abc%22", + "cmcd_header": null, + "report": { + "pathway": "tree", + "throughput": 48000000, + "buffer": 13000 + } + }, + { + "name": "hls_cmcd_v1_integers", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?CMCD=bl%3D4100%2Cmtp%3D25400", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": 25400000, + "buffer": 4100 + } + }, + { + "name": "hls_cmcd_throughput_parameter_first", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_throughput=3000000&CMCD=mtp%3D25400%2Cbl%3D900", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": 3000000, + "buffer": 900 + } + }, + { + "name": "hls_cmcd_header", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?_HLS_pathway=edge-a", + "cmcd_header": "bl=(8000;v),br=(3200;v),mtp=(12000;v)", + "report": { + "pathway": "edge-a", + "throughput": 12000000, + "buffer": 8000 + } + }, + { + "name": "hls_cmcd_query_over_header", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?CMCD=bl%3D700", + "cmcd_header": "bl=(8000;v),mtp=(12000;v)", + "report": { + "pathway": null, + "throughput": null, + "buffer": 700 + } + }, + { + "name": "hls_cmcd_unterminated", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?CMCD=bl%3D%281200", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": null, + "buffer": null + } + }, + { + "name": "hls_cmcd_trailing_comma", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?CMCD=bl%3D300%2C", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": null, + "buffer": null + } + }, + { + "name": "hls_cmcd_negative", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?CMCD=bl%3D-5%2Cmtp%3D(-1)", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": null, + "buffer": null + } + }, + { + "name": "hls_cmcd_decimal_in_list", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?CMCD=bl%3D%281.5%3Bv%29", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": null, + "buffer": null + } + }, + { + "name": "hls_cmcd_display_string_and_date", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?CMCD=bl%3D300%2Cx%3D%25%22caf%25c3%25a9%22%2Cy%3D%401700000000%2Cz%3D%3AAQID%3A", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": null, + "buffer": 300 + } + }, + { + "name": "dash_list_second_known", + "protocol": "dash", + "policy": "three", + "target": "/v1/room?_DASH_pathway=%22cdn-z,edge-b%22&_DASH_throughput=1000,2000", + "cmcd_header": null, + "report": { + "pathway": "edge-b", + "throughput": 2000, + "buffer": null + } + }, + { + "name": "dash_unquoted_single", + "protocol": "dash", + "policy": "three", + "target": "/v1/room?steeringToken=12345&_DASH_pathway=tree&_DASH_throughput=5140000", + "cmcd_header": null, + "report": { + "pathway": "tree", + "throughput": 5140000, + "buffer": null + } + }, + { + "name": "dash_throughput_list_short", + "protocol": "dash", + "policy": "three", + "target": "/v1/room?_DASH_pathway=%22cdn-z,edge-b%22&_DASH_throughput=1000", + "cmcd_header": null, + "report": { + "pathway": "edge-b", + "throughput": null, + "buffer": null + } + }, + { + "name": "dash_no_pathway_first_throughput", + "protocol": "dash", + "policy": "three", + "target": "/v1/room?_DASH_throughput=777,888&_HLS_pathway=edge-a", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": 777, + "buffer": null + } + } + ], + "records": { + "00d227b6d74067a616ae0e9b6926319310fba441feafb8779aaf2b9c85dac0d2": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c300b78ba673f0a16a84264bc59333d9e3836eb1dd75aab27b76f247c9a8c30a5f0180f0fa020000000000879303000000008093dc14000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000065cd1d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000923f4e89a4869c57d4ddc66afc211fd45579b6512ed7a24af4cfed016d19522970087f42651ffe1e424ac0a2cf3b600466ac97434e2ac67e0d1288b6c447d506", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c30e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500180f0fa0200000000008793030000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f000000000000809698000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d575e724240a29ac76872b8e223f3fd789b699ba16640c4dca1ad933a7827e1db3ba0f9be6524ea5a5a0ab94ea57678382cbdf3818bbfd23b9e8bfb40d00090f", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f401005a62020000000080f0fa02000000000084d717000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02007102000000000000c2eb0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000e1f5050000000000a3e11100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bfd0c1ba247c1c953e18775d183aec029369a736003e3f985239daa09eae0efa6c358e089e16699e79e9fe82625bfdb716cb145a118ae566fa740364b6cb3608", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa100180f0fa0200000000008793030000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e1110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009f3e822bd661454544dcf48087b3e043aae3f2682c063da3f4a0553f26baa0fc75853d7f18d1ddf28b305e97eae3519284d73359e7f901fea08b3ac7b248c000", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c31794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e501005a62020000000080f0fa020000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003561f92cdccb9b1c830846102d0a11d5b4a20770d13575d20eee01fbea3ff50e9f7af0c7d825071bb24d7980b00fddc69bd20d4a62e245d51bcc86694e46c600", + "15d0cfd22cce80a680a8b12fef1993df2a8db44cecef6697a3ca29280a73904e": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d878010087930300000000801d2c040000000080d1f008000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f900f566a273080f9076b3d2c23743a84ea064b48ff5276fdbc84b5d510277f885905766b02f72da8d292095f1ee770429496b3efc4e8d07f33fd21b12e890f", + "19c3cafd29a850cb6b97f69162d9ca808430f424604e496ea4c7dd6a1c5031a0": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c33f2b8f6e1cd82356f6b65ee907e884182b327b26eafe350e60b9d55ebd2d482301005a62020000000080f0fa020000000000000000000000000002000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02409c00000000000080f0fa0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000e1f5050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f7e897dbfca3fa0dd6b15711f75ed0070c56e74d429e9d04e83561b4fd20e1f03f4db48725ba27c158cd71eea52507a6e7fe68ff001d7884570ae29b9a8f4603", + "1be513a6d0497d9ade32aab909cd46afb7c5660c6a77aed1c754693774e64f70": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa100180f0fa0200000000008793030000000000c2eb0b000000000302000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e1110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007ba914d447d95477a829583246f1ece7f05314db51653a81828759215b7e4ae94d385fcd6149ccebe7b1fcbef7114391712b5d00f2d62bdc345790e6691a7005", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f13380180f0fa020000000000879303000000000065cd1d000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02007102000000000000c2eb0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000084d71700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053af2ac2e2e2fa8e18205a7b3f0620f1e99530805e367455412f5bf65d5b3c035d62034376baf054b9765800acf3cab0db5f5329dc219c878dc8aba7c6da4e02", + "1fdcac14c5ca0c3539283e6436f8f906ff5e7f10b77b26141dd042a20f48f6ab": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa10010087930300000000801d2c040000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e11100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f7737e75b56da42bd12440e0ce47e19c8d2a6b9f7210749e466348432e6ffb3265cb699e68eeefd56ef7e40f6be6e69bfebc2f2f6628403542ac9183440704", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f133801005a62020000000080f0fa02000000000065cd1d000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02007102000000000000c2eb0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000084d717000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000be5b4e0a5772a372c3e83c863cbc7a767d1b354ba64977064a9e2e56ba57111436f53a8d8da8c3b4392c3d31220f4db9377c34290622883a57318ae54978590c", + "2e3bc455db37fa9a4a631da0e4d162616e02f38c26892b3e09086c8950a69fdf": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d8780180f0fa0200000000008793030000000000c2eb0b000000000302000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004df64098cd80518d2ef7ed01329975f20a5cade9025228bb730b70f46105282080915793f8d4d2d74bd2584a343687132c612128fa6b21a91dd068fcf9f0f10d", + "3175f3683ebadf651cb86d73c42c865d1a11b304d3ef24e4923309e9879dc17d": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c30e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e744450010087930300000000801d2c040000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a713986e3415fa681e842c71dfecc6745836f7d58c615dff5f9957f9e068aeef0094d05d4d6f8c5d4b64bf1c19bb9350b24a0ac77cfae5fdd383e7a37b86205", + "31c8e910208e39c3823f5e0c880a964e1f7aaa3e113e05e500830a50dc327c02": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3870c467cdd9860be693d7c74575abcdbe97d33b3ff2011ae46fc020a3269c0ed0180f0fa0200000000008793030000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000496f4edca20949075df93709441f3cd6f6a6ec243b6e7c6ce8453877e59d57f3c11a24af1bdd72ff6611de565246031be09aa82b2848c784dd27abcd9a02590e", + "3392606edc4476ce0e21918ea2f4a2152ab75b83535a7f7b9c87f47a7b3259db": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3870c467cdd9860be693d7c74575abcdbe97d33b3ff2011ae46fc020a3269c0ed01005a62020000000080f0fa020000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0ec059a5e2fbd981b849de70862c2c67680e032b22a2ab658a9c6463b5ac4a4576804e883046c3b434c4b5eb24c01b191663df1579f616165a30f8507c20205", + "35bfbbbcd40817a3860cb7e5b494a92656e1206bf49837494141559ad6472ac7": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c30e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e74445001005a62020000000080f0fa020000000000a3e11100000000050000138800000000009896800000000d63616c2d323032362d3030343202000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a87601753705e993408e0f421601d9611327f2478ef49734c93cd8f2fd94dd09b1bbd738d6cf5c0fe6e7d8ebba34c47727b9795697664bb7f3de909f9d5db00", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f13380180c3c90100000000005a6202000000000065cd1d000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02007102000000000000c2eb0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000084d717000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e320eaf59c282f276358912e1c0245217eecb806d76e3d9d52035538f7b0b1bccb3919787e9ef7ae453fe6d5ded8b82564a57c2b815bd0c87575008074b65301", + "4b78fcc9318b3041a3ff2fcb45659affad13b39f150b7e80c87cd6a48da61ad6": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c31794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e501005a62020000000080f0fa020000000000000000000000000002000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f000000000000809698000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cdbf912038254ad34820b4637d9f16b770da6561e380c32f14a78cead43d476a02a124646f7ba58a5527ae3b67d265540b45de81dd625cd134b9d498f569440b", + "4c8977c8378019d222025073221741598750fb1e1c5baa925a57c57e82f85a65": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f40180c3c90100000000005a6202000000000084d71700000000050000138800000000009896800000000d63616c2d323032362d3030343202000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02007102000000000000c2eb0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000e1f5050000000000a3e1110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010942d0b6ee5b8606b7ec3ac7a02fb23e79f58ddb67d02f31799b469e68e6c94a2ec4b6eda736545c5b1ebe81cd08bc02d7b465c92efa0faa21dbbc34ec9a807", + "4ccd16a3dc9c472e7fdec6f528ff29230abd8b8f3a13883d670511c2c7734ad6": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d8780180c3c90100000000005a62020000000000c2eb0b000000000302000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006e887b49751d2c6031581358ec04ea5609c8e3a176475acf3d709660fa8857bc48217c7946eff2c1baf0334af49fc97a76d73f26942536dc934a9447eeb10809", + "4d54f577adde552acc95ba9a7c140346447a7915b9e6bee709085eea3a3af002": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c31794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e5010087930300000000801d2c040000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de7151ab6bc382a22355139b010265af38f2cda7cbbae5705abcd06217930ecf871b48092492d73aaa3cc024220e3dca7e56eabf94235f64dca449f1982bb04", + "535c9d8cfb8716120e06c89d38e43c6490e98a9df1dca73c0a9b1a40dfec9536": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3780811ac754ae7b149721027583c2cba205c2f9e6c6b259fdc5d604a8e5c6a1d0180c3c90100000000005a62020000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e11100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028fc020bc7ec7b5c924b12b7ff9986f2ec10dc45a5de7f1a95e676c9aff29720f572abfe81e5a94735c86ed2470f8449dec04372d63828c62c80b25c9e44b204", + "5a28ac8c75ab8b7187bd47d90ca4d41163f64ad6b21ac9f9398e02fc2f9cb248": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa100180c3c90100000000005a62020000000000c2eb0b000000000302000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d6fbeee7b08ee2113619af1b7e6f25d68d76bca055fb606c39ee658a98af04e362ba52e55d7a663e886697a7e708931160f64503d8cd126cd0247a690066f505", + "5e169ba66b49f831269b2727bb5a6aeda2d30ee7181a8b08bcfe9c81aec96297": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c31794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e50180c3c90100000000005a62020000000000000000000000000002000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f000000000000809698000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a4a704e71b9b6e478fe788855094b2d4558d2710e6aaf5607175b7489085259476736c9a96ea8bdac1b9dc46b9d4d6cfd3aef22a2807b03d7b1b6cbfeaa4a107", + "69633671606f1c8c35638a9e646d099afa9ce7310d56066cd31b951565a855d3": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3aae12acab4925e54d8735a8f696ad76128e2eeea2a6091dc83315bd2fe7f1338010087930300000000801d2c04000000000065cd1d000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02007102000000000000c2eb0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000084d71700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014fe6306d813a632af5343c4274fd064249b5fb99db820b4ec69d3f10c5deddb4cc69572ce33cbd430d2821ffa5ea26f47b8f01b4ff5375f9924a24e83e85c06", + "6e1ffe067a8d61b64424835c72b0ba088210e2b5eb91f7bb686f82234905cba4": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c30e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500180c3c90100000000005a62020000000000a3e11100000000050000138800000000009896800000000d63616c2d323032362d3030343202000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f000000000000809698000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000358d7461c627a3498d368fbb5b6afe2694bb7d670e67170d5e281c95fa86894ac785d4a248f7c777868720e4721a095935f9cd485c999f9ac4ae5295db58d501", + "7276294b42a09c41ae4f90e70c61e2ea53221c1ce495822928aec06500539756": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d87801005a62020000000080f0fa020000000000c2eb0b000000000302000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f000000000000809698000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fe3925e0c984bec5be6be9f7ca44939f5cc1b3b77822f30d0238e45e1cf6d7eff7ab1ab658a8a7f016826c1dd41ba19f95b38deb842727838d326880cb4e5a05", + "75fd1121d048f1541b80f3d2150e32265eae63cee9776123755d6dcce08e361c": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f401005a62020000000080f0fa02000000000084d71700000000050000138800000000009896800000000d63616c2d323032362d3030343202000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02007102000000000000c2eb0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000e1f5050000000000a3e111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003698f10ea92eec4aea88166069fb14af047ac8613912f620fbea07a40f05d02b463ca86d57291f3926774441de66030cbb8324dceeaab551820434b525adc90e", + "7d63fb9ef3ec73ef8e406cd0023cb724b33686f2264142730c19f7f5c39cd9df": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3780811ac754ae7b149721027583c2cba205c2f9e6c6b259fdc5d604a8e5c6a1d0180f0fa0200000000008793030000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e1110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c64db44b14b302c4d4758334c99234f39220672ee7b2e82749a69fa3b0feb628ebfcc335f2248b993b0e14e291446148a31a86167ea007b7a74fdd9613f0300", + "7f0b8903fe7f8ab4fe254cf50c72501e323a7d82009021fa8095a5f9dd5ccddb": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3870c467cdd9860be693d7c74575abcdbe97d33b3ff2011ae46fc020a3269c0ed0180c3c90100000000005a62020000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e1110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004cc8c0f1d199c85a30af2955ef24cc820aeb1a2dd71e49bca13c1f31e7136d94199b44c5ff129d2bc0393391274d8db5add6a8ec0b7c113fb9e69e216ed3a305", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d87801005a62020000000080f0fa02000000008074d21a000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005128d4e8deeabc88f50846e611807c97685dfee0f9d58bc691783cc405bdc180581b468f9b2254436948c2b68946792e5463c5e4894989d3d76c0fd867a7f708", + "85c216f0d009f22ec7d3939c68cfa08da716c8006d47dfd7b5f9cec4e018b68d": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c30e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500180c3c90100000000005a62020000000000a3e111000000000202000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000efe419fa0806d6c86d89d2e8b5fef7adc81c9699137aa514f74a8570b5e09228606d709354e4de62c4e49d6463acb64c71327ece5c8efabe0b61af4c5980d04", + "869669eb39eb16f598e202104fbeccee1af70c573eca00c2f257794c16f7d522": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c300b78ba673f0a16a84264bc59333d9e3836eb1dd75aab27b76f247c9a8c30a5f0180c3c90100000000005a6202000000008093dc14000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000065cd1d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fe6fefef9306a387c3bfac6d6095ff49128b77160fdb4660990d93f1c1ac4055584c6d6832cee6e3bcdeaec137f0d5b48f9df3c05ef4c7cb61d53dbd33f35906", + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f40180c3c90100000000005a6202000000000084d717000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02007102000000000000c2eb0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000e1f5050000000000a3e111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004659b5780d79732a9c1bd85b189442290bc108879142a24e7a06e746d12e2ad7ccd38be5a76c8e7521dfd34e70f6824b9e6c8cca4f4c914cf33760b8f0795501", + "8f56be8d09bd70b4b7e8a1e778d71069dd53fafc0e4b6efe08e7a3eb92ca91b2": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c30e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e74445001005a62020000000080f0fa020000000000a3e111000000000202000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f00000000000080969800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000079c6bd65a21cde9b0f4d148dbf55dc36b6faf90c63d28cb15e89ba645efb4e2ea044d7af174f60bbcade4bce0e5689823dc905b6a734b2cfae3a13e53fdae004", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c30e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e74445001005a62020000000080f0fa020000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e94b58dd128a9e34602a9a1696d25c733cf3e048a2ec11195179dd562ea3dc8722dd6116c114fd03e19c46da3cf809a7e7d89a7f99bacde05f1681bc0832b04", + "94dc2c73f25d7fda4ae7dcd18e58a6b36646d2fc6e0780a84f85b660eed51d75": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c33f2b8f6e1cd82356f6b65ee907e884182b327b26eafe350e60b9d55ebd2d48230180f0fa0200000000008793030000000000000000000000000002000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02409c00000000000080f0fa0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000e1f5050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e43a7e538b65ee0a998cdbca1217a7f64a0c5ec25afe0b13854e87b0642cb04687edaafb6631a71c2e5f8351fb0633440c26e81b8ef48e32c66737e82b8bdf01", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d8780180c3c90100000000005a6202000000008074d21a000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f000000000000809698000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5eb36c91e182f044a2b804c2e8e7a46fce0b82bd22987ce61bfeef84fe82ed876244c31165206bf770d6e1cd4f0b45b3203c56336a898c99312407b46a6f407", + "953c0c73e539f4941a517f2db9e7fd29374e708fa8121dab53ec4159c014d1f7": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c30e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500180f0fa0200000000008793030000000000a3e11100000000050000138800000000009896800000000d63616c2d323032362d3030343202000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f000000000000809698000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6f47f014e3b04f30979d37e74796f62305f9627a348457165a66a481c199999a86ce8ee92628cac524141ccac99009a43958ab73772001e0179f4f888739d05", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa1001005a62020000000080f0fa020000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e11100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000073cb0f7be235d9125e63c3325700e72a6b177b944c11dedb4b966fee358b08b5b729dd22ca19423795296b808f597626124687c6343748da8a255db0a148c70f", + "a76701597ba200ec54fab9557e3879af24994811ab94c53bc5522b3efac36f06": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa1001005a62020000000080f0fa020000000000c2eb0b000000000302000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b2470466c5f170cad726c6d03859239fcda752b9b4209050388977401ae0b6e25f59e68bc8395c1d5ea271792b0c9e0108668a8ce06a587ee7c3a55227560909", + "b78a06c3346bc10c6020c2fab2e98009c219029fafd855747bb07b89bf81539d": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c33f2b8f6e1cd82356f6b65ee907e884182b327b26eafe350e60b9d55ebd2d48230180c3c90100000000005a62020000000000000000000000000002000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02409c00000000000080f0fa0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000e1f5050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ba5fb35cf43e35331eb3f522cac32b843b25fbcab6450bc4afbee7bf5b51c7a117ea3bd701976daacf1c3e7d0dd0583517ff96500dceda19bac2db9a32ba800e", + "c3acddfe3893368e6e47393c82cd4850bbf3e5c573eb6bc958a8ecc7c41d620c": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c31794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e50180f0fa0200000000008793030000000000000000000000000002000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f00000000000080969800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024ab47226c53674d883e6f19aafaf098b13e8830d5142edc070fdf88ea694a7245b9e2f3bd7362beecc3a38abc51c558889a49fe9ac33be55b33ac3303bfad00", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c31794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e50180f0fa0200000000008793030000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005fdd2e72d79930eccc7e96d33c49e4ce99bef8bfb17eea256d3b88b6aee715434d9a04642b090564b9bbd680ecc9db280abcfd8fe0a9c19b85c97ff33fb4930c", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f40180f0fa020000000000879303000000000084d717000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02007102000000000000c2eb0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000e1f5050000000000a3e111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e746d7d14d6e95f05875c43989ee31108a4fc3c6815a2c6d8c835196d7f6b98ba880baf4332dec34a2c7464c2af871c512c7db5809f83d465cb91459dcbbc01", + "d2801ec97c03dc8baee0e4a99060f06049a1f489a94db84883e9e332612601fe": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c30e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500180f0fa0200000000008793030000000000a3e111000000000202000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f000000000000809698000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000baa34fd794d8f18adc42119a509c4c154c6f12f18fbca4b73d913d4fa82ee7afdc8abdf2814ff0460be64af47aea9195c5dd414a224f6cbee58bb5f79875990a", + "d28e6e8222b33a802933c76ab74439e0a8964513cbb3a218a8f047838c874396": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3780811ac754ae7b149721027583c2cba205c2f9e6c6b259fdc5d604a8e5c6a1d01005a62020000000080f0fa020000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000509930932b1cf0544282d6fbad6a1dab711c185184a44501b72a7b755776e8f88f3b7c26000b918e4851312271e28e0e2405598497e1bcb34bce5da4c9be6405", + "dde96a4ac91ce92a6d3244b511f668f323561e1edd752772ba6344afe4c28dc1": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c300b78ba673f0a16a84264bc59333d9e3836eb1dd75aab27b76f247c9a8c30a5f01005a62020000000080f0fa02000000008093dc14000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000065cd1d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053bdb6ddc57f631c5878405c245cb5a0acc37bf66900309f7adc68a2b6787c267f291927e823677b9250c61c5976ade64c65d38f6d135ec64803b480047cae0e", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3c6492ee13788405fb5da423ec5a807476c46d68bd1488f455a817a6d6c70fa100180c3c90100000000005a62020000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02400d03000000000080b2e60e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c00000000a3e11100000000000000000000000000a3e111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000899f64b3b0a5fe6476a0376b533c0c3fedd7dc38fae6290060c5ec4ee8b36269724649b968bf42d867852473fd6decf493f6d86afcf422093818681fdf2ec60f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3891469e0df5bb5be2947ba5d00f8b84e55bb024eb005156baa0bfa82dc38d8780180f0fa020000000000879303000000008074d21a000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f000000000000809698000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a305f524b19ffcc7b28bd3a134c8044bf1f6b05ed5f888d01d04e12d8245f4ccd2d46f8f606fbf55b5208ce908ae1f6b9abf94dd297d0cbce5d2d1261a9ce10c", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c30e99c67d2941eddc250d0673966d253abe7cb95a01eed17cc6f24bbf9e7444500180c3c90100000000005a62020000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f00000000000080969800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063bb32785661c570f2e3b24044eda073bcb047d66071b1dc6241ac2bc85cdbcb60281015ba5beed24870773ed5c40fd6a599c363fa12c191a17e958ae4394307", + "f0f3a9e89069a64d8eab940828073bf41bf03d093c1dee63c2f7dff2b976ae8f": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f4010087930300000000801d2c04000000000084d717000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02007102000000000000c2eb0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000e1f5050000000000a3e111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007ae85eebe12460de5545937007d011746b7bfde3385acffd1c76f092f076a83e81d9bbbc02b32d32626dfa759bd0fd42b900c9d7343efc762e63ce4079849009", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c31794c70e619aa5e4a81936f9485a2d8b182d953785bd988d6321bb452110a9e50180c3c90100000000005a62020000000000a3e111000000000400004e2000000000009896800102000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02401f0000000000008096980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001de2fe67fe87ca2bbff992c14e97f04b164c4864e963f73a057616ca80725888d037bd63dc9d55e70faf2332e5f781c2290a56c90ad344e623bdd47ca4a98305", + "f9b426a47deac280e752f8d93a4a048598a1f59c59ba1327b4eb3a04f3ac45e9": "574d483101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3d37933408b255514043ed93b9b9a086b6305839e5775d9a6d2cbc05df36789f40180f0fa020000000000879303000000000084d71700000000050000138800000000009896800000000d63616c2d323032362d3030343202000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e01a00f000000000000404b4c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000156beb9ec1ceff9b693a92bc7ba6ba03bf486e7c1be8053298c3d73bf8c45859e02007102000000000000c2eb0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000e1f5050000000000a3e11100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000333efecb22b41c3cc29cd82c6d71367c83db7442f1e9a5ad045ae0749aff3776d376b4952a24c4eb77b1aa03167c0c0e7a17d8bfecbbcbbeac7667abc6ecf004" + }, + "vectors": [ + { + "name": "three_metered", + "about": "three pathways counter-metered: the relay tree's figure is the lowest, and it is first", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030200014d69931f229934638dffad05ea58d03f82b7596e867923c171ccc1f64299ce474958f59cc6c0ae3ae973b3cf65d0b5c6bbf06c29c399d6ac47f1e5139f5a490c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-a\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/9d86f36d179971166346c39085d2407e785184ed24fda05c28ae4f2e3b10d213\"}", + "outcome": "accept", + "digest": "9d86f36d179971166346c39085d2407e785184ed24fda05c28ae4f2e3b10d213", + "priority": [ + "tree", + "edge-a", + "edge-b" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-a\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/9d86f36d179971166346c39085d2407e785184ed24fda05c28ae4f2e3b10d213\"}", + "uri": "https://steer.stream.example/v1/decisions/9d86f36d179971166346c39085d2407e785184ed24fda05c28ae4f2e3b10d213" + }, + { + "name": "mixed_classes", + "about": "one class per pathway, three classes: ordered by what backs each figure, the calibrated pathway first and the estimated one last though its figure is the lowest", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000500752b7d0000000000000000000000000046c323000000000000000000000000064c8977c8378019d222025073221741598750fb1e1c5baa925a57c57e82f85a6575fd1121d048f1541b80f3d2150e32265eae63cee9776123755d6dcce08e361cf9b426a47deac280e752f8d93a4a048598a1f59c59ba1327b4eb3a04f3ac45e96e1ffe067a8d61b64424835c72b0ba088210e2b5eb91f7bb686f82234905cba435bfbbbcd40817a3860cb7e5b494a92656e1206bf49837494141559ad6472ac7953c0c73e539f4941a517f2db9e7fd29374e708fa8121dab53ec4159c014d1f75000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c010000070003008c86470000000000000000000000008017b42c000000000000000000000000065a28ac8c75ab8b7187bd47d90ca4d41163f64ad6b21ac9f9398e02fc2f9cb248a76701597ba200ec54fab9557e3879af24994811ab94c53bc5522b3efac36f061be513a6d0497d9ade32aab909cd46afb7c5660c6a77aed1c754693774e64f704ccd16a3dc9c472e7fdec6f528ff29230abd8b8f3a13883d670511c2c7734ad67276294b42a09c41ae4f90e70c61e2ea53221c1ce495822928aec065005397562e3bc455db37fa9a4a631da0e4d162616e02f38c26892b3e09086c8950a69fdf2c0100000503000102efa4517fef18a9c020cb83d879e7a912dc8b7eb82d4b4824e7d08cf4f3c635e03bacbc1eed847fade1046fc9a90b310c185f98e379e24bd09616b6d9583c150e", + "records": [ + "4c8977c8378019d222025073221741598750fb1e1c5baa925a57c57e82f85a65", + "75fd1121d048f1541b80f3d2150e32265eae63cee9776123755d6dcce08e361c", + "f9b426a47deac280e752f8d93a4a048598a1f59c59ba1327b4eb3a04f3ac45e9", + "6e1ffe067a8d61b64424835c72b0ba088210e2b5eb91f7bb686f82234905cba4", + "35bfbbbcd40817a3860cb7e5b494a92656e1206bf49837494141559ad6472ac7", + "953c0c73e539f4941a517f2db9e7fd29374e708fa8121dab53ec4159c014d1f7", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "5a28ac8c75ab8b7187bd47d90ca4d41163f64ad6b21ac9f9398e02fc2f9cb248", + "a76701597ba200ec54fab9557e3879af24994811ab94c53bc5522b3efac36f06", + "1be513a6d0497d9ade32aab909cd46afb7c5660c6a77aed1c754693774e64f70", + "4ccd16a3dc9c472e7fdec6f528ff29230abd8b8f3a13883d670511c2c7734ad6", + "7276294b42a09c41ae4f90e70c61e2ea53221c1ce495822928aec06500539756", + "2e3bc455db37fa9a4a631da0e4d162616e02f38c26892b3e09086c8950a69fdf" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"edge-b\",\"tree\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/1abe1d385890be7d48713458ee2e8b35dd9d6f91157ede507f2c606eb0af8d83\"}", + "outcome": "accept", + "digest": "1abe1d385890be7d48713458ee2e8b35dd9d6f91157ede507f2c606eb0af8d83", + "priority": [ + "edge-a", + "edge-b", + "tree" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "CalibratedInstrument", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "Estimator", + "joules": "1200000000", + "delivery": "750000000", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"edge-b\",\"tree\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/1abe1d385890be7d48713458ee2e8b35dd9d6f91157ede507f2c606eb0af8d83\"}", + "uri": "https://steer.stream.example/v1/decisions/1abe1d385890be7d48713458ee2e8b35dd9d6f91157ede507f2c606eb0af8d83" + }, + { + "name": "one_unmetered", + "about": "edge-b's interior hop is unmetered: edge-b has no figure and comes last, though its metered hop alone is the cheapest of all", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe500000000701000000000000000000000000000000000000000000000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c75e169ba66b49f831269b2727bb5a6aeda2d30ee7181a8b08bcfe9c81aec962974b78fcc9318b3041a3ff2fcb45659affad13b39f150b7e80c87cd6a48da61ad6c3acddfe3893368e6e47393c82cd4850bbf3e5c573eb6bc958a8ecc7c41d620c2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030200016df8f97e4facfe3792048df3018ef6018c840de37ac6760e9a6af84d3aeb958a02f94ee364be66c1ce625126debae8d345255aecd049238b7187eb796870ea0c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "5e169ba66b49f831269b2727bb5a6aeda2d30ee7181a8b08bcfe9c81aec96297", + "4b78fcc9318b3041a3ff2fcb45659affad13b39f150b7e80c87cd6a48da61ad6", + "c3acddfe3893368e6e47393c82cd4850bbf3e5c573eb6bc958a8ecc7c41d620c", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-a\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/8c61fdf01752140f9757d368524363c3827e80959c78f0587a5cfa4e8226c037\"}", + "outcome": "accept", + "digest": "8c61fdf01752140f9757d368524363c3827e80959c78f0587a5cfa4e8226c037", + "priority": [ + "tree", + "edge-a", + "edge-b" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "unmetered", + "class": null, + "joules": "0", + "delivery": "0", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-a\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/8c61fdf01752140f9757d368524363c3827e80959c78f0587a5cfa4e8226c037\"}", + "uri": "https://steer.stream.example/v1/decisions/8c61fdf01752140f9757d368524363c3827e80959c78f0587a5cfa4e8226c037" + }, + { + "name": "mixed_hops", + "about": "edge-a's hops are of two classes: edge-a has no figure and comes after both pathways with one", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed20200010000000000000000000000000000000000000378000000070200000000000000000000000000000000000000000000000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f185c216f0d009f22ec7d3939c68cfa08da716c8006d47dfd7b5f9cec4e018b68d8f56be8d09bd70b4b7e8a1e778d71069dd53fafc0e4b6efe08e7a3eb92ca91b2d2801ec97c03dc8baee0e4a99060f06049a1f489a94db84883e9e332612601fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c0100000503020100cef906b4b1da7b0e18648d091d55b98d8790d10ba379d17765a8b35f13e1f24b4d5c89c2de0436d5fe9f957a5fc55881e23c571f3919a0eac8dec84e9e559906", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "85c216f0d009f22ec7d3939c68cfa08da716c8006d47dfd7b5f9cec4e018b68d", + "8f56be8d09bd70b4b7e8a1e778d71069dd53fafc0e4b6efe08e7a3eb92ca91b2", + "d2801ec97c03dc8baee0e4a99060f06049a1f489a94db84883e9e332612601fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-b\",\"edge-a\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/134bca0ac4a79135e7b4af2716ce87e46a0a028c5224757f7e0974e6f2b42c94\"}", + "outcome": "accept", + "digest": "134bca0ac4a79135e7b4af2716ce87e46a0a028c5224757f7e0974e6f2b42c94", + "priority": [ + "tree", + "edge-b", + "edge-a" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "mixed", + "class": null, + "joules": "0", + "delivery": "0", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-b\",\"edge-a\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/134bca0ac4a79135e7b4af2716ce87e46a0a028c5224757f7e0974e6f2b42c94\"}", + "uri": "https://steer.stream.example/v1/decisions/134bca0ac4a79135e7b4af2716ce87e46a0a028c5224757f7e0974e6f2b42c94" + }, + { + "name": "stale", + "about": "the relay tree's interior hop has no record of its last period: its evidence is stale, and the cheapest pathway of three_metered comes last", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c010000070300000000000000000000000000000000000000000000000000000000000000000005de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f2c01000005030001029404bf615b6874aacee70a01f1c5c1b0d95863f0afcdbe50a2f408ab0a919d4e42bfaadc65f4fa4fab8178c8adf82091339ef39139457d1dc337bc99af00de05", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"edge-b\",\"tree\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/283d2bbfe40d1cc9fc482eb8a255a29ba0c929b3a3b753217d1c0ff6458d5978\"}", + "outcome": "accept", + "digest": "283d2bbfe40d1cc9fc482eb8a255a29ba0c929b3a3b753217d1c0ff6458d5978", + "priority": [ + "edge-a", + "edge-b", + "tree" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "stale", + "class": null, + "joules": "0", + "delivery": "0", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"edge-b\",\"tree\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/283d2bbfe40d1cc9fc482eb8a255a29ba0c929b3a3b753217d1c0ff6458d5978\"}", + "uri": "https://steer.stream.example/v1/decisions/283d2bbfe40d1cc9fc482eb8a255a29ba0c929b3a3b753217d1c0ff6458d5978" + }, + { + "name": "floor_client", + "about": "a client on edge-b whose throughput sustains only tier 0, at or below the floor: held on edge-b, with the short TTL", + "policy": "three", + "request": { + "protocol": "hls", + "target": "/v1/room?asset=lesson&_HLS_pathway=%22edge-b%22&_HLS_throughput=1500000", + "cmcd_header": null + }, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": "edge-b", + "throughput": 1500000, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001030160e31600000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d1e00000002030102007dfd77fe2928f52298b5169d771df22f5cf326fdb560e558ce5eb6e3cae0d9c51947151084f76afb0b6b85e3c7d2aba74d13f04ff4cf0fa868386770b4eb7e02", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":30,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-b\",\"tree\",\"edge-a\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/68f1c4fafe7076de8175c73e208cff74838a19da2ed4bd25571f97e93f459288\"}", + "outcome": "accept", + "digest": "68f1c4fafe7076de8175c73e208cff74838a19da2ed4bd25571f97e93f459288", + "priority": [ + "edge-b", + "tree", + "edge-a" + ], + "ttl": 30, + "why": "floor", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":30,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-b\",\"tree\",\"edge-a\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/68f1c4fafe7076de8175c73e208cff74838a19da2ed4bd25571f97e93f459288\"}", + "uri": "https://steer.stream.example/v1/decisions/68f1c4fafe7076de8175c73e208cff74838a19da2ed4bd25571f97e93f459288" + }, + { + "name": "buffer_guard", + "about": "a client on edge-b with 1.2 s of video buffered, below the policy's 4 s: held", + "policy": "three", + "request": { + "protocol": "hls", + "target": "/v1/room?_HLS_pathway=%22edge-b%22&_HLS_throughput=8000000&CMCD=bl%3D%281200%3Bv%201800%3Ba%29", + "cmcd_header": null + }, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": "edge-b", + "throughput": 8000000, + "buffer": 1200 + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001070100127a0000000000b004000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d1e0000000103010200dd98885c9bc2017c925ec0398e91762dd908f3c3a7de9fb49942d7d601560d3de9c645a6fb07e7048391145eec2f4ba96c0278a0bd086ff3ad75458987c52201", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":30,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-b\",\"tree\",\"edge-a\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/c6d4bf5768094fc73881dc66c4c758edacb984c3cc8c45409439b4976eaf6833\"}", + "outcome": "accept", + "digest": "c6d4bf5768094fc73881dc66c4c758edacb984c3cc8c45409439b4976eaf6833", + "priority": [ + "edge-b", + "tree", + "edge-a" + ], + "ttl": 30, + "why": "buffer", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":30,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-b\",\"tree\",\"edge-a\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/c6d4bf5768094fc73881dc66c4c758edacb984c3cc8c45409439b4976eaf6833\"}", + "uri": "https://steer.stream.example/v1/decisions/c6d4bf5768094fc73881dc66c4c758edacb984c3cc8c45409439b4976eaf6833" + }, + { + "name": "hysteresis_hold", + "about": "a client on edge-a: the relay tree's figure is lower by less than the 20% margin, so the client stays", + "policy": "three", + "request": { + "protocol": "hls", + "target": "/v1/room?_HLS_pathway=%22edge-a%22&_HLS_throughput=8000000&CMCD=bl%3D20000", + "cmcd_header": null + }, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": "edge-a", + "throughput": 8000000, + "buffer": 20000 + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001070000127a0000000000204e000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000004030002018540fdfa91a2d19cd389d0a77e0edfba03d3c5f6f075831ab58021f23bcef34b44df892a5b5f66c431edf81e48cfb2d8679060aebfde9ffa30087dba30a8a409", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"tree\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/6802ae4e94c3b7ce21c95c12c92a4b6e2ff0909b20e5a6ef72525b96bc967a87\"}", + "outcome": "accept", + "digest": "6802ae4e94c3b7ce21c95c12c92a4b6e2ff0909b20e5a6ef72525b96bc967a87", + "priority": [ + "edge-a", + "tree", + "edge-b" + ], + "ttl": 300, + "why": "hold", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"tree\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/6802ae4e94c3b7ce21c95c12c92a4b6e2ff0909b20e5a6ef72525b96bc967a87\"}", + "uri": "https://steer.stream.example/v1/decisions/6802ae4e94c3b7ce21c95c12c92a4b6e2ff0909b20e5a6ef72525b96bc967a87" + }, + { + "name": "hysteresis_switch", + "about": "the same client a period later: the relay tree's newest period brings its figure below edge-a's by more than the margin, and the client moves", + "policy": "three", + "request": { + "protocol": "hls", + "target": "/v1/room?_HLS_pathway=%22edge-a%22&_HLS_throughput=8000000&CMCD=bl%3D20000", + "cmcd_header": null + }, + "inputs": { + "t": 72000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": "edge-a", + "throughput": 8000000, + "buffer": 20000 + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c00a24a040000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001070000127a0000000000204e000000000000037800000007000400752b7d0000000000000000000000000046c323000000000000000000000000060bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1f0f3a9e89069a64d8eab940828073bf41bf03d093c1dee63c2f7dff2b976ae8f934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe3175f3683ebadf651cb86d73c42c865d1a11b304d3ef24e4923309e9879dc17d5000000007000400180d8f0000000000000000000000000046c32300000000000000000000000006204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c769633671606f1c8c35638a9e646d099afa9ce7310d56066cd31b951565a855d313a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab4d54f577adde552acc95ba9a7c140346447a7915b9e6bee709085eea3a3af0022c01000007000480a33a740000000000000000000000008017b42c00000000000000000000000006a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c801fdcac14c5ca0c3539283e6436f8f906ff5e7f10b77b26141dd042a20f48f6ab815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d15d0cfd22cce80a680a8b12fef1993df2a8db44cecef6697a3ca29280a73904e2c0100000503020001f6ff123634c1c96ce891112323915da6f8b6dd23d24c5eb49511ddd18cbcbf6f78b18337e5e0625fe1612413517fe90bc0be0ead651c21dcd784dd68fd33800f", + "records": [ + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "f0f3a9e89069a64d8eab940828073bf41bf03d093c1dee63c2f7dff2b976ae8f", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3175f3683ebadf651cb86d73c42c865d1a11b304d3ef24e4923309e9879dc17d", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "69633671606f1c8c35638a9e646d099afa9ce7310d56066cd31b951565a855d3", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "4d54f577adde552acc95ba9a7c140346447a7915b9e6bee709085eea3a3af002", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "1fdcac14c5ca0c3539283e6436f8f906ff5e7f10b77b26141dd042a20f48f6ab", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d", + "15d0cfd22cce80a680a8b12fef1993df2a8db44cecef6697a3ca29280a73904e" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-a\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/eb5fb5810aeab8f20bb6b491fcfc16d3cc8c7da485b6a87fd3bab73f2ed500bc\"}", + "outcome": "accept", + "digest": "eb5fb5810aeab8f20bb6b491fcfc16d3cc8c7da485b6a87fd3bab73f2ed500bc", + "priority": [ + "tree", + "edge-a", + "edge-b" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "1950000000", + "delivery": "750000000", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-a\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/eb5fb5810aeab8f20bb6b491fcfc16d3cc8c7da485b6a87fd3bab73f2ed500bc\"}", + "uri": "https://steer.stream.example/v1/decisions/eb5fb5810aeab8f20bb6b491fcfc16d3cc8c7da485b6a87fd3bab73f2ed500bc" + }, + { + "name": "clones", + "about": "a clone of edge-a through another host, with its own edge hop: first, and every clone written in PATHWAY-CLONES", + "policy": "cloned", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300, + 40 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b92f511e1f399103978bab2c2b0fcde3106d72c9ab6f8dac7c7ee03186d3079d10800bb2030000000074e9af89f9cbec3bbcc0e59dedea0577f949010001000000000000000000000000000000000000047800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2800000007000480ba953e0000000000000000000000008017b42c00000000000000000000000003869669eb39eb16f598e202104fbeccee1af70c573eca00c2f257794c16f7d522dde96a4ac91ce92a6d3244b511f668f323561e1edd752772ba6344afe4c28dc100d227b6d74067a616ae0e9b6926319310fba441feafb8779aaf2b9c85dac0d22c01000005040302000178ed255f4aec48125d7572bf6c000bf95fbb57a8c25687338b30d008eb9de8e909a7b93bb50f338ff754f7717748bef62f872b4b30b9842895b681c43aae980e", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d", + "869669eb39eb16f598e202104fbeccee1af70c573eca00c2f257794c16f7d522", + "dde96a4ac91ce92a6d3244b511f668f323561e1edd752772ba6344afe4c28dc1", + "00d227b6d74067a616ae0e9b6926319310fba441feafb8779aaf2b9c85dac0d2" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a2\",\"tree\",\"edge-a\",\"edge-b\"],\"PATHWAY-CLONES\":[{\"BASE-ID\":\"edge-a\",\"ID\":\"edge-a2\",\"URI-REPLACEMENT\":{\"HOST\":\"a2.edge.stream.example\",\"PARAMS\":{\"pop\":\"east-2\",\"tk\":\"x%2Fy\"}}}],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/b0bfbe6085410e6db48a345bdcc47537a02b4beb41b73d63f9c6b566dbd61fc1\"}", + "outcome": "accept", + "digest": "b0bfbe6085410e6db48a345bdcc47537a02b4beb41b73d63f9c6b566dbd61fc1", + "priority": [ + "edge-a2", + "tree", + "edge-a", + "edge-b" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 7 + }, + { + "id": "edge-a2", + "status": "figure", + "class": "OnChipCounter", + "joules": "1050000000", + "delivery": "750000000", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a2\",\"tree\",\"edge-a\",\"edge-b\"],\"PATHWAY-CLONES\":[{\"BASE-ID\":\"edge-a\",\"ID\":\"edge-a2\",\"URI-REPLACEMENT\":{\"HOST\":\"a2.edge.stream.example\",\"PARAMS\":{\"pop\":\"east-2\",\"tk\":\"x%2Fy\"}}}],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/b0bfbe6085410e6db48a345bdcc47537a02b4beb41b73d63f9c6b566dbd61fc1\"}", + "uri": "https://steer.stream.example/v1/decisions/b0bfbe6085410e6db48a345bdcc47537a02b4beb41b73d63f9c6b566dbd61fc1" + }, + { + "name": "share", + "about": "the origin path is unmetered and holds a 10% minimum share: a session whose draw is in its band is placed on it first", + "policy": "explore", + "request": null, + "inputs": { + "t": 62000000, + "session": "48fb1ee10c22646e3d9d6471335686b4", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300, + 40 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9db0fdb6b0527c003be8f503bcc6b339271b2f2fe960c43c5506798b61b035e63800bb2030000000048fb1ee10c22646e3d9d6471335686b4c8c0000001000000000000000000000000000000000000047800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d28000000070100000000000000000000000000000000000000000000000000000000000000000003b78a06c3346bc10c6020c2fab2e98009c219029fafd855747bb07b89bf81539d19c3cafd29a850cb6b97f69162d9ca808430f424604e496ea4c7dd6a1c5031a094dc2c73f25d7fda4ae7dcd18e58a6b36646d2fc6e0780a84f85b660eed51d752c010000030403020001c84fddbe81221621d0e832d48da5922f9f8ad2d3b58dd36775afe2b31c7257f4b22a73f65dc33c8ff5df047dcdd124a336a3019f15d12559742cdda9ab7e3706", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d", + "b78a06c3346bc10c6020c2fab2e98009c219029fafd855747bb07b89bf81539d", + "19c3cafd29a850cb6b97f69162d9ca808430f424604e496ea4c7dd6a1c5031a0", + "94dc2c73f25d7fda4ae7dcd18e58a6b36646d2fc6e0780a84f85b660eed51d75" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=48fb1ee10c22646e3d9d6471335686b4\",\"PATHWAY-PRIORITY\":[\"origin\",\"tree\",\"edge-a\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/f2b7933f9de27efa3186ed85274b57ba52700397d73f57a625ec2b63f28b75f0\"}", + "outcome": "accept", + "digest": "f2b7933f9de27efa3186ed85274b57ba52700397d73f57a625ec2b63f28b75f0", + "priority": [ + "origin", + "tree", + "edge-a", + "edge-b" + ], + "ttl": 300, + "why": "share", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 7 + }, + { + "id": "origin", + "status": "unmetered", + "class": null, + "joules": "0", + "delivery": "0", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=48fb1ee10c22646e3d9d6471335686b4\",\"PATHWAY-PRIORITY\":[\"origin\",\"tree\",\"edge-a\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/f2b7933f9de27efa3186ed85274b57ba52700397d73f57a625ec2b63f28b75f0\"}", + "uri": "https://steer.stream.example/v1/decisions/f2b7933f9de27efa3186ed85274b57ba52700397d73f57a625ec2b63f28b75f0" + }, + { + "name": "share_outside", + "about": "a session whose draw is outside the origin path's band: the origin path has no figure and comes last", + "policy": "explore", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300, + 40 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9db0fdb6b0527c003be8f503bcc6b339271b2f2fe960c43c5506798b61b035e63800bb2030000000074e9af89f9cbec3bbcc0e59dedea05773f820c0001000000000000000000000000000000000000047800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d28000000070100000000000000000000000000000000000000000000000000000000000000000003b78a06c3346bc10c6020c2fab2e98009c219029fafd855747bb07b89bf81539d19c3cafd29a850cb6b97f69162d9ca808430f424604e496ea4c7dd6a1c5031a094dc2c73f25d7fda4ae7dcd18e58a6b36646d2fc6e0780a84f85b660eed51d752c010000050402000103ff845ea97bc3b73c3e526575819cff0d7bc8b50fb0698bd97695f0626fb347fead72897294389f2669d386c2d38aa4fa735236d4ba06d2ad9d59285f79390600", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d", + "b78a06c3346bc10c6020c2fab2e98009c219029fafd855747bb07b89bf81539d", + "19c3cafd29a850cb6b97f69162d9ca808430f424604e496ea4c7dd6a1c5031a0", + "94dc2c73f25d7fda4ae7dcd18e58a6b36646d2fc6e0780a84f85b660eed51d75" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-a\",\"edge-b\",\"origin\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/0d7ca39b2fbe85f58d234d2388cfbf3eed141573af28dc586cc47ddf6b1fdd97\"}", + "outcome": "accept", + "digest": "0d7ca39b2fbe85f58d234d2388cfbf3eed141573af28dc586cc47ddf6b1fdd97", + "priority": [ + "tree", + "edge-a", + "edge-b", + "origin" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 7 + }, + { + "id": "origin", + "status": "unmetered", + "class": null, + "joules": "0", + "delivery": "0", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-a\",\"edge-b\",\"origin\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/0d7ca39b2fbe85f58d234d2388cfbf3eed141573af28dc586cc47ddf6b1fdd97\"}", + "uri": "https://steer.stream.example/v1/decisions/0d7ca39b2fbe85f58d234d2388cfbf3eed141573af28dc586cc47ddf6b1fdd97" + }, + { + "name": "capacity", + "about": "the relay tree is at its capacity of 1000 sessions: edge-a is first and the relay tree last", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 1000 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eabe803000003000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c010000050300010267894a48cdbe581e08e3f9502381cd26695eb160960daccfbf644af1f7567a3b3067666eaab96011a44271671b0d8d14e000168b748ffcb8958dca690c5a8306", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"edge-b\",\"tree\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/4ce17bf59b07aa9806589360ce5fed3983c6b75b50e8b3274540223262fba697\"}", + "outcome": "accept", + "digest": "4ce17bf59b07aa9806589360ce5fed3983c6b75b50e8b3274540223262fba697", + "priority": [ + "edge-a", + "edge-b", + "tree" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 3 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"edge-b\",\"tree\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/4ce17bf59b07aa9806589360ce5fed3983c6b75b50e8b3274540223262fba697\"}", + "uri": "https://steer.stream.example/v1/decisions/4ce17bf59b07aa9806589360ce5fed3983c6b75b50e8b3274540223262fba697" + }, + { + "name": "latency", + "about": "the relay tree's latency is past the ceiling: it comes after the pathways within the limits", + "policy": "latency", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b970d3a204f8cb084fd671097159f71f748ef83fc36f1aa4b1741c33f615873356800bb2030000000074e9af89f9cbec3bbcc0e59dedea0577c8b7030001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000006000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c010000050300010238e56837bbb3c51af4051735747c4bee84c8b08e11d6d4824effd0acd829d2debbd611b48c5dd8f9debf0a58ab763eaf847c87cbf0aa658698f659204d8ab900", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"edge-b\",\"tree\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/c3639b6e6ea766391c939fdf11323623a7cc304f325fc187e051557b0f7bf18d\"}", + "outcome": "accept", + "digest": "c3639b6e6ea766391c939fdf11323623a7cc304f325fc187e051557b0f7bf18d", + "priority": [ + "edge-a", + "edge-b", + "tree" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 6 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"edge-b\",\"tree\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/c3639b6e6ea766391c939fdf11323623a7cc304f325fc187e051557b0f7bf18d\"}", + "uri": "https://steer.stream.example/v1/decisions/c3639b6e6ea766391c939fdf11323623a7cc304f325fc187e051557b0f7bf18d" + }, + { + "name": "per_second", + "about": "the per-second basis: the relay tree delivers the most bytes but half its sessions below the floor tier, and comes last", + "policy": "seconds", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b923e020629598fd202cbde11a4ccd403444f05f281b1926e68a9bb4d5f31832b3800bb2030000000074e9af89f9cbec3bbcc0e59dedea05773bb00a0001000000000000000000000000000000000000037800000007000400752b7d000000000000000000000000008c864700000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f000000000000000000000000008c8647000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c8600000000000000000000000000e9a43500000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c0100000503000102fbca4303a5613e891665f4fb1d6f11adcae70d82ff8e57a5446eb5e105b78d39886dfd78b969ee57c5e08613e0afaed18412b073c34d263166dfc3ccf8d7400d", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"edge-b\",\"tree\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/620103329453f240bd6219dfa40e17e4c81ef670b4f4bcf0792146cc01bd1ecf\"}", + "outcome": "accept", + "digest": "620103329453f240bd6219dfa40e17e4c81ef670b4f4bcf0792146cc01bd1ecf", + "priority": [ + "edge-a", + "edge-b", + "tree" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "1200000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "1200000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "900000000", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"edge-b\",\"tree\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/620103329453f240bd6219dfa40e17e4c81ef670b4f4bcf0792146cc01bd1ecf\"}", + "uri": "https://steer.stream.example/v1/decisions/620103329453f240bd6219dfa40e17e4c81ef670b4f4bcf0792146cc01bd1ecf" + }, + { + "name": "dash", + "about": "DASH: the client used edge-b then edge-a; edge-b is current, and the relay tree beats it by more than the margin", + "policy": "three", + "request": { + "protocol": "dash", + "target": "/v1/room?asset=lesson&wai_session=00&_DASH_pathway=%22edge-b,edge-a%22&_DASH_throughput=9000000,7000000", + "cmcd_header": "bl=(25000;v 30000;a)" + }, + "inputs": { + "t": 62000000, + "session": "48fb1ee10c22646e3d9d6471335686b4", + "protocol": "dash", + "report": { + "pathway": "edge-b", + "throughput": 9000000, + "buffer": 25000 + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000048fb1ee10c22646e3d9d6471335686b4bc760c000207014054890000000000a861000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c010000050302000128f7601de53fce36545cb9c57a5a34870b4fcbb7007644a92c9b80791d539cb042228a8e940d003a2d982219cfbc010f87a06c9dd3356d0e0dc1e74c48d5ab08", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=48fb1ee10c22646e3d9d6471335686b4\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-a\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/34669873c3d1b5496483719b6f19ed1efa5d9e4bbe17f06f721bc76aa33fc723\"}", + "outcome": "accept", + "digest": "34669873c3d1b5496483719b6f19ed1efa5d9e4bbe17f06f721bc76aa33fc723", + "priority": [ + "tree", + "edge-a", + "edge-b" + ], + "ttl": 300, + "why": "rank", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 7 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 7 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=48fb1ee10c22646e3d9d6471335686b4\",\"PATHWAY-PRIORITY\":[\"tree\",\"edge-a\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/34669873c3d1b5496483719b6f19ed1efa5d9e4bbe17f06f721bc76aa33fc723\"}", + "uri": "https://steer.stream.example/v1/decisions/34669873c3d1b5496483719b6f19ed1efa5d9e4bbe17f06f721bc76aa33fc723" + }, + { + "name": "none_within", + "about": "a latency ceiling no pathway meets: the client stays on edge-a, with the short TTL", + "policy": "strict", + "request": { + "protocol": "hls", + "target": "/v1/room?_HLS_pathway=edge-a&_HLS_throughput=8000000", + "cmcd_header": null + }, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": "edge-a", + "throughput": 8000000, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9fec88fd88459bc9fb1ccde3aa36c399c0723dc3141ceac477ac9f2cda4853182800bb2030000000074e9af89f9cbec3bbcc0e59dedea05771dec0c0001030000127a00000000000000000000000000037800000006000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000006000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000006000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d1e000000060300020147f374219a5133ade03f638c4d8fa246ec91a4008a2915d55f80506662e3a24bce20f765d0f6415d13aaf8f0de1ed0e8d0358bcad8bbc95dbbd0cdbf48975204", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":30,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"tree\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/bbbecaac0035bf2041b4ea0d4a0c9d22451f1806c7736b22df91ddf1f1c694a0\"}", + "outcome": "accept", + "digest": "bbbecaac0035bf2041b4ea0d4a0c9d22451f1806c7736b22df91ddf1f1c694a0", + "priority": [ + "edge-a", + "tree", + "edge-b" + ], + "ttl": 30, + "why": "none", + "pathways": [ + { + "id": "edge-a", + "status": "figure", + "class": "OnChipCounter", + "joules": "2100000000", + "delivery": "600000000", + "limits": 6 + }, + { + "id": "edge-b", + "status": "figure", + "class": "OnChipCounter", + "joules": "2400000000", + "delivery": "600000000", + "limits": 6 + }, + { + "id": "tree", + "status": "figure", + "class": "OnChipCounter", + "joules": "2250000000", + "delivery": "750000000", + "limits": 6 + } + ], + "steering_manifest": "{\"VERSION\":1,\"TTL\":30,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a\",\"tree\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/bbbecaac0035bf2041b4ea0d4a0c9d22451f1806c7736b22df91ddf1f1c694a0\"}", + "uri": "https://steer.stream.example/v1/decisions/bbbecaac0035bf2041b4ea0d4a0c9d22451f1806c7736b22df91ddf1f1c694a0" + }, + { + "name": "policy_invalid", + "about": "the policy's minimum shares sum past one million", + "policy": "shares_over", + "request": null, + "inputs": null, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030200014d69931f229934638dffad05ea58d03f82b7596e867923c171ccc1f64299ce474958f59cc6c0ae3ae973b3cf65d0b5c6bbf06c29c399d6ac47f1e5139f5a490c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "policy_invalid", + "at": [ + "policy" + ] + }, + { + "name": "clone_unknown", + "about": "a clone whose base is itself, a pathway not before it", + "policy": "clone_later", + "request": null, + "inputs": null, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030200014d69931f229934638dffad05ea58d03f82b7596e867923c171ccc1f64299ce474958f59cc6c0ae3ae973b3cf65d0b5c6bbf06c29c399d6ac47f1e5139f5a490c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "clone_unknown", + "at": [ + "policy" + ] + }, + { + "name": "policy_signer_not_authorised", + "about": "a policy signed by a key the key log does not list", + "policy": "stranger_signed", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030200014d69931f229934638dffad05ea58d03f82b7596e867923c171ccc1f64299ce474958f59cc6c0ae3ae973b3cf65d0b5c6bbf06c29c399d6ac47f1e5139f5a490c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "signer_not_authorised", + "at": [ + "policy" + ] + }, + { + "name": "decision_policy_missing", + "about": "a decision under another policy than the one given", + "policy": "latency", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030200014d69931f229934638dffad05ea58d03f82b7596e867923c171ccc1f64299ce474958f59cc6c0ae3ae973b3cf65d0b5c6bbf06c29c399d6ac47f1e5139f5a490c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "record_missing", + "at": [ + "decision" + ] + }, + { + "name": "decision_bad_signature", + "about": "the decision's signature with one bit flipped", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030200014d69931f229934638dffad05ea58d03f82b7596e867923c171ccc1f64299ce474958f59cc6c0ae3ae973b3cf65d0b5c6bbf06c29c399d6ac47f1e5139f5a490d", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "decision" + ] + }, + { + "name": "decision_signer_not_authorised", + "about": "a decision signed by a key the key log does not list", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3ee356e26d8df9c5c2e2b5b20ff87fe4c1f68bf43461430de6d2cad3b45e3c11ef823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c0100000503020001232135765169f7afed800f02a25d551a48eec5e59f7849832926cac4c0ba9cdc8fb69c6c31e5694559c101f1a3274b8ba77ceddb3c84792151c625d68709390c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "signer_not_authorised", + "at": [ + "decision" + ] + }, + { + "name": "decision_signer_revoked", + "about": "a decision signed by a revoked steering key", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c33fb2eb59810fde4638e6825f59e131a9dae94e1cb46ef488c40e6de5d187261bf823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030200019dd21b9eabcb24325972b8f485c130fd71c80444a0f52d459589ca40a8405adf5840286cdf8c9b5788c166d5a32733c5c4fb1f6de9781a86f328b1d3cae71a0a", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "signer_revoked", + "at": [ + "decision" + ] + }, + { + "name": "decision_invalid", + "about": "a priority that names a pathway twice", + "policy": "three", + "request": null, + "inputs": null, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030202001756fc856338453f2c949866964e74d6223c03bc9c9815a7e667e5e4a6aa99f50dcb5c6ccc566902b203b5d3c4fa166a0b8af0980e415d8ed94f1c7988beb80c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "decision_invalid", + "at": [ + "decision" + ] + }, + { + "name": "evidence_out_of_order", + "about": "edge-a's records named in reverse order", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c3230000000000000000000000000603b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f10bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb05000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c0100000503020001c3381a06ebd40a1cfea0b8fd37132c75c7b2b2c7d5a3e78749a794b1846d0a2cbdf718220c7d8f633dbabc243ccedac007a844c4db960c0322865bc53957200d", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "decision_invalid", + "at": [ + "evidence", + 0, + 1 + ] + }, + { + "name": "record_missing", + "about": "a record the decision names is not given", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030200014d69931f229934638dffad05ea58d03f82b7596e867923c171ccc1f64299ce474958f59cc6c0ae3ae973b3cf65d0b5c6bbf06c29c399d6ac47f1e5139f5a490c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "record_missing", + "at": [ + "evidence", + 2, + 4 + ] + }, + { + "name": "hop_not_authorised", + "about": "evidence signed by a hop key the key log does not list", + "policy": "unlisted", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9e1e7685147a058a3244c74a660d85bd29bce9890ecd4105c8e935d3a2aac7f01800bb2030000000074e9af89f9cbec3bbcc0e59dedea0577deac050001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006535c9d8cfb8716120e06c89d38e43c6490e98a9df1dca73c0a9b1a40dfec9536d28e6e8222b33a802933c76ab74439e0a8964513cbb3a218a8f047838c8743967d63fb9ef3ec73ef8e406cd0023cb724b33686f2264142730c19f7f5c39cd9df94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c0100000503020001a4fd2992da200c20e710d264d6ce6fcde45af3108848fa1615ecee856105a11359fc71d9b700a6b40dd568daa4aa6d0c992c50c88e0939fe1e3d72777920ba0d", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "535c9d8cfb8716120e06c89d38e43c6490e98a9df1dca73c0a9b1a40dfec9536", + "d28e6e8222b33a802933c76ab74439e0a8964513cbb3a218a8f047838c874396", + "7d63fb9ef3ec73ef8e406cd0023cb724b33686f2264142730c19f7f5c39cd9df", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "hop_not_authorised", + "at": [ + "evidence", + 2, + 0 + ] + }, + { + "name": "hop_revoked", + "about": "evidence signed by a hop key the key log revoked", + "policy": "revoked", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b99808b9d5b80f387d33f475e2812169f2c2d3dce50ec23520f0950c683b3756c9800bb2030000000074e9af89f9cbec3bbcc0e59dedea057773e0070001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c000000000000000000000000067f0b8903fe7f8ab4fe254cf50c72501e323a7d82009021fa8095a5f9dd5ccddb3392606edc4476ce0e21918ea2f4a2152ab75b83535a7f7b9c87f47a7b3259db31c8e910208e39c3823f5e0c880a964e1f7aaa3e113e05e500830a50dc327c0294f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c0100000503020001e4176d98ae6a490ebd54c9e9166dc7e4533ca2dd69f1190e22d1a35990a9ffec71a87e739148af0ef77b6f32f72bf0728de9c3ff792f5d764fb383ae8707750c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "7f0b8903fe7f8ab4fe254cf50c72501e323a7d82009021fa8095a5f9dd5ccddb", + "3392606edc4476ce0e21918ea2f4a2152ab75b83535a7f7b9c87f47a7b3259db", + "31c8e910208e39c3823f5e0c880a964e1f7aaa3e113e05e500830a50dc327c02", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "hop_revoked", + "at": [ + "evidence", + 2, + 0 + ] + }, + { + "name": "evidence_not_on_pathway", + "about": "edge-b's edge record named under edge-a", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000007884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd5000000007000400180d8f0000000000000000000000000046c32300000000000000000000000005204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c0100000503020001aa88811610c47046d05401458073dbad3e88f1ec1db72d2ab3c3a80ad121c563efd8357f296fa99a5bc34219d811fd99b911100b237c536895a007b3322c160d", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "evidence_not_on_pathway", + "at": [ + "evidence", + 0, + 6 + ] + }, + { + "name": "evidence_outside_window", + "about": "a record of the period from 60 s named at 62 s, whose window ends at 60 s", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000007884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1f0f3a9e89069a64d8eab940828073bf41bf03d093c1dee63c2f7dff2b976ae8febb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030200018e4bf46ad794a8da658e98b761f3010554bc751c36fa44bf2f7ff3bd6baa4c3a0ac8d908cc65ee1b7c6cb213ead4c25c83986ee0950ea7a7da671d53aa9bc00c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d", + "f0f3a9e89069a64d8eab940828073bf41bf03d093c1dee63c2f7dff2b976ae8f" + ], + "manifest": null, + "outcome": "refuse", + "reason": "evidence_outside_window", + "at": [ + "evidence", + 0, + 3 + ] + }, + { + "name": "evidence_mismatch", + "about": "the relay tree's delivery stated one byte more than its records give", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008117b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c0100000503020001a1f22eba856849e47a3a769d62571aea6f269fe2a75452e1c16eae4899f4f770f13765cadf626fc99ffd8854ddba02ef1308c7f442b9d36b793359de7ffdad08", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "evidence_mismatch", + "at": [ + "pathway", + 2 + ] + }, + { + "name": "unmetered_as_zero", + "about": "edge-b stated with a figure that counts its unmetered hop as zero joules", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe50000000070004002f68590000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c75e169ba66b49f831269b2727bb5a6aeda2d30ee7181a8b08bcfe9c81aec962974b78fcc9318b3041a3ff2fcb45659affad13b39f150b7e80c87cd6a48da61ad6c3acddfe3893368e6e47393c82cd4850bbf3e5c573eb6bc958a8ecc7c41d620c2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c0100000503020001d9d4be6a5a7f2f5ae06e78b326136ed42068c02e9afaa5372e46661d4459bd03c9dc867ee9898a4d7e6169fb640835f96793019003204d44dde84058894b8005", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "5e169ba66b49f831269b2727bb5a6aeda2d30ee7181a8b08bcfe9c81aec96297", + "4b78fcc9318b3041a3ff2fcb45659affad13b39f150b7e80c87cd6a48da61ad6", + "c3acddfe3893368e6e47393c82cd4850bbf3e5c573eb6bc958a8ecc7c41d620c", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "unmetered_as_zero", + "at": [ + "pathway", + 1 + ] + }, + { + "name": "classes_summed", + "about": "edge-a stated with one figure that sums a counter reading and a model's output", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f185c216f0d009f22ec7d3939c68cfa08da716c8006d47dfd7b5f9cec4e018b68d8f56be8d09bd70b4b7e8a1e778d71069dd53fafc0e4b6efe08e7a3eb92ca91b2d2801ec97c03dc8baee0e4a99060f06049a1f489a94db84883e9e332612601fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c0100000503020100a142ea45a75b9a796cbc6680b1b7cc40a2db5a6392db6ccecb7b3e6fe109919819b322538ac6e511d082e5aa371fbbd9df2dd3cf74c225f0822b0188de10b304", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "85c216f0d009f22ec7d3939c68cfa08da716c8006d47dfd7b5f9cec4e018b68d", + "8f56be8d09bd70b4b7e8a1e778d71069dd53fafc0e4b6efe08e7a3eb92ca91b2", + "d2801ec97c03dc8baee0e4a99060f06049a1f489a94db84883e9e332612601fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "classes_summed", + "at": [ + "pathway", + 0 + ] + }, + { + "name": "unknown_preferred", + "about": "the stale relay tree placed first", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c010000070300000000000000000000000000000000000000000000000000000000000000000005de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f2c01000005030200017e9365189843ccafb99bce96a564c1c41d4d6b40956e2bc7861e005b239d399757700ab703f2ffb0a7d893f3eb257e4dabf00ab45c5a8f454562e3fdbce18a00", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f" + ], + "manifest": null, + "outcome": "refuse", + "reason": "unknown_preferred", + "at": [ + "output" + ] + }, + { + "name": "decision_mismatch", + "about": "the second and third pathways swapped", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c0100000503020100547c1ba98ae7481e549d054126575b4a071983d5f5888111386831ae2f06df9d9f9e111eda522b363bee7510e367d705133f5d25129ff790ca5b68f280a4fe09", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "decision_mismatch", + "at": [ + "output" + ] + }, + { + "name": "ttl_mismatch", + "about": "a TTL one second longer than the rule gives", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2d010000050302000106620264732619f5f74404a155af7b9309d02776eeca5feab86233939e9f1f070e0caad67f656e1dad450c7bb79d45890daafb71a8f9b253ddbdc5d8d66df801", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": null, + "outcome": "refuse", + "reason": "decision_mismatch", + "at": [ + "output" + ] + }, + { + "name": "manifest_mismatch", + "about": "a manifest with a pathway the policy does not have", + "policy": "three", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b9f823bb391e640a6eb06d1c4587739bcf86391371ee85c677f80ad603dd1f965c800bb2030000000074e9af89f9cbec3bbcc0e59dedea05774ed2020001000000000000000000000000000000000000037800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2c01000005030200014d69931f229934638dffad05ea58d03f82b7596e867923c171ccc1f64299ce474958f59cc6c0ae3ae973b3cf65d0b5c6bbf06c29c399d6ac47f1e5139f5a490c", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"cdn-z\",\"tree\",\"edge-a\",\"edge-b\"],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/9d86f36d179971166346c39085d2407e785184ed24fda05c28ae4f2e3b10d213\"}", + "outcome": "refuse", + "reason": "manifest_mismatch", + "at": [ + "manifest" + ] + }, + { + "name": "manifest_clone_unknown", + "about": "a manifest whose clone names an unknown base pathway", + "policy": "cloned", + "request": null, + "inputs": { + "t": 62000000, + "session": "74e9af89f9cbec3bbcc0e59dedea0577", + "protocol": "hls", + "report": { + "pathway": null, + "throughput": null, + "buffer": null + }, + "loads": [ + 120, + 80, + 300, + 40 + ] + }, + "decision": "5753443101c0cc956280c0b2f2d3e9f79ecb0c1fa204a377578fdebe4288be2256c4ec63c3633f699762260f74d263ab598770107226ad1a3e4ecf774406232173eee4f2b92f511e1f399103978bab2c2b0fcde3106d72c9ab6f8dac7c7ee03186d3079d10800bb2030000000074e9af89f9cbec3bbcc0e59dedea0577f949010001000000000000000000000000000000000000047800000007000400752b7d0000000000000000000000000046c32300000000000000000000000006884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb00bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b1003b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe5000000007000400180d8f0000000000000000000000000046c323000000000000000000000000063fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a41db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab2c01000007000480461c860000000000000000000000008017b42c00000000000000000000000006de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c8094f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0fe3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d2800000007000480ba953e0000000000000000000000008017b42c00000000000000000000000003869669eb39eb16f598e202104fbeccee1af70c573eca00c2f257794c16f7d522dde96a4ac91ce92a6d3244b511f668f323561e1edd752772ba6344afe4c28dc100d227b6d74067a616ae0e9b6926319310fba441feafb8779aaf2b9c85dac0d22c01000005040302000178ed255f4aec48125d7572bf6c000bf95fbb57a8c25687338b30d008eb9de8e909a7b93bb50f338ff754f7717748bef62f872b4b30b9842895b681c43aae980e", + "records": [ + "884cf32396184eb81133e9591be4b258c6003b51e0deb078a6d9d5f54c6e0bb0", + "0bda5e590d5697b19fbe01aef27c3ca1b60a9faa1a071ce4311bfd79600629b4", + "d1efdc375c3f54473765fd411e1b4297036496a7469c4a760178c12bfb3757f1", + "ebb78e2632de1121d58d03657cd64c49005919d2bf66f5b87f6d875a139db536", + "934c58421672e0064a522c78f03a211df75bfd4707110a5009d6be326add1b10", + "03b00ba2e625a0deec3f8ab343d4af57d460c8e020c1d0b23411b1c6abd9c4fe", + "3fb54a8cc15a95bb8de41b4af40bc75bf746e9353ee88ffe27e11fa301626fcd", + "204e2c6570b1f7d0b0987e940e1b8cf1ea8e993cc5f69c8663e83b843fa312a4", + "1db702fef40e55d10736af5253b3fa17858863ef956cbab1b1f691cd393239c7", + "f504828a1b113824e86478bd1aea92f7a926e3bf128116558cb442a7f386fddf", + "13a105e9be5b306e1989c1e6e782a733e911e3c7a9f3fa53184b5ba87a8172e1", + "c88b00e5fd6407cbdc61ac362cd267be50b4aa56b917ebd0fa8e7d237c5b2eab", + "de565e2575f3975d727cdca14ba78a63e4c2c3a4d01161b08b4b587d869f6530", + "a5a3aab8d91de6b13e1936184ff500c0fc2b7ba3b2f000c6f7fe59e0d908a65d", + "0dcc617b396648fdd85af518eef7cf2a5c482e9e1ca68ceb11d67af4258b5c80", + "94f2d5a5189ec4bb7904e22be9aa57f714adaea4bdaddd27af235cb28aa4c74c", + "815f244877415165acc4a94ca9ed4a16dbd086381b9518454e1cf67174140c0f", + "e3a1e2b9cb05f5fe57be0a1ad3a55691964cb5cfff14be256bf3a5c0d0dbe76d", + "869669eb39eb16f598e202104fbeccee1af70c573eca00c2f257794c16f7d522", + "dde96a4ac91ce92a6d3244b511f668f323561e1edd752772ba6344afe4c28dc1", + "00d227b6d74067a616ae0e9b6926319310fba441feafb8779aaf2b9c85dac0d2" + ], + "manifest": "{\"VERSION\":1,\"TTL\":300,\"RELOAD-URI\":\"https://steer.stream.example/v1/room?asset=lesson&wai_session=74e9af89f9cbec3bbcc0e59dedea0577\",\"PATHWAY-PRIORITY\":[\"edge-a2\",\"tree\",\"edge-a\",\"edge-b\"],\"PATHWAY-CLONES\":[{\"BASE-ID\":\"cdn-z\",\"ID\":\"edge-a2\",\"URI-REPLACEMENT\":{\"HOST\":\"a2.edge.stream.example\",\"PARAMS\":{\"pop\":\"east-2\",\"tk\":\"x%2Fy\"}}}],\"science.transaction.wai-decision\":\"https://steer.stream.example/v1/decisions/b0bfbe6085410e6db48a345bdcc47537a02b4beb41b73d63f9c6b566dbd61fc1\"}", + "outcome": "refuse", + "reason": "manifest_mismatch", + "at": [ + "manifest" + ] + } + ] +} diff --git a/wai/wai-rs/src/energy_steering.rs b/wai/wai-rs/src/energy_steering.rs index 2fad2b873..8773e9e77 100644 --- a/wai/wai-rs/src/energy_steering.rs +++ b/wai/wai-rs/src/energy_steering.rs @@ -776,26 +776,14 @@ fn cmcd_least(m: &sf::Member) -> Option { /// `CMCD-Request` header, when it has one. pub fn read_request(protocol: u8, target: &[u8], cmcd_header: Option<&[u8]>, policy: &Policy) -> Report { let parts = query_parts(target); - let (mut c, mut t) = (None, None); - match protocol { - protocol::HLS => { - c = first(&parts, "_HLS_pathway").and_then(|v| policy.index_of(unquote(v))); - t = first(&parts, "_HLS_throughput").and_then(integer); - } + let (c, mut t) = match protocol { + protocol::HLS => (first(&parts, "_HLS_pathway").and_then(|v| policy.index_of(unquote(v))), first(&parts, "_HLS_throughput").and_then(integer)), _ => { - let mut j = 0; - if let Some(v) = first(&parts, "_DASH_pathway") { - for (k, item) in unquote(v).split(|&x| x == b',').enumerate() { - if let Some(i) = policy.index_of(item) { - c = Some(i); - j = k; - break; - } - } - } - t = first(&parts, "_DASH_throughput").and_then(|v| v.split(|&x| x == b',').nth(j)).and_then(integer); + let found = first(&parts, "_DASH_pathway").and_then(|v| unquote(v).split(|&x| x == b',').enumerate().find_map(|(j, item)| policy.index_of(item).map(|i| (i, j)))); + let j = found.map_or(0, |(_, j)| j); + (found.map(|(i, _)| i), first(&parts, "_DASH_throughput").and_then(|v| v.split(|&x| x == b',').nth(j)).and_then(integer)) } - } + }; let cmcd = match first(&parts, "CMCD") { Some(v) => Some(v), None => cmcd_header, diff --git a/wai/wai-rs/src/energy_steering/tests.rs b/wai/wai-rs/src/energy_steering/tests.rs index 964aef056..ee7d5bb11 100644 --- a/wai/wai-rs/src/energy_steering/tests.rs +++ b/wai/wai-rs/src/energy_steering/tests.rs @@ -145,7 +145,11 @@ fn products_are_exact_at_their_limits() { assert_eq!(cmp_figures(&a, &b), Ordering::Greater); assert_eq!(cmp_figures(&b, &a), Ordering::Less); assert_eq!(cmp_figures(&a, &a), Ordering::Equal); - assert_eq!(product(&[m, m, PPM as u128]).len(), 6); + // 2^64 × 2^64 is 2^128: one limb set, two above the u128 range's limbs. + let p = product(&[1u128 << 64, 1u128 << 64]); + assert!(p.iter().enumerate().all(|(i, &l)| l == u64::from(i == 2)), "{p:?}"); + assert_eq!(cmp_limbs(&product(&[m, m, 3]), &product(&[m, m, 2])), Ordering::Greater); + assert_eq!(cmp_limbs(&product(&[m, m, PPM as u128 + 1]), &product(&[m, m, PPM as u128])), Ordering::Greater); } #[test] -- 2.54.0 (Apple Git-157) From cfc617cb24b420cff94328de787eb273e4857e25 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 18 Oct 2026 20:39:10 -0400 Subject: [PATCH 4/9] wai: energy_steering_verify.py, the steering corpus's second implementation (L15) Co-Authored-By: Claude Opus 5.5 --- .../energy_steering_verify.py | 1018 +++++++++++++++++ 1 file changed, 1018 insertions(+) create mode 100644 wai/live-conformance/energy_steering_verify.py diff --git a/wai/live-conformance/energy_steering_verify.py b/wai/live-conformance/energy_steering_verify.py new file mode 100644 index 000000000..88d3ef07b --- /dev/null +++ b/wai/live-conformance/energy_steering_verify.py @@ -0,0 +1,1018 @@ +#!/usr/bin/env python3 +"""Independent verifier of the energy-steering corpus (extensions/energy-steering.md). + +Written from the extension's text. It shares no code with wai-rs, which generates the +corpus, and never runs it. It checks Ed25519 signatures with +../claim-conformance/_ed25519.py, WAI's strict rule (SPEC §7 *Signatures*) written from +RFC 8032 with the standard library. The room is read by the live-rooms corpus's own second +implementation, live_rooms_verify.py (live-rooms §9.1 steps 1 and 2); each per-hop record by +the live-metering corpus's, live_metering_verify.py (live-metering §3.6, §5.2). Everything +else is here, from the text: + +- the steering policy (§3) and the steering decision (§7): read, their rules, who signs; +- the client's report (§4), with an RFC 9651 parser written from that RFC's §4.2; +- each pathway's window, coverage, status and figure (§5); +- the decision rule (§6): the limits, the order, the draw and the bands, the first pathway, + the priority and the TTL, all with Python's integers; +- the steering manifest, the HLS tag and the DASH element (§8), byte for byte. + +For every request it requires the report the corpus states; for every vector the outcome, +reason and place, and the digest, priority, TTL, reason for the first pathway, each +pathway's evidence and limits, the manifest and the URI; for every policy its HLS tag and +DASH element; of the corpus, that every reason of §10 refuses a vector and that §10 lists +exactly those reasons. Then it mutates copies of the corpus, one defect each, and requires +every copy to fail. + + python3 energy_steering_verify.py + +Exit status 0 only if every check holds and every mutated copy fails. Python 3.8 or later; +standard library only. +""" +import copy +import functools +import hashlib +import json +import os +import struct +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) +import live_events_verify as LEV # noqa: E402 +import live_rooms_verify as LRV # noqa: E402 +import live_metering_verify as LMV # noqa: E402 +import _ed25519 as ED # noqa: E402 + +Refuse = LEV.Refuse + +POLICY_MAGIC = b"WSP1" +DECISION_MAGIC = b"WSD1" +POLICY_DOMAIN = b"wai:steering-policy\x01" +DECISION_DOMAIN = b"wai:steering-decision\x01" +DRAW_DOMAIN = b"wai:steering-draw\x01" +STEER_AUTHORITY = "live/steer" +PPM = 1000000 +NO_CAPACITY = 2 ** 32 - 1 +NOT_CLONE = 255 +U64 = 2 ** 64 - 1 +MANIFEST_KEY = "science.transaction.wai-decision" +LABELS = {2: "ModelBased", 3: "Estimator", 4: "OnChipCounter", 5: "CalibratedInstrument"} +RANK = {5: 0, 4: 1, 2: 2, 3: 3} +STATUS = ["figure", "unmetered", "mixed", "stale", "empty"] +WHY = [None, "buffer", "floor", "share", "hold", "rank", "none"] +BUFFER, FLOOR, SHARE, HOLD, BY_RANK, NONE = 1, 2, 3, 4, 5, 6 +HLS, DASH = 1, 2 +LATENCY, QUALITY, CAPACITY, ALL = 1, 2, 4, 7 + +REASONS = [ + "policy_invalid", "clone_unknown", "decision_invalid", "evidence_not_on_pathway", + "evidence_outside_window", "evidence_mismatch", "unmetered_as_zero", "unknown_preferred", + "decision_mismatch", "manifest_mismatch", +] + + +class Stop(Exception): + def __init__(self, reason, at): + super().__init__(reason) + self.reason, self.at = reason, at + + +def sha(*parts): + h = hashlib.sha256() + for p in parts: + h.update(p) + return h.digest() + + +class R: + """A cursor; running out is `truncated`.""" + + def __init__(self, b, p=0): + self.b, self.p = b, p + + def take(self, n): + if self.p + n > len(self.b): + raise Refuse("truncated") + s = self.b[self.p:self.p + n] + self.p += n + return s + + def int(self, n): + return int.from_bytes(self.take(n), "little") + + def u8(self): + return self.take(1)[0] + + def short(self): + return self.take(self.u8()) + + def long(self): + return self.take(self.int(2)) + + def done(self): + return self.p == len(self.b) + + +def read_head(b, magic): + if len(b) < 4: + raise Refuse("truncated") + if b[:4] != magic: + raise Refuse("malformed") + if len(b) < 5: + raise Refuse("truncated") + if b[4] != 1: + raise Refuse("unknown_version") + + +def check_sig(key, domain, body, sig): + bad = ED.verify(key, domain + body, sig) + if bad: + raise Refuse(bad) + + +# ---------------------------------------------------------------- §3.3 character rules + +ALPHA_DIGIT = set(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") +HEX = set(b"0123456789abcdefABCDEF") +UNRESERVED = ALPHA_DIGIT | set(b"-._~") +URI_CHARS = UNRESERVED | set(b":/?[]@!$&'()*+,;=%") + + +def percent_ok(s): + i = 0 + while i < len(s): + if s[i] == 0x25: + if i + 2 >= len(s) or s[i + 1] not in HEX or s[i + 2] not in HEX: + return False + i += 3 + else: + i += 1 + return True + + +def uri_ok(s): + return 1 <= len(s) <= 2048 and all(c in URI_CHARS for c in s) and percent_ok(s) + + +def id_ok(s): + return 1 <= len(s) <= 64 and all(c in ALPHA_DIGIT or c in b".-_" for c in s) + + +def host_ok(s): + return all(c in ALPHA_DIGIT or c in b"-." for c in s) + + +def param_ok(s): + return all(c in UNRESERVED or c == 0x25 for c in s) and percent_ok(s) + + +# ---------------------------------------------------------------- §3 the policy + +def read_policy(b): + """§3.3: the policy, or Refuse. Its room and signature are checked by §9.1 step 2.""" + read_head(b, POLICY_MAGIC) + r = R(b, 5) + p = {"room": r.take(32), "signer": r.take(32), "period": r.int(8), "window": r.int(8), "lag": r.int(8), + "basis": r.u8(), "floor": r.u8(), "ceiling": r.int(4), "min_buffer": r.int(4), "h": r.int(4), + "ttl": r.int(4), "min_ttl": r.int(4), "flags": r.u8(), "initial": r.u8()} + p["tiers"] = [r.int(8) for _ in range(r.u8())] + p["server_uri"], p["reload_uri"], p["decision_uri"] = r.long(), r.long(), r.long() + p["pathways"] = [] + for _ in range(r.u8()): + x = {"id": r.short(), "base": r.u8(), "host": r.short()} + x["params"] = [(r.short(), r.short()) for _ in range(r.u8())] + x["top"], x["latency"], x["capacity"], x["share"] = r.u8(), r.int(4), r.int(4), r.int(4) + x["hops"] = [(r.take(32), r.u8()) for _ in range(r.u8())] + p["pathways"].append(x) + sig = r.take(64) + if not r.done(): + raise Refuse("malformed") + ps, n = p["pathways"], len(p["tiers"]) + if any(x["base"] != NOT_CLONE and x["base"] >= i for i, x in enumerate(ps)): + raise Refuse("clone_unknown") + ids, keys = set(), set() + + def pathway_ok(x): + if not id_ok(x["id"]) or x["id"] in ids: + return False + ids.add(x["id"]) + if x["base"] == NOT_CLONE: + if x["host"] or x["params"]: + return False + elif not (host_ok(x["host"]) and all(1 <= len(k) <= 64 and param_ok(k) and param_ok(v) for k, v in x["params"]) + and all(a[0] < z[0] for a, z in zip(x["params"], x["params"][1:]))): + return False + if x["top"] >= n or not x["hops"] or not any(pl == 1 for _, pl in x["hops"]): + return False + for k, pl in x["hops"]: + if pl not in (1, 2) or k in keys: + return False + keys.add(k) + return True + rules = (p["period"] > 0 and p["window"] > 0 and p["window"] % p["period"] == 0 and p["basis"] in (1, 2) + and 1 <= p["min_ttl"] <= p["ttl"] and p["h"] <= PPM and p["flags"] <= 1 + and 1 <= n <= 8 and p["tiers"][0] > 0 and all(a < z for a, z in zip(p["tiers"], p["tiers"][1:])) + and p["floor"] < n and all(uri_ok(p[k]) for k in ("server_uri", "reload_uri", "decision_uri")) + and 1 <= len(ps) <= 32 and all([pathway_ok(x) for x in ps]) + and p["initial"] < len(ps) and ps[p["initial"]]["base"] == NOT_CLONE + and sum(x["share"] for x in ps) <= PPM) + if not rules: + raise Refuse("policy_invalid") + p["body"], p["sig"] = b[:-64], sig + p["digest"] = sha(p["body"]) + return p + + +def index_of(p, ident): + return next((i for i, x in enumerate(p["pathways"]) if x["id"] == ident), None) + + +# ---------------------------------------------------------------- §4 the report, and RFC 9651 + +class SF: + """RFC 9651 §4.2: a Dictionary, parsed whole. Values are (type, value) pairs.""" + + def __init__(self, s): + self.b, self.i = s, 0 + + def peek(self): + return self.b[self.i] if self.i < len(self.b) else None + + def fail(self): + raise ValueError("structured field") + + def sp(self): + while self.peek() == 0x20: + self.i += 1 + + def ows(self): + while self.peek() in (0x20, 0x09): + self.i += 1 + + def key(self): + c = self.peek() + if c is None or not (0x61 <= c <= 0x7a or c == 0x2a): + self.fail() + s = self.i + while self.peek() is not None and (0x61 <= self.peek() <= 0x7a or 0x30 <= self.peek() <= 0x39 or self.peek() in b"_-.*"): + self.i += 1 + return self.b[s:self.i].decode("ascii") + + def params(self): + out = {} + while self.peek() == 0x3b: + self.i += 1 + self.sp() + k = self.key() + v = ("boolean", True) + if self.peek() == 0x3d: + self.i += 1 + v = self.bare() + out[k] = v + return out + + def item(self): + return (self.bare(), self.params()) + + def number(self): + neg = self.peek() == 0x2d + if neg: + self.i += 1 + if self.peek() is None or not 0x30 <= self.peek() <= 0x39: + self.fail() + s, dot = self.i, None + while self.peek() is not None: + c = self.peek() + if 0x30 <= c <= 0x39: + self.i += 1 + elif c == 0x2e and dot is None: + if self.i - s > 12: + self.fail() + dot = self.i + self.i += 1 + else: + break + if (dot is None and self.i - s > 15) or (dot is not None and self.i - s > 16): + self.fail() + text = self.b[s:self.i].decode("ascii") + sign = -1 if neg else 1 + if dot is None: + return ("integer", sign * int(text)) + whole, frac = text.split(".") + if not 1 <= len(frac) <= 3: + self.fail() + return ("decimal", sign * (int(whole) * 1000 + int(frac) * 10 ** (3 - len(frac)))) + + def bare(self): + c = self.peek() + if c is None: + self.fail() + if c == 0x2d or 0x30 <= c <= 0x39: + return self.number() + if c == 0x22: + self.i += 1 + out = bytearray() + while True: + c = self.peek() + if c is None: + self.fail() + self.i += 1 + if c == 0x5c: + n = self.peek() + if n not in (0x22, 0x5c): + self.fail() + self.i += 1 + out.append(n) + elif c == 0x22: + return ("string", bytes(out)) + elif 0x20 <= c <= 0x7e: + out.append(c) + else: + self.fail() + if (0x41 <= c <= 0x5a or 0x61 <= c <= 0x7a) or c == 0x2a: + s = self.i + self.i += 1 + while self.peek() is not None and (self.peek() in ALPHA_DIGIT or self.peek() in b"!#$%&'*+-.^_`|~:/"): + self.i += 1 + return ("token", self.b[s:self.i].decode("ascii")) + if c == 0x3a: + self.i += 1 + s = self.i + while True: + c = self.peek() + if c is None: + self.fail() + if c == 0x3a: + break + if not (c in ALPHA_DIGIT or c in b"+/="): + self.fail() + self.i += 1 + v = self.b[s:self.i] + self.i += 1 + return ("bytes", v) + if c == 0x3f: + self.i += 1 + if self.peek() not in (0x30, 0x31): + self.fail() + v = self.peek() == 0x31 + self.i += 1 + return ("boolean", v) + if c == 0x40: + self.i += 1 + t, v = self.number() + if t != "integer": + self.fail() + return ("date", v) + if c == 0x25: + self.i += 1 + if self.peek() != 0x22: + self.fail() + self.i += 1 + out = bytearray() + while True: + c = self.peek() + if c is None: + self.fail() + self.i += 1 + if c == 0x25: + h = self.b[self.i:self.i + 2] + if len(h) != 2 or not all(x in b"0123456789abcdef" for x in h): + self.fail() + out.append(int(h, 16)) + self.i += 2 + elif c == 0x22: + return ("display", bytes(out).decode("utf-8")) + elif 0x20 <= c <= 0x7e: + out.append(c) + else: + self.fail() + self.fail() + + def member(self): + if self.peek() == 0x28: + self.i += 1 + items = [] + while True: + self.sp() + if self.peek() == 0x29: + self.i += 1 + return ("inner", items, self.params()) + items.append(self.item()) + if self.peek() not in (0x20, 0x29): + self.fail() + return ("item",) + self.item() + + def dictionary(self): + self.sp() + out = {} + while self.i < len(self.b): + k = self.key() + if self.peek() == 0x3d: + self.i += 1 + out[k] = self.member() + else: + out[k] = ("item", ("boolean", True), self.params()) + self.ows() + if self.i == len(self.b): + break + if self.peek() != 0x2c: + self.fail() + self.i += 1 + self.ows() + if self.i == len(self.b): + self.fail() + return out + + +def parse_dictionary(s): + """The dictionary, or None when it does not parse (UTF-8 errors included).""" + try: + return SF(s).dictionary() + except (ValueError, UnicodeDecodeError): + return None + + +def percent_decode(s): + out, i = bytearray(), 0 + while i < len(s): + if s[i] == 0x25: + h = s[i + 1:i + 3] + if len(h) != 2 or not all(c in HEX for c in h): + return None + out.append(int(h, 16)) + i += 3 + else: + out.append(s[i]) + i += 1 + return bytes(out) + + +def query_parts(target): + """§4.1: the decoded parts, in order; a part that does not decode is skipped.""" + q = target.find(b"?") + if q < 0: + return [] + rest = target[q + 1:] + f = rest.find(b"#") + if f >= 0: + rest = rest[:f] + out = [] + for part in rest.split(b"&"): + e = part.find(b"=") + n, v = (part[:e], part[e + 1:]) if e >= 0 else (part, b"") + n, v = percent_decode(n), percent_decode(v) + if n is not None and v is not None: + out.append((n, v)) + return out + + +def first(parts, name): + return next((v for n, v in parts if n == name), None) + + +def unquote(v): + return v[1:-1] if len(v) >= 2 and v[:1] == b'"' and v[-1:] == b'"' else v + + +def integer(v): + if v is None or not 1 <= len(v) <= 20 or not all(0x30 <= c <= 0x39 for c in v): + return None + x = int(v) + return x if x <= U64 else None + + +def least(m): + """§4.4: an Integer of 0 or above, or the least of a non-empty Inner List of them.""" + if m is None: + return None + if m[0] == "item": + values = [m[1]] + elif m[0] == "inner" and m[1]: + values = [bare for bare, _ in m[1]] + else: + return None + if any(t != "integer" or x < 0 for t, x in values): + return None + return min(x for _, x in values) + + +def read_request(p, protocol, target, header): + """§4: (c, T, B), each None when absent.""" + parts = query_parts(target) + c = t = None + if protocol == HLS: + v = first(parts, b"_HLS_pathway") + c = index_of(p, unquote(v)) if v is not None else None + t = integer(first(parts, b"_HLS_throughput")) + else: + j = 0 + v = first(parts, b"_DASH_pathway") + if v is not None: + for k, item in enumerate(unquote(v).split(b",")): + i = index_of(p, item) + if i is not None: + c, j = i, k + break + v = first(parts, b"_DASH_throughput") + if v is not None: + items = v.split(b",") + t = integer(items[j]) if j < len(items) else None + cmcd = first(parts, b"CMCD") + if cmcd is None: + cmcd = header + d = None + if cmcd is not None: + try: + d = parse_dictionary(cmcd.decode("utf-8").encode("utf-8")) + except UnicodeDecodeError: + d = None + d = d or {} + b = least(d.get("bl")) + if t is None: + m = least(d.get("mtp")) + t = m * 1000 if m is not None and m * 1000 <= U64 else None + return c, t, b + + +# ---------------------------------------------------------------- §5 the evidence + +def window(p, t): + b = 0 if t < p["lag"] else (t - p["lag"]) // p["period"] * p["period"] + return (b - p["window"] if b >= p["window"] else 0), b + + +def tag(x): + return x["class"][0] if x["class"] else 0 + + +def evidence(p, i, recs, t): + """§5.2, §5.3: (status, class, J, D).""" + a, b = window(p, t) + path = p["pathways"][i] + for key, _ in path["hops"]: + mine = [x for x in recs if x["hop"] == key] + if not (mine and mine[0]["start"] == a and all(z["start"] == y["end"] for y, z in zip(mine, mine[1:])) + and mine[-1]["end"] == b): + return (3, 0, 0, 0) + if any(x["class"] is None for x in recs): + return (1, 0, 0, 0) + tags = {tag(x) for x in recs} + if len(tags) > 1: + return (2, 0, 0, 0) + edges = {k for k, pl in path["hops"] if pl == 1} + d = 0 + for x in recs: + if x["hop"] in edges: + if p["basis"] == 1: + d += sum(e["bytes"] for e in x["tracks"] if e["kind"] == 1 and e["dir"] == 2) + else: + d += sum(x["sub"][p["floor"]:8]) + if d == 0: + return (4, 0, 0, 0) + return (0, tags.pop(), sum(x["joules"] for x in recs), d) + + +def rank(e): + return RANK[e[1]] if e[0] == 0 else 4 + + +# ---------------------------------------------------------------- §6 the rule + +def order(p, ev): + def cmp(x, y): + ex, ey = ev[x], ev[y] + if rank(ex) != rank(ey): + return -1 if rank(ex) < rank(ey) else 1 + if ex[0] == 0 and ey[0] == 0 and ex[1] == ey[1]: + l, r = ex[2] * ey[3], ey[2] * ex[3] + if l != r: + return -1 if l < r else 1 + a, b = p["pathways"][x]["id"], p["pathways"][y]["id"] + return -1 if a < b else (1 if a > b else 0) + return sorted(range(len(p["pathways"])), key=functools.cmp_to_key(cmp)) + + +def draw(digest, session): + return int.from_bytes(sha(DRAW_DOMAIN, digest, session)[:4], "little") % PPM + + +def beats(w, x, h): + return w[0] == 0 and x[0] == 0 and w[2] * x[3] * (PPM + h) < x[2] * w[3] * PPM + + +def decide(p, session, report, loads, ev): + """§6: (draw, limits, priority, ttl, reason).""" + c, t, b = report + ps = p["pathways"] + limits = [] + for i, x in enumerate(ps): + bits = 0 + if x["latency"] <= p["ceiling"]: + bits |= LATENCY + if x["top"] >= p["floor"]: + bits |= QUALITY + if x["capacity"] == NO_CAPACITY or loads[i] < x["capacity"] or (c == i and loads[i] <= x["capacity"]): + bits |= CAPACITY + limits.append(bits) + + def within(i): + return limits[i] == ALL + od = order(p, ev) + d = draw(p["digest"], session) + at_floor = False + if t is not None: + tiers = [k for k, rate in enumerate(p["tiers"]) if rate <= t] + at_floor = not tiers or max(tiers) <= p["floor"] + c_within = c is not None and within(c) + w = next((i for i in od if within(i)), None) + band, s = None, 0 + for i, x in enumerate(ps): + if s <= d < s + x["share"]: + band = i + s += x["share"] + if c_within and b is not None and b < p["min_buffer"]: + head, why = c, BUFFER + elif c_within and at_floor: + head, why = c, FLOOR + elif w is None: + head, why = (c if c is not None else od[0]), NONE + elif band is not None and within(band): + head, why = band, SHARE + elif c_within and c != w and rank(ev[c]) == rank(ev[w]) and (rank(ev[w]) == 4 or not beats(ev[w], ev[c], p["h"])): + head, why = c, HOLD + else: + head, why = w, BY_RANK + priority = [head] + [i for i in od if i != head and within(i)] + [i for i in od if i != head and not within(i)] + ttl = p["min_ttl"] if why in (BUFFER, FLOOR, NONE) else p["ttl"] + return d, limits, priority, ttl, why + + +# ---------------------------------------------------------------- §7 the decision + +def read_decision(b): + """§7.2: the decision, or Refuse.""" + read_head(b, DECISION_MAGIC) + r = R(b, 5) + x = {"room": r.take(32), "signer": r.take(32), "policy": r.take(32), "t": r.int(8), "session": r.take(16), + "draw": r.int(4), "protocol": r.u8(), "present": r.u8(), "pathway": r.u8(), "throughput": r.int(8), + "buffer": r.int(8)} + x["pathways"] = [] + for _ in range(r.u8()): + s = {"load": r.int(4), "limits": r.u8(), "status": r.u8(), "class": r.u8(), "joules": r.int(16), + "delivery": r.int(16)} + s["evidence"] = [r.take(32) for _ in range(r.u8())] + x["pathways"].append(s) + x["ttl"], x["reason"] = r.int(4), r.u8() + x["priority"] = list(r.take(r.u8())) + sig = r.take(64) + if not r.done(): + raise Refuse("malformed") + n, pr = len(x["pathways"]), x["present"] + report_ok = (pr & ~7 == 0 and (pr & 1 or x["pathway"] == 0) and (pr & 2 or x["throughput"] == 0) + and (pr & 4 or x["buffer"] == 0) and (not pr & 1 or x["pathway"] < n)) + + def state_ok(s): + if s["limits"] & ~ALL or s["status"] > 4: + return False + if s["status"] == 0: + return 2 <= s["class"] <= 5 and s["joules"] > 0 and s["delivery"] > 0 + return s["class"] == 0 and s["joules"] == 0 and s["delivery"] == 0 + digests = [g for s in x["pathways"] for g in s["evidence"]] + rules = (x["protocol"] in (HLS, DASH) and x["draw"] < PPM and x["ttl"] >= 1 and 1 <= x["reason"] <= 6 + and report_ok and all(state_ok(s) for s in x["pathways"]) and len(set(digests)) == len(digests) + and sorted(x["priority"]) == list(range(n))) + if not rules: + raise Refuse("decision_invalid") + x["body"], x["sig"] = b[:-64], sig + x["digest"] = sha(x["body"]) + return x + + +def report_of(x): + return (x["pathway"] if x["present"] & 1 else None, x["throughput"] if x["present"] & 2 else None, + x["buffer"] if x["present"] & 4 else None) + + +# ---------------------------------------------------------------- §8 the mappings + +def manifest(p, x): + """§8.1.""" + ps = p["pathways"] + reload = p["reload_uri"].decode() + ("&" if b"?" in p["reload_uri"] else "?") + "wai_session=" + x["session"].hex() + ids = ",".join('"%s"' % ps[i]["id"].decode() for i in x["priority"]) + out = '{"VERSION":1,"TTL":%d,"RELOAD-URI":"%s","PATHWAY-PRIORITY":[%s]' % (x["ttl"], reload, ids) + clones = [] + for y in ps: + if y["base"] == NOT_CLONE: + continue + rep = [] + if y["host"]: + rep.append('"HOST":"%s"' % y["host"].decode()) + if y["params"]: + rep.append('"PARAMS":{%s}' % ",".join('"%s":"%s"' % (k.decode(), v.decode()) for k, v in y["params"])) + clones.append('{"BASE-ID":"%s","ID":"%s","URI-REPLACEMENT":{%s}}' % (ps[y["base"]]["id"].decode(), y["id"].decode(), ",".join(rep))) + if clones: + out += ',"PATHWAY-CLONES":[%s]' % ",".join(clones) + return out + ',"%s":"%s%s"}' % (MANIFEST_KEY, p["decision_uri"].decode(), x["digest"].hex()) + + +def hls_tag(p): + return '#EXT-X-CONTENT-STEERING:SERVER-URI="%s",PATHWAY-ID="%s"' % (p["server_uri"].decode(), p["pathways"][p["initial"]]["id"].decode()) + + +def dash_element(p): + return '%s' % ( + p["pathways"][p["initial"]]["id"].decode(), "true" if p["flags"] & 1 else "false", + p["server_uri"].decode().replace("&", "&")) + + +# ---------------------------------------------------------------- §9 verification + +class Keys: + """What live_metering_verify.hop_authorised reads of a room: its descriptor and log.""" + + def __init__(self, d, log): + self.d, self.log = d, log + + def entry(self, key): + if self.log is None: + return None + return next((e for e in self.log["keys"] if e["key"] == key.hex()), None) + + +def signer_ok(keys, signer, classes): + """§3.5.""" + d = keys.d + if d["origin"] == b"": + if signer != d["authority"]: + raise Refuse("signer_not_authorised") + return + e = keys.entry(signer) + if e is None or not (signer == d["authority"] or STEER_AUTHORITY in e["may_sign"]): + raise Refuse("signer_not_authorised") + if not all("energy/" + LABELS[c] in e["may_sign"] for c in classes): + raise Refuse("signer_not_authorised") + if e["status"] == "revoked": + raise Refuse("signer_revoked") + + +def at(where, f, *args): + try: + return f(*args) + except Refuse as e: + raise Stop(e.reason, where) + + +def verify(descriptor, head, policy, decision, records, given_manifest): + """§9.1: the outcome as the corpus states it.""" + d = at(["room"], LRV.read_descriptor, descriptor) + at(["room"], LRV.check_key_log, d, head) + keys = Keys(d, json.loads(head) if d["origin"] != b"" else None) + room = d["room"] + p = at(["policy"], read_policy, policy) + if p["room"] != room: + raise Stop("wrong_room", ["policy"]) + at(["policy"], check_sig, p["signer"], POLICY_DOMAIN, p["body"], p["sig"]) + at(["policy"], signer_ok, keys, p["signer"], []) + x = at(["decision"], read_decision, decision) + if x["room"] != room: + raise Stop("wrong_room", ["decision"]) + if x["policy"] != p["digest"]: + raise Stop("record_missing", ["decision"]) + if len(x["pathways"]) != len(p["pathways"]): + raise Stop("decision_invalid", ["decision"]) + at(["decision"], check_sig, x["signer"], DECISION_DOMAIN, x["body"], x["sig"]) + at(["decision"], signer_ok, keys, x["signer"], sorted({s["class"] for s in x["pathways"] if s["status"] == 0})) + a, b = window(p, x["t"]) + named = [] + for pi, s in enumerate(x["pathways"]): + hops = [k for k, _ in p["pathways"][pi]["hops"]] + out, last = [], None + for k, g in enumerate(s["evidence"]): + where = ["evidence", pi, k] + given = next((r for r in records if LMV.given_digest(r) == g), None) + if given is None: + raise Stop("record_missing", where) + rec = at(where, LMV.read_hop, given, room) + at(where, LMV.hop_authorised, keys, rec) + if rec["hop"] not in hops: + raise Stop("evidence_not_on_pathway", where) + if rec["start"] < a or rec["end"] > b: + raise Stop("evidence_outside_window", where) + place = (hops.index(rec["hop"]), rec["start"]) + if last is not None and last >= place: + raise Stop("decision_invalid", where) + last = place + out.append(rec) + named.append(out) + ev = [evidence(p, i, named[i], x["t"]) for i in range(len(p["pathways"]))] + for pi, s in enumerate(x["pathways"]): + stated = (s["status"], s["class"], s["joules"], s["delivery"]) + if stated != ev[pi]: + if stated[0] == 0 and ev[pi][0] == 1: + raise Stop("unmetered_as_zero", ["pathway", pi]) + if stated[0] == 0 and ev[pi][0] == 2: + raise Stop("classes_summed", ["pathway", pi]) + raise Stop("evidence_mismatch", ["pathway", pi]) + loads = [s["load"] for s in x["pathways"]] + dr, limits, priority, ttl, why = decide(p, x["session"], report_of(x), loads, ev) + if (dr, limits, priority, ttl, why) != (x["draw"], [s["limits"] for s in x["pathways"]], x["priority"], x["ttl"], x["reason"]): + unknown_first = ev[x["priority"][0]][0] != 0 and ev[priority[0]][0] == 0 + raise Stop("unknown_preferred" if unknown_first else "decision_mismatch", ["output"]) + m = manifest(p, x) + if given_manifest is not None and given_manifest != m: + raise Stop("manifest_mismatch", ["manifest"]) + return { + "outcome": "accept", + "digest": x["digest"].hex(), + "priority": [p["pathways"][i]["id"].decode() for i in priority], + "ttl": ttl, + "why": WHY[why], + "pathways": [{"id": y["id"].decode(), "status": STATUS[e[0]], "class": LABELS[e[1]] if e[0] == 0 else None, + "joules": str(e[2]), "delivery": str(e[3]), "limits": limits[i]} + for i, (y, e) in enumerate(zip(p["pathways"], ev))], + "steering_manifest": m, + "uri": p["decision_uri"].decode() + x["digest"].hex(), + } + + +def inputs_of(policy, decision): + try: + p, x = read_policy(policy), read_decision(decision) + except Refuse: + return None + if len(x["pathways"]) != len(p["pathways"]): + return None + return {"t": x["t"], "session": x["session"].hex(), "protocol": "hls" if x["protocol"] == HLS else "dash", + "report": report_json(p, report_of(x)), "loads": [s["load"] for s in x["pathways"]]} + + +def report_json(p, rep): + c, t, b = rep + return {"pathway": p["pathways"][c]["id"].decode() if c is not None else None, "throughput": t, "buffer": b} + + +# ---------------------------------------------------------------- the corpus + +OUTCOME_KEYS = ("outcome", "reason", "at", "digest", "priority", "ttl", "why", "pathways", "steering_manifest", "uri") + + +def check(doc, quiet=False): + """Every failure of the corpus against this text, as strings.""" + fails = [] + try: + desc = bytes.fromhex(doc["room"]["descriptor"]) + head = doc["room"]["key_log_head"] + pols = {n: bytes.fromhex(v["policy"]) for n, v in doc["policies"].items()} + pool = {k: bytes.fromhex(v) for k, v in doc["records"].items()} + except (KeyError, ValueError, TypeError, AttributeError) as e: + return ["the corpus does not read: %r" % e] + for n, v in doc["policies"].items(): + try: + p = read_policy(pols[n]) + want = (hls_tag(p), dash_element(p)) + except Refuse: + want = (None, None) + if (v.get("hls_tag"), v.get("dash_element")) != want: + fails.append("policy %s: its HLS tag or DASH element differs" % n) + for r in doc["requests"]: + try: + p = read_policy(pols[r["policy"]]) + header = r["cmcd_header"].encode() if r["cmcd_header"] is not None else None + got = report_json(p, read_request(p, HLS if r["protocol"] == "hls" else DASH, r["target"].encode(), header)) + except (Refuse, KeyError, AttributeError) as e: + fails.append("request %s does not read: %r" % (r.get("name"), e)) + continue + if got != r["report"]: + fails.append("request %s: the report is %s, the corpus states %s" % (r["name"], got, r["report"])) + seen = set() + for v in doc["vectors"]: + name = v.get("name", "?") + try: + policy = pols[v["policy"]] + decision = bytes.fromhex(v["decision"]) + records = [pool[g] for g in v["records"]] + except (KeyError, ValueError, TypeError) as e: + fails.append("%s does not read: %r" % (name, e)) + continue + try: + got = verify(desc, head, policy, decision, records, v["manifest"]) + except Stop as e: + got = {"outcome": "refuse", "reason": e.reason, "at": e.at} + for k in OUTCOME_KEYS: + if v.get(k) != got.get(k): + fails.append("%s: %s is %s, the corpus states %s" % (name, k, got.get(k), v.get(k))) + if v.get("inputs") != inputs_of(policy, decision): + fails.append("%s: its inputs are not the decision's" % name) + if v.get("request") is not None: + try: + p, x = read_policy(policy), read_decision(decision) + rq = v["request"] + header = rq["cmcd_header"].encode() if rq["cmcd_header"] is not None else None + if read_request(p, HLS if rq["protocol"] == "hls" else DASH, rq["target"].encode(), header) != report_of(x): + fails.append("%s: its request does not give the decision's report" % name) + except (Refuse, KeyError, AttributeError): + fails.append("%s: its request does not read" % name) + if got["outcome"] == "refuse": + seen.add(got["reason"]) + for r in REASONS + ["classes_summed", "hop_not_authorised", "hop_revoked", "record_missing"]: + if r not in seen: + fails.append("nothing in the corpus is refused %s" % r) + if not quiet: + for f in fails: + print("FAIL " + f) + return fails + + +def reasons_listed(): + """§10's table, in order.""" + path = os.path.join(HERE, "..", "extensions", "energy-steering.md") + with open(path, encoding="utf-8") as f: + text = f.read() + at_ = text.index("\n## 10. Reasons") + end = text.find("\n## ", at_ + 1) + body = text[at_:end if end >= 0 else len(text)] + return [line.split("`")[1] for line in body.splitlines() if line.startswith("| `")] + + +def flip(h, at_, bit=0x01): + b = bytearray(bytes.fromhex(h)) + b[at_] ^= bit + return b.hex() + + +def mutations(doc): + """Copies of the corpus, each with one defect, by name.""" + out = [] + + def mutate(name, f): + d = copy.deepcopy(doc) + f(d) + out.append((name, d)) + + def vec(d, n): + return next(v for v in d["vectors"] if v["name"] == n) + + def first_record(d, n): + return vec(d, n)["records"][0] + mutate("a request's report", lambda d: d["requests"][0]["report"].update(throughput=4000001)) + mutate("a request's target", lambda d: d["requests"][1].update(target="/v1/room?_HLS_pathway=edge-a")) + mutate("a CMCD request's header", lambda d: d["requests"][15].update(cmcd_header="bl=(9000;v)")) + mutate("an accepted decision's signature", lambda d: vec(d, "three_metered").update(decision=flip(vec(d, "three_metered")["decision"], -1))) + mutate("an accepted priority", lambda d: vec(d, "three_metered")["priority"].reverse()) + mutate("an accepted TTL", lambda d: vec(d, "floor_client").update(ttl=300)) + mutate("an accepted reason", lambda d: vec(d, "hysteresis_hold").update(why="rank")) + mutate("a pathway's joules", lambda d: vec(d, "mixed_classes")["pathways"][2].update(joules="1200000001")) + mutate("a pathway's class", lambda d: vec(d, "mixed_classes")["pathways"][0].update(**{"class": "OnChipCounter"})) + mutate("a pathway's status", lambda d: vec(d, "one_unmetered")["pathways"][1].update(status="figure")) + mutate("a pathway's limits", lambda d: vec(d, "latency")["pathways"][2].update(limits=7)) + mutate("a given manifest", lambda d: vec(d, "clones").update(manifest=vec(d, "clones")["manifest"].replace("east-2", "east-3"))) + mutate("a stated manifest", lambda d: vec(d, "dash").update(steering_manifest=vec(d, "dash")["steering_manifest"].replace('"TTL":300', '"TTL":301'))) + mutate("a stated URI", lambda d: vec(d, "share").update(uri=vec(d, "share")["uri"][:-1] + ("1" if vec(d, "share")["uri"][-1] == "0" else "0"))) + mutate("a record's byte", lambda d: d["records"].update({first_record(d, "stale"): flip(d["records"][first_record(d, "stale")], 40)})) + mutate("a record left out", lambda d: vec(d, "per_second")["records"].pop()) + mutate("a refused reason", lambda d: vec(d, "classes_summed").update(reason="evidence_mismatch")) + mutate("a refused place", lambda d: vec(d, "record_missing").update(at=["evidence", 2, 3])) + mutate("a vector's inputs", lambda d: vec(d, "dash")["inputs"]["report"].update(buffer=30000)) + mutate("a policy's HLS tag", lambda d: d["policies"]["three"].update(hls_tag=d["policies"]["three"]["hls_tag"].replace("edge-a", "edge-b"))) + mutate("a policy's DASH element", lambda d: d["policies"]["three"].update(dash_element=d["policies"]["three"]["dash_element"].replace("&", "&"))) + def key_log_sig(d): + h = d["room"]["key_log_head"] + i = h.rindex('"sig":"') + 7 + d["room"]["key_log_head"] = h[:i] + ("1" if h[i] == "0" else "0") + h[i + 1:] + mutate("the key log's signature", key_log_sig) + mutate("the reason's only vector", lambda d: d["vectors"].remove(vec(d, "unknown_preferred"))) + mutate("a policy's byte", lambda d: d["policies"]["explore"].update(policy=flip(d["policies"]["explore"]["policy"], -1))) + return out + + +def main(corpus_dir): + with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f: + doc = json.load(f) + fail = 0 + fails = check(doc) + n_req, n_vec = len(doc["requests"]), len(doc["vectors"]) + print("%s %d requests and %d vectors verify from the text" % ("PASS" if not fails else "FAIL", n_req, n_vec)) + fail += bool(fails) + listed = reasons_listed() + ok = listed == REASONS + print("%s §10 lists exactly the reasons this verifier raises" % ("PASS" if ok else "FAIL")) + fail += not ok + caught = 0 + muts = mutations(doc) + for name, d in muts: + if check(d, quiet=True): + caught += 1 + else: + print("FAIL the mutation '%s' goes unnoticed" % name) + fail += 1 + print("%s %d of %d mutated copies of the corpus fail" % ("PASS" if caught == len(muts) else "FAIL", caught, len(muts))) + return 1 if fail else 0 + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("usage: energy_steering_verify.py ", file=sys.stderr) + sys.exit(2) + sys.exit(main(sys.argv[1])) -- 2.54.0 (Apple Git-157) From 39467caf7444454544221d55418bb2936544179c Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 18 Oct 2026 20:41:05 -0400 Subject: [PATCH 5/9] wai: steering's RFC 9651 parsers refuse a byte sequence that is not base64 Co-Authored-By: Claude Opus 5.5 --- wai/extensions/energy-steering.md | 10 +++++++--- wai/live-conformance/energy_steering_verify.py | 4 ++++ wai/live-conformance/steering/vectors.json | 12 ++++++++++++ wai/wai-rs/src/energy_steering.rs | 12 ++++++++++++ wai/wai-rs/src/energy_steering/corpus.rs | 3 ++- wai/wai-rs/src/energy_steering/tests.rs | 5 ++++- 6 files changed, 41 insertions(+), 5 deletions(-) diff --git a/wai/extensions/energy-steering.md b/wai/extensions/energy-steering.md index f39ee0fa0..e4a15aa68 100644 --- a/wai/extensions/energy-steering.md +++ b/wai/extensions/energy-steering.md @@ -222,14 +222,16 @@ the header shards; CTA-5004-B assigns `bl` (buffer length, milliseconds) and `mt (measured throughput, kilobits per second) to the shard `CMCD-Request`. The server reads the `CMCD` parameter's value or, when the query has no such part, the `CMCD-Request` header's value, as an RFC 9651 Dictionary (§4.2.2 of that RFC, the whole grammar, Dates and -Display Strings included); a value that does not parse gives neither key. A key read is: +Display Strings included, and a Byte Sequence whose content is not base64 of RFC 4648 §4, its +padding supplied where it is missing, refused); a value that does not parse gives neither +key. A key read is: - an Integer of 0 or above; or a non-empty Inner List of such Integers, each with any parameters (CTA-5004-B carries one per object type, its token a parameter): the **least** of them, the object type whose buffer runs out first, or the slowest measurement; - anything else, absent. -Then *B* is `bl`, and when the query gives no *T*, *T* is `mtp` × 1000. +Then *B* is `bl`, and when §4.2 or §4.3 gives no *T*, *T* is `mtp` × 1000. ## 5. A pathway's evidence @@ -637,7 +639,9 @@ records of the pathways' hops, and: its floor, and one with a short buffer, held; hysteresis holding a client and, a period later, moving it; pathway clones; a session placed by its band; a pathway at its capacity and one past the latency ceiling; the per-second basis; DASH; and no pathway within the - limits; each with its steering manifest, HLS tag and DASH element byte for byte. + limits; each with its steering manifest, HLS tag and DASH element byte for byte. Two + more show what has no figure: hops of two classes in one pathway, and, beside the session + its band places, one outside the band, whose unmetered pathway comes last. **Refused**: each reason of §10, and the codes it reuses from the extensions it reads, at least once, each vector a control of one defect. diff --git a/wai/live-conformance/energy_steering_verify.py b/wai/live-conformance/energy_steering_verify.py index 88d3ef07b..a806e73c7 100644 --- a/wai/live-conformance/energy_steering_verify.py +++ b/wai/live-conformance/energy_steering_verify.py @@ -350,6 +350,10 @@ class SF: self.i += 1 v = self.b[s:self.i] self.i += 1 + core = v.rstrip(b"=") + q = len(v) - len(core) + if q > 2 or b"=" in core or (len(core) % 4 == 1 if q == 0 else (len(core) + q) % 4 != 0): + self.fail() return ("bytes", v) if c == 0x3f: self.i += 1 diff --git a/wai/live-conformance/steering/vectors.json b/wai/live-conformance/steering/vectors.json index 0b6d7d879..d33870095 100644 --- a/wai/live-conformance/steering/vectors.json +++ b/wai/live-conformance/steering/vectors.json @@ -348,6 +348,18 @@ "buffer": 300 } }, + { + "name": "hls_cmcd_bytes_not_base64", + "protocol": "hls", + "policy": "three", + "target": "/v1/room?CMCD=bl%3D300%2Cz%3D%3AA%3A", + "cmcd_header": null, + "report": { + "pathway": null, + "throughput": null, + "buffer": null + } + }, { "name": "dash_list_second_known", "protocol": "dash", diff --git a/wai/wai-rs/src/energy_steering.rs b/wai/wai-rs/src/energy_steering.rs index 8773e9e77..965e9ff13 100644 --- a/wai/wai-rs/src/energy_steering.rs +++ b/wai/wai-rs/src/energy_steering.rs @@ -1003,6 +1003,9 @@ pub mod sf { } let v = self.b[s..self.i].to_vec(); self.i += 1; + if !base64_ok(&v) { + return Err(()); + } Ok(Bare::Bytes(v)) } b'?' => { @@ -1052,6 +1055,15 @@ pub mod sf { } } + /// Whether a Byte Sequence's content decodes as base64 (RFC 4648 §4), its padding + /// synthesized where it is missing (RFC 9651 §4.2.7): at most two `=`, only at the end, + /// and a length the padding completes to a multiple of four. + fn base64_ok(v: &[u8]) -> bool { + let q = v.iter().rev().take_while(|&&c| c == b'=').count(); + let core = &v[..v.len() - q]; + q <= 2 && core.iter().all(|&c| c.is_ascii_alphanumeric() || c == b'+' || c == b'/') && if q == 0 { core.len() % 4 != 1 } else { (core.len() + q).is_multiple_of(4) } + } + /// Parse a Dictionary field value (RFC 9651 §4.2, §4.2.2). A key given twice keeps its /// first place and its last value. pub fn parse_dictionary(s: &str) -> Result, ()> { diff --git a/wai/wai-rs/src/energy_steering/corpus.rs b/wai/wai-rs/src/energy_steering/corpus.rs index ba46e5582..2f873f30d 100644 --- a/wai/wai-rs/src/energy_steering/corpus.rs +++ b/wai/wai-rs/src/energy_steering/corpus.rs @@ -374,7 +374,7 @@ fn policies(room_id: [u8; 32]) -> Vec<(&'static str, Vec)> { // ---- the requests ---- /// The steering requests of §11, by protocol, target and `CMCD-Request` header. -const REQUESTS: [(&str, u8, &str, Option<&str>); 26] = [ +const REQUESTS: [(&str, u8, &str, Option<&str>); 27] = [ ("hls_quoted", protocol::HLS, "/v1/room?asset=lesson&_HLS_pathway=%22edge-a%22&_HLS_throughput=4000000", None), ("hls_unquoted", protocol::HLS, "/v1/room?_HLS_pathway=edge-b&_HLS_throughput=123", None), ("hls_literal_quotes", protocol::HLS, "/v1/room?_HLS_pathway=\"tree\"", None), @@ -397,6 +397,7 @@ const REQUESTS: [(&str, u8, &str, Option<&str>); 26] = [ ("hls_cmcd_negative", protocol::HLS, "/v1/room?CMCD=bl%3D-5%2Cmtp%3D(-1)", None), ("hls_cmcd_decimal_in_list", protocol::HLS, "/v1/room?CMCD=bl%3D%281.5%3Bv%29", None), ("hls_cmcd_display_string_and_date", protocol::HLS, "/v1/room?CMCD=bl%3D300%2Cx%3D%25%22caf%25c3%25a9%22%2Cy%3D%401700000000%2Cz%3D%3AAQID%3A", None), + ("hls_cmcd_bytes_not_base64", protocol::HLS, "/v1/room?CMCD=bl%3D300%2Cz%3D%3AA%3A", None), ("dash_list_second_known", protocol::DASH, "/v1/room?_DASH_pathway=%22cdn-z,edge-b%22&_DASH_throughput=1000,2000", None), ("dash_unquoted_single", protocol::DASH, "/v1/room?steeringToken=12345&_DASH_pathway=tree&_DASH_throughput=5140000", None), ("dash_throughput_list_short", protocol::DASH, "/v1/room?_DASH_pathway=%22cdn-z,edge-b%22&_DASH_throughput=1000", None), diff --git a/wai/wai-rs/src/energy_steering/tests.rs b/wai/wai-rs/src/energy_steering/tests.rs index ee7d5bb11..ff3be66e8 100644 --- a/wai/wai-rs/src/energy_steering/tests.rs +++ b/wai/wai-rs/src/energy_steering/tests.rs @@ -130,8 +130,11 @@ fn the_structured_field_parser_reads_rfc_9651_dictionaries() { assert_eq!(ok[7].1, Member::Item(sf::Item { bare: Bare::Boolean(true), params: vec![] })); assert!(matches!(&ok[8].1, Member::Inner(items, ps) if items.len() == 2 && ps.len() == 1)); assert_eq!(d("").unwrap(), vec![]); + for good in ["a=::", "a=:AQ:", "a=:AQ==:", "a=:AQI:", "a=:AQI=:", "a=:AQID:"] { + assert!(d(good).is_ok(), "{good} does not parse"); + } assert_eq!(d("a=1,a=2").unwrap().len(), 1, "a key given twice keeps one place"); - for bad in ["a=1,", "A=1", "a=(1", "a=1 b=2", "a=1234567890123456", "a=1.2345", "a=1.", "a=\"x", "a=?2", "a=%\"\u{e9}\"", "a=%\"%C3\"", "a=@1.5", "a=:a*b:"] { + for bad in ["a=1,", "A=1", "a=(1", "a=1 b=2", "a=1234567890123456", "a=1.2345", "a=1.", "a=\"x", "a=?2", "a=%\"\u{e9}\"", "a=%\"%C3\"", "a=@1.5", "a=:a*b:", "a=:A:", "a=:AQ=:", "a=:A=Q=:", "a=:AQ===:"] { assert!(d(bad).is_err(), "{bad} parses"); } } -- 2.54.0 (Apple Git-157) From e54023cb206387a2f241b4eb8bfceff6ff98ad43 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 18 Oct 2026 20:46:44 -0400 Subject: [PATCH 6/9] =?UTF-8?q?wai:=20steering=20in=20CI,=20SPEC=20=C2=A75?= =?UTF-8?q?,=20the=20registry,=20jwp-receipts=20=C2=A77.5=20and=20the=20CH?= =?UTF-8?q?ANGELOG=20(L15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-conformance.yml | 34 ++++++++++++++++++-- wai/CHANGELOG.md | 42 +++++++++++++++++++++++++ wai/REGISTRY.md | 4 +-- wai/SPEC.md | 18 +++++++++-- wai/extensions/jwp-receipts.md | 1 + wai/wai-rs/src/codecs/registry.rs | 7 ++++- wai/wai-rs/tests/int_kernels_vectors.rs | 2 +- 7 files changed, 100 insertions(+), 8 deletions(-) diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index 0bba78714..0726ad32a 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -88,6 +88,10 @@ on: # reports, live_metering_verify.py reads that table, and the registry's wire identifiers # test reads its sections. - 'wai/extensions/live-metering.md' + # tests/energy_steering.rs requires this extension's §10 to list the codes the reference + # reports, energy_steering_verify.py reads that table, and the registry's wire + # identifiers test reads its sections. + - 'wai/extensions/energy-steering.md' - 'wai/wai-web/**' # The audio provenance check below reads the corpora above with this tool. - 'wai/tools/wai_audio_provenance_check.py' @@ -147,6 +151,10 @@ on: # reports, live_metering_verify.py reads that table, and the registry's wire identifiers # test reads its sections. - 'wai/extensions/live-metering.md' + # tests/energy_steering.rs requires this extension's §10 to list the codes the reference + # reports, energy_steering_verify.py reads that table, and the registry's wire + # identifiers test reads its sections. + - 'wai/extensions/energy-steering.md' - 'wai/wai-web/**' # The audio provenance check below reads the corpora above with this tool. - 'wai/tools/wai_audio_provenance_check.py' @@ -811,8 +819,9 @@ jobs: # live_events_verify.py (the live-events corpus), and live_rooms_verify.py (the # live-rooms corpus), and capture_take_verify.py (the capture-binding corpus), and # live_compose_verify.py (the live-compose corpus), live_record_verify.py (the - # live-recording corpus), timed_text_verify.py (the timed-text corpus), and - # live_metering_verify.py (the live-metering corpus). + # live-recording corpus), timed_text_verify.py (the timed-text corpus), + # live_metering_verify.py (the live-metering corpus), and energy_steering_verify.py (the + # energy-steering corpus). # Each exits non-zero on any failure or any difference, and the step fails with it. # One platform: what they check is the reading of the text, not the architecture. independent-verifiers: @@ -1062,6 +1071,27 @@ jobs: (cd wai-rs && cargo test --no-default-features --features live_metering --lib live_metering::) (cd wai-rs && cargo test --no-default-features --features live_metering --test live_metering) python3 -I live-conformance/live_metering_verify.py live-conformance/metering + # wai.live.steering (extensions/energy-steering.md): the reference module's unit tests + # (policies and decisions round-trip and their rules refuse, the window, the request + # reader over HLS, DASH and CMCD, the RFC 9651 parser, exact products at their limits, + # the margin at its boundary, figures compared only within a class, and a pathway with + # no figure never placed first by its rank while one with a figure is within the + # limits) and tests/energy_steering.rs (the committed corpus regenerates byte for byte + # from its fixed seeds, wai-rs gives every request and vector its stated outcome, every + # reason of §10 refuses a vector, and §10 lists exactly the reference's codes); then the + # corpus's second implementation, energy_steering_verify.py, written from the + # extension's text and sharing no code with wai-rs, which reads the room with + # live_rooms_verify.py and the per-hop records with live_metering_verify.py, parses RFC + # 9651 itself, recomputes every report, figure, decision and manifest, and requires + # every mutated copy of the corpus to fail; standard library only, run isolated (-I). + - name: energy steering · wai-rs and energy_steering_verify.py + working-directory: wai + shell: bash + run: | + set -euo pipefail + (cd wai-rs && cargo test --no-default-features --features energy_steering --lib energy_steering::) + (cd wai-rs && cargo test --no-default-features --features energy_steering --test energy_steering) + python3 -I live-conformance/energy_steering_verify.py live-conformance/steering # The quantum corpus's second implementation (quantum-sim extension), integers only: # it derives the circuit/1 phase table by its recurrence and the circuit2 table by an # interval-certified procedure, and requires both literal tables and their digests, diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 868db5018..c7c0ce805 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,48 @@ wire format may change and such changes are recorded here. ## v0.4.0 — current +### `wai.live.steering`: each client's pathway, chosen by the joules its hops signed + +- **The extension** ([energy-steering](extensions/energy-steering.md)) registers the record + `wai.live.steering`. The operator signs a **steering policy**, `WSP1`: the pathways (each a + pathway ID of the content description or a clone of one, through another host), each + pathway's hops (edge or interior, a hop in one pathway only), the quality floor, the latency + ceiling, each pathway's capacity and minimum share, the hysteresis margin, the TTLs, and the + period and window over which the hops' per-hop records (`WMH1`) are read. The new key-log + authority `live/steer` lets an operator key sign policies and decisions. +- **The evidence.** A pathway's figure is its hops' joules over the window, per media byte its + edge hops delivered or per microsecond of delivery at the floor tier or above, with the + records' common acquisition class. The window ends on the last period past a lag, and every + hop's records must tile it exactly, so every pathway is read over the same span and a + record left out makes the pathway **stale**, never cheaper. A pathway with an unmetered hop, + hops of two classes, stale records or no delivery has **no figure**: a decision that gives + one a figure is refused `unmetered_as_zero` or `classes_summed`. +- **The rule.** Pathways are ordered by class (calibrated instrument, counter, model, + estimate), within a class by figure, compared by exact products, and then by ID; a pathway + with no figure comes after every pathway with one, so not measuring is never rewarded. A + client with a short buffer or at its quality floor is held; a session's draw, fixed for the + session, places it on a pathway by that pathway's minimum share whatever its evidence; a + client moves off its pathway only for a figure lower by more than the margin. The output is + the priority, the TTL and the reason the first pathway is first. +- **The steering decision**, `WSD1`, names the policy, the records by digest, the client's + report (read from `_HLS_pathway` and `_HLS_throughput`, `_DASH_pathway` and + `_DASH_throughput`, and CMCD's `bl` and `mtp` in the query or the `CMCD-Request` header), the + server's session and loads, each pathway's evidence and limits, and the output; a verifier + recomputes all of it; ten refusal reasons. +- **The mappings.** The steering manifest of `draft-pantos-content-steering-04`, which HLS + (`draft-pantos-hls-rfc8216bis-22` §7) and DASH (ETSI TS 103 998 V1.1.1) read, written byte + for byte with `PATHWAY-CLONES`, a `RELOAD-URI` carrying the session, and one member, + `science.transaction.wai-decision`, naming the decision's URI, which players ignore; the + `EXT-X-CONTENT-STEERING` tag and the DASH `ContentSteering` element. +- **Reference and corpus.** `wai-rs` feature `energy_steering` (`src/energy_steering.rs`, with + an RFC 9651 parser of its own); `live-conformance/steering/`, written from fixed seeds by + `wai_energy_steering_vectors`, its figures synthetic, and checked by + `tests/energy_steering.rs` and by `live-conformance/energy_steering_verify.py`, a second + implementation that shares no code with `wai-rs`, reads the room and the per-hop records + with their corpora's second implementations, recomputes every report, figure, decision and + manifest, and requires every mutated copy of the corpus to fail. The registry lists the + record and the magics `WSP1` and `WSD1`; jwp-receipts §7.5 lists `live/steer`. + ### `wai.live.metering`: a room session's units and joules per hop - **The extension** ([live-metering](extensions/live-metering.md)) registers the record diff --git a/wai/REGISTRY.md b/wai/REGISTRY.md index e453c4789..1ba29068f 100644 --- a/wai/REGISTRY.md +++ b/wai/REGISTRY.md @@ -40,8 +40,8 @@ Every registered string has one kind. - **record** — a signed record a WAI tool emits and verifies (a receipt kind: `wai.asset.provenance`, the quantum-operations receipts, the delivery records `wai.video.manifest` and its siblings; the live records `wai.live.events`, - `wai.live.room`, `wai.capture.take`, `wai.live.compose`, `wai.live.recording` and - `wai.live.metering`; timed text, `wai.text.timed`). + `wai.live.room`, `wai.capture.take`, `wai.live.compose`, `wai.live.recording`, + `wai.live.metering` and `wai.live.steering`; timed text, `wai.text.timed`). It names no payload format. A derivation or record string is not a payload capability, so it is not diff --git a/wai/SPEC.md b/wai/SPEC.md index 5d7b151de..1b3cd8f45 100644 --- a/wai/SPEC.md +++ b/wai/SPEC.md @@ -592,8 +592,9 @@ kind: `wai.asset.provenance`, `wai.quantum.calibration`, `wai.quantum.job`, and the delivery records `wai.video.manifest`, `wai.video.ssai`, `wai.video.keys`, `wai.video.package` and `wai.video.channel`; and the live records `wai.live.events`, - `wai.live.room`, `wai.capture.take`, `wai.live.compose`, `wai.live.recording` and - `wai.live.metering`, and timed text, `wai.text.timed`), which names no payload format. + `wai.live.room`, `wai.capture.take`, `wai.live.compose`, `wai.live.recording`, + `wai.live.metering` and `wai.live.steering`, and timed text, `wai.text.timed`), which names + no payload format. Only payload, companion and refinement capabilities have a media class, a fidelity tier, a replicate class and a role. A derivation or record string is not a payload @@ -957,6 +958,19 @@ event timeline, and only a cell of at least fifty sessions reaches the receipt. onto C2PA's `c2pa.environmental-sustainability` assertion where one class covers the session whole: [`extensions/live-metering.md`](extensions/live-metering.md). +And for **which pathway** delivers a room's stream to each client — `wai.live.steering`: the +operator signs a steering policy, `WSP1`, naming the pathways (CDNs, relay trees, an origin +path, and their clones), the hops of each, the quality floor, the latency ceiling, each +pathway's capacity and minimum share, and the window over which the hops' per-hop records are +read. A steering server derives each pathway's joules per delivered byte, or per delivered +second at the floor tier, from those records with their acquisition class, compares figures +only within a class, ranks a pathway whose energy is not known (an unmetered hop, two +classes, stale records) after every pathway whose energy is, holds a client against a figure +within a margin, and signs a steering decision, `WSD1`, naming the records, the client's +report, its own inputs and the output, which a verifier recomputes. The output is the steering +manifest HLS and DASH players already read, written byte for byte, with the decision's URI in +one member players ignore: [`extensions/energy-steering.md`](extensions/energy-steering.md). + ### Payloads of the WAI-native integer codecs This revision pins a payload format to each WAI-native integer codec whose row named diff --git a/wai/extensions/jwp-receipts.md b/wai/extensions/jwp-receipts.md index 0bec5ea27..402790ac9 100644 --- a/wai/extensions/jwp-receipts.md +++ b/wai/extensions/jwp-receipts.md @@ -3117,6 +3117,7 @@ A confirmation needs every one of them: | an upload receipt of a take ([capture-binding](capture-binding.md) §6) | `capture/store` | | a per-hop session record ([live-metering](live-metering.md) §3) | `live/relay`, `live/compositor` or `live/ingest`, by its role; and `energy/` for its figure's class where it is metered | | a room-session receipt (live-metering §4) signed by a key other than the room authority | `live/meter`; and, from any signer, `energy/` for each class it totals | +| a steering policy or steering decision ([energy-steering](energy-steering.md) §3.5) signed by a key other than the room authority | `live/steer`; and, from any signer of a decision, `energy/` for each class among its figures | A class registered in §2.4.4 registers its authority here with it, and a family that needs a narrower authority than `receipt` registers one here. diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs index 2d251b9f9..1217c80ca 100644 --- a/wai/wai-rs/src/codecs/registry.rs +++ b/wai/wai-rs/src/codecs/registry.rs @@ -215,6 +215,7 @@ pub const CAP_CAPTURE_TAKE: &str = "wai.capture.take"; pub const CAP_LIVE_COMPOSE: &str = "wai.live.compose"; pub const CAP_LIVE_RECORDING: &str = "wai.live.recording"; pub const CAP_LIVE_METERING: &str = "wai.live.metering"; +pub const CAP_LIVE_STEERING: &str = "wai.live.steering"; pub const CAP_TEXT_TIMED: &str = "wai.text.timed"; /// One row per string SPEC §5 registers that is a payload, companion or record: @@ -299,6 +300,7 @@ const ROWS: &[(&str, &str, Option<&str>, &str)] = &[ (CAP_LIVE_COMPOSE, S_INTERACTIVE, None, "2026-10-17"), (CAP_LIVE_RECORDING, S_INTERACTIVE, None, "2026-10-17"), (CAP_LIVE_METERING, S_INTERACTIVE, None, "2026-10-18"), + (CAP_LIVE_STEERING, S_INTERACTIVE, None, "2026-10-18"), (CAP_TEXT_TIMED, S_INTERACTIVE, None, "2026-10-17"), (CAP_AUDIO_IAMF, S_IMMERSIVE, None, "2026-06-17"), (CAP_AUDIO_AC4, S_IMMERSIVE, None, "2026-06-17"), @@ -347,7 +349,7 @@ const RECORDS: &[&str] = &[ CAP_ASSET_PROVENANCE, CAP_QUANTUM_CALIBRATION, CAP_QUANTUM_JOB, CAP_VIDEO_MANIFEST, CAP_VIDEO_SSAI, CAP_VIDEO_KEYS, CAP_VIDEO_PACKAGE, CAP_VIDEO_CHANNEL, CAP_LIVE_EVENTS, CAP_LIVE_ROOM, CAP_CAPTURE_TAKE, CAP_LIVE_COMPOSE, CAP_LIVE_RECORDING, - CAP_LIVE_METERING, CAP_TEXT_TIMED, + CAP_LIVE_METERING, CAP_LIVE_STEERING, CAP_TEXT_TIMED, ]; /// A named identifier SPEC uses that has the shape of a capability string but is @@ -492,6 +494,7 @@ const LIVE_COMPOSE: &str = "extensions/live-compose.md"; const LIVE_RECORDING: &str = "extensions/live-recording.md"; const TIMED_TEXT: &str = "extensions/timed-text.md"; const LIVE_METERING: &str = "extensions/live-metering.md"; +const ENERGY_STEERING: &str = "extensions/energy-steering.md"; const fn wire(name: &'static str, kind: WireKind, document: &'static str, section: &'static str, status: WireStatus) -> WireIdentifier { @@ -569,6 +572,8 @@ pub const WIRE_IDENTIFIERS: &[WireIdentifier] = &[ wire("science.transaction.wai.timed-text", WireKind::DataId, TIMED_TEXT, "§10.5", WireStatus::Defined), wire("WMH1", WireKind::Magic, LIVE_METERING, "§3.1", WireStatus::Defined), wire("WMR1", WireKind::Magic, LIVE_METERING, "§4.1", WireStatus::Defined), + wire("WSP1", WireKind::Magic, ENERGY_STEERING, "§3.1", WireStatus::Defined), + wire("WSD1", WireKind::Magic, ENERGY_STEERING, "§7.1", WireStatus::Defined), ]; /// A string with the shape of a capability that the reference implementation uses diff --git a/wai/wai-rs/tests/int_kernels_vectors.rs b/wai/wai-rs/tests/int_kernels_vectors.rs index 10f551784..d25b21aa4 100644 --- a/wai/wai-rs/tests/int_kernels_vectors.rs +++ b/wai/wai-rs/tests/int_kernels_vectors.rs @@ -223,7 +223,7 @@ fn every_decoded_output_states_a_sha256() { ("pin-conformance", "parameter-set pins: every digest there is a SHA-256 of a parameter set"), ("ops-conformance", "integer operators: every operator, stage and graph vector, the lower_* decodes of the sets under sets/ among them, states its output integers in full or its refusal code, never a digest of them; certificates/ states each lowered set's SHA-256 (a parameter-set identity) and holds the lowered graphs as bytes, compared byte for byte; the output digests a capability decoded through its lowering reproduces (integer-operators §14.4) are stated by that capability's own corpus"), ("signer-conformance", "key logs, grants and statuses: signed documents, no decoded output"), - ("live-conformance", "live events, rooms, takes, compositions, recordings, captions and metering: signed records, commitments, packages, clips, cues and their renderings, per-hop records, ledgers and session receipts with their units and joules recomputed, and their verdicts, and layouts and mixes stated in full, rectangle for rectangle and sample for sample; nothing decoded against a parameter set"), + ("live-conformance", "live events, rooms, takes, compositions, recordings, captions, metering and steering: signed records, commitments, packages, clips, cues and their renderings, per-hop records, ledgers and session receipts with their units and joules recomputed, steering policies and decisions with each pathway's evidence and the steering manifests recomputed byte for byte, and their verdicts, and layouts and mixes stated in full, rectangle for rectangle and sample for sample; nothing decoded against a parameter set"), ("http-conformance", "objects and head proofs for transport: no case decodes a payload, and the component a fetch case picks is stated by its SHA-256"), ("cache-conformance", "cache rules: a driver, its controls and their reports, with no objects of its own"), ("byte-exact-conformance", "goldens over tests/vectors, whose sidecars state the SHA-256"), -- 2.54.0 (Apple Git-157) From 7c194ec559d08136c37650a92d33dc0e2660e09a Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 18 Oct 2026 20:47:28 -0400 Subject: [PATCH 7/9] wai: registry export, wai.live.steering and the magics WSP1 and WSD1 Co-Authored-By: Claude Opus 5.5 --- wai/REGISTRY.md | 3 +++ wai/registry/capabilities.json | 28 ++++++++++++++++++++++++++++ wai/vocab/v2.ttl | 7 +++++++ 3 files changed, 38 insertions(+) diff --git a/wai/REGISTRY.md b/wai/REGISTRY.md index 1ba29068f..2072c1b41 100644 --- a/wai/REGISTRY.md +++ b/wai/REGISTRY.md @@ -199,6 +199,7 @@ and a role are fixed once registered. | `wai.live.metering` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | | `wai.live.recording` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | | `wai.live.room` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | +| `wai.live.steering` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | | `wai.mesh.draco` | payload | registered | volumetric | standalone | royalty-free | standard-unclassified | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | | `wai.mesh.glb` | payload | registered | volumetric | standalone | royalty-free | standard-unclassified | — | §5 Interactive, temporal & computed content | | `wai.meta.dovi` | companion | registered | metadata | companion | proprietary | standard-unclassified | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | @@ -306,7 +307,9 @@ and a role are fixed once registered. | `WRD1` | magic | defined | live-rooms §3.1 | | `WRG1` | magic | defined | live-rooms §4.1 | | `WRM1` | magic | defined | live-recording §4.1 | +| `WSD1` | magic | defined | energy-steering §7.1 | | `WSIX` | magic | defined | packaging §6 | +| `WSP1` | magic | defined | energy-steering §3.1 | | `WTM1` | magic | defined | capture-binding §7.1 | | `WTX1` | magic | defined | timed-text §4.2 | | `WUR1` | magic | defined | capture-binding §6.2 | diff --git a/wai/registry/capabilities.json b/wai/registry/capabilities.json index 70df4168b..d6d2e96a2 100644 --- a/wai/registry/capabilities.json +++ b/wai/registry/capabilities.json @@ -658,6 +658,20 @@ "edition": null, "registered": "2026-10-16" }, + { + "capability": "wai.live.steering", + "kind": "record", + "status": "registered", + "media": null, + "role": null, + "tier": null, + "licence": null, + "replicate": null, + "sets": [], + "spec_section": "§5 Interactive, temporal & computed content", + "edition": null, + "registered": "2026-10-18" + }, { "capability": "wai.mesh.draco", "kind": "payload", @@ -1885,6 +1899,13 @@ "section": "§4.1", "status": "defined" }, + { + "name": "WSD1", + "kind": "magic", + "document": "extensions/energy-steering.md", + "section": "§7.1", + "status": "defined" + }, { "name": "WSIX", "kind": "magic", @@ -1892,6 +1913,13 @@ "section": "§6", "status": "defined" }, + { + "name": "WSP1", + "kind": "magic", + "document": "extensions/energy-steering.md", + "section": "§3.1", + "status": "defined" + }, { "name": "WTM1", "kind": "magic", diff --git a/wai/vocab/v2.ttl b/wai/vocab/v2.ttl index 75d136593..1acb16f1b 100644 --- a/wai/vocab/v2.ttl +++ b/wai/vocab/v2.ttl @@ -557,6 +557,13 @@ wai:wai.live.room a wai:Capability ; wai:specSection "§5 Interactive, temporal & computed content" ; wai:registeredOn "2026-10-16"^^xsd:date . +wai:wai.live.steering a wai:Capability ; + rdfs:label "wai.live.steering" ; + wai:kind wai:kind-record ; + wai:status wai:status-registered ; + wai:specSection "§5 Interactive, temporal & computed content" ; + wai:registeredOn "2026-10-18"^^xsd:date . + wai:wai.mesh.draco a wai:Capability ; rdfs:label "wai.mesh.draco" ; wai:kind wai:kind-payload ; -- 2.54.0 (Apple Git-157) From dccec57e3978e90f752d9c263851b5761f98c9bf Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 19 Oct 2026 02:34:09 -0400 Subject: [PATCH 8/9] wai-rs: energy_steering is clippy-clean parse_dictionary returns an Option, the first pathway's steps take the best pathway by a let rather than an expect, and the corpus helper with eight arguments says so. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/energy_steering.rs | 26 +++++++++++++----------- wai/wai-rs/src/energy_steering/corpus.rs | 1 + wai/wai-rs/src/energy_steering/tests.rs | 4 ++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/wai/wai-rs/src/energy_steering.rs b/wai/wai-rs/src/energy_steering.rs index 965e9ff13..34cb45843 100644 --- a/wai/wai-rs/src/energy_steering.rs +++ b/wai/wai-rs/src/energy_steering.rs @@ -788,7 +788,7 @@ pub fn read_request(protocol: u8, target: &[u8], cmcd_header: Option<&[u8]>, pol Some(v) => Some(v), None => cmcd_header, }; - let dict = cmcd.and_then(|v| std::str::from_utf8(v).ok()).and_then(|s| sf::parse_dictionary(s).ok()); + let dict = cmcd.and_then(|v| std::str::from_utf8(v).ok()).and_then(sf::parse_dictionary); let get = |k: &str| dict.as_ref().and_then(|d| d.iter().find(|(n, _)| n == k)).and_then(|(_, m)| cmcd_least(m)); let b = get("bl"); if t.is_none() { @@ -1064,9 +1064,13 @@ pub mod sf { q <= 2 && core.iter().all(|&c| c.is_ascii_alphanumeric() || c == b'+' || c == b'/') && if q == 0 { core.len() % 4 != 1 } else { (core.len() + q).is_multiple_of(4) } } - /// Parse a Dictionary field value (RFC 9651 §4.2, §4.2.2). A key given twice keeps its - /// first place and its last value. - pub fn parse_dictionary(s: &str) -> Result, ()> { + /// Parse a Dictionary field value (RFC 9651 §4.2, §4.2.2): `None` when it does not + /// parse. A key given twice keeps its first place and its last value. + pub fn parse_dictionary(s: &str) -> Option> { + dictionary(s).ok() + } + + fn dictionary(s: &str) -> R> { let mut p = P { b: s.as_bytes(), i: 0 }; p.sp(); let mut out: Vec<(String, Member)> = Vec::new(); @@ -1326,16 +1330,14 @@ pub fn decide(p: &Policy, digest: &[u8; 32], session: &[u8; 16], report: &Report (c.expect("c is known"), why::BUFFER) } else if c_within && at_floor { (c.expect("c is known"), why::FLOOR) - } else if w.is_none() { - (c.unwrap_or(ord[0]), why::NONE) - } else if let Some(e) = band.filter(|&e| within(e)) { - (e, why::SHARE) - } else { - let w = w.expect("some pathway is within the limits"); - match c { - Some(c) if c_within && c != w && ev[c].rank() == ev[w].rank() && (ev[w].rank() == 4 || !beats(&ev[w], &ev[c], p.hysteresis_ppm)) => (c, why::HOLD), + } else if let Some(w) = w { + match (band.filter(|&e| within(e)), c) { + (Some(e), _) => (e, why::SHARE), + (None, Some(c)) if c_within && c != w && ev[c].rank() == ev[w].rank() && (ev[w].rank() == 4 || !beats(&ev[w], &ev[c], p.hysteresis_ppm)) => (c, why::HOLD), _ => (w, why::RANK), } + } else { + (c.unwrap_or(ord[0]), why::NONE) }; let mut priority = vec![first as u8]; priority.extend(ord.iter().filter(|&&i| i != first && within(i)).map(|&i| i as u8)); diff --git a/wai/wai-rs/src/energy_steering/corpus.rs b/wai/wai-rs/src/energy_steering/corpus.rs index 2f873f30d..aa23edb6b 100644 --- a/wai/wai-rs/src/energy_steering/corpus.rs +++ b/wai/wai-rs/src/energy_steering/corpus.rs @@ -468,6 +468,7 @@ impl Builder { const LOADS: [u32; 4] = [120, 80, 300, 40]; +#[allow(clippy::too_many_arguments)] fn accept(name: &'static str, about: &'static str, policy: &'static str, d: &Decision, records: Vec<[u8; 32]>, b: &Builder, request: Option<(u8, String, Option)>, why_: u8) -> Vector { let m = manifest(&b.policy(policy), d); Vector { name, about, policy, decision: d.to_bytes(), records, manifest: Some(m), request, intent: Intent::Accept(why_) } diff --git a/wai/wai-rs/src/energy_steering/tests.rs b/wai/wai-rs/src/energy_steering/tests.rs index ff3be66e8..1b5e34859 100644 --- a/wai/wai-rs/src/energy_steering/tests.rs +++ b/wai/wai-rs/src/energy_steering/tests.rs @@ -131,11 +131,11 @@ fn the_structured_field_parser_reads_rfc_9651_dictionaries() { assert!(matches!(&ok[8].1, Member::Inner(items, ps) if items.len() == 2 && ps.len() == 1)); assert_eq!(d("").unwrap(), vec![]); for good in ["a=::", "a=:AQ:", "a=:AQ==:", "a=:AQI:", "a=:AQI=:", "a=:AQID:"] { - assert!(d(good).is_ok(), "{good} does not parse"); + assert!(d(good).is_some(), "{good} does not parse"); } assert_eq!(d("a=1,a=2").unwrap().len(), 1, "a key given twice keeps one place"); for bad in ["a=1,", "A=1", "a=(1", "a=1 b=2", "a=1234567890123456", "a=1.2345", "a=1.", "a=\"x", "a=?2", "a=%\"\u{e9}\"", "a=%\"%C3\"", "a=@1.5", "a=:a*b:", "a=:A:", "a=:AQ=:", "a=:A=Q=:", "a=:AQ===:"] { - assert!(d(bad).is_err(), "{bad} parses"); + assert!(d(bad).is_none(), "{bad} parses"); } } -- 2.54.0 (Apple Git-157) From 224080a6fdc474468de6e3ed52f8569c7c027f70 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Mon, 19 Oct 2026 05:57:50 -0400 Subject: [PATCH 9/9] wai registry export: steering beside the WRP1 proofs Co-Authored-By: Claude Opus 5.5 --- wai/registry/capabilities.json | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/wai/registry/capabilities.json b/wai/registry/capabilities.json index 6d6c3e59e..422707641 100644 --- a/wai/registry/capabilities.json +++ b/wai/registry/capabilities.json @@ -672,6 +672,20 @@ "edition": null, "registered": "2026-10-16" }, + { + "capability": "wai.live.steering", + "kind": "record", + "status": "registered", + "media": null, + "role": null, + "tier": null, + "licence": null, + "replicate": null, + "sets": [], + "spec_section": "§5 Interactive, temporal & computed content", + "edition": null, + "registered": "2026-10-18" + }, { "capability": "wai.mesh.draco", "kind": "payload", @@ -1955,6 +1969,13 @@ "section": "§10.5.1", "status": "defined" }, + { + "name": "WSD1", + "kind": "magic", + "document": "extensions/energy-steering.md", + "section": "§7.1", + "status": "defined" + }, { "name": "WSIX", "kind": "magic", @@ -1962,6 +1983,13 @@ "section": "§6", "status": "defined" }, + { + "name": "WSP1", + "kind": "magic", + "document": "extensions/energy-steering.md", + "section": "§3.1", + "status": "defined" + }, { "name": "WTM1", "kind": "magic", -- 2.54.0 (Apple Git-157)