Fixes URL
nit: can you wrap the lines at 80 chars?
In the call last Wednesday, we agreed this design was the correct approach, so merging even though there are some reviews outstanding requesting changes.
Consider an edge relay, receiving and forwarding relative SUBSCRIBE requests for -5, -3, and -1 for the same track. The first object it receives back is group 37. It cannot tell where to place this object in the sequence and must therefore hold it until it knows more information. If the next object is Group 39, it still cannot forward it, because the head sate is undefined. We can solve this problem simply by having the SUBSCRIBEOK response indicate the latest group and object number: SUBSCRIBEOK { Track Namespace (b), Track Name (b), Track Alias (i), Expires (i), LastestGroup(i), LastestObject(i) } One consequence of adding these fields is that an edge, which has no active subscription for the track, cannot send the SUBSCRIBEOK response until it itself receives a SUBSCRIBEOK after forwarding the SUBSCRIBE. This makes subscription an async process in which the acknowledgment of the subscription may take in some cases a RTT back to the origin. I think this is necessary for stable state management.
+1 this is required to build a relay and a non-realtime player. This was required anyway, otherwise the relay would swallow any application SUBSCRIBEERROR codes. My mental model is that SUBSCRIBEOK == HTTP response headers.
Moqt is a pub/sub protocol and is hop-by-hop protocol. A subscribe ok means the susbcribe request was validated at the relay. A OK isn't tied to getting a OK from the original publisher. Its sub/sub-ok is a async operation ( like any typical pub/sub systems out there). An ok for subscribe means a promise that if there is a matching data ( either in cache or coming in from a live publisher at some point), it will be forwarded .
Clarifying question , if the use-case is something like VOD or non real-time flows, I think the same can be done by
I absolutely don't want this behavior. It means that a generic relay will always return SUBSCRIBEOK, even if it cannot route (or authenticate) a SUBSCRIBE. A typo or finished broadcast would be indistinguishable from a valid request unless OBJECTs start to flow. SUBSCRIBEOK would be equivalent to HTTP 100 CONTINUE, which is truly useless, and there would be no equivalent to 200 OK or 404 Not Found. Pub/sub doesn't mean black hole simulator either. I've only used RabbitMQ a long time ago, but if you tried to bind a queue (ie. SUBSCRIBE) to an exchange (ie. ANNOUNCE) it will return an error if the exchange doesn't exist. Is the justification that pub/sub protocols are supposed to black hole, or is this behavior that you actually want? Instead of hanging indefinitely if a relay can't route a SUBSCRIBE, wouldn't it just be better to receive a 404 Not Found? You're allowed to try again, but now there's no timers or guess work as to if the SUBSCRIBE could be routed to an origin.
It's for higher latency targets. HLS/DASH clients don't start at the latest segment, but rather start further back based on the target latency. If I understand correctly, you're saying the relay: has an empty cache. receives a and . issues issues a live subscription upstream: waits for the first OBJECT to arrive for that subscribe, using it as the live playhead: issues a backfill subscribe upstream: forwards any objects based on the mapping from relative to absolute. It works for relays, but at the cost of an extra round-trip to origin. However, it won't work for clients that want to start playback N groups back (jitter buffer size), because the player doesn't know when/what to render without first receiving N distinct groups. Here's what we would like: has an empty cache. receives a and . (re)issues a subscription upstream: waits for forwards any objects based on the mapping from relative to absolute.
Individual Comment: I think SUBSCRIBEOK needs to resolve all relative Subscribe Hints that were passed in the Subscribe. Otherwise, I'm really not sure how my library will know when an Object arrives if it belongs to a particular subscription or not -- the OBJECTs that arrive have only track name and absolute group/object numbers. I see the use case for some relays wanting to optimistically SUBSCRIBEOK, but then there would need to be a follow up message that resolves the relative hints when the relay finds out where they map.
Couple of observations what use-cases would involve same client sending multiple subscription hints at the same time ? Hints are read access points on the same track and if a client ask for multiple such points, it will get objects on the same track regardless .. how relays puts them together in such a case for a given client cannot be normatively specified NAME can you give me a concrete use-case example of "resolve all relative Subscribe Hints that were passed in the Subscribe" from a single client so that it helps understand the issue better. I personally don't think the issue being discussed is directly related to this ...
The use case of an edge relay forwarding simultaneous requests from multiple connected clients. This is a common use-case for any edge or fan-out relay.
Individual Comment: This is definitely one use case. Another is a client making two simultaneous subscriptions: eg live edge and range request starting some time in the past.
One thing that will not scale is every subscription needing to go back to the original publisher. Consider the cases of a million users are joining a session that starts at the top of the hour and that is published from a mobile device. I think that in any real system there is also going to be no single idea of the live edge. The live edge is relative to which relay you subscribed to. If the relay you are on is over a geo sync satellite, it is going to be behind ones that are not. So here is what I think we should do. We should not allow relative subscription to things in the past. I think the uses cases I have heard for this can be deal with as subscribe to live edge forward and as once the client find out what object that is, do an absolute subscribe for any data in the past to fill in buffers etc. So to be clear, no relative group or object sequences that are negative but still allows absolute. This seems to meet the use cases and remove a ton of edge case design problems. To be clear, I think we should be able to subscribe to the current group, but give me object 0 forward in that group. If it is a group per GOP, this allows one to get everything needed to decode the current frame.
But not every subscription has to go back to publisher. It only has to go back to a node that is already subscribed and understands the live edge. Furthermore, concurrent requests would be coalesced at each node. Imagine in the worst case that 1 million requests are made within 100ms to a (really powerful) edge. That edge would only forward one request to the origin. All the rest would be held pending that response. This is exactly what happens when a few million people ask for a live HAS stream. Due to fan out and request coalescing, the CDN only sends a few requests back to the actual origin.
Yeah exactly. Only the first SUBSCRIBE will propagate upstream and the SUBSCRIBEOK is used to bootstrap the live edge. The relay can infer the live edge while the single upstream subscribe remains active. Because of the SUBSCRIBEOK, the relay knows that the received OBJECT only gets forwarded to the first downstream subscribe. Otherwise it would have to guess the live edge. The relay keeps track of while the upstream subscription remains active. The initial value is set by but all future updates are based on received messages. This way is can map from relative->absolute and reply with without issuing a new upstream subscription.
For the original use-case reported on this issue, if there are multiple subscribes at a relay with varying relative offsets, the relay has no choice but to send each combination of subscribe and a given -ve relative offsite upstream since that identifies each unique data point in the track. This has couple of issues it doesn't scale across relays and also when there are very high rate of subscriptions across relays and clients it makes the lookup and state management complicated latencies across relays and client locations ends up producing varying results for the same query since it depends on when the query was made and how long would it take to for conferencing cases when there are multiple publishers who can be active (have live edge), unjoined yet ( so no live edge) , the expectation for subscribes to get a OK back from original publishers will not scale in terms of latency of joins The more i think of it, subscribing back into paste using a relative offsets and expecting the answer to be vetted by the original publisher "unnecessarily complicates the protocol".
The group sequence numbers are consistent between subscriptions . If a relay receives relative subscribes of -5,-3 and -1, all it has to is send a single subscribe upstream of -5. The response would give it the live edge group as well as all the content for the various subscriptions. If the subscriptions were -345, -2, -1, the relay could decide to make two subscriptions upstream - one for -345, the other for -2, because it didn't want to force the smaller relative subscriptions to wait for the larger one. The point is, this is optimization logic for the relay. It does have a choice in what subscriptions it sends upstream based on the timing and diversity of the input subscriptions. Why is the latency of the join affected? Consider a subscriber talking to an edge where the subscriber is 100ms away from the origin. We consider two scenarios: in the first case the subscriber receives a SUBSCRIBEOK immediately after subscribing but then waits 100ms for the audio and video to arrive from the origin. In the second case the the subscriber waits 100ms to receive SUBSCRIBEOK but it is immediately followed by the audio and video. So in both cases the subscriber gets its audio and video after 100ms. The media latency is consistent under either scenario.
If the relay logc is to send only a single subscribe upstream, then you don't need anything to be said in Subscribe OK either. Since when the first object arrives it can compute the live edge by adding 5 to the object being reported and the compute the absolute values for others (in this case for -3 and -1). That would definitely make things simpler for sure.
For cases where a client wants to go back 345 groups in past, i will be very much inclined to do an absolute request after learning the latest known state of the group as an end application OR something catalog can describe more information to make the right absolute request on the first go
It can't do this because the order of items it receives as Streams is not guaranteed. The origin may send the groups in order -5,-4,-3,-3,-1, but they could be received at the relay (or any hop) as -3, -5,-4,-1,-2. That's why you need an explicit notification from the origin as to where the live edge is. Only that plus the group sequence numbers allow the correct sequencing, not the order of arrival.
In this case there was just a single subscribe request with -5 was sent upstream, based on your explanation above. This avoids all the confusion which would happen when there are multiple inflight -ve offsets.
This seems like it will have many race conditions. It seems like it would be better to transfer this in the STREAMHEADERTRACK and equivalent. Thoughts on that ? I also worry about how long a subscribe would wait to find out this information - that seems like it could be problematic for the client knowing when to give up on a subscribe. Hopefully we can discuss in Denver


Update main to reflect the tx-mode branch after was merged.
Agreed, but I'm not sure there's any normative language there. Is this a case of writing a paragraph cautioning that such limits could cause issues?
Whoa, I've missed some PRs, but LGTMIndividual Review: There was some feedback on previous iterations about this PR lacking guidance when encountering flow control limits (eg: send as datagram, relay converts to stream, but is out of streams or flow control)


This PR cleans up some nits to make all the places we use the RFC9000 encoding syntax valid, updates our extensions to it, and add a non normative summary of parts of the syntax we use. I don't think this is making any changes to anything on the wire or normative changes to spec other than the statement SHOULD use smallest number of bytes for variable length integers which RFC9000 only requires for some fields.
I am not in love with the ABNF idea, but this is non-controversial.


