Skip to main content

WAI Extension: Live Metering

Mirrored from the canonical text at commit f871af55 ().

Status: Draft. Registers the record wai.live.metering: what a room session cost, in units and in joules, recomputable from what each hop signed. Every hop that carried the session (a relay, a compositor, an ingest gateway) signs one per-hop session record, WMH1, per room session: per track and direction the objects and bytes it carried and the objects it dropped by reason, its subscriber sessions’ time by layer class, its own energy with the acquisition class of energy-measurement, and a ledger root over the grants its sessions ran under. The record is a batch: it holds no entry per object and none per viewer, and its size is bounded by what the room published, whatever the audience. The room authority, or a key its origin’s key log lists for metering, signs the room-session receipt, WMR1, which names the per-hop records, the takes and the composition receipts it aggregates, and states the session’s units (publisher-seconds, subscriber-seconds by layer class, composition-seconds, events sent and delivered, recorded-seconds), the joules of every hop with its class, a total per class and never one across classes, and each grant’s consumption against its joule and funds ceilings. Every number in the receipt is recomputed by its verifier from the records it names. Clients add quality-of-experience observations, CMCD version 2 Event-mode reports carried on the CMCD event timeline, of which only an aggregate over enough sessions enters the receipt. The receipt maps onto C2PA 2.4’s c2pa.environmental-sustainability assertion. No new transport. The reference implementation is the live_metering module of wai-rs (feature live_metering); the reference relay, joule-moq-relay, signs a per-hop record from its session summaries; the corpus is live-conformance/metering/, and its second implementation is live-conformance/live_metering_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision: CTA-5004-B (CMCD version 2: Event mode, its batch body and the media type application/cmcd); draft-wilaw-moq-cmcd-event-timeline-00 and draft-ietf-moq-msf-01, Internet-Drafts not past Working Group Last Call; C2PA 2.4 §18.26; RFC 6962 §2.1; RFC 8032; RFC 8941 and RFC 9651; FIPS 180-4. A section number of one of those drafts in this text is that revision’s.

1. Scope

In scope: the per-hop session record in its two versions, its track entries, its subscriber time by layer class, its ledger, version 2’s DVR figures, its size bound and its signature (§3); the room-session receipt, the key that may sign it, its units, its joules per hop and per class, and its ceilings (§4); verification (§5); quality-of-experience observations, how they are carried and what of them reaches the receipt (§6); the mapping onto C2PA (§7); and what the reference relay emits (§8).

Out of scope: a transport; how a hop measures its energy, which energy-measurement governs; how a deployment prices a unit, of which this extension fixes only how a stated tariff is applied (§4.6); billing and payment; and how a meter enforces a ceiling while the session runs, which live-rooms §4.5 leaves to the deployment and this extension does not change (§4.6).

Relationship to the core spec: wai.live.metering is a record (SPEC §5): a signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable.

2. Terms

3. The per-hop session record

3.1 Layout

Each hop signs one record per room session, WMH1, when its part in the session ends:

fieldsizemeaning
magic4WMH1 (ASCII)
version11 or 2
room32the room id
hop32the hop’s key, which signs the record
role11 relay, 2 compositor, 3 ingest gateway
start, end8 eachu64: the span on the room clock the record covers, [start, end)
joules8u64: the hop’s own energy over the span, in microjoules; 0 is the unmetered marker
class1 + …its acquisition class: energy-measurement §2.7’s tag byte, then that tag’s evidence
tracks2 + 90 × na u16 count n, then each track entry (§3.2)
sessions4u32: the subscriber sessions it served in the span
subscriber_us72nine u64: the subscriber-microseconds of layer classes 0 to 8 (§3.3)
events8u64: the events it delivered to subscriber sessions (§3.3)
grants4u32: the entries of its ledger (§3.4)
ledger32the ledger’s root (§3.4)
dvr48version 2 only: six u64, the hop’s DVR figures (§3.9)
sig64§3.5

Version 2 is version 1’s fields in the same order and one more, dvr, after ledger and before sig. A hop that keeps a DVR window (live-recording §8) signs version 2, and a hop without one signs version 1.

3.2 Track entries

A track entry is 90 bytes:

