Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ae5b4aa
Updated CHANGELOG
lminiero Mar 3, 2026
b40d343
Advertise MoQ v17 support
lminiero Mar 3, 2026
876d09c
Added methods for reading and writing MoQ varints
lminiero Mar 3, 2026
06b1538
Removed support for all MoQ versions older than v16 (only leaves v16 …
lminiero Mar 4, 2026
5d80d6a
Renamed Setup Parameters to Setup Options
lminiero Mar 4, 2026
02b8455
Renamed Extension headers to Properties
lminiero Mar 4, 2026
72ba023
Added timeout to GOAWAY
lminiero Mar 4, 2026
ed56138
Added new error codes
lminiero Mar 4, 2026
e5558fe
Added new error code
lminiero Mar 4, 2026
b509991
Use the right varint implementation depending on the negotiated version
lminiero Mar 4, 2026
39600d3
Added hooks to RESET_STREAM and STOP_SENDING (WIP)
lminiero Mar 5, 2026
c7bbb62
Split bidirectional control stream in unidirectional control streams
lminiero Mar 5, 2026
78b70be
Buffer STREAM data if MoQ connection hasnt been SETUP yet
lminiero Mar 6, 2026
dc0865c
Moved requests to bidirectional streams (WIP)
lminiero Mar 6, 2026
38db87a
Added Required Request ID Delta to API
lminiero Mar 6, 2026
5f43dd9
Added new message PUBLISH_BLOCKED
lminiero Mar 6, 2026
b460c2e
Implemented new format of Message Parameters, and added RENDEZVOUS_TI…
lminiero Mar 6, 2026
4f7e5d9
Fixed broken REQUEST_UPDATE
lminiero Mar 9, 2026
0dd7e82
Use ID from the reserved space, for test properties in moq-pub
lminiero Mar 9, 2026
6d391a4
Added support for GREASE, and used in for SETUP options
lminiero Mar 9, 2026
52cfbc8
Close with a PROTOCOL_VIOLATION if parsing parameters fails
lminiero Mar 9, 2026
4e40278
Fixed parsing of request parameters
lminiero Mar 9, 2026
e9d530c
Updated SUBSCRIPTION_FILTER to use deltas for end group
lminiero Mar 9, 2026
b926e93
Some more actions on streams when interacting with the API
lminiero Mar 9, 2026
7410ef3
Allow SUBSCRIBE_NAMESPACE to have an empty namespace tuple
lminiero Mar 10, 2026
5b10431
Fixed debug line in demo
lminiero Mar 10, 2026
3e5b0a0
Fixed state management for NAMESPACE events
lminiero Mar 10, 2026
6c94bf1
Relay demo now honors SUBSCRIBE_NAMESPACE with zero namespace fields
lminiero Mar 10, 2026
b6b3754
Fixed missing lock
lminiero Mar 10, 2026
3a15fd1
Use reference counters for MoQ streams
lminiero Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
All notable changes to this project will be documented in this file.


## [v0.0.1] - 2024-XX-XX
## [v0.0.1] - 2026-03-03

- First alpha release
- First alpha release (homemade QUIC stack)

## [v0.0.2] - XXXX-XX-XX

- Switched to picoquic as the underlying QUIC stack
- Added support for external logging functions
45 changes: 16 additions & 29 deletions examples/moq-interop-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ typedef struct imquic_moq_interop_client {
imquic_client *client;
imquic_connection *conn;
gboolean publisher;
uint64_t request_id;
} imquic_moq_interop_client;
static void imquic_moq_interop_client_destroy(imquic_moq_interop_client *mc) {
if(mc != NULL) {
Expand Down Expand Up @@ -136,18 +137,17 @@ static imquic_moq_interop_client *imquic_moq_interop_client_create(imquic_moq_in

/* Callbacks */
static void imquic_moq_interop_new_connection(imquic_connection *conn, void *user_data);
static void imquic_moq_interop_connection_failed(void *user_data);
static void imquic_moq_interop_ready(imquic_connection *conn);
static void imquic_moq_interop_publish_namespace_accepted(imquic_connection *conn, uint64_t request_id,
imquic_moq_request_parameters *parameters);
static void imquic_moq_interop_publish_namespace_error(imquic_connection *conn, uint64_t request_id,
imquic_moq_request_error_code error_code, const char *reason, uint64_t retry_interval);
static void imquic_moq_interop_incoming_subscribe(imquic_connection *conn, uint64_t request_id,
uint64_t track_alias, imquic_moq_namespace *tns, imquic_moq_name *tn, imquic_moq_request_parameters *parameters);
static void imquic_moq_interop_incoming_subscribe(imquic_connection *conn, uint64_t request_id, uint64_t required_id_delta,
imquic_moq_namespace *tns, imquic_moq_name *tn, imquic_moq_request_parameters *parameters);
static void imquic_moq_interop_subscribe_accepted(imquic_connection *conn, uint64_t request_id,
uint64_t track_alias, imquic_moq_request_parameters *parameters, GList *track_extensions);
uint64_t track_alias, imquic_moq_request_parameters *parameters, GList *track_properties);
static void imquic_moq_interop_subscribe_error(imquic_connection *conn, uint64_t request_id,
imquic_moq_request_error_code error_code, const char *reason, uint64_t track_alias, uint64_t retry_interval);
imquic_moq_request_error_code error_code, const char *reason, uint64_t retry_interval);
static void imquic_moq_interop_connection_gone(imquic_connection *conn);

/* Main */
Expand Down Expand Up @@ -454,7 +454,6 @@ static imquic_moq_interop_client *imquic_moq_interop_client_create(imquic_moq_in
return NULL;
}
imquic_set_new_moq_connection_cb(mc->client, imquic_moq_interop_new_connection);
imquic_set_connection_failed_cb(mc->client, imquic_moq_interop_connection_failed);
imquic_set_moq_ready_cb(mc->client, imquic_moq_interop_ready);
imquic_set_moq_connection_gone_cb(mc->client, imquic_moq_interop_connection_gone);
if(publisher) {
Expand All @@ -481,14 +480,6 @@ static void imquic_moq_interop_new_connection(imquic_connection *conn, void *use
imquic_moq_set_max_request_id(conn, max_request_id);
}

static void imquic_moq_interop_connection_failed(void *user_data) {
imquic_moq_interop_client *client = (imquic_moq_interop_client *)user_data;
if(client != NULL) {
imquic_moq_interop_test_context *test = (imquic_moq_interop_test_context *)client->test;
g_atomic_int_set(&test->done, 1);
}
}

static void imquic_moq_interop_ready(imquic_connection *conn) {
/* Depending on the test, we may or may not be done */
imquic_mutex_lock(&mutex);
Expand Down Expand Up @@ -525,7 +516,8 @@ static void imquic_moq_interop_ready(imquic_connection *conn) {
tns[1].buffer = (uint8_t *)"interop";
tns[1].length = strlen("interop");
tns[1].next = NULL;
imquic_moq_publish_namespace(conn, imquic_moq_get_next_request_id(conn), &tns[0], NULL);
client->request_id = imquic_moq_get_next_request_id(conn);
imquic_moq_publish_namespace(conn, client->request_id, 0, &tns[0], NULL);
if(verbose)
test->subtests = g_list_append(test->subtests, g_strdup("publisher announced namespace"));
} else if(test->name == IMQUIC_INTEROP_SUBSCRIBE_ERROR) {
Expand All @@ -541,7 +533,8 @@ static void imquic_moq_interop_ready(imquic_connection *conn) {
.buffer = (uint8_t *)"test-track",
.length = strlen("test-track")
};
imquic_moq_subscribe(conn, imquic_moq_get_next_request_id(conn), 0, &tns[0], &tn, NULL);
client->request_id = imquic_moq_get_next_request_id(conn);
imquic_moq_subscribe(conn, client->request_id, 0, &tns[0], &tn, NULL);
if(verbose)
test->subtests = g_list_append(test->subtests, g_strdup("subscriber subscribed to non-existing track"));
} else if((test->name == IMQUIC_INTEROP_ANNOUNCE_SUBSCRIBE ||
Expand All @@ -558,7 +551,8 @@ static void imquic_moq_interop_ready(imquic_connection *conn) {
.buffer = (uint8_t *)"test-track",
.length = strlen("test-track")
};
imquic_moq_subscribe(conn, imquic_moq_get_next_request_id(conn), 0, &tns[0], &tn, NULL);
client->request_id = imquic_moq_get_next_request_id(conn);
imquic_moq_subscribe(conn, client->request_id, 0, &tns[0], &tn, NULL);
if(verbose)
test->subtests = g_list_append(test->subtests, g_strdup("subscriber subscribed to track"));
if(test->name == IMQUIC_INTEROP_SUBSCRIBE_BEFORE_ANNOUNCE) {
Expand All @@ -584,14 +578,7 @@ static void imquic_moq_interop_publish_namespace_accepted(imquic_connection *con
g_atomic_int_set(&test->done, 1);
} else if(test->name == IMQUIC_INTEROP_PUBLISH_NAMESPACE_DONE) {
/* Send a PUBLISH_NAMESPACE_DONE */
imquic_moq_namespace tns[2];
tns[0].buffer = (uint8_t *)"moq-test";
tns[0].length = strlen("moq-test");
tns[0].next = &tns[1];
tns[1].buffer = (uint8_t *)"interop";
tns[1].length = strlen("interop");
tns[1].next = NULL;
int ret = imquic_moq_publish_namespace_done(conn, &tns[0]);
int ret = imquic_moq_publish_namespace_done(conn, client->request_id);
if(ret == 0) {
g_atomic_int_set(&test->success, 1);
if(verbose)
Expand Down Expand Up @@ -629,8 +616,8 @@ static void imquic_moq_interop_publish_namespace_error(imquic_connection *conn,
/* TODO Other tests */
}

static void imquic_moq_interop_incoming_subscribe(imquic_connection *conn, uint64_t request_id,
uint64_t track_alias, imquic_moq_namespace *tns, imquic_moq_name *tn, imquic_moq_request_parameters *parameters) {
static void imquic_moq_interop_incoming_subscribe(imquic_connection *conn, uint64_t request_id, uint64_t required_id_delta,
imquic_moq_namespace *tns, imquic_moq_name *tn, imquic_moq_request_parameters *parameters) {
/* Depending on the test, we may or may not be done */
imquic_mutex_lock(&mutex);
imquic_moq_interop_client *client = (imquic_moq_interop_client *)g_hash_table_lookup(connections, conn);
Expand All @@ -647,7 +634,7 @@ static void imquic_moq_interop_incoming_subscribe(imquic_connection *conn, uint6
}

static void imquic_moq_interop_subscribe_accepted(imquic_connection *conn, uint64_t request_id,
uint64_t track_alias, imquic_moq_request_parameters *parameters, GList *track_extensions) {
uint64_t track_alias, imquic_moq_request_parameters *parameters, GList *track_properties) {
/* Depending on the test, we may or may not be done */
imquic_mutex_lock(&mutex);
imquic_moq_interop_client *client = (imquic_moq_interop_client *)g_hash_table_lookup(connections, conn);
Expand All @@ -672,7 +659,7 @@ static void imquic_moq_interop_subscribe_accepted(imquic_connection *conn, uint6
}

static void imquic_moq_interop_subscribe_error(imquic_connection *conn, uint64_t request_id,
imquic_moq_request_error_code error_code, const char *reason, uint64_t track_alias, uint64_t retry_interval) {
imquic_moq_request_error_code error_code, const char *reason, uint64_t retry_interval) {
/* Depending on the test, we may or may not be done */
imquic_mutex_lock(&mutex);
imquic_moq_interop_client *client = (imquic_moq_interop_client *)g_hash_table_lookup(connections, conn);
Expand Down
8 changes: 4 additions & 4 deletions examples/moq-pub-options.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ static GOptionContext *opts = NULL;
gboolean demo_options_parse(demo_options *options, int argc, char *argv[]) {
/* Supported command-line arguments */
GOptionEntry opt_entries[] = {
{ "moq-draft-version", 'M', 0, G_OPTION_ARG_STRING, &options->moq_version, "MoQ draft version number to negotiate (default=any)", "<number>|any|legacy" },
{ "moq-draft-version", 'M', 0, G_OPTION_ARG_STRING, &options->moq_version, "MoQ draft version number to negotiate (default=any)", "<number>|any" },
{ "track-namespace", 'n', 0, G_OPTION_ARG_STRING_ARRAY, &options->track_namespace, "MoQ track namespace to publish (can be called multiple times to create a tuple; default=none)", "namespace" },
{ "track-name", 'N', 0, G_OPTION_ARG_STRING, &options->track_name, "MoQ track name to publish (default=none)", "name" },
{ "first-group", 'f', 0, G_OPTION_ARG_INT64, &options->first_group, "First group ID to send (default=0; sends 'Prior Group ID Gap' extension for the first sent object in that group, if set))", "group_id" },
{ "first-object", 'F', 0, G_OPTION_ARG_INT64, &options->first_object, "First object ID to send (default=0; sends 'Prior Object ID Gap' extension for the first sent object in that group, if set))", "object_id" },
{ "first-group", 'f', 0, G_OPTION_ARG_INT64, &options->first_group, "First group ID to send (default=0; sends 'Prior Group ID Gap' property for the first sent object in that group, if set))", "group_id" },
{ "first-object", 'F', 0, G_OPTION_ARG_INT64, &options->first_object, "First object ID to send (default=0; sends 'Prior Object ID Gap' property for the first sent object in that group, if set))", "object_id" },
{ "relay-auth-info", 'a', 0, G_OPTION_ARG_STRING, &options->relay_auth_info, "Auth info required to connect to the relay, if any (default=none)", "string" },
{ "auth-info", 'A', 0, G_OPTION_ARG_STRING, &options->auth_info, "Auth info to publish_namespace/publish, if needed (default=none)", "string" },
{ "delivery", 'D', 0, G_OPTION_ARG_STRING, &options->delivery, "How MoQ objects should be sent (default=subgroup; supported=datagram,subgroup)", "type" },
{ "publish", 'X', 0, G_OPTION_ARG_NONE, &options->publish, "Use a PUBLISH right away instead of waiting for a SUBSCRIBE (default=no; only supported for v12 and later)", NULL },
{ "track-alias", 't', 0, G_OPTION_ARG_INT64, &options->track_alias, "Track alias to use for subscriptions (default=0; only supported for v12 and later)", NULL },
{ "extensions", 'x', 0, G_OPTION_ARG_NONE, &options->extensions, "Send some extensions along objects (default=no)", NULL },
{ "properties", 'x', 0, G_OPTION_ARG_NONE, &options->properties, "Send some properties along objects (default=no)", NULL },
{ "bind", 'b', 0, G_OPTION_ARG_STRING, &options->ip, "Local IP address to bind to (default=all interfaces)", "IP" },
{ "port", 'p', 0, G_OPTION_ARG_INT, &options->port, "Local port to bind to (default=0, random)", "port" },
{ "remote-host", 'r', 0, G_OPTION_ARG_STRING, &options->remote_host, "QUIC server to connect to (default=none)", "IP" },
Expand Down
2 changes: 1 addition & 1 deletion examples/moq-pub-options.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ typedef struct demo_options {
const char *delivery;
gboolean publish;
uint64_t track_alias;
gboolean extensions;
gboolean properties;
const char *ip;
int port;
const char *remote_host;
Expand Down
Loading