This PR
Track namespace and track name properties were removed from SUBSCRIBEOK message in draft 02. But they are still part of the text under "6.5. SUBSCRIBEOK" which is confusing.


Regardless of where they're retrieved from and over time.
I put a two very small nit comments but don't care either way. They do not change the substance of the PR and +1 on the overall thing this is saying.
This came as part of interim at Denver when discussing multiple announce for same track. There needs to text that mandates, if 2 objects comes with same name, they need to be bit identical. Also needs to clarify on how long the name needs to be unique


They won't be wasted until we have 64 message types valid on unidirectional streams. We currently only have 3 valid messages types. I think you mean that we could save the message type byte if OBJECT is the only message allowed on unidirectional streams, but that's not forward-compatible.
Draft-02 strongly implies the forwarding preference is a per-object decision. thus the following sequences would be legal: [sequences are written as (group, object)] Stream 2: (0, 1), (0, 2) (0, 3) (0, 5) -- type is StreamHeaderGroup Stream 6: (0, 4) -- type is ObjectStream or Stream 2: (0, 0), (1, 0), (2, 0), (3, 0) -- type is StreamHeaderTrack Stream 6: (1,1) -- type is Object Stream etc. If someone can imagine a use case for this, that's great. If not, it would be easier to implement with different assumptions, e.g. (1) Forwarding preference is a track-level property. Thus the preference could be provided in the SUBSCRIBE_OK and we wouldn't evens strictly need four different object codepoints (though you would want the distinct formats, and might want to retain the codepoints for that reason) (2) Forwarding preference is group-level -- some groups could be aggregated under StreamHeaderTrack, but others could be under StreamHeaderGroup. If there is a StreamHeaderGroup for a group, there MUST NOT be object-level streams for that group. Again, I have little sense of the use cases out there, but I think we should explicitly decide what we are doing and make it clear in the doc.
A related question is whether forwarding preference applies across subscribes. That is, for per-group streams, a current subscriber gets all of group 12 and then the stream closes. a later subscribe in the same session asks for group 12. Obviously, this will need a new stream. So maybe the answer is no, it does not apply across subscribes
I agree, this is very difficult for a decoder to support. The target use-case is non-reference frames. In your first example, object 4 is optional so it gets its own stream. However the receiver doesn't know that, instead it just sees a gap between 3 and 5. Does the receiver try to decode and catch any errors? How long does it wait before trying? If I want minimal latency, do I have to partially decode (on a per codec basis) to get the frame dependency graph? I really don't like this option. I instead want an explicit way to know that this object is a non-reference frame. Ideas: Make a separate track for non-reference frames and SVC layers. Receive a formula that every 3x+1 object is optional? Receive a live object catalog containing metadata about each object. My vote is a separate track and each track has a uniform forwarding preference.
I like the idea of uniform forwrarding preference for a track. That's what I had in mind when we landed that PR. I can't think of any use cases this restriction prevents.
I think the consensus in Denver was to have one preference per track
just to be clear , the preference is set as track level property and can have 4 values - track, object, group and datagram.
Yes, although for various reasons the PR I'm doing is going to make it subscription-level property. I think is hard to write it otherwise, and I can just about see a use case for it.
When a few of us discussed it last week, the feeling was that if it came back in the SUBSCRIBEOK, and Objects started arriving before the SUBSCRIBEOK, the receiver wouldn't know how to process the incoming Objects. In the past, we've decided we don't like that, so the feeling was to land a PR to add the restriction now, and then we can talk more about wire format changes. NAME Is Object decoding not an issue for some reason I'm forgetting? Or were you thinking that buffering Objects was fine?
I am arguing that buffering objects is fine. The usual case, I think, is that SUBSCRIBE_OK arrives first. It's irritating we're using a byte in the object encoding (and creating new error conditions to check for) just to avoid buffering. But I can live with keeping it for now.
This is an improvement, though I would still like to get rid of all the wasted bits


QUIC uses RESET, we should too.
Teeechnically, QUIC uses STOP for receiver-initiated closes. But RESET is better than RST.


Since they're on different streams, an object can arrive before its SUBSCRIBEOK. This is easy to process because the Subscribe ID and Track Alias must have been the ones that the subscriber chose. What if the publisher then sends a SUBSCRIBEERROR? Is this valid behavior or an error? (If it weren't for the Expires field, SUBSCRIBE_OK would be entirely redundant and we could get rid of it.)
Individual Comment: This is covered a bit in and Luke expressed a desire to document the full state machine in . My read is that you can't send objects before SUBSCRIBE OK (but they can arrive out of order). I don't think you can send SUBSCRIBEERROR after SUBSCRIBEOK (objects or no objects), instead, use SUBSCRIBE_RST?
I agree that SUBSCRIBEERROR cannot follow SUBSCRIBEOK. I think you're saying that if Objects are in flight, then SUBSCRIBEOK must be as well, so a SUBSCRIBEERROR would in itself be an error. That sounds reasonable to me.
We're clear that only one of SUBSCRIBEOK or SUBSCRIBEERROR can be sent: the subscription, via SUBSCRIBEOK ({{message-subscribe-ok}}) or the SUBSCRIBEERROR {{message-subscribe-error}} control message. The entity receiving the SUBSCRIBE MUST send only a single response to a given SUBSCRIBE of either SUBSCRIBEOK or SUBSCRIBEERROR. I wrote to clarify that you can't send objects and a SUBSCRIBE_ERROR.


by adding an explicit bool indicating whether any Objects were published for a subscription, similar to the explicit bool added to SUBSCRIBE_OK in .
and define: However, what happens if no objects were ever produced/sent?
Individual Comment: Oops. Two possible ways to handle this: 1) Flag indicating presence of these fields (Use Location now that is defined elsewhere?) 2) Change the semantics of the fields to be exclusive (eg: it is the first object NOT sent), so 0/0 means none.
I'm inclined towards 0/0, because optional fields are more complex to parse and I would expect the existing logic to work correctly with 0/0.
Let's say the last object delivered by the relay is . Does the relay send RESET or ? Is the subscriber able to infer the presence of future objects? What if either of those objects actually exist in the cache?
Individual Comment: If we use exclusive encoding then I'd say that the receiver shouldn't infer the existence of non-existence of the named object, and either 5/30 or 6/0 works, but 5/30 is preferable?
If you define there will be no objects greater than or equal to these, then 0/0 makes total sense. So 0/0 for null case and say the last group ID of the last object publish was 10 then the value in SUB_FIN would be 11.
In we went with an explicit flag, so I'm inclined to do the same here for consistency.


To allow subscribers to indicate that no new subscriptions will be routed to the publisher for a given namespace. Happy to bikeshed on the name and whether we really need another frame for this.
The problem is not possible for the subscriber to reject an ANNOUNCE afterwards. This would be very useful, for example to inform a publisher that no more subscriptions will be routed to them for whatever reason. Examples: The auth parameter in ANNOUNCE has expired. Another publisher has sent an overriding ANNOUNCE (reconnect?) The broadcast has been closed remotely, ex. some moderation tool. Without this message, the only recourse for the subscriber is to close the connection. This won't work if multiple ANNOUNCE messages are pooled over the same connection, nor is it clean.
I propose two options: can be sent after . We add and to mirror . I personally hate 3 separate messages for a subscriber to close an announce, or a publisher to close a subscription. See
very trivial bikeshed comment ... I would prefer to call it something other RESET ( and also other places we use RESET ). I get the TCP analogy but I think that is the wrong way to think about this. I would prefer something like REVOKE.
how about ANNOUNCE_CANCEL to cancel an open announce ?
Conclusion is that we do need to add a new frame to end an ANNOUNCE. Additionally, there was a principle stated that we should not rely on closing the session as the typical or optimal way to close state and close either an ANNOUNCE or SUBSCRIBE.
On the call today, people tend to prefer ANNOUNCECLOSE over ANNOUNCECANCEL as a name. They also prefer merging SUBSCRIBERESET and SUBSCRIBEFIN into a single message, possibly SUBSCRIBE_CLOSE.
On an editorial note, it'd be nice to have subsections for publisher/subscriber to make it clearer to the reader.
If we're going to combine and , then we should just combine and now. My desire is to have four messages: publisher create: ANNOUNCE publisher close: UNANNOUNCE subscriber accept: ANNOUNCEOK subscriber close: ANNOUNCESTOP The same goes for subscriptions: subscriber create: SUBSCRIBE subscriber close: UNSUBSCRIBE publisher accept: SUBSCRIBEOK publisher close: SUBSCRIBESTOP