fieldsizemeaning
kind11 a media track; 2 every events track of the room, together; 3 every other track of the room, together
track32for kind 1, the track digest; for kinds 2 and 3, 32 zero bytes
dir11 in: what the hop received; 2 out: what it sent
objects8u64. In: every object it received on the track, those it dropped included. Out: every object it sent, live or fetched
bytes8u64: those objects’ payload bytes
dropped_timeout8u64, out: objects not sent because their delivery timeout passed at this hop (MOQT §5.2), the rest of a subgroup reset for it included
dropped_refused8u64, in: objects refused at the door, their receipt not verifying (jwp-receipts §4.1)
dropped_duplicate8u64, in: objects another upstream of the track had already delivered
dropped_closed8u64, out: objects still queued when their session ended
receipted8u64, out: of the objects dropped, those whose receipts the hop forwarded to the same session on the track’s receipts track (jwp-receipts §4.4)

Media tracks are counted one by one, since a room publishes few of them. Events tracks are counted together, since every principal of the room, an audience member who chats among them, has one, and so are other tracks: a record holds no entry that grows with the audience (§3.7).

3.3 Subscribers and layer classes

The layer class of a subscriber session at an instant is the highest layer of the room’s media tracks it is subscribed to then, 0 to 7; or 8 when it is subscribed to tracks of the room but to none of its media tracks (an events-only viewer). subscriber_us[c] is the sum, over the hop’s subscriber sessions, of the room-clock microseconds within the span during which the session was of class c. A session is a subscriber session from its first subscription to a track of the room until it holds none; sessions counts those of the span. A session’s class follows the subscriptions it holds at each instant, including as it closes: a hop that ends a closing session’s subscriptions one at a time classes the moments between by what is still held, class 8 once its last media subscription has ended and while another of the room’s tracks is still held.

events counts the objects of Object ID 1 or above the hop delivered on the room’s events tracks to subscriber sessions: on an MSF event timeline each is one event (live-events §8.1), while object 0 of a group repeats the join set and is not counted.

A session another hop holds with this one (a relay that subscribes upstream for its own subscribers, a compositor’s subscriptions to the inputs it composes) is a hop link: its objects are counted in the track entries, and it is not a subscriber session. So a subscriber is counted at exactly one hop, the one that serves it, and a room’s subscriber time sums over its hops. A compositor is a principal of the room under its own grant, and its subscriptions at a relay are that relay’s subscriber sessions.

3.4 The ledger

The hop’s ledger is the list of its entries, one for each grant its sessions in the span ran under, in strictly ascending order of grant. An entry is 143 bytes:

fieldsizemeaning
grant32the grant’s digest (live-rooms §4.2); 32 zero bytes for sessions under no grant, such as a viewer admitted by Privacy Pass (live-rooms §10.4)
joules_ceiling8u64: the grant’s joules_ceiling; 2^64 − 1 for none, and for the zero grant
funds_ceiling8u64: the grant’s funds_ceiling; 2^64 − 1 for none
currency3the grant’s currency; three zero bytes for none
sessions4u32: the hop’s sessions under the grant, as publisher or subscriber
bytes_in8u64: the payload bytes the hop received from them on tracks of the room
bytes_out8u64: the payload bytes it sent to them on tracks of the room
subscriber_us72nine u64: their subscriber-microseconds by layer class (§3.3)

A hop knows the grant a session runs under from the token it was admitted with, which maps the grant’s claims (live-rooms §10): the hop holds the grant beside the token, and the ceilings are the grant’s. Each entry’s leaf is SHA-256 over the 20 bytes wai:metering-ledger (ASCII) and 0x01, followed by the entry; ledger is the root of the entries’ leaves in order, and grants their number. The ledger travels beside the record, unsigned; the record signs its root, so the ledger can only be the one its hop signed, and one entry can be shown to the principal it concerns with its RFC 6962 audit path, without the others.

3.5 Digest and signature

The digest of a record is SHA-256 over its bytes before sig. Its signature is Ed25519 by hop over the 17 bytes wai:metering-hop (ASCII) and 0x01, followed by the bytes the digest covers, verified strictly, as SPEC §7 Signatures requires: a key that fails step 1 or 2 is bad_key, and a signature that fails a later step is bad_signature.

3.6 Reading and rules

A reader reads exactly the bytes it is given as one record, as live-events §3.3 reads a record with WMH1 for WEV1 (truncated, malformed, unknown_version), except that it reads version 1 or 2, each in the order of §3.1 for its version; any other version is unknown_version. The class’s evidence is read as energy-measurement §2.7 writes it; for tag 5 a reference length above 255 is malformed. It then refuses a record that breaks any of these rules as energy_invalid:

and as record_invalid one that breaks any of these:

A ledger is read as entries of 143 bytes, its length a multiple of 143, in strictly ascending order of grant, each currency three zero bytes exactly when its funds_ceiling is 2^64 − 1 and three capitals A–Z otherwise, and the zero grant with both ceilings 2^64 − 1: otherwise ledger_invalid. Its number of entries is checked against the record’s grants when the record is verified (§5.1 step 4).

3.7 The size bound

Let M be the number of distinct media tracks the room published at some instant of the room session (§5.3). A record of version 1 is then at most

B(M) = 552 + 180 × (M + 2)

bytes, and one of version 2 at most

B₂(M) = 600 + 180 × (M + 2)

bytes. A record longer than the bound of its version is refused record_too_large before it is read: its version is read from its fifth byte before anything else is read, and a record whose fifth byte is not 2, or that has no fifth byte, is held to B(M). 552 is the record’s fixed fields, 281 bytes, with the longest class evidence, 271 bytes (tag 5 with a reference of 255 bytes); 600 is those and version 2’s dvr, 48 bytes; and a record has at most two entries for each media track (in and out) and two each for the events and other tracks together, 2 × (M + 2) entries of 90 bytes.

The bound does not depend on the audience. A session with three viewers and one with a million, carried by the same publishers, give records of one size: subscribers are summed into nine layer classes and their grants into the ledger, which the record signs by its root. A hop’s ledger grows with the grants it served; the record, which is what travels upstream and into the receipt, does not. Receipts batch per group upstream in the same way (jwp-receipts §2.3): a relay forwards a group receipt, not one per viewer per object.

3.8 What a hop counts

A hop’s record covers one room session, every session it held with a track of the room in the span. A hop that signs more than one record in a session (a long session flushed in parts) gives them spans that do not overlap and cuts them at group boundaries: an object is counted in the record whose span holds the arrival of its group’s first object at the hop.

3.9 DVR figures

A hop that keeps a DVR window of the room (live-recording §8) signs version 2, whose dvr field is six u64 over the record’s span, in this order:

fieldmeaning
dvr_objects_outof the objects the record’s out entries count (§3.2), those the hop sent from its DVR store in answer to a FETCH or a fill
dvr_bytes_outthose objects’ payload bytes
dvr_objects_storedthe objects of the room the hop wrote to its DVR store in the span
dvr_bytes_storedthose objects’ payload bytes
dvr_byte_sthe payload bytes of the room its store held, integrated over the span, in byte-seconds, rounded down: ⌊Σ b × h / 10^6⌋ over the objects it held, b an object’s payload bytes and h the microseconds within the span during which the store held it
dvr_droppedthe objects of the room the hop dropped from its store because they did not verify against their receipts (jwp-receipts §4.1), which it never served

A hop that keeps no DVR window signs version 1, which states no DVR figures. The objects a hop sends from its store are among those its out entries count, and the objects it stores among those its in entries count, so §3.6 holds the out figures to the out entries and the stored figures to the in entries. A record flushed in parts (§3.8) states in each part the figures of its own span.

4. The room-session receipt

4.1 Layout

When the session is over, the room authority, or a metering key (§4.3), signs the receipt, WMR1:

fieldsizemeaning
magic4WMR1 (ASCII)
version11 in this revision
room32the room id
signer32the key that signs the receipt
start, end8 eachu64: the room session, [start, end)
hops1 + …a u8 count, then for each per-hop record it aggregates, in strictly ascending order of record: record (32, the record’s digest), hop (32), role (u8), joules (u64) and class (1 + …), restated from the record
takes1 + 32 × ka u8 count, then the digest of each take manifest it aggregates (capture-binding §7.1), strictly ascending
compose4 + 32u32: the composition receipts it aggregates (live-compose §8); then their list digest, SHA-256 over their digests in ascending order, concatenated (32 zero bytes when none)
qoe4 + 32u32: the session logs of QoE observations it aggregates (§6); then the SHA-256 of the logs document (32 zero bytes when none)
units120fifteen u64: publisher_us, subscriber_us for layer classes 0 to 8, sessions, composition_us, events_sent, events_delivered, recorded_us (§4.4)
unmetered1u8: the hop entries whose figure is the unmetered marker
totals1 + 13 × ma u8 count m, then for each acquisition class among the hop entries: tag (u8), figures (u32, the hop entries of that class) and joules (u64, their sum), in strictly ascending order of tag (§4.5)
tariff3 + 52currency (3; zero for no tariff), then thirteen u32 prices in millionths of a minor unit: per second of publisher_us, of subscriber_us for each layer class 0 to 8, of composition_us and of recorded_us, and per event sent (§4.6)
ceilings4 + 32 + 4 + 33 × pu32: the grants of the session (§4.6); the consumption root (32); then a u32 count p and each grant past a ceiling: grant (32) and flags (u8: bit 0 past its joule ceiling, bit 1 past its funds ceiling), in strictly ascending order of grant
qoe_cell49the QoE aggregate (§4.7): suppressed (u8), started, start_failed, exit_before_start (u32 each), stalls, stall_ms, engaged_ms (u64 each), vst_count (u32), vst_ms (u64)
sig64§4.2

