Datagram vs. stream delivery
MoQT Objects can travel over two different wire shapes with different guarantees. This page sends one Object as a QUIC DATAGRAM (lossy, no retry, bounded by the connection’s own datagram size) and a bigger one as a stream (reliable, and chunked at a 16 KiB per-Object cap).
Code
#define WIRED_MAIN#include "wired.h"
/* Bigger than MOQDATA_BLOB_CHUNK (16384), so the stream delivery below * demonstrates the per-Object cap by arriving as two Objects, not one. */#define BLOB_LEN 20000static u8 g_blob[BLOB_LEN];static u8 g_wire[MOQDATA_BLOB_WIRE_CAP(BLOB_LEN)];
/* Any datagram from the client is just a "go ahead" trigger (content * unchecked): send one lossy OBJECT_DATAGRAM (draft-ietf-moq-transport-22 * 11.2.1), then one reliable Object stream (11.3.1) whose blob exceeds the * 16 KiB per-Object cap and so arrives chunked into two Objects. */static void on_datagram(void* ctx, wired_wt_session* s, wired_span data) { (void)ctx; (void)data;
u8 dg[32]; usz off = 0; moqdg_obj o = {0}; o.track_alias = 2; o.payload = wired_span_cstr("ping-obj"); if (moqdg_put(wired_mspan_of(dg, sizeof dg), &off, &o) == MOQDATA_OK) wired_server_wt_send_datagram_to(s, wired_span_of(dg, off));
usz n = moqdata_blob_build( wired_mspan_of(g_wire, sizeof g_wire), 1, wired_span_of(g_blob, BLOB_LEN)); if (n) wired_moqraw_io().send_uni(s, wired_span_of(g_wire, n));}
int wired_main(int argc, char** argv) { /* The same fixed demo identity as the HTTP/3 hello page. */ static wired_srvboot_demo_keys keys; wired_srvboot_id id; wired_srvboot_demo(&id, &keys, 0x50, "guide-md"); id.max_datagram_frame_size = 65535; /* WebTransport requires DATAGRAM */
for (usz i = 0; i < BLOB_LEN; i++) g_blob[i] = (u8)('a' + i % 26);
/* No hub here, so no SETUP or requests: the "moqt-22" subprotocol only * says the session speaks draft-22's data plane. */ wired_srvrun_opt opt = {0}; opt.incoming_cpu = -1; opt.wt_protocols = "moqt-22"; opt.wt_on_datagram = on_datagram;
u16 port = (u16)wired_cliargs_int(argc, argv, "--port", 4433); wired_srvrun_handler h = {0}; wired_srvrun_obs obs = {0}; return wired_server_run_opt(port, &id, h, obs, &opt) ? 0 : 1;}Client
package main
import ( "bufio" "bytes" "context" "fmt" "io" "os" "time"
"wired-guide/moqtclient")
func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // A "moqt-22" WebTransport session; this server runs no hub, so no // SETUP or request follows -- only the data plane. s, conn, status := moqtclient.DialWT(ctx, os.Args[1]+"/deliver") fmt.Println("status", status)
// A "go" datagram tells the server to send one of each delivery mode. moqtclient.Check(s.SendDatagram([]byte("go")))
// Lossy: one OBJECT_DATAGRAM, bounded by the connection's own QUIC // datagram size, dropped (never retried) if the network loses it. dg, err := s.ReceiveDatagram(ctx) moqtclient.Check(err) dr := bufio.NewReader(bytes.NewReader(dg)) moqtclient.Varint(dr) // Type 0x00: Object ID and priority present, no properties, payload alias := moqtclient.Varint(dr) group := moqtclient.Varint(dr) objID := moqtclient.Varint(dr) _, err = dr.ReadByte() // raw Publisher Priority byte, not a varint moqtclient.Check(err) payload, err := io.ReadAll(dr) moqtclient.Check(err) fmt.Printf("datagram alias=%d group=%d id=%d payload=%q\n", alias, group, objID, payload)
// Reliable: one Object stream, its 20000-byte blob split at the 16 KiB // per-Object cap into two Objects that arrive complete, in order. uni, err := s.AcceptUniStream(ctx) moqtclient.Check(err) ur := bufio.NewReader(uni) salias, sgroup, obj1 := moqtclient.ReadObject(ur) moqtclient.Varint(ur) // second Object's ID delta obj2 := make([]byte, moqtclient.Varint(ur)) _, err = io.ReadFull(ur, obj2) moqtclient.Check(err) fmt.Printf("stream alias=%d group=%d object_1=%d bytes object_2=%d bytes total=%d bytes\n", salias, sgroup, len(obj1), len(obj2), len(obj1)+len(obj2))
conn.CloseWithError(0, "")}Output
$ ./moqt-delivery --port 4433conn closed by peer (CONNECTION_CLOSE)$ go run . https://127.0.0.1:4433status 200datagram alias=2 group=0 id=0 payload="ping-obj"stream alias=1 group=0 object_1=16384 bytes object_2=3616 bytes total=20000 bytesHow it works
Section titled “How it works”moqdg_putencodes one OBJECT_DATAGRAM (draft-ietf-moq-transport-22 11.2.1) directly;wired_server_wt_send_datagram_toqueues it as a QUIC DATAGRAM, no stream and no retransmission involved.moqdata_blob_buildframes a blob as one SUBGROUP_HEADER followed by Objects of at mostMOQDATA_BLOB_CHUNK(16384) bytes each (draft-ietf-moq-transport-22 11.3.1). This page’s blob is 20000 bytes, so it comes out as two Objects: 16384 bytes, then the remaining 3616.wired_server_wt_open_unithen sends the whole framed stream reliably, FIN included.- The client’s
go runoutput shows both: the datagram arrives whole as a single payload, while the stream arrives asobject_1andobject_2whose lengths add back up to the original 20000 bytes. - This page builds the Objects directly with
moqdata/moqdg, bypassingwired_moqt_hub: there is no PUBLISH/SUBSCRIBE control-plane exchange here, just the two WebTransport delivery primitives the hub itself is built on. See Publish a track for the hub’s own higher-level API.