PR updated, PTAL
This came out of discussion on When a relay receives an SUBSCRIBE it may need to issue its own SUBSCRIBE to a publisher that has previously ANNOUNCE'd a namespace matching that track. The current draft doesn't specify how this match is done. A relay could implement an exact match on Track Namespace (requires a tweak to SUBSCRIBE), or could do a longest prefix match on track name vs all ANNOUNCE'd namespaces, or possibly something else. Is it ok to leave this up to implementations or should the draft specify this behavior more exactly? If more than one matching type is allowed, does it need to be negotiated, and by whom (publisher, subscriber, or both)?
An issue that came up in interop that is somewhat related - NAME relay was implemented to take the namespace from the connect URL, because in his use-case, ANNOUNCE can be made redundant with the connect URL (eg it contains a namespace and enough auth). However, this caused an interop problem because my chat application assumes a certain functionality from relays, and that includes supporting ANNOUNCE messages to determine routing to publishers. Do we need to be more prescriptive about relay behavior?
ANNOUNCE is what my implementation expects as well. A producer can announce track namespaces at any point (after being connected). Overloading connect URL with track info is limiting
This is going to be a slightly long comment but this is how I think this should work. The catalog has two fields for the track namespace , and track name. We also specify in the catalog a way to form a URL from these two fields. See Teds proposal and that is catalog specific. The things we put in these track namespaces and track name fields are already canonicalized in a way defined by the catalog and URL. When the application using the moq transport puts these two fields into message, they are treated as just a bunch of bits and any comparison of them is done with bitwise compare. A relay that needs to check if a namespace matches another does not need to worry about string prep or escape encoding of the bits, the will all have been done when the catalog was formed. This allows the relays to work faster by just doing bitwise compare yet at the same time allows us to use URI at the application level. The transport draft just needs to deal with a bag of bits. The catalog draft pushes the work to the URL definition, and the URL definition does the heavy lifting but that is where this type of stuff should be
NAME Just to clarify that when you say "bitwise compare" you also mean exact matching the entire namespace field from announce to the one in subscribe?
I would like to decouple the media layer from the transport layer. A tool like ffmpeg should be able to generate a MoQ catalog without knowing the namespace it will be served from, much like a tool like zip should be able to generate a file without knowing the path it will be stored. HLS/DASH provides this functionality with absolute or relative paths. If the playlist contains relative paths, then it can be served from any HTTP host/path provided the segments are served from the same host/path. If you want to decouple the two (ex. application serves playlist, CDN serves segments), then you use absolute URLs. To accomplish the same thing, the namespace should be optional in a MoQ catalog. This would mean media tracks are served from the same namespace as the catalog: whatever that might be.
To expand further, I think that MoQ broadcasts should be namespace agnostic: a broadcast ingested with namespace XXX is NOT required to be delivered as XXX. I understand where Cullen is coming from: an application like WebEx can use the same namespace for contribution/distribution because it controls the full pipeline. The application has all of the information required for all future routing/identification so it can set it on the first hop and expect any relays to blindly proxy. However, for a more generic or distributed system, this is not true. A client like OBS knows nothing about how the broadcast will eventually be distributed, fanned out, or the requirements of potentially multiple CDNs that might be used, nor will those requirements be the same between vendors (present or future). So I don't think we should require that input namespace == output namespace. In the HTTP world, this would be like requiring that all HTTP paths match the origin. It's quite restrictive and just means a CDN needs find another way to perform routing via a custom header/parameter. I propose we emulate the HTTP model where there's non in-band notification that a path/namespace was modified. An origin might serve and a CDN might decide to serve it as . Any namespace remapping from XXX -> YYY is business logic and not signaled over the wire. If you don't want any remapping, then that's a business agreement between you and the CDN, wherein you may need to encode namespaces in a vendor-specific way.
I think we have agreement on that from past discussions. Can the catalog draft have a mechanism to inherit the namespace for tracks from somewhere else, like a relative URL in an HTML page? I'm not sure this is relevant for how relays perform matching between SUBSCRIBE and ANNOUNCE though. The only available information in announce is the namespace.
Yeah I'm probably rambling about something unrelated. But in order to support namespace remapping, the downstream ANNOUCE/SUBSCRIBE won't necessarily match the upstream ANNOUCE/SUBSCRIBE.
This is a useful property. I opened to address this issue and PR URL to fix it.
So if the input full track name does not equal output full track name ( for some version of matching the two ), what is it that tells the relays there is mapping between the two. I'm not objecting to some other way to do this, I just don't understand what it is that tells the relays to do that. Keep in mind relays do not need to read the catalog.
To clarify the "bitwise compare" ... yes I mean exact matching the entire namespace field from announce to the one in subscribe? If the namespace was utf-8 or even a url restricted utf-8, that would match. I am just aruging that a relay should not have to verify every string is a valid utf-8 string, it should just match the bits.
HTTP proxies will not modify the path by default. A downstream request for arrives, there's some rules to match it to a backend, and then an upstream request for is sent. A simple MoQ relay would work the same way. However, you can configure a HTTP proxy to perform special behavior, including rewriting the path. Note that a the HTTP server does rewrite the HLS/DASH playlist provided they are using relative URLs since both the playlist and segment would match the same prefix rules. For example, here's a simple nginx configuration: I would allow the same exact thing for MoQ, but of course use our terminology instead. I think a key improvement with MoQ over HTTP is actually the namespace/name tuple. I propose: A relay SHOULD NOT change the track name. That means a producer can make a track called "video_480p" and reference it in a catalog, inheriting the current namespace. A relay MAY change the track namespace when negotiated out-of-band. It's RECOMMENDED that an application support being served from arbitrary namespaces, including using relative namespaces when tracks reference each other. Basically, the track name is controlled by the application (encoder/decoder) while the track namespace is controlled by the relay (hop-by-hop). This makes a ton of sense if the namespace is primarily meant for routing (critical for relays), and it allows relays to do stuff like encode the route or a unique ID into the namespace itself.
Going back to the original issue Alan posted (if I understand it correctly), I think we need to support out-of-band negotiation given the nature of fanout. Maybe we can provide something in-band but it won't always be possible. For example, an OBS client sends: In theory, the server could reply with the information required to watch the broadcast (final hop), including any remapping along the way: But this gets really messy when you factor in auth, multiple CDNs, multiple edge hosts, and just the nature of ingest being on the other side of the pipe from distribution. In this example, I think the application has to negotiate with Akamai on how they need to ingest and how they need to egress. I'm not opposed to using extensions to make this mapping explicit though. It would be cool to have a scheme that defines routing/authentication using globally unique namespace, kind of like a decentralized CDN. But it shouldn't be required as part of the base protocol and there's a LOT of sharp edges to figure out.
Individual Comment: My opinion is that anything that does this kind of rewriting is out-of-band, at least for now? For a completely generic relay input=output.
Yes. I opened issue URL to address this and PR URL to fix it. I really like the flexibility that relative names bring. We know this works at scale for current web and media delivery across disparate systems. I'd hope that relative track names are the normal mode of operation. We should allow absolute namespaces in catalogs to allow mixing between sources, however I think this will be an edge case. By default, a relay SHOULD NOT modify the track namespace or track name. However, what a distribution network does within the bounds of its own network is up to it. As it restores the original namespace and name at its boundaries (to match the catalog), then external systems cannot tell and distribution intent is preserved. Additionally, there will be business logic and distribution agreements between CDNs, coordinated by the content distributor, to rewrite namespaces between networks. Such namespace rewriting is common between networks and should not be prohibited. Here's how I envisage a scalable solution working. When a twitch user wants to use OBS to publish, they would log in to URL and authenticate against the Twitch CMS. It would vend them two URLS: a connect and publish URL a playback URL to share with their followers The OBS client would use this to The client then publishes twitch/kixelated/catalog, a track which describes two relative tracks - audio and video. The playback client connects to URL and subscribes to premiergaming/gamer34/catalog. After parsing the catalog it subscribes to The Fastly relays have been configured by Twitch, out-of--band, so that when they go forward to origin (which is Akamai), they do a namespace rewrite to The subscriptions make their way back to the OBS instance. The encoder prepare Moqt Objects with in their headers. As these are received by Fastly, business logic rewrites them to , caches them as such and they are delivered to the client.
As per the discussions in person, it is more nuanced than renaming few bits in the header. It might break e2e encryption /auth for example or reencryption in some cases. As this is very application specific, one needs to be take into consideration the costs as it may not be just rewrite few bits in and out of network edges. The more appropriate or clearer framing may be - whenever the name changes, it is ,in a way, republishing the content with a new name ..
just to note, the network wouldn't have access to the catalog
Conclusion is that we'll use exact matching like specified in
Thanks for the update I'm not sure how to wordsmith this but +1 to what it is saying.


