Skip to content

Commit 1471317

Browse files
committed
Add MOQ Lite publisher operations
1 parent c5c66a6 commit 1471317

6 files changed

Lines changed: 400 additions & 18 deletions

File tree

.scratch/moq-lite-04-protocol/PRD.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# MOQ Lite 04 protocol layer
22

3-
Status: in-progress
3+
Status: done
44

55
## Problem Statement
66

@@ -171,11 +171,16 @@ Delivered so far:
171171
- Role-neutral subscriber operations on `MOQX.MOQLite04` for
172172
AnnounceInterest, Subscribe, SubscribeUpdate, Fetch, Probe, repeated Probe,
173173
and Goaway transaction streams
174+
- Role-neutral publisher operations on `MOQX.MOQLite04` for accepting
175+
peer-opened streams, sending Announce, SubscribeOk, and SubscribeDrop
176+
responses, and publishing Group plus Frame messages on publisher-created
177+
unidirectional streams
178+
- A client-level support-transport smoke flow that subscribes, accepts the
179+
subscription, sends SubscribeOk, publishes a Group, and delivers the original
180+
Frame payload through public client APIs
174181
- Session tests for Announce, Subscribe, Fetch, Probe, Goaway, Group,
175182
stream lifecycle, support transport normalized events, and the
176183
reference-style subscribe/group/frame smoke flow
177-
- Client/API follow-up issues:
178-
- issue 08: role-neutral publisher operations and smoke flow
179184

180185
## References
181186

.scratch/moq-lite-04-protocol/issues/08-add-role-neutral-moq-lite-publisher-operations.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
# Add role-neutral MOQ Lite 04 publisher operations
44

5-
Status: ready-for-agent
5+
Status: done
66
Type: enhancement
77

88
## Parent
@@ -25,26 +25,25 @@ flow that mirrors the reducer smoke flow from issue 04, but now passes through
2525

2626
## Acceptance criteria
2727

28-
- [ ] Public functions exist on `MOQX.MOQLite04` for sending Announce messages
28+
- [x] Public functions exist on `MOQX.MOQLite04` for sending Announce messages
2929
on an Announce transaction stream.
30-
- [ ] Public functions exist for sending SubscribeOk and SubscribeDrop on a
30+
- [x] Public functions exist for sending SubscribeOk and SubscribeDrop on a
3131
Subscribe transaction stream.
32-
- [ ] Public functions exist for opening a publisher-created Group stream and
32+
- [x] Public functions exist for opening a publisher-created Group stream and
3333
sending Group plus Frame messages.
34-
- [ ] SubscribeDrop before SubscribeOk is rejected through the client API using
34+
- [x] SubscribeDrop before SubscribeOk is rejected through the client API using
3535
the Session reducer's structured error.
36-
- [ ] Duplicate Announce status for one suffix is rejected through the client
36+
- [x] Duplicate Announce status for one suffix is rejected through the client
3737
API using the Session reducer's structured error.
38-
- [ ] Group publishing is rejected unless the peer subscription is active.
39-
- [ ] `connect/2` remains role-neutral; no publisher/subscriber connect mode is
38+
- [x] Group publishing is rejected unless the peer subscription is active.
39+
- [x] `connect/2` remains role-neutral; no publisher/subscriber connect mode is
4040
added.
41-
- [ ] A support-transport smoke test proves one endpoint can subscribe and the
41+
- [x] A support-transport smoke test proves one endpoint can subscribe and the
4242
other can accept the subscription, send SubscribeOk, publish a Group, and
4343
deliver the original Frame payload.
44-
- [ ] The implementation preserves the pure Session reducer boundary and does
44+
- [x] The implementation preserves the pure Session reducer boundary and does
4545
not match raw `quicer` messages.
4646

4747
## Blocked by
4848

4949
- `.scratch/moq-lite-04-protocol/issues/07-add-role-neutral-moq-lite-subscriber-operations.md`
50-