4.2 Digest, signature and rules

The digest of a receipt is SHA-256 over its bytes before sig; its signature is Ed25519 by signer over the 21 bytes wai:metering-session (ASCII) and 0x01, followed by the bytes the digest covers, verified strictly (SPEC §7 Signatures).

A reader reads exactly the bytes it is given, as §3.6 reads a record with WMR1 for WMH1 (truncated, malformed, unknown_version), and refuses as classes_summed a receipt whose totals names tag 0: a total over figures of no one class, which this layout has no other way to state. It refuses as session_invalid a receipt that breaks any of these:

4.3 Who signs: the metering authority

The receipt is signed by the room authority (live-rooms §3.1), or by a metering key: a key the room’s origin’s key log lists, not revoked, with the authority live/meter in its may_sign (jwp-receipts §7.5). Where the room names an origin, the signer’s key-log entry, the room authority’s included, MUST also hold energy/ followed by the class label of each class the receipt totals, 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 its receipts.

The metering power is a key-log authority and not one of a grant’s powers (live-rooms §3.3), for three reasons:

  1. A receipt states every principal’s consumption. A participant’s power to meter would let one participant sign the bill of another, the authority’s own ceilings included. Metering is the operator’s act, as storing a take is (capture/store), and the operator’s keys live in the origin’s key log.
  2. Revocation and trust come with it. A metering key is revoked through the same log as the room authority and the hops (§5.2), and a relying party anchors it as it anchors any origin key (jwp-receipts §7.4), with the energy authorities its figures need.
  3. A grant’s powers are a closed set of four bits, which live-rooms §3.4 holds a descriptor and a grant to; this extension does not change them.

A signer that is neither is refused signer_not_authorised; a metering key whose entry is revoked is refused signer_revoked.

4.4 Units

Every unit is an integer of room-clock microseconds or a count, over the room session [start, end), and each is recomputed from the records the receipt names:

The units do not sum the DVR figures of version 2 records (§3.9) in this revision.

4.5 Joules per hop and per class

Each hop entry restates its record’s hop, role, joules and class, so the receipt states the energy of every hop with the class it was obtained under. unmetered counts the entries whose figure is the unmetered marker; they are in no total, and a reader MUST NOT present them as zero joules (energy-measurement §2).

The totals are one per class: for each tag among the metered entries, the number of entries of that tag and the sum of their joules. Nothing in the receipt sums figures of two classes. A ModelBased figure added to an OnChipCounter figure is neither a counter reading nor a model’s output, and a total of the two, labelled with either class, would present a model as a measurement or a measurement as a model. So a receipt whose total names tag 0 is refused classes_summed (§4.2), and so is one whose total for a class is the sum of the figures of that class and of at least one other while not the sum of its own: the total of class X equals Σc ∈ S Tc for some set S of the receipt’s classes that holds X and another, where Tc is the sum of the figures of class c, and does not equal TX. Any other total that does not recompute is unit_mismatch.

The receipt’s energy covers the session whole when unmetered is 0 and it has at least one hop entry, and partially otherwise (energy-binding §4’s Whole and Partial). A partial total MUST NOT be presented as the session’s energy.

4.6 Ceilings

The grants of the session are every grant a hop ledger names, the zero grant included, and every grant that was in effect for a present principal at some instant of the session (§5.3), in strictly ascending order of digest. For each, its consumption is:

Its verdicts, against its ceilings, each the first row of the table below that applies, taken in the order none, past, unmetered, partial, within for the joule ceiling, and none, no tariff, past, within for the funds ceiling:

verdictjoule ceilingfunds ceiling
0 nonejoules_ceiling is 2^64 − 1funds_ceiling is 2^64 − 1
1 withinevery hop that moved its bytes was metered, at least one did, and the attributed joules of every class together are at most the ceilingthe funds are at most the ceiling
2 pastthe attributed joules of every class together are above the ceilingthe funds are above the ceiling
3 partial / no tariffwithin on what was metered, and an unmetered hop moved its bytesno funds were computed
4 unmeteredno metered hop moved its bytes—

A joule ceiling is one number, and holding a use to it adds what each hop spent, whatever its class. The verdict is that comparison; the sum it compares is stated nowhere as a figure, and the attributed joules stay per class.

A grant’s consumption entry is 198 bytes: grant (32), joules_ceiling (8), funds_ceiling (8), currency (3), publisher_us (8), subscriber_us (9 × 8), composition_us (8), recorded_us (8), events_sent (8), the attributed joules of tags 2, 3, 4 and 5 (4 × 8), unmetered (u8: 1 when its share is unmetered at some hop), funds (8), and the joule and funds verdicts (u8 each). Its leaf is SHA-256 over the 25 bytes wai:metering-consumption (ASCII) and 0x01, followed by the entry; the consumption root is the root of the grants’ leaves in order, and the receipt’s ceilings states their number, the root and every grant with a verdict of 2. Every principal can be shown its own entry with its audit path against the receipt, and nobody else’s.

Past a ceiling. live-rooms §4.5 fixes what a ceiling is: the energy and the funds a grant’s use may spend, which a meter holds the use to. How a meter holds it while the session runs is the deployment’s, and this extension changes neither the ceiling nor that. A meter that ends a use past its ceiling does so through the room’s own records: the clock removes the principal (leave, reason 2) or the authority exchanges its grant (live-rooms §4.6). A receipt records what the session did, not what it was allowed: a use past its ceiling is counted whole, and the receipt names its grant among those past.

4.7 The QoE cell

The cell aggregates the session logs of §6 into one cell. With fewer than 50 logs it is suppressed (delivery-telemetry §8.4: suppressed, not rounded): suppressed is 1 and every other field 0. Otherwise suppressed is 0, and over the logs’ session metrics (delivery-telemetry §7.2): started, start_failed and exit_before_start count the logs for which each holds; stalls, stall_ms and engaged_ms are sums; vst_count counts the logs with a video start time and vst_ms sums those times. A cell of fewer than 50 logs that is not suppressed is refused qoe_not_suppressed. The cell is the signer’s aggregate of statements nobody signed (delivery-telemetry §1.1) and MUST NOT be presented as evidence of what a viewer saw; it carries no joule figure (§1.2 there).

5. Verification

5.1 Inputs and steps