Add some normative text and requires both client and server to send their role. I can also see the argument for removing this entirely, but it's in the current draft, so if we're going to keep it, I'd like to define it better.
Having a flag in SETUP doesn't stop from someone sending unsupported messages. A faulty implementation or intentionally malicious endpoint can send unsupported messages. A receiver should be prepared to handle unsupported messages regardless.
Right, an implementation is still going to crash if it receives an unsupported message, but there's a difference between signaling that upfront and yolo. A one-sided implementation will look something like this: The purpose of the ROLE parameter is to avoid accidentally triggering that default, as it will close the connection. For example, I absolutely want to have a way to get per-viewer feedback: a track produced by each viewer. Without this ROLE field, a viewer might cause a crash if it sends an to the server, or the server might cause a crash if it sends a to the viewer. It will only work if both endpoints explicitly flag . The only alternative to the ROLE field is to implement the entire protocol, but that's unnecessarily restrictive. Take HTTP for example, it's almost taken for granted that an implementation is for a client or a server, but rarely both. I imagine that will also be the case with MoQ with the sole exception of relays. OBS is not going to implement subscribing, and VLC is not going to implement publishing. Even in my URL library that does support both modes, I ran into a deadlock because of ROLE=both. My publisher was not consuming ANNOUNCE messages because it was not expecting them. Explicitly signaling ROLE=publisher would have prevented this, making it a protocol violation to send half of the messages in the draft.
A resilient implementation state machine will be something like Since for unsupported messages, you don't want a protocol action as it will be DOS surface, for example. Also, Many of the conferencing examples will mostly always be send and receive by default. Our client for example does pub and subscribe always. I personally feel the implementation constraints are resolvable and I feel still unconvinced for a ROLE parameter.
Heh, please don't do that. This is exactly why we have version/extension negotiation; not supporting a message after negotiating it is a protocol violation. Otherwise, my endpoint has to assume that your endpoint could ignore messages, and the MoQ draft in general, on a whim. Just tell me that you don't support ANNOUNCE via ROLE. Otherwise I would have to send an ANNOUNCE, start a timer, and guess that no OK/ERROR means no support? Probing for support is miserable. You close the connection on an unsupported message. Just send ROLE=both then. Not every conferencing is bidirectional though, for example clubhouse-style. At Discord we also have a dedicated connection for screen sharing (don't ask) and a very explicit role=publisher vs role=subscriber flag.
I don't think this is necessarily true. Without ROLE, you can still choose to only implement half the protocol and reject the messages you don't support. Every client has to be built to be resilient to arbitrary incoming messages. It can't break because the other side said their role was subscriber but then they send you an ANNOUCE. Therefore sending an upfront role seems like a promise to behave in a certain way. A system that relies on promises to not break will be brittle. I agree that ROLE seems superfluous.
I want publisher and subscriber support to be optional. That way it's only necessary to implement half of the draft on many cases. A CDN distribution edge would be one such situation. In fact, it's a hardening measure to ensure the edge cannot receive a ANNOUNCE which might otherwise cause a vulnerability. And generally speaking, if something is optional, then it should be negotiated. Otherwise you end up with Schroeder's extension. Silently ignoring unknown/unsupported messages is just terrible protocol design and it's s far better to close the connection with a protocol violation instead. That's the point of the ROLE parameter. But yeah let's chat about it on Wed.
Individual Comment: Every implementation has to have that switch statement and some not-crash action when it receives a message it doesn't support. Some logical options are: Ignore the message Send the corresponding error message (eg SUBSCRIBEERROR, ANNOUNCEERROR) with an error code like "not-implemented" Close the entire connection I think the value of ROLE is that it gives a consistent behavior early in the life of the session. If I want to send subscribes and the peer says they are subscriber only, I close the connection. But I still have to have the switch statement because of broken or malicious peers. The MUST in this PR can be improved by explaining what happens when you don't like the value you see, or the peer sends messages inconsistent with its advertised role.
In that case the peer will close the connection since it knows that it is subscribe only implementation and the role in the setup is supreflous
This is the type of things where I don't think we need it but it does not break anything, is not a big deal to implement, and does not hit performance. Given other people feel strongly this is needed, I can easily live with this. If this helps us get to consensus, I'm fine with adding it. I think the rewrite makes things clearer than they were before.
Discussion concluded that supporting this was not that difficult and it's a clear signal of what's supported, to so AI is to fix up the PR to clarify behavior.
Sec 6.1.1.1 specifies client sending or ROLE, and says nothing of server-sent. I guess server-sent ROLE is not needed, but maybe we should fail if it is sent?
I filed about the role parameter. The client doesn't actually know what the server intends to do. The server should be able to reply with its functionality, which might be a subset of what the client can support.
The SETUP message contains a ROLE parameter. In the current draft, the client specifies the value, indicating if the client will send data (0x1), if the server will send data (0x2), or if both endpoints will send data (0x3). A conferencing client supports the ability to send and receive media, and would always send role=0x3 because it supports both functionalities. However, it's unaware of the server functionality, and the server has no way of informing the client if it intends to send media. This matters because a generic client needs to know if it shows a black screen and/or should send a SUBSCRIBE message (ex. for /catalog). I propose that both endpoints send the ROLE parameter: 0x1 = publisher, 0x2 = consumer, 0x3 = both. This gives the server the ability to specify its own functionality.
Wondering if we even need Role in the first place .. can it be changed mid-session ? Say the use-case Participant in media conference joins as subscriber only since he is driving and want to listen in Then at some point mid session , he wants to contribute and he now becomes as publisher and subscriber what is the expected flow in this case ?
Current draft doesn't really explain what ROLE parameter is for, only that client needs to send it - what was original idea? Why not just always allow everyone to send and receive data?
It seems a better way to control if you can send and receive might just be if the client has the appropriate auth tokens for each role. I lean towards remove this if we don't need it, but if we need it, I have would be fine with a design where the client says which roles it is capable of , and the server response contains which roles it is allowed to do.
I think ROLE is good for negotiating half-functionality. If the remote endpoint cannot send media (ex. player), then sending a SUBSCRIBE is invalid. If the remote endpoint cannot receive media (ex. OBS), then sending a ANNOUNCE/OBJECT is invalid. This removes a burden on implementations, as most will only care about specific role. NAME brings up a good point. If an endpoint could switch roles, then it should advertise full functionality. That would include publishing an empty catalog which it can append it later when it wishes to publish tracks.
I don't think I seem much use for the Role - it seems like something the application would know at a higher layer. But I have no objection to including it if others do see a need for it. It's not like it makes life harder for applications that don't use it.
If the ROLE is used to enforce the half-functionality, then it needs to be clear what should be done when invalid message is received. Sending a GOAWAY? Sending a generic error message? Does the connection need to be terminated? I think most of the time, application will just choose the full functionality in case of role switching on the fly and avoid the invalid response code(just ignore the invalid message).
Oh hey, I found a good use-case for ROLE. If my client connects with ROLE=publisher, then the server knows that it should not send ANNOUNCE messages. The messages are useless and actually triggered a deadlock in my broadcaster, since the application was not reading them and overflowed the receive channel. The same would be true with the reverse; a subscriber will not be able to process any SUBSCRIBE messages. I like the ability to specify half-functionality. At the very least the library should support half-functionality (ex. automatically reply with ANNOUNCE_ERROR) but it's even better if it's enforced by the protocol.
LGTM me. Ship it!I wonder if it helps to discuss the role of role parameter during authors call for a short bit NAME


Internal server error has been useful in HTTP, so it makes sense to include something similar for MoQ.


Into SUBSCRIBEDONE. I didn't remove the SUBSCRIBEERROR message in favor of SUBSCRIBE_CLOSED as NAME suggested in this PR. Addresses some comments in , but possibly not all
So my notes from this discussion. CLOSE is confusing with QUIC layer. People seemed to prefer UNSUBSCRIBE over SUBSCRIBE_CLOSE. I love the move aware from RST and FIN as naming.
Propose names from my understanding of discussion call Announce (send by publisher) , AnnouceOK, AnnouceError UnAnnounce (send by publisher) UnAnnounceOK, UnAnnounceError AnnouceEnd ( sent by thing that received Annouce) , AnnouceEndOK, AnnouceEndOK Subscribe (send by subscriber) , SubscribeOK, SubscribeError UnSubscribe (send by subscriber) , SubscribeOK, SubscribeError SubscribeEnd (send by thing that received subscriber), SubscribeOK, SubscribeError Mostly trying to get rid of FIN / RST and have different names for the messages that flow in opposite directions.
I don't like the name of the different messages used to terminate. I'd rather have one message that transitions to the terminal state, with an optional error code and final group/object. IMO the subscription flow: And the ANNOUNCE flow mirrors: The brackets mean a message is optional. The sender is responsible for creating and the receiver is responsible for closing, possibly at the bequest of the sender (like QUIC's STOP_SENDING).
has been merged, adding yet another term (CANCEL), but I figured we'd clean that up with this issue.
Since we are bikeshedding, I prefer ANNOUNCE_RESCIND instead of CANCEL, since an announce is more of a declarative statement than an active request from the sender.
Heh and UNANNOUNCE and UNSUBSCRIBE stick out because they don't share the same prefix.
I like the names being proposed as they help make make is easier to understand. I do want to separate out one issue. The STOP / CLOSE type thing happens way after the initial SUBSCRIBE thus it can end up in a situation where auth has expired or something else has gone wrong this I like the current design that allows an OK or ERROR to be returned for those. I think it is also key for sender of the things stoping the subscribe to know that the other side has acted on that so we need the OK.
I'm leaning towards CLOSED instead of CLOSE because CLOSE implies its a requested action, so to me SUBSCRIBE_CLOSE sounds like something the subscriber would send, not the publisher. For a similar reason, I like CANCEL slightly better than STOP, but both work. UNANNOUNCE and UNSUBSCRIBE are good words, but I think it's better to follow a pattern in this case and I like NAME sequence of messages.
NAME CANCEL and CLOSED are slightly better, sure. NAME there MUST still be an OK before objects can be sent. That way the receiver can tell upon receipt of the CLOSED if the subscription was ever active.
So to add an OK, we need to add another state. The question for me is what is the use case where we need to deliver that information in the end of the subscribe.
This discusion is probably easier looking at state machine diagrams
Sections 6.8 and 6.9 strongly imply that SUBSCRIBEFIN/RST are sent at the true end of the track. So are they not sent at the conclusion of a SUBSCRIBE that does not cover the end of the track? For example, if the last object of a track is (10, 12), and there's one subscribe for (4,0) to (5, 100) Is there a SUBSCRIBEFIN? If not, how does the subscriber know that in fact (5, 60) is the last object in that subscription? If so, does Final Group and Final Object refer to the last object in the subscription?
Individual Comment: Putting my HTTP hat on for a second, when you ask for something with unknown length, you infer from a graceful termination that the last thing you got was the true end of that resource. If you send a range request, the graceful termination at the end does not convey that the last thing was the true end. Once the object expires, you can issue another request to the same name and potentially get a totally different thing. Does this model work for moq, or do we need something different?
There are three different things you might want to communicate to a receiver at the end of the subscribe: 1) You got all of the objects in the range; 2) You got almost all of the objects, but some were dropped for latency/bandwidth reasons 3) The track was truncated for some reason and possibly, 4? 4) You got everything I have but i'm not sure if that's everything
is somewhat relevant to this issue.
As mentioned in , there are 3 different ways for a publisher to close a subscriber, each with minor differences. : I never sent you an object. : There are no more objects to send after group/object X/Y. : There are more objects after group/object X/Y... but I won't send them. A few things: I think it should be renamed since it's not the only ERROR message. Maybe ? The only difference is that one has an error code. Otherwise they both include a final group/object and it's up to the application/library to decide if they wait for the final object(s). I'd like to only implement this once but right now I have to copy/paste any code to handle these almost identical messages. What if we merged them and use error code 0 to signal a FIN. The reason phrase should be removed as part of . There really needs to be a state machine like with this many ways to terminate a subscription. I think these are all protocol violations: then then then My interpretation of the draft is that a followed by a is invalid, which is not obvious and unlike QUIC. I don't think so. The library/relay behavior doesn't change based on . The only thing that miiiight differentiate between the two is the application, which can use errors codes for this purpose. At the very least I would merge these two messages. But I would also like to explore a single way for the publisher to close a subscription. The application can signal the nuanced differences between errors (ex. error code 404 vs 410). The library/relay doesn't care, it just forwards the error code.
Individual Comment: The inspiration for FIN vs RST in moq came from HTTP caching. If a resource has no content-length, and the caching proxy has received N bytes, does it have the entire resource, or does it need to refetch the tail? If that response was terminated with a FIN (H2 or H3), then it has the whole resource. If it was terminated by a RST, it can't assume that it does. So the value I see in these two messages is for a moq relay is similar. A client connected to the relay and subscribed to a track, which fowarded the subscribe to a publisher. The track was published to the relay and ended with a FIN. Another client subscribes to this track from the beginning (Absolute/0,0) and no end. Does the relay need to resubscribe upstream? Now suppose that the connection from the publisher to the relay closed abruptly, or the track was terminated with SUBSCRIBE_RST. This is a signal to the relay that it's worth another subscribe to find out where the end of the track is. I can live if we consolidate the framing to a single message, and define a special error code to mean FIN, but I do think the transport needs to see the difference.
I like it. I would also like to merge ERROR with CLOSED, but we'll chat about that.