lib/moqx/moq_lite_04.ex

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ defmodule MOQX.MOQLite04 do
7474
| {:timeout, Client.t()}
7575
def recv(client, timeout \\ :infinity), do: Client.recv(client, timeout)
7676

77+
@doc """
78+
Accepts a peer-opened transport stream and starts active delivery.
79+
"""
80+
@spec accept_stream(Client.t(), timeout()) ::
81+
{:ok, Client.t(), MOQX.Transport.Stream.t()}
82+
| {:error, Client.t(), term()}
83+
def accept_stream(client, timeout \\ :infinity), do: Client.accept_stream(client, timeout)
84+
7785
@doc """
7886
Opens an Announce transaction stream and sends `ANNOUNCE_INTEREST`.
7987
"""
@@ -131,6 +139,38 @@ defmodule MOQX.MOQLite04 do
131139
| {:error, Client.t(), term(), [term()]}
132140
def goaway(client, message), do: Client.goaway(client, message)
133141

142+
@doc """
143+
Sends `ANNOUNCE` on an existing Announce transaction stream.
144+
"""
145+
@spec announce(Client.t(), MOQX.Transport.Stream.t(), Announce.t()) ::
146+
{:ok, Client.t(), MOQX.Transport.Stream.t(), [term()]}
147+
| {:error, Client.t(), term(), [term()]}
148+
def announce(client, stream, message), do: Client.announce(client, stream, message)
149+
150+
@doc """
151+
Sends `SUBSCRIBE_OK` on an existing Subscribe transaction stream.
152+
"""
153+
@spec subscribe_ok(Client.t(), MOQX.Transport.Stream.t(), SubscribeOk.t()) ::
154+
{:ok, Client.t(), MOQX.Transport.Stream.t(), [term()]}
155+
| {:error, Client.t(), term(), [term()]}
156+
def subscribe_ok(client, stream, message), do: Client.subscribe_ok(client, stream, message)
157+
158+
@doc """
159+
Sends `SUBSCRIBE_DROP` on an existing Subscribe transaction stream.
160+
"""
161+
@spec subscribe_drop(Client.t(), MOQX.Transport.Stream.t(), SubscribeDrop.t()) ::
162+
{:ok, Client.t(), MOQX.Transport.Stream.t(), [term()]}
163+
| {:error, Client.t(), term(), [term()]}
164+
def subscribe_drop(client, stream, message), do: Client.subscribe_drop(client, stream, message)
165+
166+
@doc """
167+
Opens a publisher Group stream and sends `GROUP` followed by `FRAME` messages.
168+
"""
169+
@spec publish_group(Client.t(), Group.t(), [Frame.t()]) ::
170+
{:ok, Client.t(), MOQX.Transport.Stream.t(), [term()]}
171+
| {:error, Client.t(), term(), [term()]}
172+
def publish_group(client, group, frames), do: Client.publish_group(client, group, frames)
173+
134174
@doc """
135175
Returns the draft-04 numeric stream type ID for a known stream type.
136176
"""

lib/moqx/moq_lite_04/client.ex

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ defmodule MOQX.MOQLite04.Client do
5757
@type operation_result ::
5858
{:ok, t(), Stream.t(), [term()]} | {:error, t(), term(), [term()]}
5959

60+
@type accept_result :: {:ok, t(), Stream.t()} | {:error, t(), term()}
61+
6062
@doc """
6163
Connects to a native QUIC MOQ Lite draft-04 endpoint.
6264
"""
@@ -136,6 +138,27 @@ defmodule MOQX.MOQLite04.Client do
136138
end
137139
end
138140

