Skip to content

Commit 5207ed7

Browse files
committed
Complete MOQT draft-16 subscriber lifecycle
1 parent 465674e commit 5207ed7

18 files changed

Lines changed: 1500 additions & 108 deletions

README.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,41 @@ end
106106
```
107107

108108
This path negotiates ALPN `moqt-16`, sends native-QUIC `PATH` and `AUTHORITY`
109-
setup parameters, and decodes draft-16 subgroup objects. The catalog payload is
110-
currently delivered as `ObjectReceived`; CMSF catalog parsing is tracked
111-
separately.
109+
setup parameters, and decodes draft-16 subgroup streams and object datagrams.
110+
Objects preserve extension headers and end-of-group metadata. The catalog
111+
payload is currently delivered as `ObjectReceived`; CMSF catalog parsing is
112+
tracked separately.
113+
114+
Draft-16 also accepts the complete protocol-neutral filter model:
115+
116+
```elixir
117+
filter = %MOQX.SubscriptionFilter{
118+
type: :absolute_range,
119+
start_location: {12, 4},
120+
end_group: 20
121+
}
122+
123+
{:ok, subscription} =
124+
MOQX.subscribe(client, track,
125+
filter: filter,
126+
priority: 127,
127+
group_order: :ascending,
128+
delivery_timeout: 5_000
129+
)
130+
131+
:ok =
132+
MOQX.update_subscription(client, subscription,
133+
start: :next_group,
134+
priority: 64
135+
)
136+
```
137+
138+
The relative `:start` policies remain the portable API shared with Cloudflare.
139+
Absolute start/range filters, request updates, datagrams, and accepted
140+
subscription parameters are currently implemented by `:draft_16`. Update
141+
success and rejection arrive as `SubscriptionUpdated` and
142+
`SubscriptionUpdateFailed`; an update rejection leaves the subscription
143+
active.
112144

113145
Catalog tracks can be subscribed directly. Delivered objects retain their
114146
subscription, group, subgroup, object, and priority coordinates:
@@ -156,6 +188,10 @@ ffmpeg -v error -f h264 -i /tmp/cloudflare-bbb.h264 -f null -
156188
only after every stream advertised by `PUBLISH_DONE` has been processed or the
157189
subscription's `:delivery_timeout` has elapsed.
158190

191+
Objects are emitted in transport arrival order. No global ordering across
192+
independent subgroup streams is claimed until
193+
[issue #29](https://github.com/dmorn/moqx/issues/29) records that policy.
194+
159195
All application-facing output uses typed `MOQX.Event.*` structs inside the
160196
stable `{:moqx, client, event}` envelope. By default events go to the process
161197
that calls `MOQX.connect/2`; shared connection owners can choose a router:

docs/adr/0010-compose-versioned-wire-packages-into-explicit-protocol-implementations.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,18 @@ explicit protocol-selection decision.
2222

2323
The second active implementation is standard MOQT draft-16, selected as
2424
`:draft_16` and implemented by `MOQX.Protocol.Draft16`. It coexists with
25-
Cloudflare draft-14 rather than replacing it. The first landed slice covers
26-
native QUIC ALPN `moqt-16`, draft-16 setup with `PATH` and `AUTHORITY`,
27-
subscription using relative start policies, subscription acceptance/error, and
28-
subgroup object delivery through the existing typed public API.
25+
Cloudflare draft-14 rather than replacing it. Its subscriber path covers native
26+
QUIC ALPN `moqt-16`, strict draft-16 setup and request credit, all four
27+
protocol-neutral subscription filters, request updates, subscription
28+
acceptance/error/completion, subgroup streams, object datagrams, extension
29+
preservation, and stream draining through the existing typed public API.
2930

3031
The normative wire reference is
3132
`draft-ietf-moq-transport-16`. Interoperability behavior is checked against
3233
Moqtail's `draft-16` branch pinned at
3334
`c2ff7253479c6a0d7c8282a1cad289d591ebc302` and its public
34-
`relay.moqtail.dev` endpoint. CMSF catalog decoding and draft-16 publication
35-
remain separate incremental work.
35+
`relay.moqtail.dev` endpoint. CMSF catalog decoding, cross-stream ordering
36+
policy, and draft-16 publication remain separate incremental work.
3637

3738
## Context
3839

@@ -150,9 +151,10 @@ Implementation namespaces follow this shape:
150151
- a future MOQ Lite implementation under `MOQX.Protocol.MOQLite04`.
151152

152153
Cloudflare draft-14 has subscriber and publisher support. Standard draft-16
153-
currently has its first subscriber slice. Concrete implementations are
154-
independent; version and deployment differences must not accumulate as
155-
conditionals in one global state machine.
154+
has a complete subscriber lifecycle, subject to the separate cross-stream
155+
ordering decision. Concrete implementations are independent; version and
156+
deployment differences must not accumulate as conditionals in one global state
157+
machine.
156158

157159
### Versioned wire packages
158160

@@ -270,8 +272,8 @@ namespace publication. Secret lookup, issuance, permission, and rotation stay
270272
outside the library. Secret values and sensitive encoded actions have redacted
271273
inspection and are unwrapped only at the driver's transport-send boundary.
272274

273-
Datagram delivery and other protocol implementations remain incremental work
274-
behind the same boundaries.
275+
Other protocol implementations remain incremental work behind the same
276+
boundaries.
275277

276278
## Consequences
277279

lib/moqx.ex

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,21 @@ defmodule MOQX do
2222
@typedoc "Option accepted by `subscribe/3`."
2323
@type subscription_option ::
2424
{:start, subscription_start()}
25+
| {:filter, MOQX.SubscriptionFilter.t()}
2526
| {:priority, 0..255}
26-
| {:delivery_timeout, non_neg_integer()}
27+
| {:group_order, :ascending | :descending}
28+
| {:delivery_timeout, pos_integer()}
29+
| {:parameters, [MOQX.SubscriptionParameter.t()]}
30+
31+
@typedoc "Option accepted by `update_subscription/3`."
32+
@type subscription_update_option ::
33+
{:start, subscription_start()}
34+
| {:filter, MOQX.SubscriptionFilter.t()}
35+
| {:priority, 0..255}
36+
| {:delivery_timeout, pos_integer()}
37+
| {:forward, boolean()}
38+
| {:new_group, non_neg_integer()}
39+
| {:parameters, [MOQX.SubscriptionParameter.t()]}
2740

2841
@doc "Returns the default native QUIC transport implementation."
2942
@spec transport() :: module()
@@ -63,6 +76,17 @@ defmodule MOQX do
6376
ConnectionDriver.subscribe(client, track, options)
6477
end
6578

79+
@doc "Updates an active subscription's draft-neutral filter and delivery parameters."
80+
@spec update_subscription(
81+
MOQX.Client.t(),
82+
MOQX.Subscription.t(),
83+
[subscription_update_option()]
84+
) ::
85+
:ok | {:error, term()}
86+
def update_subscription(client, subscription, options) do
87+
ConnectionDriver.update_subscription(client, subscription, options)
88+
end
89+
6690
@doc "Ends an active subscription and sends the selected protocol's unsubscribe message."
6791
@spec unsubscribe(MOQX.Client.t(), MOQX.Subscription.t()) :: :ok | {:error, term()}
6892
def unsubscribe(client, subscription) do

lib/moqx/event.ex

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ defmodule MOQX.Event do
33

44
@type t ::
55
MOQX.Event.SubscriptionAccepted.t()
6+
| MOQX.Event.SubscriptionUpdated.t()
7+
| MOQX.Event.SubscriptionUpdateFailed.t()
68
| MOQX.Event.SubscriptionFailed.t()
79
| MOQX.Event.SubscriptionDone.t()
810
| MOQX.Event.ObjectReceived.t()
@@ -19,6 +21,24 @@ defmodule MOQX.Event do
1921
| MOQX.Event.ProtocolFailed.t()
2022
end
2123

24+
defmodule MOQX.Event.SubscriptionUpdated do
25+
@moduledoc "The relay accepted a subscription parameter update."
26+
@enforce_keys [:subscription]
27+
defstruct [:subscription, parameters: []]
28+
29+
@type t :: %__MODULE__{
30+
subscription: MOQX.Subscription.t(),
31+
parameters: [MOQX.SubscriptionParameter.t()]
32+
}
33+
end
34+
35+
defmodule MOQX.Event.SubscriptionUpdateFailed do
36+
@moduledoc "The relay rejected a subscription update while leaving the subscription active."
37+
@enforce_keys [:subscription, :error]
38+
defstruct [:subscription, :error]
39+
@type t :: %__MODULE__{subscription: MOQX.Subscription.t(), error: MOQX.ProtocolError.t()}
40+
end
41+
2242
defmodule MOQX.Event.PublicationSubscriptionRequested do
2343
@moduledoc "An inbound publisher subscription is waiting for an application decision."
2444
@enforce_keys [:request]
@@ -41,8 +61,13 @@ end
4161
defmodule MOQX.Event.SubscriptionAccepted do
4262
@moduledoc "The relay accepted a subscription."
4363
@enforce_keys [:subscription]
44-
defstruct [:subscription]
45-
@type t :: %__MODULE__{subscription: MOQX.Subscription.t()}
64+
defstruct [:subscription, parameters: [], track_extensions: []]
65+
66+
@type t :: %__MODULE__{
67+
subscription: MOQX.Subscription.t(),
68+
parameters: [MOQX.SubscriptionParameter.t()],
69+
track_extensions: [MOQX.Extension.t()]
70+
}
4671
end
4772

4873
defmodule MOQX.Event.SubscriptionFailed do

lib/moqx/extension.ex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
defmodule MOQX.Extension do
2+
@moduledoc "Protocol-specific extension information retained losslessly at a public boundary."
3+
4+
@enforce_keys [:protocol, :identifier, :value]
5+
defstruct [:protocol, :identifier, :value]
6+
7+
@type t :: %__MODULE__{
8+
protocol: atom(),
9+
identifier: non_neg_integer(),
10+
value: non_neg_integer() | binary()
11+
}
12+
end

lib/moqx/object.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ defmodule MOQX.Object do
1515
:object_id,
1616
:publisher_priority,
1717
:status,
18+
:extensions,
19+
:end_of_group?,
1820
:payload
1921
]
2022

@@ -25,6 +27,8 @@ defmodule MOQX.Object do
2527
object_id: non_neg_integer(),
2628
publisher_priority: 0..255 | nil,
2729
status: :object_does_not_exist | :end_of_group | :end_of_track | nil,
30+
extensions: [MOQX.Extension.t()] | nil,
31+
end_of_group?: boolean() | nil,
2832
payload: binary()
2933
}
3034
end

lib/moqx/operation.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ defmodule MOQX.Operation do
77

88
@type t ::
99
Subscribe.t()
10+
| UpdateSubscription.t()
1011
| Unsubscribe.t()
1112
| Publish.t()
1213
| AddTrack.t()
@@ -34,6 +35,15 @@ defmodule MOQX.Operation do
3435
@type t :: %__MODULE__{subscription: term()}
3536
end
3637

38+
defmodule UpdateSubscription do
39+
@moduledoc "Updates the parameters or object boundary of an active subscription."
40+
41+
@enforce_keys [:subscription]
42+
defstruct [:subscription, options: []]
43+
44+
@type t :: %__MODULE__{subscription: MOQX.Subscription.t(), options: keyword()}
45+
end
46+
3747
defmodule Publish do
3848
@moduledoc "Advertises one application-level track namespace."
3949

0 commit comments

Comments
 (0)