Note from call. Fixe subscribeDone at same time. Define a new bool type. p[ick new leatter other than b
Also make sure to fix SUBSCRIBE_DONE at the same time.
NAME - I updated, have another read and see if it looks OK. Thanks
and introduced a field to SUBSCRIBEOK, SUBSCRIBEFIN, and SUBSCRIBE_RESET. If I read the notation correctly, it has a size of 1 bit, which makes it hard to parse the rest of the message. Could we encode that information in the message type by using two types per message instead of a 1-bit field?
I think the intent is that it is one byte not one bit.
Maybe it should have been:
or just a byte field with only 2 possible values allowed (1) and (0) ..
I agree we need to keep this byte aligned however we do it.
IMO just make it a VarInt (or byte), with only values of 0 and 1 currently valid. This encoding is temporary until we nail down all of the fields/flags so we shouldn't be making life difficult or efficient.
Individual Comment: Even if it's temporary, my preference is for a one-bit field and unused/reserved bits to align, because there are no error conditions.
Have a look at PR


And add a new SUBSCRIBE_DONE status code. Fixes part of , but it's still unclear how one might refresh a subscription without interruption.
Subscriptions always end with either SUBSCRIBEFIN or SUBSCRIBERESET, except when they Expire and then no control message is sent. This complicates the state machine and adds another case to handle. The subscriber needs to start a timer or something similar to know when the subscription will end, and it doesn't get to know the final group or object ID.
I'd like to get rid of , but my thought was that it's a signal to SUBSCRIBE again before the timeout to avoid interruption. Otherwise, there would be a SUBSCRIBE_RESET with an "expired" error code containing the final group/object.
I agree that a subscriber can deal with this field, but I don't know what purpose it serves for the publisher/relay? It's different from an idle connection timeout, because assuming there is data flowing for the subscription, it would never go idle. Unlike QUIC, I don't see value in having a 'silent close' for subscriptions, because typically we expect subscriptions to have data flowing at a somewhat regular interval. Also, there's no guarantee that the subscription will last until the expiry time, it can always end early with SUBSCRIBEFIN or SUBSCRIBERESET.
Based on last week's conversation, the Expires field doesn't change the state of the subscription by itself, a publisher would still have to send a FIN or RESET to close it. It's still unclear as to what a subscriber should do with it. There was a fair amount of discussion around refreshing one's authorization. It's unclear if that would be done with a SUBSCRIBE_UPDATE or something similar or via UNSUBSCRIBE/SUBSCRIBE?
Would the solution to this be when the relay expires a subscription, it sends a control message ? This might all become clearer once we figure out re authentication but at a high level, I want some way that the relays can clean up state on a time line that is under relay control particularly when poorly implemented clients make subscriptions.
I think this was resolved by -
LGTM


There is a bit of angst about using SUBSCRIBE to obtain the current Group ID. This PR is a straw man to provide an explicit means of obtaining the group ID. I would be surprised if this ended up in the final RFC, but it might be useful to keep in the draft as a primitive while the other SUBSCRIBE/FETCH stuff moves around.
Summary from meeting. We want to make it more extensible for other things. Scope to things the relay knows, not a substitute for Catalog. Rename to make it more generic.
What about returning latest object in addition to latest group?
OK, changes: I painted my bikeshed TRACKSTATUS, on the grounds that TRACKINFO sounds like more permanent data that maybe out to be in the Catalog. I added last Object ID because Will asked for it and it seemed easy enough. I did not fill Suhas's suggestion to make it optional because the parameterized idea got a lot of pushback in the call, and this seemed adjacent. Please have a look, and if Ian's happy with it he can merge.
I'll also note that we could put a track alias hint in here, but I didn't care enough to add it without more positive feedback.
This is a good start. We can massage the status codes later. They should correspond to some sort of state machine that we define for moqt in general. I'm sure we will have future changes to this, but I think we should merge it.


This PR adds a status field to the object that allows an relay to indicate an object or group was lost or dropped. It also allows a producer to indicate a object or group will not be produced including the cases for end of group and end of track. The PR probably needs a bit more text on what what producers and relays do but I think this is close enough to get some initial discussion on this solution. Fixes most of
I think I'm happy to use a byte for this. We can always optimize it later in a number of ways if we want.
We can actually save a byte by omitting Object ID except for the first Object in the stream, because this allows the Objects to be sequential. Similarly, we can omit GroupID if the delivery preference is track per stream.
This is a good point. Conceptually we need two enums: One to indicate if it's a Group Fin or Track Fin and one to indicate why there's no payload. There are a lot of ways to encode that, but it's a bit nice, because the first enum is an immutable property of the Object and the latter is sort of a payload alternative.
I've only had a chance to glance, but how does this interact with RESETSTREAM? Putting drop notifications within the OBJECT itself means that an object can only be dropped prior to being written to a stream. This is okay when used in conjunction with stream priorities, but it's not great. Ideally we can renege after committing data to a stream by resetting it. IMO there should be a message indicating which objects were dropped upon a RESETSTREAM.
Notes to myself: Need text on reset. could send object group drop on new stream open an issue if we need this it Way to deal with sending zero length object add an enum for this object is empty Way to indicate end track and I dropped it. perhaps better way than sending same object twice
NAME i did leave the following comment URL
After reviewing fully, yeah I don't think we can decouple drops from resets. When any publisher (including a relay) decides to drop an object, it will also reset the associated QUIC stream. Otherwise, the publisher has to delay writing the object to streams (like TCP low watermark) so it retains the ability to drop it. And the problem with TCP low watermark in MoQ is of course... priorities. Newer content is usually more important, so even if we already have N bytes queued in the QUIC layer, our new content needs to skip the queue. The only way to do that is to actually write to the QUIC stream marked at a higher priority... or reset streams currently queued. The PR doesn't allow either of these as I understand.
I like what Cullen suggested in the editor's meeting. When we want to drop an object, we RESETSTREAM and write a dropped message on a new stream (that won't be reset). Relays will forward this message too. The caveat is that the object may have been successfully delivered despite the so the subscriber has to be prepared to ignore some drop messages.
Sorry I left a PR review a little too fast between meetings and taking care of the newborn. I had a chance to think things through a bit more instead of leaving vague and passive-agressive messages like: I'm warming up to the status in OBJECT, however it needs to be a bitmask. An object can be both dropped, the end of a group, and the end of a track. It's a little annoying to have to process duplicate OBJECTs with different statuses but it's fine I guess. An alternative would be a reliable SUBSCRIBE control stream that contains reliable messages GROUPEND, TRACKEND, OBJECTDROPPED messages. There would be no need to retransmit anything on a RESETSTREAM but the overhead would be marginally higher. ex. GROUPEND would consist of the message type, the groupid VarInt, and a object_id VarInt only. The track would be inferred from the subscribe control stream. The added benefit is that a subscriber would know the size of the group BEFORE receiving the final object; it would know how much is still in flight.
I think Cullen's update addressed this by making "end of group" and "end of track" have different object IDs than the last actual object. Should we further state that only objects with status 0 can be "dropped by a relay"? All the other statuses may be needed by the subscriber, and all should be very small. Would you be ok with moving this PR forward if we split out 0x5 and 0x6 for now?
Note to cullen - remove the two codes. Clean up other comments. Ping Ian when done and ready to merge.
NAME - Can you give this a read and merge if you think it is OK. I did make the change in the PR luke sent to this but it ended up in another PR so github did not see it as done.
I did another read through and it looks good to me !! Thanks Cullen
Individual Comment: This means a relay cannot assume the existence of these marker objects. That's unfortunate, because I'd like the end-of-group marker to help my relay know when to close fanout streams. I don't want to rely on QUIC FIN for this - a publisher may use a QUIC FIN to terminate a stream cleanly (delivering all written objects) as part of a graceful shutdown, without wanting to signal the end of group. In this case, I explicitly don't want to close fanout streams, in case the publisher is reconnecting (make before break). Would making end markers optional for DATAGRAM tracks sufficiently address the audio only use case?
I get your point and need to think about all of that a bit. Lets say the client did send them, would it be a problem if it just crashed without sending them. Would the relay also have a timer or some other way to clean up the state ? Or just wait for subscribe to end ? Just thinking through implications but you raise a good point.
As far as I understand, we currently allow group IDs to have gaps in them, with only requirement for them is that they are monotonically increasing. This presents a problem with caching. Assume I am a relay, and I receive a range request for ; if the origin has never produced group 15, then even if I have the entirety of that range in cache, I would not know that, since 15 would be just missing, and the only thing I can do with that is to request 15 from the origin every time. Possible solutions to this problem: Add some mechanism to indicate that 15 is not there (e.g. by putting the number of previous group in the beginning of every group). Require group numbers to be incrementing by one. I am fan of approach number 2 since it's conceptually simpler, but I think we can make some version of 1 work.
I am not fan of approach 2 since it is limiting and applies to typical media applications only. I think we need to understand that, the application controls the groupIds and they are independent sync points. Catalog should provide that information for the players. Regarding if there is a gap due to object being dropped, that information is identified within the object itself. Let's take an application example where the group numbers increment by 2. So the groups would be 10,12,14,16,18,20 in the above example. Now consider that group 12 was dropped by the publisher , then the relay's cache will be in this form [10, 12 (not sent by the publisher), 14,16,18, 20] Keeping relay unaware of application semantics is something we should allow. Regardless, the end applications know exactly how the groups behave and if there are geps (either due to broadcaster not producing or dropped for other reasons), things will be identified over dataplane as such
NAME can you give a specific use case for how an application could use non-sequential group IDs for some benefit?
Now consider that group 12 was not sent by the publisher , then the relay's cache will be in this form [10, 12 (not sent by the publisher), 14,16,18, 20] The problem I have here is not with 12, the problem here is that the relay cannot distinguish 11,13,15,17,19 being in a state "has not arrived yet" vs state "does not and will never exist", so it will be always forced to fetch those.
The relay should not worry about the semantic structure . Relay needs to fetch only those that have been marked as dropped. If it hasn't arrived, then it will eventually arrive or the upstream will mark it as dropped. In this example, Relay fetches 12 and sends rest from its cache. If relay makes decision on behalf of the application, it will end up fetching things that are not even part of the application ( here the groups 11,13,15,17,19) My proposal is to act on the things you know explicitly and don't guess on the unknown.
I'm confused. How is the relay supposed to know that when being asked for range , it does not need to fetch 15?
Say relay cache is empty, it will ask upstream from range [10-20) .. The upstream publisher will send 10,12,16,18, 20 .. Relay responds with that. OTOH if the upstream provides 10, 12,, 16, 18(dropped due TTL), 20 and if the operation is fetch , the relay will fetch 18 to fill in the gap and once 18 arrives , it will be able to send 18 with others.
Sure, let's assume there are no drops, the first fetch is successful, and it puts 10, 12, 14, 16, 18 in cache. Now the second fetch arrives for the exact same range, . It looks at its cache; it has objects 10, 12, 14, 16, 18, that it can return immediately, but it doesn't know if that's all objects that exist in that range or not (we know that because we know that the previous request was , but it is entirely possible that those objects ended up in cache since they were results of five different fetches), so it has to repeat the fetch for those.
I strongly think we should have sequential groups. Like Victor said, the group ID can be used to detect gaps at the moq-transport layer if it's sequential. I understand the desire to stuff application-specific metadata into fields, but it's a layer violation. Put application-specific stuff in the object payload (ex. timestamps) and put properties useful to relays in the moq-transport layer.
GroupIds are just application sync points and are by definition independent of each other. Adding a correlation between them can be invalid/incorrect/misleading as a Relay is unaware of how application defines its sync points. OTOH objects have dependencies within a group, hence the app decided to send them part of a given group and having relay maintain a state to find out gaps may be useful.
I am also a supporter of incrementally increasing Group IDs. Having such IDs does not break the contract that these points are independent of one-another. It simply means that they occur in a sequence. And being part of a sequence is a fundamental attribute of all MOQT groups, since their parent, a track, represents a temporal flow of data. This example from Victor illustrates the core problem. For anything other than a contiguous sequence of group numbers, a relay can never know if the Groups exist or not and it would always have to make a request upstream to check. It could keep track of prior subscriptions (for example [10..12], [14..16], [18..20]), however if any of those did not overlap then it would be forced to make a fetch for [12..14] and [16..18] in case groups 13 and 17 existed. That is a wasted and convoluted workflow. I tried to think of a use-case where I would want non-incremental group numbers. One example would be to use epoch time as group number. So if I'm making variable length segments, and doing segment-per-group, I might number my groups: A player wanting to seek back in time would need a timeline track to tell it the precise group number it needed. That timeline track could equally map group numbers to epoch time, which would allow me to number my groups incrementally: The meta-point here is Group IDs only define sequence and any other relationship between those sync points can be expressed via an application level timeline track and/or catalog.
temporal flow of data doesn't imply sequential nature of groups either.
Second fetch gets the exact same answer (10,12,14,16,18 since it falls in the range). If there were gaps due to groups being dropped on path or a source deciding to skip a group , those should be marked for relay to see if it needs to satisfy fetch request. I feel it will help us to think on why gaps can occur and those can be explicitly signaled . Relays then in application agnostic way acts on the data in cache.
NAME How does the subscriber know the SUBSCRIBE/FETCH is done? We have a SUBSCRIBE_DONE message with end=20. The subscriber receives 10,12,14,16,18,20 but when does it stop waiting? Is group 19 still in transit? There's no drop notification for it since it doesn't exist.
Subscriber application knows its catalog and deduce the group distribution and find out there will be no grpup 19.
The moq-transport library should tell the application that a subscription has terminated, and not the other way around. Especially since the moq-transport library can't release any state until this optional signal is provided by the application (which might not care about gaps). If the application needs to use a catalog/timeline to detect gaps, then it can use that same catalog/timeline to carry the timestamp that is being shoved into the . Or it can put it in the object payload.
So we're talking about two different fields and I'm going to use disambiguating names: : Increases by 1 each group. Provides ordering and gap detection. : Increases by an application specific amount for each group (no units). Provides ordering. There's a world where both are separate properties (and fields) of a group in the moq-transport layer. Maybe the relay could use the epoch (as a timestamp with units) to perform TTLs, or latency budgets, or fetches. But I think we should punt timestamps and metadata in general to the application for now. I'm also not sure how this would work in practice anyway without an equivalent , but are already sequential.
We define a track (our subscribable entity) as a "sequence of Groups". That is part of our object model. There is no part of a track which does not belong to a group. Therefore, a track consists of a sequence of groups. ! This diagram shows three tracks. The first two tracks are valid, one with temporally contiguous and the other with temporally non-contiguous groups. The third track is not valid, as we don't allow a publisher to produce objects at the same time in the same track which belong to different groups.
this looks like an example constructed incorrectly. None of my points above said overlapping groups Ids. All I am saying is group Ids are independent and don't have to increase in sequence. As you pointed out, both track 1 and track 2 are valid, which i 100% agree. I am proposing we shouldn't force an application to follow what looks like just the track 1 model, since track 2 is also a valid group distribution. MoQT should have necessary things to allow track 1 and track 2, if not, let's define it.
I think you missed the point in my comments . No catalog is not used to define gaps. Gaps are signaled in the data plane
Exactly. I am not arguing either. All I am saying is, when a subscribe done is processed and reported to the application, the application ha enough information to know if there needs to be group 19 or not, from your example. MoQ transport library reports status of what it has seen and only the application knows more about, if group 19 was supposed to be even produced for a given track or not , since that info is in catalog. Please note , this is nothing to do with GAPs. Gaps happens when things are dropped for variety of reasons. Track properties/characteristics (like number of groups, how groupIds are used, group duration) is catalog scoped and not up to for the moq transport to enforce. This is just the principle of separation of concerns across layers.
In the current state, since the moq-transport library cannot detect gaps, it has to immediately report any SUBSCRIBEDONE to the application. OBJECTs may still be received during this state so the library does not drop state associated with the session. The application has to tell the library when the subscription is actually done so it can remove the lookup table entry. With sequential groups and drop notifications, the moq-transport library can buffer the SUBSCRIBEDONE until it has received and flushed all OBJECTs to the application. The application only learns the subscription is done after receiving the last object. This is how transport APIs generally work and how QUIC works itself.
And just to draw inspiration from QUIC itself, it does not allow gaps in the Stream ID or Stream Offset (roughly analogous to groupid and objectid). QUIC could have allowed the application to introduce gaps in either but it just complicates everything.
Not sure how to interepret this in the context of this discussion. QUIC is transport layer protocol and MOQT is application protocol. Also StreamIds and GroupIds are semantically different.
Chair Comment: I think the WG can build the system to support either sequential or non-sequential groups. Several folks have put forward reasoning for why sequential groups provide benefit and/or why non-sequential groups add complexity. NAME as the lead proponent for non-sequential groups, can you state the use case and application benefits. This will help us evaluate the tradeoffs.
The problem is how to resolve this on the cache. Right now, the cache, when it does not have any information about the state of object 15, cannot tell apart the scenario where 15 does not exist, vs the scenario where 15 is missing from the cache because the cache got filled by one client doing a request, and then another doing . It is possible to work this around by putting a "does not exist" kind of entry into cache on FETCH: if I fetch , and get , I can cache " does not exist" from my knowledge of the properties FETCH provides. Outside of being more complex, this also does not solve the entire problem, since our goal is to be able to cachefill from SUBSCRIBEs as well as FETCHes. So non-sequential group IDs inherently makes caching less efficient.
Let's take the example of sequential groups for a similar use-case Fetch 1: 10-14, cache is filled with 10, 11,12,13,14 Fetch 2: 18-20: cache is now filled with 10, 11,12,13,14, 18.19,20 now fetch 3 arrives with 10-20, Cache doesn't know the state of 15,16,17 . So as it needs to do, it will have to ask upstream for . In the case where 15 was never produced for whatever reason, the publisher will need to mark it as such in the response regardless (like ), as this will aid subsequent requests to not repeat the same request. One observation on the cacheFill, When producer sends groups and objects, either as response to subscribe or fetch, the cache fill happens regardless. Let's say when subscription was made , an object was marked as dropped due to congestion. An eventual fetch request can end up replacing that object with proper object if it is still available upstream.
Are you saying that the publisher is allowed to skip group IDs, but it has to explicitly mark groups in the middle as missing? This de-facto makes them sequential.
Not exactly that. This is the gap use-case that had a requirement for players who want to know if a given group that is expected is marked explicitly when source drops, thus allowing player to not wait forever. There are couple of things we are talking here IIUC. Publisher explicitly marking things when it decides to drop. Publisher responding to the request for a non existent things (never produced, permanently gone or dropped) Players knowing not to expect certain groupIds since they know from catalog that the group distribution will not generate such groupIds. Players knowing not to wait for missing things. Cache-fill from either subscribe and/or publish. Relay asking upstream for things that is not its cache Clients and Relays need to be able to process groups out of order. All these can be done or needed regardless of how group numbers are distributed.
Want to clarify that, one needs to request for 15 only one time, if and only if the fetch request asks for it. Also a client who is aware that group 15 will not be generated, per catalog, will not ask for it either. Regardless of the group distribution, relays can be asked for things that it not in its cache. Asking origin for that evaluates to a non existent things ( never produced, not produced since origin decided to drop it or permanently gone from the origin) needs to be updated in the cache. Since a fetch can happen for things that fall in to any of these cases and a relay that doesn't have that information in the cache will need to go back and find out the answer and it needs to do only once.
I want to second this. NAME would allowing groups of size 0 work for you? The problem today is the ambiguity. A subscriber doesn't know if 15 is in flight or will never arrive. If the publisher explicitly tells the subscriber that 15 is empty (or dropped) then that solves things.
yes if there is a reason for a producer to drop entire groups and should be explicitly signaled. "The problem today is the ambiguity. A subscriber doesn't know if 15 is in flight or will never arrive. If the publisher explicitly tells the subscriber that 15 is empty (or dropped) then that solves things."__ We need things to be marked to indicate things when dropped at the publisher. This is needed for reasons pointed out in URL and URL
I don't get what the problem is here. The things the relays cache is the object. I am very concerned that any assumption on groups being sequential are being produced in order are not going to work with the way people want to use complex reference frames and layer in modern codecs like AV1. Of course an given application like youtube might specify for it's usage of moq, everything was sequential. But the moq relays don't need to know this or assume this and keeping them flexible allows for all kinds of interesting media uses cases for media beyond video. I have helped write a relay, and I don't see what problem this is causing for relays.
Ah, my concern was that someone would try to have groups numbered 0, 1000, 2000, or use group ID as a timestamp, in which case you'd have to send hundreds of gap indicators. If that is out of scope, simple "a group is missing" would work.
+1. PR provides aspects of that for the cases when the publisher explicitly drops a group IIRC.
The current approach of allowing any OBJECTs to arrive on any stream makes it impossible for the receiver to know when a group ends. There's a few conditions when you need to know the end of a group: A relay being told to terminate the subscription after group N (subscription hints). A relay knowing when to close a QUIC stream (if it writes a group per stream). A decoder deciding when to start decoding/rendering the next group (ex. GoP). Likewise there's a similar problem of knowing when there's an gap within a group. There's no way to signal that OBJECT X will never arrive, so the receiver should not wait for it.
I strongly advocate for a 1:1 mapping of group to stream. Objects within a group are in order, but there may be gaps. The stream FIN signals the end of a group. The sequence number (or FIN) can be used to determine gaps. An application that doesn't support gaps or skips a group can send STOP_SENDING; something that is not possible with an arbitrary stream mapping.
Alternatively, we could have , and control messages. I think this is a mess, and the receiver would need a corresponding in response to an unrecoverable gap caused by . I don't think we can annotate OBJECTs like NAME once proposed with a GROUPFIN flag because they're lossy. You need some reliable mechanism to signal the end. For example, sending the OBJECT with a GROUPFIN flag over a datagram would be illegal, as would sending a resetstream/stopsending for any stream containing a GROUPFIN object.
Maybe this a duplicate of ?
The issue mentioning Last Object Previous Group is in
I think the best solutions to this is a header on the last object in the group that indicates it is the last object in the group.
Individual Comment: I share Luke's concern above about OBJECT being somewhat unreliable. I think a GROUPFIN message with stronger reliability guarantees would look similar on the wire but work better. eg: Instead of Have This works better than implicit mapping from groups to streams. A relay might need to close a connection with an open stream, but wouldn't want to reset it and wouldn't want the stream FIN to be interpreted as a group FIN. I don't want to put GROUPFIN on the control stream, but that would be the safest. I think the other messages Luke mentioned related to DROP are probably an orthogonal issue.
Agreed, I'm just bringing it up early because stream mapping has massive ramifications on what control messages we'll need in the future. If we can't utilize QUIC's STREAMFIN, STREAMRESET, STOPSENDING, MAXSTREAMS... then we'll need our own, analogous control messages.
you guys have convinced me
As I mentioned during the interim meeting, it makes sense to put that information in the header of the next group. Something like: That information can be used by the receiver to decide when to start playing the new group, and when to wait for more information instead. It is useful: when using "stream per object", to understand how many individual objects are expected for the previous group, when using "stream per group", if group numbers are skipped. The downside is that the relay needs to know the value of previous group and last object before sending the first object of the next group. This may or may not be a problem in practice -- relays may be receiving the information from the origin. If the relay sets an overoptimistic value of previous object, it will have to send some kind of replacement for the skipped objects, such as Which will inform the receiver that the specified object ID and the following ones will not be sent for this group.
Individual ReviewI removed my -1 so if others think it's fine, merge it.I think this fine. Like you, at this point I don't really care much how we spell it. Compared with my proposal in , this is conceptually way simpler (no special rules for each forwarding preference) but is less byte-efficient. I'm happy to go with whatever the group decides.This is a dramatic improvement over the status quo, I think we should merge this without bikeshedding too much about the encoding for now.


