名前空間の発見
購読者は、トラック名を前もって知っている必要はありません。
名前空間のプレフィックス(名前空間の先頭部分)を監視すれば、その下にどの名前空間があるかを、パブリッシャーの出入りに合わせてハブが知らせます。
このページでは、プレフィックス guide を監視する 1 つのウォッチャーと、4 つの名前空間の公開を動かします。
公開のうち 1 つはウォッチャーの開始より前、2 つは後に行われ、残る 1 つはサーバーのポリシーに拒否されます。
最後に、パブリッシャーの 1 つが退出します。
コード
#define WIRED_MAIN#include "wired.h"
/* draft-ietf-moq-transport-22 9.14 / 9.15: the hub asks this hook before * it accepts a PUBLISH_NAMESPACE or SUBSCRIBE_NAMESPACE. This sample's * policy refuses any namespace ending in "secret" (a real deployment * would verify the token too; this demo ignores it). */static int authorize_ns( void* ctx, u64 msg_type, const moqctl_ns* ns, const moqctl_token* token) { (void)ctx; (void)msg_type; (void)token; return ns->n == 0 || !wired_span_eq_cstr(ns->fields[ns->n - 1], "secret");}
static wired_moqt_hub g_hub;
static void on_step(void* ctx, u64 now_ms) { wired_moqt_tick((wired_moqt_hub*)ctx, now_ms);}
int wired_main(int argc, char** argv) { /* The same fixed demo identity as the other MoQT pages, plus QUIC * DATAGRAM support, which WebTransport clients ask for. */ static wired_srvboot_demo_keys keys; wired_srvboot_id id; wired_srvboot_demo(&id, &keys, 0x50, "guide-nd"); id.max_datagram_frame_size = 65535;
/* PUBLISH_NAMESPACE and SUBSCRIBE_NAMESPACE arrive on the client's own * bidi streams (draft-ietf-moq-transport-22 6.4.2) and stay open: the hub * answers on them through stream_reply_open, and later pushes NAMESPACE / * NAMESPACE_DONE onto the subscriber's stream with stream_send. */ wired_moqt_io io = wired_moqraw_io(); wired_moqt_init(&g_hub, io); g_hub.authorize_namespace = authorize_ns;
/* on_session_close withdraws every namespace a leaving session * published (NAMESPACE_DONE to its watchers); the tick retries a push * the transport refused. "moqt-22" selects draft-22 (see the hub page). */ wired_srvrun_opt opt = { .incoming_cpu = -1, .wt_protocols = "moqt-22", .on_step = on_step, .on_step_ctx = &g_hub, .wt_on_session = wired_moqt_on_session, .wt_session_ctx = &g_hub, .wt_on_stream_data = wired_moqt_on_stream_data, .wt_stream_data_ctx = &g_hub, .wt_on_stream_reset = wired_moqt_on_stream_reset, .wt_stream_reset_ctx = &g_hub, .wt_on_session_close = wired_moqt_on_session_close, .wt_session_close_ctx = &g_hub, };
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;}クライアント
package main
import ( "bufio" "bytes" "context" "fmt" "log" "os" "strings" "time"
"wired-guide/moqtclient")
// request sends one request on a fresh bidi request stream// (draft-ietf-moq-transport-22 SS6.4.2) and prints the hub's first// answer: REQUEST_OK (0x7) or REQUEST_ERROR (0x5) with its error code.// The stream is left alone: resetting it would cancel the request.func request(ctx context.Context, s *moqtclient.Session, who string, typ byte, id byte, name string) *bufio.Reader { label := map[byte]string{0x6: "PUBLISH_NAMESPACE", 0x50: "SUBSCRIBE_NAMESPACE"}[typ] // Request ID, Track Namespace (or Prefix), no parameters. _, r := s.Request(ctx, typ, append(append([]byte{id}, moqtclient.Namespace(strings.Split(name, "/")...)...), 0)) switch t, body := moqtclient.ReadMsg(r); t { case 0x7: fmt.Printf("%s: %s %s -> REQUEST_OK\n", who, label, name) case 0x5: code := moqtclient.Varint(bufio.NewReader(bytes.NewReader(body))) fmt.Printf("%s: %s %s -> REQUEST_ERROR error_code=%d\n", who, label, name, code) default: log.Fatalf("%s answered with type %#x", label, t) } return r}
// publish opens a publisher session (one MoQT peer; closing its QUIC// connection is how it leaves) that announces name (Request ID 0).func publish(ctx context.Context, who, name string) *moqtclient.Session { s := moqtclient.Dial(ctx, os.Args[1]+"/moqt") request(ctx, s, who, 0x6, 0, name) return s}
// watch reads the next push on the SUBSCRIBE_NAMESPACE stream and prints// it with the namespace suffix (the fields after the prefix "guide").func watch(r *bufio.Reader) { typ, body := moqtclient.ReadMsg(r) label := map[uint64]string{0x8: "NAMESPACE", 0xE: "NAMESPACE_DONE"}[typ] if label == "" { log.Fatalf("unexpected push type %#x", typ) } fmt.Printf("watcher: %s %s\n", label, strings.Join(moqtclient.ReadNamespace(body), "/"))}
func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel()
// A namespace already published before anyone watches. lobby := publish(ctx, "lobby", "guide/lobby") defer lobby.Conn.CloseWithError(0, "")
// The watcher subscribes to the prefix "guide". After REQUEST_OK the // hub sends one NAMESPACE per namespace already published under it: // the initial set. w := moqtclient.Dial(ctx, os.Args[1]+"/moqt") defer w.Conn.CloseWithError(0, "") watcher := request(ctx, w, "watcher", 0x50, 0, "guide") watch(watcher)
// Two publishers join. Each waits for its own REQUEST_OK, and the // watcher then reads the NAMESPACE the hub pushed for it, so every // step is ordered by messages on the wire, never by a sleep. roomA := publish(ctx, "room-a", "guide/room-a") watch(watcher) roomB := publish(ctx, "room-b", "guide/room-b") defer roomB.Conn.CloseWithError(0, "") watch(watcher)
// The server's authorize_namespace hook refuses "secret": the // publisher gets REQUEST_ERROR UNAUTHORIZED (0x1) and the watcher is // never told about it (the next push it sees is room-a leaving). request(ctx, roomB, "room-b", 0x6, 2, "guide/secret")
// room-a leaves: closing its session withdraws its namespace, and the // hub pushes NAMESPACE_DONE to the watcher. moqtclient.Check(roomA.Conn.CloseWithError(0, "")) fmt.Println("room-a: session closed") watch(watcher)}実行結果
$ ./moqt-discovery --port 4433conn closed by peer (CONNECTION_CLOSE)conn closed by peer (CONNECTION_CLOSE)conn closed by peer (CONNECTION_CLOSE)conn closed by peer (CONNECTION_CLOSE)$ go run . https://127.0.0.1:4433lobby: PUBLISH_NAMESPACE guide/lobby -> REQUEST_OKwatcher: SUBSCRIBE_NAMESPACE guide -> REQUEST_OKwatcher: NAMESPACE lobbyroom-a: PUBLISH_NAMESPACE guide/room-a -> REQUEST_OKwatcher: NAMESPACE room-aroom-b: PUBLISH_NAMESPACE guide/room-b -> REQUEST_OKwatcher: NAMESPACE room-broom-b: PUBLISH_NAMESPACE guide/secret -> REQUEST_ERROR error_code=1room-a: session closedwatcher: NAMESPACE_DONE room-a- このサンプルは、WebTransport サブプロトコル
moqt-22で draft-ietf-moq-transport-22 をネゴシエートします(サーバーがopt.wt_protocolsを設定します)。 両側がそれぞれ自分の単方向制御ストリームを開いて SETUP(9.1)を送り、ハブは両方の SETUP が揃ってからリクエストを処理します。 - 各リクエストは、クライアントが開いた新しい双方向ストリームで送られます(draft-ietf-moq-transport-22 6.4.2)。
ハブは
stream_reply_openを通じて同じストリームで応答します。 名前空間の 2 種類のリクエストは、応答の後も、ストリームが取り消されるか(6.4.2.3)セッションが終わるまで有効です。 NAMESPACE と NAMESPACE_DONE は、SUBSCRIBE_NAMESPACE のストリーム自身に送られます。 - PUBLISH_NAMESPACE(9.14、4.1)は、Track Namespace(
guide/lobby、guide/room-aなど)を公開し、ハブは REQUEST_OK で応答します。 - SUBSCRIBE_NAMESPACE(9.15、4.2)は、Track Namespace Prefix を指定します。
REQUEST_OK の後、ハブはプレフィックスの下で公開済みの名前空間ごとに NAMESPACE(9.16)を 1 つ送ります(初期集合で、ここでは
lobby)。 その後も、一致する名前空間が公開されるたびに NAMESPACE を送ります。 どの NAMESPACE も、プレフィックスより後ろのフィールドであるサフィックスだけを運びます。 hub.authorize_namespaceは、すべての PUBLISH_NAMESPACE と SUBSCRIBE_NAMESPACE を、名前空間と提示された AUTHORIZATION TOKEN とともに受け取ります。 どちらのメッセージかはmsg_typeでわかります。 このサンプルは、最後のフィールドがsecretの名前空間を拒否します。 パブリッシャーには REQUEST_ERROR UNAUTHORIZED(1)が返り、ウォッチャーにはその名前空間が知らされません。- room-a がセッションを閉じると、
wired_moqt_on_session_closeがそのセッションの公開した名前空間をすべて取り下げます。 ハブは、その名前空間を知らせていたウォッチャーそれぞれに NAMESPACE_DONE(9.17)を送ります。 - クライアントは、ハブを待つために sleep しません。
各パブリッシャーは自分への REQUEST_OK を待ち、ウォッチャーはハブが送ってくる NAMESPACE または NAMESPACE_DONE を読みます。
そのため、各段階の順序はワイヤ上のメッセージで決まります。
トランスポートが拒否した送信は、
on_stepから呼ぶハブのティック(wired_moqt_tick)が再試行します。