141+
@doc """
142+
Accepts a peer-opened stream and starts active transport delivery.
143+
"""
144+
@spec accept_stream(t(), timeout()) :: accept_result()
145+
def accept_stream(%__MODULE__{} = client, timeout \\ :infinity) do
146+
with {:ok, stream, context} <-
147+
Transport.accept_stream(client.context, client.connection, [], timeout),
148+
{:ok, context} <- Transport.set_active(context, stream, true) do
149+
{:ok, put_context(client, context), stream}
150+
else
151+
{:error, reason, context} ->
152+
error =
153+
Error.new(:transport_action_failed,
154+
action: :accept_stream,
155+
details: %{transport_reason: reason}
156+
)
157+
158+
{:error, put_context(client, context), error}
159+
end
160+
end
161+
139162
@doc """
140163
Opens an Announce transaction stream and sends `ANNOUNCE_INTEREST`.
141164
"""
@@ -196,6 +219,43 @@ defmodule MOQX.MOQLite04.Client do
196219
open_transaction(client, :goaway, message)
197220
end
198221

222+
@doc """
223+
Sends `ANNOUNCE` on an existing Announce transaction stream.
224+
"""
225+
@spec announce(t(), Stream.t(), MOQLite04.Announce.t()) :: operation_result()
226+
def announce(%__MODULE__{} = client, %Stream{} = stream, %MOQLite04.Announce{} = message) do
227+
send_on_transaction(client, stream, :announce, message)
228+
end
229+
230+
@doc """
231+
Sends `SUBSCRIBE_OK` on an existing Subscribe transaction stream.
232+
"""
233+
@spec subscribe_ok(t(), Stream.t(), MOQLite04.SubscribeOk.t()) :: operation_result()
234+
def subscribe_ok(%__MODULE__{} = client, %Stream{} = stream, %MOQLite04.SubscribeOk{} = message) do
235+
send_on_transaction(client, stream, :subscribe, message)
236+
end
237+
238+
@doc """
239+
Sends `SUBSCRIBE_DROP` on an existing Subscribe transaction stream.
240+
"""
241+
@spec subscribe_drop(t(), Stream.t(), MOQLite04.SubscribeDrop.t()) :: operation_result()
242+
def subscribe_drop(
243+
%__MODULE__{} = client,
244+
%Stream{} = stream,
245+
%MOQLite04.SubscribeDrop{} = message
246+
) do
247+
send_on_transaction(client, stream, :subscribe, message)
248+
end
249+
250+
@doc """
251+
Opens a publisher Group stream and sends `GROUP` followed by `FRAME` messages.
252+
"""
253+
@spec publish_group(t(), MOQLite04.Group.t(), [MOQLite04.Frame.t()]) :: operation_result()
254+
def publish_group(%__MODULE__{} = client, %MOQLite04.Group{} = group, frames)
255+
when is_list(frames) do
256+
open_group_stream(client, [group | frames])
257+
end
258+
199259
defp reject_mode(opts) do
200260
if Keyword.has_key?(opts, :mode) do
201261
{:error, {:unsupported_option, :mode}}
@@ -279,8 +339,30 @@ defmodule MOQX.MOQLite04.Client do
279339
end
280340
end
281341

342+
defp open_group_stream(%__MODULE__{} = client, messages) do
343+
case Transport.open_stream(client.context, client.connection, direction: :unidirectional) do
344+
{:ok, stream, context} ->
345+
client
346+
|> put_context(context)
347+
|> send_on_stream(stream, :group, messages)
348+
349+
{:error, reason, context} ->
350+
error =
351+
Error.new(:transport_action_failed,
352+
action: {:open_stream, :group},
353+
details: %{transport_reason: reason}
354+
)
355+
356+
{:error, put_context(client, context), error, []}
357+
end
358+
end
359+
282360
defp send_on_transaction(%__MODULE__{} = client, %Stream{} = stream, stream_type, message) do
283-
case command(client, {:send, stream, stream_type, [message]}) do
361+
send_on_stream(client, stream, stream_type, [message])
362+
end
363+
364+
defp send_on_stream(%__MODULE__{} = client, %Stream{} = stream, stream_type, messages) do
365+
case command(client, {:send, stream, stream_type, messages}) do
284366
{:ok, client, events} -> {:ok, client, stream, events}
285367
{:error, client, reason, events} -> {:error, client, reason, events}
286368
end

0 commit comments

Comments
 (0)