Based on
Consider the following sequence of groups and objects, some already produced , some that will be produced in the future. The largest group ID is currently 23 and the largest object ID is 3 (it also happens to be the last object in the group). ! Our definitions for RelativePrevious and RelativeNext are shown here. Example 6.4.3.1 for 'now' shows In our example, largest group is 23, however Largest Object + 1 is 4, which does not exist. We need to adjust the definition of RelativeNext to allow for the fact that the next object may be in a different group. There may be some players who simply want the relay to send the next available Object, in this example Object 0 of group 24. There is no current way to reliably ask for this, because StartGroup mode cannot be 'none', and we don't know which group that object will belong to. It may be in the currently largest group, or it may be in the next group. A solution here would be to relax the constraint that StartGroup mode cannot be 'none'. If we then request it would instruct the relay "Send me your next object, no matter which group it belongs to". In 6.4.3 we have examples titled 'Now', 'Current', "previous" and 'Next". 'Now', 'Current' are synonymous to most readers. We need to better titles to illustrate what is actually being requested here. Additionally, "Previous' doesn't indicate whether we want the previous group, or previous object. Similarly for next.
Individual Comment: You are right that the example (and the text likely) don't show the full range of possibilities for what the next object will be. The intent was that Start = Prev/0, Next/0 will get you the next object, which could be any object in the current group greater than largest, or the first object in the next group. This is how my implementation handles it. I don't see a way to get only the next object in the current draft, however, because of the ambiguity you describe.
If I wanted to receive all objects in group N, I would issue: However the draft sneaks this restriction in, and it would actually be a protocol violation: It wasn't clear to me until chatting with Alan how you're supposed to do it: It's clarified in example 5, but I don't think this is obvious and it feels like an off-by-one. For example, if N was the last group in the broadcast, I can imagine some implementations incorrectly throwing an error or somehow mishandling it.
Individual Comment: This was discussed on the PR here: URL Given how subscribe locations work (end is exclusive), end=N+1/0 means "including through the end of group N". The question is whether we should have more than one way to do things, and defaults when values are not present.
Sorry, bad choice of word there. I meant that it's not immediately obvious why the restriction exists, and what it impacts.
Individual Review: I like removing relative options from subscribe. I don't love the group ID + 1 spelling, but I guess I can live with it. An alternative is to add 4 code points for SUBSCRIBE to indicate the presence/absence of start/end (SUBSCRIBE, SUBSCRIBEFROM, SUBSCRIBETO, SUBSCRIBE_RANGE).


