Repository navigation
Design: an object-level IETF API (subgroup, object ID, properties, status) and a relay mode that forwards them unchanged #4926
Description
Measured at 375453856f18a84776a9d05bca4c6d6a2adfafdf, draft-20.
What I'm asking for
A way for an application to publish and receive moq-transport objects with their wire identity intact — subgroup ID, object ID, object properties, object status — and a relay mode that forwards those unchanged.
This is a design request, not a bug report. The current behaviour looks deliberate and is pinned by your own tests; I'd like to understand whether an object-level path is in scope, and if so what shape you'd want it to take.
Where the limit is
Not in the IETF codec — in the shared model both the codec and the relay are built on:
// rs/moq-net/src/model/frame.rs
pub struct Frame {
pub timestamp: Timestamp,
pub payload: Bytes,
}
// rs/moq-net/src/model/group.rs
pub struct Info {
pub sequence: u64,
}js/net/src/group.ts:23 is the same shape. Object identity is position in the group; group identity is a u64. So the IETF path necessarily narrows to that, and does, consistently:
rs/moq-net/src/ietf/subscriber.rs:2605— subgroup != 0:warn!+Err(Unsupported), stream droppedrs/moq-net/src/ietf/subscriber.rs:3299,:3752— same for fills and group fetchesrs/moq-net/src/ietf/subscriber.rs:6814next_object_id— any gap isErr(Unsupported)rs/moq-net/src/ietf/group.rs:84decode_object_time—_ => {}discards every property id but Timestamp (0x10) and Timescale (0x08)js/net/src/ietf/subscriber.ts:1038—throw new Error("subgroups are not supported")js/net/src/ietf/object.tsFrame.decode—if (delta !== 0) throw
The comment on GroupFlags::has_subgroup_object (rs/moq-net/src/ietf/group.rs:158) states the position directly: "Since we don't support subgroups or object ID > 0, this is trivial to support." And ietf::subscriber::stitch_tests::a_non_zero_subgroup_leaves_the_track_flowing pins the drop as intended. So I'm not reporting these as oversights.
Two observations that may be worth separating out
1. The publisher has no way to emit what the subscriber refuses. Every emit site writes the literal sub_group_id: 0 (rs/moq-net/src/ietf/publisher.rs:2510, 2551, 3003, 3179, 3222, 3270, 4908) and both fetch sites write FetchSubgroup::Zero (:1238, :1246). Worth noting in case a future object-level API is approached from the subscriber side first — the egress leg would still have nothing to write.
2. The non-contiguous-object-ID drop is quieter than the subgroup one, and this one may be a genuine bug. A gapped group makes recv_group return Ok(()): the error from next_object_id reaches
Err(err) => {
tracing::debug!(%err, group = %producer.sequence, "group error");
let _ = producer.abort(err);
}and then falls through to Ok(()). The subgroup case is warn! + an Err the caller sees; this one is debug! and the caller is told the stream was handled. Measured: 3 objects in, 0 out, Ok(()), session open. A relay in this state forwards a truncated track with no error anywhere above debug. If an object-level API is out of scope, raising this one to warn! — or returning the refusal — would still be valuable on its own.
Measurements, each against its own control (same objects, subgroup 0 / contiguous IDs):
[subgroup] sent=3 received(subgroup=0)=3 received(subgroup=1)=0 verdict=Err(Unsupported)
[object-ids] sent=3 received(ids=[0,1,2])=3 received(ids=[0,2,5])=0 verdict=Ok(())
[properties] sent=2 received=2 properties_sent/object=2 properties_received/object={Timestamp}
Why we'd use it
We carry MPEG MMTP over MoQ, including AL-FEC repair. The mapping needs all four: a repair flow is a separate subgroup from its source flow, repair objects are deliberately non-contiguous in object ID against the source numbering, and MMTP per-object metadata rides in object properties. Today a moq-relay between our publisher and our receiver silently reduces that to a timestamped byte stream.
I'd be glad to hear that this is out of scope and that an MMTP mapping belongs outside moq-net's model — that's a useful answer and we'll plan around it. What would help most is knowing which of these you'd consider:
- an opaque per-object properties blob carried through the model unmodified, leaving subgroup/object ID alone;
- object identity (subgroup + object ID) in the model, with groups allowed to be multi-stream;
- a relay-only forward-unchanged mode that never materialises the model at all;
- none of the above — the model is the product, and this belongs in a separate stack.
Happy to do the work on whichever shape you'd accept.
Activity
kixelated commentedon Oct 7, 2026
Heh sorry, lots of bagagge to unpack here. Years of trauma from IETF debates.
I don't really agree with everything in the moq-transport draft although I'm semi-open to some things if there's a good use-case.
IMO each sub-group could be a separate track, aligned by the application. It's more work for this use case, but it does mean you can have the subscriber choose/prioritize the repair tracks. Same ideal with SVC; each layer should be a track (not a sub-group) so you can choose/prioritize the enhancement layers.
Another reason I don't support sub-groups, or filters, or gaps in object IDs because they create a fragmented cache. If the relay only gets object 0,5,10... it might not know if 1-4 and 6-9 exist. In the worst case scenario we get a FETCH request and now have to query upstream for every hole in the cache. It's a general problem with moq-transport (sparse group IDs have a similar issue).
If you do want non-contiguous object IDs with moq-lite... your application can encode the object ID delta at the start of each payload. It's the exact same result as moq-transport sans the previous object ambiguity. It doesn't waste any bytes and the relay doesn't actually need to know the Object ID.
I don't think object properties should be used as an opaque K/V storage; it's just the wrong layering. The relay can't use any of this information and yet these object properties are plain-text with a crude encoding. If you want structured data, use protobuf or json or something with a schema instead.
Maaaaaaaybe sub-groups could make sense to avoid a race conditions where base and enhancement layer subscriptions start at different group IDs, but IDK it feels niche. I just don't think it's worth introducing another layer into the moq-net API, because now every subscriber has to be prepared to potentially receive sub-groups.
That might be worth doing in general. A full moq-transport relay is a non-goal though, because there's so many extremely niche/difficult features stuck in the draft (ex. multi-publisher). I think a dedicated relay mode could make sense for performance reasons, but maybe what you're actually asking for is full moq-transport parity, which would be a nightmare (nobody is close to it).
I'm mostly trying to focus on the good bits of moq-transport. Unfortunately every niche/questionable feature like that adds up, both in terms of the API surface and maintenance. I have to draw the line somewhere and I'm on the more minimal side of the spectrum. I don't think anybody will support the entire moq-transport specification because there be dragons.
quest: import #4951, #4950, #4945, #4847; decline #4926 (#4952)
test(relay): commit the arm-B MMTP→MoQ harness in-repo (BLO-41723) (#4)