A receipt verifier is given the room (a descriptor, grants, the head of the authority’s key log or none, and the chains, as live-rooms §9.1), the receipt, the per-hop records with their ledgers, the composition descriptors and receipts, the take manifests, and the logs document or none. It runs these steps and stops at the first refusal, which it reports with the reason and where:

  1. The room (live-rooms §9): its refusal, at room, with the room’s own place. A gap in a room key’s chain is lifecycle_incomplete (live-compose §4.1), at room: the lifecycle, and with it every unit, is unknown after it. A gap in another principal’s chain is not a refusal: the units count what the room verifier holds.
  2. The receipt (§4.2), at receipt: truncated, malformed, unknown_version, classes_summed, session_invalid; wrong_room when its room is not the room id; bad_key, bad_signature.
  3. The signer (§4.3), at receipt: signer_not_authorised, signer_revoked.
  4. Each hop entry, in order, at hop and the entry’s index:
    1. a record whose digest is the entry’s record is given, with a ledger: otherwise record_missing. (The digest of given bytes is SHA-256 over all but their last 64.)
    2. Its length is at most the bound of its version, read from its fifth byte (§3.7): otherwise record_too_large.
    3. It reads as a record of version 1 or 2 (§3.6): truncated, malformed, unknown_version, energy_invalid, record_invalid; its room is the room id (wrong_room); its signature verifies (bad_key, bad_signature).
    4. The entry’s hop, role, joules and class are the record’s: otherwise unit_mismatch.
    5. The hop may sign for its role (§5.2): hop_not_authorised, hop_revoked.
    6. Its span is within the session: start ≤ its start and its end ≤ end. Otherwise span_outside_session.
    7. Each media entry’s track is a track the room published in the session (§5.3): otherwise track_unknown.
    8. Its ledger reads (§3.6, ledger_invalid); has grants entries and the root ledger; and each entry names the zero grant or a grant of the room, with that grant’s ceilings and currency. Otherwise ledger_mismatch.
    9. Its subscriber_us are the sums of its ledger’s, class by class: otherwise unit_mismatch.
  5. Each take, in the receipt’s order, at take and its index: a manifest with that digest is given (record_missing); it reads and verifies (capture-binding §7.2, with that section’s reasons); and it is in the room as capture-binding §9.2 finds it, with that section’s reasons.
  6. The composition receipts, at compose: the receipts given are as many as compose states and have its list digest, otherwise record_missing; then each, in ascending order of digest, at its index: it reads (live-compose §8.1, with that section’s reasons), the composition descriptor it names is given (record_missing), it verifies against the room and that descriptor (live-compose §8.3, with that section’s reasons), and its t is in the session (span_outside_session).
  7. The logs, at qoe: when qoe states logs, a logs document whose SHA-256 is its digest is given (record_missing); it reads (§6.1, qoe_invalid); and it holds as many logs as qoe states (unit_mismatch).
  8. The units (§4.4), at units: unit_mismatch.
  9. The energy (§4.5), at energy: unmetered is the number of unmetered hop entries; then each stated total, in order, names a class among the hop entries’ and states that class’s sum, or else is classes_summed when the sum it states is one §4.5 describes and unit_mismatch otherwise, and states that class’s number of figures; and the totals name every class of the metered entries. Otherwise unit_mismatch.
  10. The QoE cell (§4.7), at qoe: qoe_not_suppressed, then unit_mismatch.
  11. The ceilings (§4.6), at ceilings: the number of grants, the consumption root and the grants past a ceiling recompute; otherwise ceiling_mismatch.

A receipt that passes is accepted: every unit, every hop’s figure, every total and every grant’s consumption is what the records it names give.

5.2 Who may sign a hop record

Where the room names an origin, the hop’s key has an entry in the head given for the room (live-rooms §5) whose may_sign holds the authority of its role, live/relay, live/compositor or live/ingest, and, for a metered figure, energy/ followed by the label of its class (jwp-receipts §7.5): otherwise hop_not_authorised. An entry whose status is revoked is hop_revoked. A hardware class’s energy authority is confirmed only under a pinned anchor (jwp-receipts §7.5, Pinned-only authorities); the verifier is a function of the head its caller anchored, and the caller states which anchor that was.

Where the room names no origin, a hop’s key is a room key (live-rooms §2): otherwise hop_not_authorised.

5.3 The room’s replay

Over the records the room verifier accepted, in the merged order (live-rooms §9.2), the room keys’ lifecycle events are applied as live-rooms §7.2 applies them, each taking effect at its t: the room at an instant x is the state after every lifecycle event whose t is at most x (live-compose §4.1). Between two consecutive instants at which lifecycle events fall, the state is constant. From it:

5.4 Outcome

6. Quality-of-experience observations

6.1 Observations and their logs

A client of the room reports its experience with the observations of delivery-telemetry §2, unchanged: start, present, stall-start, stall-end, end and the rest, one session’s in sn order (§7.1 there). Their Event-mode reports are CMCD version 2 (CTA-5004-B), as delivery-telemetry §3 maps them. A session log is one session’s observations; the logs document is a JSON array of logs, each a non-empty JSON array of observations, as UTF-8 bytes; it reads when every observation reads (delivery-telemetry §2.4) and every log is a log (§7.1 there), and is refused qoe_invalid otherwise. The receipt names the document by its SHA-256 and states the number of logs; the cell (§4.7) is computed from their session metrics (delivery-telemetry §7.2).

6.2 On the CMCD event timeline

Over MoQ, a client publishes its reports on a CMCD track of draft-wilaw-moq-cmcd-event-timeline-00: an MSF event timeline (MSF §8) whose catalog entry is in the catalog’s publishTracks, with packaging eventtimeline, eventType urn:cta:cmcd:2026 and a cmcdConfig (that draft’s §4.2, §4.3). The room’s catalog names the track; its namespace’s first field is the room id, so it is a track of the room, counted among the other tracks (§3.2). Each record of the timeline is one Event-mode report:

The draft’s own example writes Integers as JSON numbers; its normative text says that every value is a string, and this revision follows the text. The reference writes the record with live_metering::cmcd_timeline_record.

6.3 Over HTTP: application/cmcd

Over HTTP, a client sends its Event-mode reports to the room’s service as CTA-5004-B’s batch: an HTTP POST whose body has the media type application/cmcd, one report a line, the lines separated by one line feed and the last not followed by one, each line a report in the key=value syntax with its keys in alphabetical order and no URL encoding. A report of delivery-telemetry §3 is such a line already: an RFC 9651 Dictionary with its keys in bytewise order, with no optional white space, every Event-mode report carrying e, ts and v. CTA-5004-B cites RFC 8941, which RFC 9651 obsoletes; the reports use no type RFC 9651 added (Date, Display String), so a line is the same bytes under either. The reference writes a batch with live_metering::cmcd_batch_body.

6.4 Privacy

7. C2PA

A room-session receipt maps onto C2PA 2.4’s c2pa.environmental-sustainability assertion (§18.26) in a manifest of an asset of the session, such as its recording (live-recording) or its composite, under energy-binding’s rules:

The reference implements this in c2pa_emit (feature c2pa_emit with live_metering): SessionManifest writes the assertions into a real signed C2PA manifest of a given asset, with the session’s recording or the receipt as its parent ingredient and the action c2pa.converted, every assertion created; recover_session reads them back, applying the reader’s rules above, and RecoveredSession::describes checks them against the receipt.

8. The reference relay

joule-moq-relay keeps a signed session summary of every MOQT session it serves, and signs a per-hop record of a room from those summaries (metering::hop_record): for each track of the room its sessions carried, the objects and bytes in and out, the drops by reason with the receipts forwarded, its subscriber sessions’ time by layer class from the subscriptions each held, the events it delivered, and a ledger from the room grants its tokens map to. It signs version 2 when it keeps a DVR store, with that store’s figures (§3.9), and version 1 otherwise. Its own energy is the unmetered marker, since no meter backs it. It connects to no other hop, so every session it serves that subscribes is a subscriber session. Its record recomputes from its summaries, which its own test checks, and the corpus holds a record it signed in that test (§10).

9. Reasons

The codes this extension adds, each with where it is raised:

reasonraised at
session_invalid§4.2, §5.1 step 2
signer_not_authorised§4.3, §5.1 step 3
signer_revoked§4.3, §5.1 step 3
record_missing§5.1 steps 4 to 7
record_too_large§3.7, §5.1 step 4
energy_invalid§3.6, §4.2
record_invalid§3.6
hop_not_authorised§5.2
hop_revoked§5.2
span_outside_session§5.1 steps 4 and 6
track_unknown§5.1 step 4
ledger_invalid§3.6
ledger_mismatch§5.1 step 4
unit_mismatch§4.4, §4.5, §4.7, §5.1
classes_summed§4.2, §4.5
ceiling_mismatch§4.6, §5.1 step 11
qoe_invalid§6.1
qoe_not_suppressed§4.7

A verifier of this extension also raises truncated, malformed and unknown_version for a record or a receipt that does not read; bad_key and bad_signature for their signatures; wrong_room for one of another room; every code a room verifier raises (live-rooms §11 and the live-events codes it lists), at room; lifecycle_incomplete (live-compose §10), at room; capture-binding §12’s codes for a take, at take; and live-compose §10’s for a composition receipt, at compose.

10. Conformance

live-conformance/metering/ holds the corpus, in one file, vectors.json, written from fixed seeds by wai_live_metering_vectors gen, and the record the reference relay signed in its test, relay-record.wmh with its ledger relay-ledger.wml, which the generator reads. It has rooms, and vectors, each a room, a receipt and what it names, with the outcome §5.4 gives them:

A conforming verifier gives every vector the outcome, the reason, the place and the facts the corpus states. wai_live_metering_vectors verify and the test tests/live_metering.rs check that the file regenerates byte for byte, that wai-rs gives every vector its outcome, that every reason of §9 refuses a vector, and that §9 lists exactly the reference’s codes. live-conformance/live_metering_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), verifies each room, take and composition receipt with the corpora’s own second implementations (live_rooms_verify.py, capture_take_verify.py, live_compose_verify.py), recomputes every unit, every total and every grant’s consumption itself, gives every vector the same outcome, and requires every mutated copy of the corpus to fail.

11. Security and privacy considerations