I took a stab at another rewrite of Subscribe message which attempts to not use special group and object values. Also add support for multiple subscription flows that have been discussed. As this is a control message, we should be Ok to be elaborate in describing the subscriber needs. This leaves 0 as a special value for the StartObject and EndObject.
NAME updated the PR .. please let me know .. thanks
I like that this would move us away from stealing special values from obejct id / group id space.
This looks good NAME .. thanks for the update
As currently defined, you can SUBSCRIBE starting from the following points: The latest group and latest object An absolute group and latest object An absolute group and absolute object You cannot SUBSCRIBE to a. The latest group with an absolute object b. The latest group from the beginning c. The next group after latest, from the beginning I'd like to remove 2) (absolute group, latest object). If this group is in the past, then likely the group is complete and no new objects will come in that group, so it's equivalent to (absolute group + 1, object 0). If this group is in the future, then it's equivalent to (absolute group, object 0). If the group is current, it's equivalent to latest/latest. Does anyone have a use case that requires this mode? I'd also like to add b) (latest group, first object) and c) (next group, object 0). This can be done now with an extra round trip via TRACKSTATUSREQUEST, so perhaps this is an optimization we can tackle later.
It's currently ambiguous if or happens. When , then is not present but doesn't have a specified default. If then occurs, and if then occurs. The correct behavior is IMO since groups are intended to be sync points. +1 to removing .
And I don't think we should support without a good use-case. It's too racey for ABR and too delayed for initial playback. It's also barely an optimization since the alternative is making the TRACKSTATUSREQUEST, which primarily eats into time that would have been spent waiting for the next group to start anyway.
means you can only start at: b. The latest group from the beginning An absolute group and absolute object Everything else is accomplished with , forcing the subscriber to converts relative values to absolute. I think we should eventually bring back Locations so the publisher can do this conversion too without an additional RTT, but the desire was to simplify start/end ranges.
FWIW, I thought/intended start_group=0 to correspond to b. Also +1 to removing case 2.
Thanks for summary. +1 to remove case 2 (abs/latest). I also thought we had b. today but we need case b so lets update to make it clear we have b (latest/0). I don't think we have a use case for a (latest/abs), +1 on remove. I originally was in favor of case c (next/0) but I have changed my mind for now. IF you need this, you just do case 1 (latest/latest) and toss out object until you get to the next group. If there was not enough bandwidth to do this, then there is probably not enough bandwidth for the next group when it arrives. If we get a bunch of code running and can show that (latest/latest) is not good enough and we really need (next/0), then I will change my mind but for now, I think we can live without (next/0). And +1 to point that TRACKSTATUSREQUEST solves most problems.
A +1 to all these points Regarding starting at the latest group at the beginning, this seems like the default starting use case for many players. Therefore per Luke's comment -
Fixed by


The other option is we can use a delta encoding to make it impossible to specify an earlier object, but I think that would make it more complex.
Good point, I changed it to the same or later object
As mentioned in the sprawling , I think we should allow empty subscriptions, aka subscriptions that match no objects. It would be nice to have a way to check if a track exists without receiving any OBJECTs. The MoQ equivalent of a HTTP HEAD request. It could be useful to pre-warm the cache. An empty subscription could be the equivalent of "I'm interested, but don't have the bandwidth to receive objects yet". Again, similar to HTTP HEAD requests. For example, a client could issue a to accomplish two separate things: The track exists if it gets a SUBSCRIBE_OK. The encoder/CDN starts producing the track. The client could resubscribe at any point to immediately start receiving the track.
Can the same be accomplished with: or any other range request where start==end? Is it ok for the publisher sends SUBSCRIBEOK and SUBSCRIBEFIN, or does your use case require the publisher to hold the subscription open? Adding a new message is also an option.
I think there's a few potential use-cases: Check if the track exists. Check if the track is active. Check if the track is active, and get notified when the track is closed. Check if the track exists and group/object N exists. "exists" vs "active" is tricky. If you request absolute/0, then it's totally valid for the relay to return OK so long as the objects haven't expired yet, even if the publisher sent a FIN a long time ago. But what should the relay do if absolute/0 has expired? Should it return an error, even if the track is still actively producing objects? Requesting relative/0 is better, although what if that object has expired too? For example, a muted audio track. IMO ? SUBSCRIBE start/end=none ... UNSUBSCRIBE SUBSCRIBE start/end=none ... SUBSCRIBEFIN SUBSCRIBE start/end=absolute/N ... SUBSCRIBEFIN
And a more concrete use-case that NAME and NAME might like. Let's say I have 3 renditions: . The client starts watching a broadcast: This is a hint to the relay that the 720p track exists and it should start warming the cache. The relay is free to ignore the hint, but at the very least it will perform authentication. When the client decides that it has enough bandwidth for 720p, it will switch up at an aligned group: If the relay warmed the cache, then this switch is instantaneous. If the relay ignored the hint, then it takes at least an RTT to origin to fetch the 720p track. The time it takes to switch renditions matters more when switching down in response to congestion. The buffer will deplete while the CDN fetches the rendition from origin, which pre-warming subscriptions will avoid. And just to complete the example, you have to pre-warm the 480p SUBSCRIBE again. This could either be done with multiple subscriptions, or waiting until the SUBSCRIBE_FIN arrives and then subscribing again. I'm not quite sure which approach is better.
I'm a little concerned about this use case, for the reasons below: Is the assumption that the catalog, which declares the existence of the track, is not trustable? How? If the relay went forward to the origin with the same start=none request then the origin should not actually send anything and therefore the cache would not fill. The only way this works is if the relay goes forward with a different request with some !none start value. This non-deterministic behavior (ask one component for one thing and it asks for somehting else) can lead to scaling and debugging problems. I appreciate the benefits of cache warming, for fast start especially when the the edge relay is far from the origin. There are other ways to achieve this. One way can be by having a "warming client" make a conventional subscription request. This client may be an agent hosted on the edge relay for this purpose and its APi would be offered by the CDN and is outside the scope of moqt. As a third point, empty subscriptions are also a DDOS attack vector. As a client, if can make a hundred requests for streams that all then flow to a relay, but no content actually comes to me, it is a highly asymmetric (and therefore dangerous) DDOS tool.
I think it's useful for applications that don't use a catalog. ex. Alan's chat protocol could check if a chat room or participant exists by performing an empty subscribe. The relay doesn't have to forward ranges verbatim. Just like if a CDN receives a HEAD request, the relay MAY issue a GET request to the origin instead. I think this is actually pretty common? In the MoQ case, the relay could request instead of from the origin. It's completely up to the relay; it would only do this if there's sufficient bandwidth/cache available. It's a little bit of business logic though; maybe the customer can choose to enable it. Just keep in mind that we don't want the relay to have access to the catalog. So you're asking applications to provide this functionality, requiring a custom service worker, a custom player, and hooks to notify the service worker on player start/stop. And the service worker itself doesn't actually want to receive these objects, so yeah there would need to be a non-standard API to actually warm a track. Plus it gets more complicated when you through in alternative renditions. Maybe the client has no intention of downloading AV1 tracks, or maybe it has no intention of downloading Japanese subtitles. This sort of selection criteria should be signaled to the service worker; pre-warming all tracks is a waste. I think this is the type of thing we build into the protocol instead of asking every application make a unique implementation. Warming would be completely optional for both the publisher and subscriber. I don't quite understand. The CDN would deduplicate subscriptions so only one would ever reach the origin for a given track. If the relay already has an active subscription for the 480p track, then it wouldn't need to issue another subscribe upstream to serve empty subscriptions. But if you're talking about using random track names, a client can already SUBSCRIBE to random track names (just like random HTTP paths) to DoS the origin. Or maybe you're talking about a client potentially pre-warming a bunch of valid tracks. Like somebody wrote a bot to scrape all Twitch broadcasts with zero viewers and issued an empty subscribe to them all. The CDN is completely in charge of determining if it pre-warms empty subscribes, which it should not do if it's near capacity or detects abuse. But I don't think this is unique to empty subscribes. A client could perform a normal subscribe and then either immediately unsubscribe, close the connection, or set MAXSTREAMSUNI=0 avoid receiving any objects. These empty subscribes follow the normal authentication logic, so the mitigation is going to be the same for both.
Individual Comment: Let's retitle this issue to be more specific about what the ask is. Empty subscriptions are possible with draft-01. Their semantics are a bit undefined - eg: a relay or publisher has multiple options - SUBSCRIBEERROR, SUBSCRIBEOK + SUBSCRIBEFIN, SUBSCRIBEOK and no FIN, in case the subscriber wants to do something else? Do we need to mandate a particular behavior for better interoperability? Then there's a separate issue about supporting these use cases: If these are required, then there's other ways to spell that without changing Subscribe - and none of these cases have anything to do with subscribing except maybe 3? A QUERY_TRACK (eg: HEAD) message that doesn't alter the subscribe state seems like a more apt vehicle. How to pre-warm a cache seems out of scope. I think we should open a separate issue about this case, and what the relay requirements are (if any) around backpressure. The same thing can happen if a client runs out of streams and doesn't grant new credit in a timely way. Are there obligations to keep the subscription open and/or cache at the relay for some period of time, or can a relay aggressively drop objects or reset subscriptions? A naive relay is going to get itself in big trouble very quickly.
Now that we have TRACKSTATUSREQUEST, can we close this one? The one use case we haven't completely addressed explicitly is cache pre-warming, which is tracked by
LGTMAfter or equals to? (in case one requests a single object)


I'm not totally sure what to do with the special value of 0 for StartObject and EndObject. If StartObject == 0, that's requesting the whole group. But if it's 1, that maps to a first object of zero, which is also consistent with requesting the whole group. For EndObject, the special value makes sense, because the subscriber might not know how many objects there are. There's also the error case where StartObject == 0 and EndObject != 0, or the reverse. IMO it would make more sense for EndObject to have the special value at zero, but not StartObject.
You're correct, this was an editorial error on my part. I did not intend to make StartObject have a special value.


Shared between and
Merging because I don't think this is controversial, and we have two PRs doing the same thing.
This sounds a bit like an extension that just gets forwarded when not understood by a relay? How does APP interact with caches, for example? Can a single QUIC stream have SEGMENT and APP data interleaved? Might be too vague to leave in
The intention was to cache/forward it the same way as media. It's just meant for arbitrary metadata and as a replacement for the current approach of shoving metadata into the media bitstream. For example, in h.264 land it's common to put arbitrary content (non-standardized) in SEI messages. Even if we don't cache/forward the stream, it would still be nice to let the application use unidirectional streams. This basically says prefix the stream with a 2 if it's used by the application instead of Warp. This wouldn't work with relays which is why I had the idea of reusing HEADERS to tell the relay which application streams should be forwarded/cached and how.
Oh and it's missing because there's no encoding yet, but I was envisioning that SEGMENT and APP frames do not specify a length. They would consume the remainder of the stream so there's no way to use them in conjunction.
Is there an MP4 box that we can just use as a part of the layer? That would provide some form of uniformity.
That was my thought too, although it means every container will need some way of specifying application data. MP4 atoms require a length up-front so they would be limited to messages instead of unbounded streams.
the RTCP APP message never turned out to be as useful in RTP as people thought it would be ... that said, fan of being able to send whatever data in the payload. If it happens to be a protobuf app message instead of media, seem like MOQ shoudl not care, it should just deliver the bytes with not much care if they are audio, vidie, haptics, or an app custom media
We need to define the difference of APP message and Media data in terms of what is inside and how it is interacted with caching and dropping. Otherwise there is a risk that the APP message becomes the container for “whatever does not fit in the current RFC“ which renders the standard useless.