Coverage Report

Created: 2026-11-12 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-moq-lite.c
Line
Count
Source
1
/* packet-moq-lite.c
2
 * Routines for Media over QUIC Lite (moq-lite) dissection
3
 *
4
 * Wireshark - Network traffic analyzer
5
 * By Gerald Combs <gerald@wireshark.org>
6
 * Copyright 1998 Gerald Combs
7
 *
8
 * SPDX-License-Identifier: GPL-2.0-or-later
9
 */
10
11
/*
12
 * moq-lite is a stripped down variant of Media over QUIC Transport designed to
13
 * fanout live media 1->N, using QUIC streams for prioritization.
14
 *
15
 * This dissector implements draft-lcurley-moq-lite-05:
16
 * https://datatracker.ietf.org/doc/draft-lcurley-moq-lite/
17
 *
18
 * Over bare QUIC the protocol is negotiated with the ALPN token "moq-lite-xx",
19
 * which is how this dissector is reached. A session running over WebTransport
20
 * keeps the "h3" ALPN and advertises moq-lite in the WT-Protocol header, which
21
 * Wireshark does not currently use to select a subprotocol dissector.
22
 *
23
 * Draft 02 and earlier negotiated the version with a SESSION_CLIENT and
24
 * SESSION_SERVER message on a Session stream instead of relying on the ALPN
25
 * token alone. Those messages are dissected as well, and the version they
26
 * carry selects the layouts that changed since: the Session stream itself, the
27
 * subscriber properties of SUBSCRIBE, and the timestamp of a FRAME. Drafts 03
28
 * and 04 are dissected with the draft 05 layouts, which they mostly share.
29
 *
30
 * A session consists of:
31
 *  - one bidirectional stream per transaction (announce, subscribe, fetch,
32
 *    probe, goaway, track), each starting with a stream type varint written by
33
 *    the endpoint that opened the stream,
34
 *  - unidirectional streams carrying setup parameters and group data,
35
 *  - optionally QUIC datagrams carrying single frame groups.
36
 *
37
 * Which message is being parsed is therefore a function of the stream type,
38
 * the direction (opener or peer) and the position within the stream, rather
39
 * than of an explicit type on the wire. The exception is the publisher side of
40
 * a Subscribe stream, where SUBSCRIBE_OK/END/DROP do carry a type varint.
41
 */
42
43
#include "config.h"
44
45
#include <epan/packet.h>
46
#include <epan/exceptions.h>
47
#include <epan/conversation.h>
48
#include <epan/expert.h>
49
#include <epan/prefs.h>
50
#include <epan/proto_data.h>
51
#include <epan/unit_strings.h>
52
#include <wsutil/array.h>
53
#include <wsutil/str_util.h>
54
55
#include "packet-quic.h"
56
#include "packet-tls.h"
57
58
void proto_register_moq_lite(void);
59
void proto_reg_handoff_moq_lite(void);
60
61
/* Bidirectional stream types (draft-05, Section 5.1). */
62
0
#define MOQL_BIDI_STREAM_SESSION    0x0     /* moq-lite-02 and earlier. */
63
0
#define MOQL_BIDI_STREAM_ANNOUNCE   0x1
64
0
#define MOQL_BIDI_STREAM_SUBSCRIBE  0x2
65
0
#define MOQL_BIDI_STREAM_FETCH      0x3
66
0
#define MOQL_BIDI_STREAM_PROBE      0x4
67
0
#define MOQL_BIDI_STREAM_GOAWAY     0x5
68
0
#define MOQL_BIDI_STREAM_TRACK      0x6
69
70
/* Unidirectional stream types (draft-05, Section 6.3). */
71
0
#define MOQL_UNI_STREAM_GROUP       0x0
72
0
#define MOQL_UNI_STREAM_SETUP       0x1
73
74
/* Message types on the publisher side of a Subscribe stream. */
75
0
#define MOQL_SUBSCRIBE_OK           0x0
76
0
#define MOQL_SUBSCRIBE_END          0x1
77
0
#define MOQL_SUBSCRIBE_DROP         0x2
78
79
/* Setup parameters (draft-05, Section 7.3). */
80
0
#define MOQL_SETUP_PARAM_PROBE      0x1
81
0
#define MOQL_SETUP_PARAM_PATH       0x2
82
83
/* Sentinel for a stream whose leading stream type was not captured. */
84
0
#define MOQL_STREAM_TYPE_UNKNOWN    UINT64_MAX
85
86
/* Maximum size of a datagram body (draft-05, Section 6.5). */
87
0
#define MOQL_MAX_DATAGRAM_SIZE      1200
88
89
/*
90
 * Version constants, as carried by SESSION_CLIENT and SESSION_SERVER in
91
 * moq-lite-02 and earlier. The low byte is the draft number.
92
 */
93
0
#define MOQL_VERSION_BASE           UINT64_C(0xff0dad00)
94
0
#define MOQL_VERSION_MASK           UINT64_C(0xffffff00)
95
96
/* The draft implemented here, assumed for sessions of an unknown version. */
97
112
#define MOQL_VERSION_LATEST         5
98
99
/*
100
 * The last draft with a Session stream; up to and including this version the
101
 * layout of several messages differs from the current draft.
102
 */
103
0
#define MOQL_VERSION_LEGACY_MAX     2
104
105
/* Number of messages described in the info column, per stream and packet. */
106
0
#define MOQL_MAX_COL_INFO_MESSAGES  4
107
108
static int proto_moq_lite;
109
110
static int hf_moql_stream_type;
111
static int hf_moql_message;
112
static int hf_moql_message_type;
113
static int hf_moql_message_length;
114
static int hf_moql_string_length;
115
static int hf_moql_setup_parameter_count;
116
static int hf_moql_setup_parameter;
117
static int hf_moql_setup_parameter_id;
118
static int hf_moql_setup_parameter_length;
119
static int hf_moql_setup_parameter_value;
120
static int hf_moql_setup_probe_level;
121
static int hf_moql_session_parameter;
122
static int hf_moql_session_parameter_count;
123
static int hf_moql_session_parameter_id;
124
static int hf_moql_session_parameter_length;
125
static int hf_moql_session_parameter_value;
126
static int hf_moql_version;
127
static int hf_moql_version_count;
128
static int hf_moql_session_bitrate;
129
static int hf_moql_message_body;
130
static int hf_moql_setup_path;
131
static int hf_moql_broadcast_path;
132
static int hf_moql_broadcast_path_prefix;
133
static int hf_moql_broadcast_path_suffix;
134
static int hf_moql_exclude_hop;
135
static int hf_moql_hop_id;
136
static int hf_moql_hop_count;
137
static int hf_moql_active_count;
138
static int hf_moql_announce_status;
139
static int hf_moql_track_name;
140
static int hf_moql_subscribe_id;
141
static int hf_moql_subscriber_priority;
142
static int hf_moql_subscriber_ordered;
143
static int hf_moql_subscriber_max_latency;
144
static int hf_moql_group_start;
145
static int hf_moql_group_end;
146
static int hf_moql_publisher_priority;
147
static int hf_moql_publisher_ordered;
148
static int hf_moql_publisher_max_latency;
149
static int hf_moql_timescale;
150
static int hf_moql_subscribe_type;
151
static int hf_moql_group;
152
static int hf_moql_drop_group_start;
153
static int hf_moql_drop_group_end;
154
static int hf_moql_error_code;
155
static int hf_moql_group_sequence;
156
static int hf_moql_probe_bitrate;
157
static int hf_moql_probe_rtt;
158
static int hf_moql_goaway_uri;
159
static int hf_moql_frame_timestamp_delta;
160
static int hf_moql_frame_timestamp;
161
static int hf_moql_frame_time;
162
static int hf_moql_frame_payload;
163
static int hf_moql_datagram_timestamp;
164
static int hf_moql_subscribe_frame;
165
static int hf_moql_track_info_frame;
166
167
static int ett_moql;
168
static int ett_moql_message;
169
static int ett_moql_setup_parameter;
170
static int ett_moql_session_parameter;
171
172
static expert_field ei_moql_unknown_stream_type;
173
static expert_field ei_moql_unknown_version;
174
static expert_field ei_moql_stream_type_missing;
175
static expert_field ei_moql_unexpected_message;
176
static expert_field ei_moql_incomplete_message;
177
static expert_field ei_moql_extra_data;
178
static expert_field ei_moql_protocol_violation;
179
180
static dissector_handle_t moql_handle;
181
static dissector_handle_t moql_datagram_handle;
182
183
/* Reassemble messages that span multiple QUIC STREAM frames. */
184
static bool moql_desegment = true;
185
186
static const value_string moql_bidi_stream_type_vals[] = {
187
    { MOQL_BIDI_STREAM_SESSION,   "Session" },
188
    { MOQL_BIDI_STREAM_ANNOUNCE,  "Announce" },
189
    { MOQL_BIDI_STREAM_SUBSCRIBE, "Subscribe" },
190
    { MOQL_BIDI_STREAM_FETCH,     "Fetch" },
191
    { MOQL_BIDI_STREAM_PROBE,     "Probe" },
192
    { MOQL_BIDI_STREAM_GOAWAY,    "Goaway" },
193
    { MOQL_BIDI_STREAM_TRACK,     "Track" },
194
    { 0, NULL }
195
};
196
197
static const value_string moql_uni_stream_type_vals[] = {
198
    { MOQL_UNI_STREAM_GROUP, "Group" },
199
    { MOQL_UNI_STREAM_SETUP, "Setup" },
200
    { 0, NULL }
201
};
202
203
static const val64_string moql_subscribe_type_vals[] = {
204
    { MOQL_SUBSCRIBE_OK,   "SUBSCRIBE_OK" },
205
    { MOQL_SUBSCRIBE_END,  "SUBSCRIBE_END" },
206
    { MOQL_SUBSCRIBE_DROP, "SUBSCRIBE_DROP" },
207
    { 0, NULL }
208
};
209
210
static const val64_string moql_setup_parameter_vals[] = {
211
    { MOQL_SETUP_PARAM_PROBE, "Probe" },
212
    { MOQL_SETUP_PARAM_PATH,  "Path" },
213
    { 0, NULL }
214
};
215
216
static const val64_string moql_probe_level_vals[] = {
217
    { 0, "None" },
218
    { 1, "Report" },
219
    { 2, "Increase" },
220
    { 0, NULL }
221
};
222
223
static const val64_string moql_announce_status_vals[] = {
224
    { 0, "ended" },
225
    { 1, "active" },
226
    { 0, NULL }
227
};
228
229
static const val64_string moql_version_vals[] = {
230
    { MOQL_VERSION_BASE + 0, "moq-lite-00" },
231
    { MOQL_VERSION_BASE + 1, "moq-lite-01" },
232
    { MOQL_VERSION_BASE + 2, "moq-lite-02" },
233
    { MOQL_VERSION_BASE + 3, "moq-lite-03" },
234
    { MOQL_VERSION_BASE + 4, "moq-lite-04" },
235
    { MOQL_VERSION_BASE + 5, "moq-lite-05" },
236
    { 0, NULL }
237
};
238
239
static const value_string moql_ordered_vals[] = {
240
    { 0, "Descending (newest group first)" },
241
    { 1, "Ascending (oldest group first)" },
242
    { 0, NULL }
243
};
244
245
/*
246
 * Message kinds, used internally and as the value of the generated
247
 * moql.message_type field. These are not wire values: moq-lite identifies most
248
 * messages by stream type and position instead.
249
 */
250
typedef enum {
251
    MOQL_MSG_NONE = 0,
252
    MOQL_MSG_SETUP,
253
    MOQL_MSG_SESSION_CLIENT,
254
    MOQL_MSG_SESSION_SERVER,
255
    MOQL_MSG_SESSION_UPDATE,
256
    MOQL_MSG_ANNOUNCE_PLEASE,
257
    MOQL_MSG_ANNOUNCE_INIT,
258
    MOQL_MSG_ANNOUNCE,
259
    MOQL_MSG_ANNOUNCE_REQUEST,
260
    MOQL_MSG_ANNOUNCE_OK,
261
    MOQL_MSG_ANNOUNCE_BROADCAST,
262
    MOQL_MSG_SUBSCRIBE,
263
    MOQL_MSG_SUBSCRIBE_UPDATE,
264
    MOQL_MSG_SUBSCRIBE_RESPONSE,    /* Resolved to _OK/_END/_DROP on the wire. */
265
    MOQL_MSG_SUBSCRIBE_OK,
266
    MOQL_MSG_SUBSCRIBE_END,
267
    MOQL_MSG_SUBSCRIBE_DROP,
268
    MOQL_MSG_TRACK,
269
    MOQL_MSG_TRACK_INFO,
270
    MOQL_MSG_FETCH,
271
    MOQL_MSG_PROBE,
272
    MOQL_MSG_GOAWAY,
273
    MOQL_MSG_GROUP,
274
    MOQL_MSG_FRAME,
275
} moql_message_type_t;
276
277
static const value_string moql_message_type_vals[] = {
278
    { MOQL_MSG_SETUP,              "SETUP" },
279
    { MOQL_MSG_SESSION_CLIENT,     "SESSION_CLIENT" },
280
    { MOQL_MSG_SESSION_SERVER,     "SESSION_SERVER" },
281
    { MOQL_MSG_SESSION_UPDATE,     "SESSION_UPDATE" },
282
    { MOQL_MSG_ANNOUNCE_PLEASE,    "ANNOUNCE_PLEASE" },
283
    { MOQL_MSG_ANNOUNCE_INIT,      "ANNOUNCE_INIT" },
284
    { MOQL_MSG_ANNOUNCE,           "ANNOUNCE" },
285
    { MOQL_MSG_ANNOUNCE_REQUEST,   "ANNOUNCE_REQUEST" },
286
    { MOQL_MSG_ANNOUNCE_OK,        "ANNOUNCE_OK" },
287
    { MOQL_MSG_ANNOUNCE_BROADCAST, "ANNOUNCE_BROADCAST" },
288
    { MOQL_MSG_SUBSCRIBE,          "SUBSCRIBE" },
289
    { MOQL_MSG_SUBSCRIBE_UPDATE,   "SUBSCRIBE_UPDATE" },
290
    { MOQL_MSG_SUBSCRIBE_OK,       "SUBSCRIBE_OK" },
291
    { MOQL_MSG_SUBSCRIBE_END,      "SUBSCRIBE_END" },
292
    { MOQL_MSG_SUBSCRIBE_DROP,     "SUBSCRIBE_DROP" },
293
    { MOQL_MSG_TRACK,              "TRACK" },
294
    { MOQL_MSG_TRACK_INFO,         "TRACK_INFO" },
295
    { MOQL_MSG_FETCH,              "FETCH" },
296
    { MOQL_MSG_PROBE,              "PROBE" },
297
    { MOQL_MSG_GOAWAY,             "GOAWAY" },
298
    { MOQL_MSG_GROUP,              "GROUP" },
299
    { MOQL_MSG_FRAME,              "FRAME" },
300
    { 0, NULL }
301
};
302
303
/* Immutable publisher properties of a track, learned from TRACK_INFO. */
304
typedef struct _moql_track {
305
    uint32_t    frame_num;      /* Frame containing the TRACK_INFO. */
306
    uint64_t    timescale;
307
} moql_track_t;
308
309
/* A subscription, learned from SUBSCRIBE. */
310
typedef struct _moql_subscription {
311
    uint32_t    frame_num;      /* Frame containing the SUBSCRIBE. */
312
    const char *broadcast_path;
313
    const char *track_name;
314
} moql_subscription_t;
315
316
/* Session (i.e. QUIC connection) wide state. */
317
typedef struct _moql_session {
318
    wmem_map_t *subscriptions;  /* Subscribe ID -> moql_subscription_t */
319
    wmem_map_t *tracks;         /* Broadcast path and track name -> moql_track_t */
320
    unsigned    version;        /* Draft number, MOQL_VERSION_LATEST if unknown. */
321
    bool        version_known;
322
} moql_session_t;
323
324
typedef struct {
325
    int64_t value;
326
    bool overflow;
327
} moql_timestamp_t;
328
329
/*
330
 * Per stream state. Both directions of a bidirectional stream share a single
331
 * instance, keyed on the direction of the endpoint that opened the stream.
332
 */
333
typedef struct _moql_stream {
334
    uint64_t    stream_type;
335
    uint64_t    data_start;     /* Stream offset after the stream type varint. */
336
    /*
337
     * Stream offset at which the messages following the leading message of a
338
     * direction start, indexed by direction (0: opener, 1: peer).
339
     */
340
    uint64_t    body_offset[2];
341
    bool        body_offset_known[2];
342
    const char *broadcast_path; /* Fetch and Track streams. */
343
    const char *track_name;
344
    moql_subscription_t *subscription;  /* Group streams. */
345
    /*
346
     * Absolute frame timestamps, keyed by the stream offset of the FRAME.
347
     * FRAME timestamps are deltas, so the running sum is computed on the first
348
     * pass and cached, which keeps random access dissection consistent.
349
     */
350
    wmem_map_t *frame_timestamps;
351
    moql_timestamp_t timestamp; /* Running sum, first pass only. */
352
} moql_stream_t;
353
354
static moql_session_t *
355
moql_session_lookup_or_create(packet_info *pinfo)
356
0
{
357
0
    conversation_t *conversation = find_or_create_conversation(pinfo);
358
0
    moql_session_t *session;
359
360
0
    session = (moql_session_t *)conversation_get_proto_data(conversation, proto_moq_lite);
361
0
    if (session == NULL) {
362
0
        session = wmem_new0(wmem_file_scope(), moql_session_t);
363
0
        session->subscriptions = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);
364
0
        session->tracks = wmem_map_new(wmem_file_scope(), wmem_str_hash, g_str_equal);
365
0
        session->version = MOQL_VERSION_LATEST;
366
0
        const char *alpn = tls_get_alpn(tls_get_current_session(pinfo));
367
0
        if (alpn != NULL && strlen(alpn) == 11 &&
368
0
            strncmp(alpn, "moq-lite-0", 10) == 0 && alpn[10] >= '0' && alpn[10] <= '5') {
369
0
            session->version = (unsigned)(alpn[10] - '0');
370
0
        }
371
0
        conversation_add_proto_data(conversation, proto_moq_lite, session);
372
0
    }
373
374
0
    return session;
375
0
}
376
377
static moql_track_t *
378
moql_track_lookup(moql_session_t *session, packet_info *pinfo,
379
                  const char *broadcast_path, const char *track_name)
380
0
{
381
0
    if (session == NULL || broadcast_path == NULL || track_name == NULL) {
382
0
        return NULL;
383
0
    }
384
385
0
    char *key = wmem_strdup_printf(pinfo->pool, "%zu:%s%s", strlen(broadcast_path), broadcast_path, track_name);
386
387
0
    return (moql_track_t *)wmem_map_lookup(session->tracks, key);
388
0
}
389
390
/*
391
 * Try to read a QUIC variable length integer, returning false (without
392
 * throwing) if the buffer does not hold the whole varint.
393
 */
394
static bool
395
moql_try_get_varint(tvbuff_t *tvb, int offset, uint64_t *value, uint32_t *lenvar)
396
0
{
397
0
    if (tvb_reported_length_remaining(tvb, offset) < 1) {
398
0
        return false;
399
0
    }
400
401
0
    unsigned len = 1U << (tvb_get_uint8(tvb, offset) >> 6);
402
0
    if (tvb_reported_length_remaining(tvb, offset) < len) {
403
0
        return false;
404
0
    }
405
406
0
    *lenvar = len;
407
0
    if (value != NULL) {
408
0
        unsigned decoded = tvb_get_varint(tvb, offset, len, value, ENC_VARINT_QUIC);
409
0
        DISSECTOR_ASSERT(decoded == len);
410
0
    }
411
412
0
    return true;
413
0
}
414
415
/* Add a QUIC varint and return its encoded length. */
416
static unsigned
417
moql_add_varint(tvbuff_t *tvb, proto_tree *tree, int offset, int hf, uint64_t *value)
418
0
{
419
0
    unsigned length;
420
421
0
    proto_tree_add_item_ret_varint(tree, hf, tvb, offset, -1,
422
0
                                  ENC_VARINT_QUIC, value, &length);
423
0
    return length;
424
0
}
425
426
/*
427
 * Add a varint length prefixed UTF-8 string, returning the number of bytes
428
 * consumed. Escaped metadata is returned in *retval, allocated from packet scope.
429
 */
430
static int
431
moql_add_string(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset,
432
                int hf_string, const char **retval)
433
0
{
434
0
    uint64_t str_len;
435
0
    uint32_t lenvar;
436
437
0
    lenvar = moql_add_varint(tvb, tree, offset, hf_moql_string_length, &str_len);
438
0
    offset += lenvar;
439
440
0
    if (str_len > INT32_MAX) {
441
0
        THROW(ReportedBoundsError);
442
0
    }
443
444
0
    proto_tree_add_item(tree, hf_string, tvb, offset, (int)str_len, ENC_UTF_8);
445
0
    if (retval != NULL) {
446
        /* Keep metadata unbounded and reversible, including NUL and backslash. */
447
0
        *retval = tvb_format_text(pinfo->pool, tvb, offset, (int)str_len);
448
0
    }
449
450
0
    return lenvar + (int)str_len;
451
0
}
452
453
/* Decode a zigzag mapped value (draft-05, Section 7.16). */
454
static int64_t
455
moql_zigzag_decode(uint64_t value)
456
0
{
457
0
    return (int64_t)(value >> 1) ^ -(int64_t)(value & 1);
458
0
}
459
460
/*
461
 * Determine the message expected on a bidirectional stream. "leading" is true
462
 * for the first message sent in the given direction.
463
 */
464
static moql_message_type_t
465
moql_expected_bidi_message(uint64_t stream_type, bool from_opener, bool leading, bool legacy)
466
0
{
467
0
    switch (stream_type) {
468
0
    case MOQL_BIDI_STREAM_SESSION:
469
        /* Only moq-lite-02 and earlier have a Session stream. */
470
0
        if (leading) {
471
0
            return from_opener ? MOQL_MSG_SESSION_CLIENT : MOQL_MSG_SESSION_SERVER;
472
0
        }
473
0
        return MOQL_MSG_SESSION_UPDATE;
474
475
0
    case MOQL_BIDI_STREAM_ANNOUNCE:
476
0
        if (from_opener) {
477
0
            if (!leading) {
478
0
                return MOQL_MSG_NONE;
479
0
            }
480
0
            return legacy ? MOQL_MSG_ANNOUNCE_PLEASE : MOQL_MSG_ANNOUNCE_REQUEST;
481
0
        }
482
0
        if (legacy) {
483
0
            return leading ? MOQL_MSG_ANNOUNCE_INIT : MOQL_MSG_ANNOUNCE;
484
0
        }
485
0
        return leading ? MOQL_MSG_ANNOUNCE_OK : MOQL_MSG_ANNOUNCE_BROADCAST;
486
487
0
    case MOQL_BIDI_STREAM_SUBSCRIBE:
488
0
        if (from_opener) {
489
0
            return leading ? MOQL_MSG_SUBSCRIBE : MOQL_MSG_SUBSCRIBE_UPDATE;
490
0
        }
491
        /* The publisher's responses only carry a type varint since draft 03. */
492
0
        return legacy ? MOQL_MSG_SUBSCRIBE_OK : MOQL_MSG_SUBSCRIBE_RESPONSE;
493
494
0
    case MOQL_BIDI_STREAM_FETCH:
495
0
        if (from_opener) {
496
0
            return leading ? MOQL_MSG_FETCH : MOQL_MSG_NONE;
497
0
        }
498
0
        return MOQL_MSG_FRAME;
499
500
0
    case MOQL_BIDI_STREAM_PROBE:
501
0
        return MOQL_MSG_PROBE;
502
503
0
    case MOQL_BIDI_STREAM_GOAWAY:
504
0
        if (from_opener) {
505
0
            return leading ? MOQL_MSG_GOAWAY : MOQL_MSG_NONE;
506
0
        }
507
0
        return MOQL_MSG_NONE;
508
509
0
    case MOQL_BIDI_STREAM_TRACK:
510
0
        if (from_opener) {
511
0
            return leading ? MOQL_MSG_TRACK : MOQL_MSG_NONE;
512
0
        }
513
0
        return leading ? MOQL_MSG_TRACK_INFO : MOQL_MSG_NONE;
514
515
0
    default:
516
0
        return MOQL_MSG_NONE;
517
0
    }
518
0
}
519
520
/*
521
 * Determine the message expected on a unidirectional stream. Only the endpoint
522
 * that opened the stream ever sends data on it.
523
 */
524
static moql_message_type_t
525
moql_expected_uni_message(uint64_t stream_type, bool leading, bool legacy)
526
0
{
527
0
    switch (stream_type) {
528
0
    case MOQL_UNI_STREAM_GROUP:
529
0
        return leading ? MOQL_MSG_GROUP : MOQL_MSG_FRAME;
530
531
0
    case MOQL_UNI_STREAM_SETUP:
532
        /* The Setup stream was added in draft 05. */
533
0
        if (legacy) {
534
0
            return MOQL_MSG_NONE;
535
0
        }
536
0
        return leading ? MOQL_MSG_SETUP : MOQL_MSG_NONE;
537
538
0
    default:
539
0
        return MOQL_MSG_NONE;
540
0
    }
541
0
}
542
543
/*
544
 * Dissect a parameter list: a count followed by that many {ID, length, value}
545
 * triples. Both the SETUP message and the legacy session messages use it, with
546
 * their own parameter registries.
547
 */
548
static int
549
moql_dissect_parameters(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset,
550
                        uint64_t param_count, bool setup)
551
0
{
552
0
    int start = offset;
553
554
0
    for (uint64_t i = 0; i < param_count; i++) {
555
0
        proto_item *param_ti;
556
0
        proto_tree *param_tree;
557
0
        uint64_t    param_id, param_len;
558
0
        uint32_t    lenvar;
559
0
        int         param_start = offset;
560
561
0
        param_ti = proto_tree_add_item(tree,
562
0
                                       setup ? hf_moql_setup_parameter : hf_moql_session_parameter,
563
0
                                       tvb, offset, -1, ENC_NA);
564
0
        param_tree = proto_item_add_subtree(param_ti, setup ? ett_moql_setup_parameter
565
0
                                                            : ett_moql_session_parameter);
566
567
0
        proto_tree_add_item_ret_varint(param_tree,
568
0
                                       setup ? hf_moql_setup_parameter_id
569
0
                                             : hf_moql_session_parameter_id,
570
0
                                       tvb, offset, -1, ENC_VARINT_QUIC, &param_id, &lenvar);
571
0
        offset += lenvar;
572
0
        proto_tree_add_item_ret_varint(param_tree,
573
0
                                       setup ? hf_moql_setup_parameter_length
574
0
                                             : hf_moql_session_parameter_length,
575
0
                                       tvb, offset, -1, ENC_VARINT_QUIC, &param_len, &lenvar);
576
0
        offset += lenvar;
577
578
0
        if (param_len > INT32_MAX) {
579
0
            THROW(ReportedBoundsError);
580
0
        }
581
582
0
        tvbuff_t *value_tvb = tvb_new_subset_length(tvb, offset, (int)param_len);
583
584
0
        if (!setup) {
585
0
            proto_item_set_text(param_ti, "Session Parameter: 0x%" PRIx64, param_id);
586
0
            proto_tree_add_item(param_tree, hf_moql_session_parameter_value, value_tvb, 0,
587
0
                                (int)param_len, ENC_NA);
588
0
        } else {
589
0
            proto_item_set_text(param_ti, "Setup Parameter: %s",
590
0
                                val64_to_str_wmem(pinfo->pool, param_id,
591
0
                                                  moql_setup_parameter_vals,
592
0
                                                  "Unknown (0x%" PRIx64 ")"));
593
594
0
            switch (param_id) {
595
0
            case MOQL_SETUP_PARAM_PROBE: {
596
0
                uint64_t level;
597
598
0
                proto_tree_add_item_ret_varint(param_tree, hf_moql_setup_probe_level, value_tvb, 0,
599
0
                                               -1, ENC_VARINT_QUIC, &level, &lenvar);
600
0
                proto_item_append_text(param_ti, " = %s",
601
0
                                       val64_to_str_wmem(pinfo->pool, level,
602
0
                                                         moql_probe_level_vals,
603
0
                                                         "Unknown (%" PRIu64 ")"));
604
0
                break;
605
0
            }
606
0
            case MOQL_SETUP_PARAM_PATH: {
607
0
                char *path = NULL;
608
609
0
                proto_tree_add_item_ret_display_string(param_tree, hf_moql_setup_path, value_tvb, 0,
610
0
                                                       (int)param_len, ENC_UTF_8, pinfo->pool,
611
0
                                                       &path);
612
0
                proto_item_append_text(param_ti, " = %s", path);
613
0
                if (param_len == 0) {
614
0
                    proto_tree_add_expert_format(param_tree, pinfo, &ei_moql_protocol_violation,
615
0
                                                 value_tvb, 0, 0,
616
0
                                                 "The Path Parameter must not be empty");
617
0
                }
618
0
                break;
619
0
            }
620
0
            default:
621
0
                proto_tree_add_item(param_tree, hf_moql_setup_parameter_value, value_tvb, 0,
622
0
                                    (int)param_len, ENC_NA);
623
0
                break;
624
0
            }
625
0
        }
626
627
0
        offset += (int)param_len;
628
0
        proto_item_set_len(param_ti, offset - param_start);
629
0
    }
630
631
0
    return offset - start;
632
0
}
633
634
static void
635
moql_dissect_setup(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
636
0
{
637
0
    uint64_t param_count;
638
0
    int      offset = 0;
639
640
0
    offset += moql_add_varint(tvb, tree, offset, hf_moql_setup_parameter_count, &param_count);
641
642
0
    offset += moql_dissect_parameters(tvb, pinfo, tree, offset, param_count, true);
643
644
0
    if (tvb_reported_length_remaining(tvb, offset) > 0) {
645
0
        proto_tree_add_expert_remaining(tree, pinfo, &ei_moql_extra_data, tvb, offset);
646
0
    }
647
0
}
648
649
/* Note the negotiated version, which selects the layout of several messages. */
650
static void
651
moql_set_version(packet_info *pinfo, moql_session_t *session, proto_item *ti, uint64_t version)
652
0
{
653
0
    if ((version & MOQL_VERSION_MASK) != MOQL_VERSION_BASE) {
654
0
        expert_add_info_format(pinfo, ti, &ei_moql_unknown_version,
655
0
                               "Unknown moq-lite version (0x%" PRIx64 ")", version);
656
0
        return;
657
0
    }
658
659
0
    if (!PINFO_FD_VISITED(pinfo)) {
660
0
        session->version = (unsigned)(version - MOQL_VERSION_BASE);
661
0
        session->version_known = true;
662
0
    }
663
0
}
664
665
/*
666
 * SESSION_CLIENT offers a list of versions, SESSION_SERVER selects one; both
667
 * are followed by a parameter list. They only exist in moq-lite-02 and earlier.
668
 */
669
static int
670
moql_dissect_session(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_item *msg_ti,
671
                     moql_session_t *session, bool from_client)
672
0
{
673
0
    uint64_t version_count = 1, param_count;
674
0
    uint32_t lenvar;
675
0
    int      offset = 0;
676
677
0
    if (from_client) {
678
0
        offset += moql_add_varint(tvb, tree, offset, hf_moql_version_count, &version_count);
679
0
    }
680
681
0
    for (uint64_t i = 0; i < version_count; i++) {
682
0
        proto_item *ti;
683
0
        uint64_t    version;
684
685
0
        ti = proto_tree_add_item_ret_varint(tree, hf_moql_version, tvb, offset, -1,
686
0
                                           ENC_VARINT_QUIC, &version, &lenvar);
687
0
        offset += lenvar;
688
0
        proto_item_append_text(msg_ti, "%s%s", i == 0 ? ": " : ", ",
689
0
                               val64_to_str_wmem(pinfo->pool, version, moql_version_vals,
690
0
                                                 "0x%" PRIx64));
691
        /*
692
         * The server's selected version is the one in use; a single version
693
         * offered by the client is a usable fallback if the response to it was
694
         * not captured.
695
         */
696
0
        if (!from_client || (version_count == 1 && !session->version_known)) {
697
0
            moql_set_version(pinfo, session, ti, version);
698
0
        }
699
0
    }
700
701
0
    offset += moql_add_varint(tvb, tree, offset, hf_moql_session_parameter_count, &param_count);
702
703
0
    offset += moql_dissect_parameters(tvb, pinfo, tree, offset, param_count, false);
704
705
0
    return offset;
706
0
}
707
708
static int
709
moql_dissect_announce(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
710
                                proto_item *msg_ti, bool add_col_info, bool broadcast)
711
0
{
712
0
    const char *suffix = NULL;
713
0
    uint64_t    status, hop_count, hops_seen = 0;
714
0
    int         offset = 0;
715
716
0
    offset += moql_add_varint(tvb, tree, offset, hf_moql_announce_status, &status);
717
718
0
    offset += moql_add_string(tvb, pinfo, tree, offset, hf_moql_broadcast_path_suffix, &suffix);
719
720
0
    if (broadcast) {
721
0
        offset += moql_add_varint(tvb, tree, offset, hf_moql_hop_count, &hop_count);
722
723
0
        while (hops_seen < hop_count && tvb_reported_length_remaining(tvb, offset) > 0) {
724
0
            offset += moql_add_varint(tvb, tree, offset, hf_moql_hop_id, NULL);
725
0
            hops_seen++;
726
0
        }
727
0
        if (hops_seen != hop_count) {
728
0
            proto_tree_add_expert_format(tree, pinfo, &ei_moql_protocol_violation, tvb, offset, 0,
729
0
                                         "Hop Count (%" PRIu64 ") does not match the number of Hop ID "
730
0
                                         "entries (%" PRIu64 ")", hop_count, hops_seen);
731
0
        }
732
0
    }
733
734
0
    proto_item_append_text(msg_ti, ": %s %s",
735
0
                           val64_to_str_wmem(pinfo->pool, status, moql_announce_status_vals,
736
0
                                             "Unknown (%" PRIu64 ")"),
737
0
                           suffix);
738
0
    if (add_col_info) {
739
0
        col_append_fstr(pinfo->cinfo, COL_INFO, "(%s %s)",
740
0
                        val64_to_str_wmem(pinfo->pool, status, moql_announce_status_vals, "%" PRIu64),
741
0
                        suffix);
742
0
    }
743
744
0
    return offset;
745
0
}
746
747
/*
748
 * Add a Group Start or Group End field, which is either 0 (meaning latest or
749
 * unbounded) or the absolute group sequence plus one.
750
 */
751
static int
752
moql_add_relative_group(tvbuff_t *tvb, proto_tree *tree, int offset, int hf, const char *zero_text)
753
0
{
754
0
    proto_item *ti;
755
0
    uint64_t    value;
756
0
    uint32_t    lenvar;
757
758
0
    ti = proto_tree_add_item_ret_varint(tree, hf, tvb, offset, -1, ENC_VARINT_QUIC, &value, &lenvar);
759
0
    if (value == 0) {
760
0
        proto_item_append_text(ti, " (%s)", zero_text);
761
0
    } else {
762
0
        proto_item_append_text(ti, " (group %" PRIu64 ")", value - 1);
763
0
    }
764
765
0
    return lenvar;
766
0
}
767
768
/* The subscriber properties shared by SUBSCRIBE and SUBSCRIBE_UPDATE. */
769
static int
770
moql_dissect_subscribe_properties(tvbuff_t *tvb, proto_tree *tree, int offset, bool legacy)
771
0
{
772
0
    int      start = offset;
773
774
0
    proto_tree_add_item(tree, hf_moql_subscriber_priority, tvb, offset, 1, ENC_NA);
775
0
    offset += 1;
776
0
    if (legacy) {
777
0
        return offset - start;
778
0
    }
779
0
    proto_tree_add_item(tree, hf_moql_subscriber_ordered, tvb, offset, 1, ENC_NA);
780
0
    offset += 1;
781
0
    offset += moql_add_varint(tvb, tree, offset, hf_moql_subscriber_max_latency, NULL);
782
0
    offset += moql_add_relative_group(tvb, tree, offset, hf_moql_group_start, "latest");
783
0
    offset += moql_add_relative_group(tvb, tree, offset, hf_moql_group_end, "unbounded");
784
785
0
    return offset - start;
786
0
}
787
788
/* Exact fractional seconds, without overflowing remainder * 10^9.
789
 * QUIC timescales are at most 2^62-1, so 3 * timescale fits in uint64_t. */
790
static int
791
moql_timestamp_nsecs(int64_t timestamp, uint64_t timescale)
792
0
{
793
0
    int64_t fraction = timestamp % (int64_t)timescale;
794
0
    uint64_t magnitude = fraction < 0 ? (uint64_t)-fraction : (uint64_t)fraction;
795
0
    uint64_t remainder = 0;
796
0
    unsigned nsecs = 0;
797
798
0
    for (unsigned bit = 1U << 29; bit != 0; bit >>= 1) {
799
0
        remainder = 2 * remainder + ((1000000000U & bit) ? magnitude : 0);
800
0
        nsecs = 2 * nsecs + (unsigned)(remainder / timescale);
801
0
        remainder %= timescale;
802
0
    }
803
0
    return fraction < 0 ? -(int)nsecs : (int)nsecs;
804
0
}
805
806
/*
807
 * Add the absolute timestamp of a frame, accumulated from the per frame deltas,
808
 * and its wall clock equivalent if the track's timescale is known.
809
 */
810
static void
811
moql_add_frame_timestamp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_item *msg_ti,
812
                         moql_session_t *session, moql_stream_t *stream, uint64_t abs_offset,
813
                         int64_t delta, int payload_len)
814
0
{
815
0
    proto_item *ti;
816
0
    moql_timestamp_t *memoized, current;
817
0
    int64_t timestamp;
818
819
0
    memoized = (moql_timestamp_t *)wmem_map_lookup(stream->frame_timestamps, &abs_offset);
820
0
    if (memoized != NULL) {
821
0
        current = *memoized;
822
0
    } else {
823
0
        current = stream->timestamp;
824
0
        current.overflow = current.overflow || ckd_add(&current.value, current.value, delta);
825
0
        if (!PINFO_FD_VISITED(pinfo)) {
826
0
            uint64_t *key = wmem_new(wmem_file_scope(), uint64_t);
827
0
            moql_timestamp_t *value = wmem_new(wmem_file_scope(), moql_timestamp_t);
828
829
0
            *key = abs_offset;
830
0
            *value = current;
831
0
            wmem_map_insert(stream->frame_timestamps, key, value);
832
0
            stream->timestamp = current;
833
0
        }
834
0
    }
835
836
0
    if (current.overflow) {
837
0
        proto_tree_add_expert_format(tree, pinfo, &ei_moql_protocol_violation, tvb, 0, 0,
838
0
                                    "Frame timestamp overflow; absolute timestamp is unknown");
839
0
        return;
840
0
    }
841
0
    timestamp = current.value;
842
843
0
    ti = proto_tree_add_int64(tree, hf_moql_frame_timestamp, tvb, 0, 0, timestamp);
844
0
    proto_item_set_generated(ti);
845
846
    /*
847
     * The timestamp is expressed in the track's timescale, which is only known
848
     * once the TRACK_INFO of the track has been seen.
849
     */
850
0
    const char *broadcast_path = stream->broadcast_path;
851
0
    const char *track_name = stream->track_name;
852
853
0
    if (stream->subscription != NULL) {
854
0
        broadcast_path = stream->subscription->broadcast_path;
855
0
        track_name = stream->subscription->track_name;
856
0
    }
857
858
0
    moql_track_t *track = moql_track_lookup(session, pinfo, broadcast_path, track_name);
859
0
    if (track != NULL && track->timescale != 0) {
860
0
        nstime_t ts;
861
862
0
        ts.secs = (time_t)(timestamp / (int64_t)track->timescale);
863
0
        ts.nsecs = moql_timestamp_nsecs(timestamp, track->timescale);
864
0
        ti = proto_tree_add_time(tree, hf_moql_frame_time, tvb, 0, 0, &ts);
865
0
        proto_item_set_generated(ti);
866
867
0
        ti = proto_tree_add_uint(tree, hf_moql_track_info_frame, tvb, 0, 0, track->frame_num);
868
0
        proto_item_set_generated(ti);
869
0
    }
870
871
0
    proto_item_append_text(msg_ti, ": Timestamp %" PRId64 ", %d byte%s payload",
872
0
                           timestamp, payload_len, plurality(payload_len, "", "s"));
873
0
}
874
875
/*
876
 * Dissect a single message. Returns the number of bytes consumed, or 0 if the
877
 * message is incomplete (in which case desegmentation has been requested) or
878
 * cannot be dissected.
879
 */
880
static int
881
moql_dissect_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset,
882
                     quic_stream_info *stream_info, moql_session_t *session,
883
                     moql_stream_t *stream, bool from_opener, bool add_col_info)
884
0
{
885
0
    proto_item *msg_ti, *ti;
886
0
    proto_tree *msg_tree;
887
0
    uint64_t    abs_offset = stream_info->offset + (uint64_t)offset;
888
0
    uint64_t    msg_len, wire_type = 0;
889
0
    uint32_t    lenvar, type_len = 0, delta_len = 0;
890
0
    int         dir = from_opener ? 0 : 1;
891
0
    int         cursor = offset;
892
0
    int64_t     delta = 0;
893
0
    bool        leading;
894
0
    bool        legacy = session->version <= MOQL_VERSION_LEGACY_MAX;
895
896
0
    if (stream->body_offset_known[dir]) {
897
0
        leading = abs_offset < stream->body_offset[dir];
898
0
    } else {
899
0
        leading = abs_offset == (from_opener ? stream->data_start : 0);
900
0
    }
901
902
0
    moql_message_type_t msg_type;
903
0
    switch (QUIC_STREAM_TYPE(stream_info->stream_id)) {
904
0
    case QUIC_STREAM_CLIENT_BIDI:
905
0
    case QUIC_STREAM_SERVER_BIDI:
906
0
        msg_type = moql_expected_bidi_message(stream->stream_type, from_opener, leading, legacy);
907
0
        break;
908
0
    default:
909
0
        msg_type = moql_expected_uni_message(stream->stream_type, leading, legacy);
910
0
        break;
911
0
    }
912
913
0
    if (msg_type == MOQL_MSG_NONE) {
914
0
        proto_tree_add_expert_remaining(tree, pinfo, &ei_moql_unexpected_message, tvb, offset);
915
0
        if (add_col_info) {
916
0
            col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", "Unexpected data");
917
0
        }
918
0
        return 0;
919
0
    }
920
921
    /*
922
     * Messages on the publisher side of a Subscribe stream carry an explicit
923
     * type varint, and the timestamp delta of a FRAME precedes its length.
924
     */
925
0
    if (msg_type == MOQL_MSG_SUBSCRIBE_RESPONSE) {
926
0
        if (!moql_try_get_varint(tvb, cursor, &wire_type, &type_len)) {
927
0
            goto desegment;
928
0
        }
929
0
        cursor += type_len;
930
0
        switch (wire_type) {
931
0
        case MOQL_SUBSCRIBE_OK:
932
0
            msg_type = MOQL_MSG_SUBSCRIBE_OK;
933
0
            break;
934
0
        case MOQL_SUBSCRIBE_END:
935
0
            msg_type = MOQL_MSG_SUBSCRIBE_END;
936
0
            break;
937
0
        case MOQL_SUBSCRIBE_DROP:
938
0
            msg_type = MOQL_MSG_SUBSCRIBE_DROP;
939
0
            break;
940
0
        default:
941
0
            proto_tree_add_expert_format_remaining(tree, pinfo, &ei_moql_unexpected_message, tvb,
942
0
                                                   offset, "Unknown Subscribe stream message type "
943
0
                                                   "(0x%" PRIx64 ")", wire_type);
944
0
            if (add_col_info) {
945
0
                col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", "Unknown message type");
946
0
            }
947
0
            return 0;
948
0
        }
949
0
    } else if (msg_type == MOQL_MSG_FRAME && session->version >= 5) {
950
        /* The frame timestamp was added in draft 05. */
951
0
        uint64_t zigzag;
952
953
0
        if (!moql_try_get_varint(tvb, cursor, &zigzag, &delta_len)) {
954
0
            goto desegment;
955
0
        }
956
0
        delta = moql_zigzag_decode(zigzag);
957
0
        cursor += delta_len;
958
0
    }
959
960
0
    int length_offset = cursor;
961
0
    if (!moql_try_get_varint(tvb, cursor, &msg_len, &lenvar)) {
962
0
        goto desegment;
963
0
    }
964
0
    cursor += lenvar;
965
966
0
    int header_len = cursor - offset;
967
0
    if (msg_len > (uint64_t)(INT32_MAX - header_len)) {
968
0
        proto_tree_add_expert_format_remaining(tree, pinfo, &ei_moql_protocol_violation, tvb, offset,
969
0
                                     "Message Length (%" PRIu64 ") is too large", msg_len);
970
0
        return 0;
971
0
    }
972
973
0
    int total_len = header_len + (int)msg_len;
974
0
    int available = (int)tvb_reported_length_remaining(tvb, offset);
975
0
    if (available < total_len) {
976
        /* Nothing more will arrive once the sender has closed the stream. */
977
0
        if (moql_desegment && pinfo->can_desegment && !stream_info->fin) {
978
0
            pinfo->desegment_offset = offset;
979
0
            pinfo->desegment_len = total_len - available;
980
0
            return 0;
981
0
        }
982
        /* Without reassembly the remainder of the message cannot be shown. */
983
0
        proto_tree_add_expert_format_remaining(tree, pinfo, &ei_moql_incomplete_message, tvb, offset,
984
0
                                     "%s message is incomplete: %d of %d bytes",
985
0
                                     val_to_str_const(msg_type, moql_message_type_vals, "Unknown"),
986
0
                                     available, total_len);
987
0
        if (add_col_info) {
988
0
            col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "%s[incomplete]",
989
0
                                val_to_str_const(msg_type, moql_message_type_vals, "Unknown"));
990
0
        }
991
0
        return 0;
992
0
    }
993
994
0
    msg_ti = proto_tree_add_item(tree, hf_moql_message, tvb, offset, total_len, ENC_NA);
995
0
    msg_tree = proto_item_add_subtree(msg_ti, ett_moql_message);
996
0
    proto_item_set_text(msg_ti, "%s",
997
0
                        val_to_str_const(msg_type, moql_message_type_vals, "Unknown"));
998
999
0
    ti = proto_tree_add_uint(msg_tree, hf_moql_message_type, tvb, offset, 0, msg_type);
1000
0
    proto_item_set_generated(ti);
1001
1002
0
    if (add_col_info) {
1003
0
        col_append_sep_str(pinfo->cinfo, COL_INFO, ", ",
1004
0
                           val_to_str_const(msg_type, moql_message_type_vals, "Unknown"));
1005
0
    }
1006
1007
0
    if (type_len != 0) {
1008
0
        proto_tree_add_item_ret_varint(msg_tree, hf_moql_subscribe_type, tvb, offset, -1,
1009
0
                                       ENC_VARINT_QUIC, NULL, &lenvar);
1010
0
    } else if (delta_len != 0) {
1011
0
        proto_tree_add_int64(msg_tree, hf_moql_frame_timestamp_delta, tvb, offset,
1012
0
                             (int)delta_len, delta);
1013
0
    }
1014
0
    proto_tree_add_item_ret_varint(msg_tree, hf_moql_message_length, tvb, length_offset, -1,
1015
0
                                   ENC_VARINT_QUIC, NULL, &lenvar);
1016
1017
    /*
1018
     * Dissect the body against a buffer bounded by the message length, so that
1019
     * a short message is reported instead of running into the next one.
1020
     */
1021
0
    tvbuff_t *body_tvb = tvb_new_subset_length(tvb, offset + header_len, (int)msg_len);
1022
0
    int       body_used = 0;
1023
0
    bool      body_fully_used = false;
1024
1025
0
    switch (msg_type) {
1026
0
    case MOQL_MSG_SETUP:
1027
0
        moql_dissect_setup(body_tvb, pinfo, msg_tree);
1028
0
        body_fully_used = true;
1029
0
        break;
1030
1031
0
    case MOQL_MSG_SESSION_CLIENT:
1032
0
    case MOQL_MSG_SESSION_SERVER:
1033
0
        body_used += moql_dissect_session(body_tvb, pinfo, msg_tree, msg_ti, session,
1034
0
                                          msg_type == MOQL_MSG_SESSION_CLIENT);
1035
0
        break;
1036
1037
0
    case MOQL_MSG_SESSION_UPDATE: {
1038
0
        uint64_t bitrate;
1039
1040
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_session_bitrate, &bitrate);
1041
0
        proto_item_append_text(msg_ti, ": %" PRIu64 " bit/s", bitrate);
1042
0
        break;
1043
0
    }
1044
1045
0
    case MOQL_MSG_ANNOUNCE_INIT:
1046
        /*
1047
         * ANNOUNCE_INIT (moq-lite-02 and earlier) carried the initial set of
1048
         * broadcast suffixes; its layout is not dissected here.
1049
         */
1050
0
        if (msg_len > 0) {
1051
0
            proto_tree_add_item(msg_tree, hf_moql_message_body, body_tvb, 0, (int)msg_len, ENC_NA);
1052
0
        }
1053
0
        body_fully_used = true;
1054
0
        break;
1055
1056
0
    case MOQL_MSG_ANNOUNCE_PLEASE:
1057
0
    case MOQL_MSG_ANNOUNCE_REQUEST: {
1058
0
        const char *prefix = NULL;
1059
1060
0
        body_used += moql_add_string(body_tvb, pinfo, msg_tree, body_used,
1061
0
                                     hf_moql_broadcast_path_prefix, &prefix);
1062
        /* Exclude Hop was added in draft 04. */
1063
0
        if (msg_type == MOQL_MSG_ANNOUNCE_REQUEST && session->version >= 4) {
1064
0
            body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_exclude_hop, NULL);
1065
0
        }
1066
1067
0
        proto_item_append_text(msg_ti, ": %s", prefix);
1068
0
        if (add_col_info) {
1069
0
            col_append_fstr(pinfo->cinfo, COL_INFO, "(%s)", prefix);
1070
0
        }
1071
0
        break;
1072
0
    }
1073
1074
0
    case MOQL_MSG_ANNOUNCE_OK: {
1075
0
        uint64_t hop_id, active_count;
1076
1077
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_hop_id, &hop_id);
1078
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_active_count, &active_count);
1079
1080
0
        proto_item_append_text(msg_ti, ": Hop ID %" PRIu64 ", %" PRIu64 " active broadcast%s",
1081
0
                               hop_id, active_count, plurality(active_count, "", "s"));
1082
0
        break;
1083
0
    }
1084
1085
0
    case MOQL_MSG_ANNOUNCE:
1086
0
    case MOQL_MSG_ANNOUNCE_BROADCAST:
1087
0
        body_used = moql_dissect_announce(body_tvb, pinfo, msg_tree, msg_ti, add_col_info,
1088
0
                                         msg_type == MOQL_MSG_ANNOUNCE_BROADCAST);
1089
0
        break;
1090
1091
0
    case MOQL_MSG_SUBSCRIBE: {
1092
0
        const char *path = NULL, *name = NULL;
1093
0
        uint64_t    subscribe_id;
1094
1095
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_subscribe_id, &subscribe_id);
1096
0
        body_used += moql_add_string(body_tvb, pinfo, msg_tree, body_used,
1097
0
                                    hf_moql_broadcast_path, &path);
1098
0
        body_used += moql_add_string(body_tvb, pinfo, msg_tree, body_used,
1099
0
                                    hf_moql_track_name, &name);
1100
0
        body_used += moql_dissect_subscribe_properties(body_tvb, msg_tree, body_used, legacy);
1101
1102
0
        if (!PINFO_FD_VISITED(pinfo) &&
1103
0
            wmem_map_lookup(session->subscriptions, &subscribe_id) == NULL) {
1104
0
            moql_subscription_t *subscription = wmem_new0(wmem_file_scope(), moql_subscription_t);
1105
0
            uint64_t            *key = wmem_new(wmem_file_scope(), uint64_t);
1106
1107
0
            *key = subscribe_id;
1108
0
            subscription->frame_num = pinfo->num;
1109
0
            subscription->broadcast_path = wmem_strdup(wmem_file_scope(), path);
1110
0
            subscription->track_name = wmem_strdup(wmem_file_scope(), name);
1111
0
            wmem_map_insert(session->subscriptions, key, subscription);
1112
0
        }
1113
1114
0
        proto_item_append_text(msg_ti, ": %s, %s (Subscribe ID %" PRIu64 ")",
1115
0
                               path, name, subscribe_id);
1116
0
        if (add_col_info) {
1117
0
            col_append_fstr(pinfo->cinfo, COL_INFO, "(id=%" PRIu64 " %s %s)", subscribe_id,
1118
0
                            path, name);
1119
0
        }
1120
0
        break;
1121
0
    }
1122
1123
0
    case MOQL_MSG_SUBSCRIBE_UPDATE:
1124
0
        body_used += moql_dissect_subscribe_properties(body_tvb, msg_tree, body_used, legacy);
1125
0
        break;
1126
1127
0
    case MOQL_MSG_SUBSCRIBE_OK:
1128
0
    case MOQL_MSG_SUBSCRIBE_END: {
1129
0
        uint64_t group;
1130
1131
0
        if (legacy) {
1132
            /* The resolved start group was added in draft 03. */
1133
0
            break;
1134
0
        }
1135
0
        ti = proto_tree_add_item_ret_varint(msg_tree, hf_moql_group, body_tvb, body_used, -1,
1136
0
                                           ENC_VARINT_QUIC, &group, &lenvar);
1137
0
        body_used += lenvar;
1138
0
        proto_item_append_text(ti, msg_type == MOQL_MSG_SUBSCRIBE_OK ? " (first group)"
1139
0
                                                                     : " (last group)");
1140
0
        proto_item_append_text(msg_ti, ": Group %" PRIu64, group);
1141
0
        if (add_col_info) {
1142
0
            col_append_fstr(pinfo->cinfo, COL_INFO, "(group=%" PRIu64 ")", group);
1143
0
        }
1144
0
        break;
1145
0
    }
1146
1147
0
    case MOQL_MSG_SUBSCRIBE_DROP: {
1148
0
        uint64_t group_start, group_end, error_code;
1149
1150
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_drop_group_start, &group_start);
1151
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_drop_group_end, &group_end);
1152
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_error_code, &error_code);
1153
1154
0
        proto_item_append_text(msg_ti, ": Groups %" PRIu64 "-%" PRIu64 ", Error Code %" PRIu64,
1155
0
                               group_start, group_end, error_code);
1156
0
        if (add_col_info) {
1157
0
            col_append_fstr(pinfo->cinfo, COL_INFO, "(groups=%" PRIu64 "-%" PRIu64 ")",
1158
0
                            group_start, group_end);
1159
0
        }
1160
0
        break;
1161
0
    }
1162
1163
0
    case MOQL_MSG_TRACK_INFO: {
1164
0
        uint64_t timescale;
1165
1166
0
        proto_tree_add_item(msg_tree, hf_moql_publisher_priority, body_tvb, body_used, 1, ENC_NA);
1167
0
        body_used += 1;
1168
0
        proto_tree_add_item(msg_tree, hf_moql_publisher_ordered, body_tvb, body_used, 1, ENC_NA);
1169
0
        body_used += 1;
1170
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_publisher_max_latency, NULL);
1171
0
        ti = proto_tree_add_item_ret_varint(msg_tree, hf_moql_timescale, body_tvb, body_used, -1,
1172
0
                                           ENC_VARINT_QUIC, &timescale, &lenvar);
1173
0
        body_used += lenvar;
1174
1175
0
        if (timescale == 0) {
1176
0
            expert_add_info_format(pinfo, ti, &ei_moql_protocol_violation,
1177
0
                                   "The Timescale must not be zero");
1178
0
        }
1179
1180
0
        if (stream->broadcast_path != NULL && stream->track_name != NULL) {
1181
0
            if (!PINFO_FD_VISITED(pinfo) && timescale != 0) {
1182
0
                moql_track_t *track = wmem_new0(wmem_file_scope(), moql_track_t);
1183
0
                char         *key = wmem_strdup_printf(wmem_file_scope(), "%zu:%s%s",
1184
0
                                                       strlen(stream->broadcast_path),
1185
0
                                                       stream->broadcast_path, stream->track_name);
1186
1187
0
                track->frame_num = pinfo->num;
1188
0
                track->timescale = timescale;
1189
0
                wmem_map_insert(session->tracks, key, track);
1190
0
            }
1191
0
            proto_item_append_text(msg_ti, ": %s, %s", stream->broadcast_path,
1192
0
                                   stream->track_name);
1193
0
        }
1194
0
        break;
1195
0
    }
1196
1197
0
    case MOQL_MSG_TRACK:
1198
0
    case MOQL_MSG_FETCH: {
1199
0
        const char *path = NULL, *name = NULL;
1200
0
        uint64_t    group_sequence;
1201
1202
0
        body_used += moql_add_string(body_tvb, pinfo, msg_tree, body_used,
1203
0
                                    hf_moql_broadcast_path, &path);
1204
0
        body_used += moql_add_string(body_tvb, pinfo, msg_tree, body_used,
1205
0
                                    hf_moql_track_name, &name);
1206
        /* FETCH extends TRACK's path/name pair with priority and group. */
1207
0
        if (msg_type == MOQL_MSG_FETCH) {
1208
0
            proto_tree_add_item(msg_tree, hf_moql_subscriber_priority, body_tvb, body_used++, 1, ENC_NA);
1209
0
            body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_group_sequence, &group_sequence);
1210
0
        }
1211
0
        if (!PINFO_FD_VISITED(pinfo)) {
1212
0
            stream->broadcast_path = wmem_strdup(wmem_file_scope(), path);
1213
0
            stream->track_name = wmem_strdup(wmem_file_scope(), name);
1214
0
        }
1215
0
        proto_item_append_text(msg_ti, ": %s, %s", path, name);
1216
0
        if (msg_type == MOQL_MSG_FETCH) {
1217
0
            proto_item_append_text(msg_ti, ", Group %" PRIu64, group_sequence);
1218
0
        }
1219
0
        if (add_col_info) {
1220
0
            col_append_fstr(pinfo->cinfo, COL_INFO, "(%s %s", path, name);
1221
0
            if (msg_type == MOQL_MSG_FETCH) {
1222
0
                col_append_fstr(pinfo->cinfo, COL_INFO, " group=%" PRIu64, group_sequence);
1223
0
            }
1224
0
            col_append_str(pinfo->cinfo, COL_INFO, ")");
1225
0
        }
1226
0
        break;
1227
0
    }
1228
1229
0
    case MOQL_MSG_PROBE: {
1230
0
        uint64_t bitrate, rtt;
1231
1232
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_probe_bitrate, &bitrate);
1233
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_probe_rtt, &rtt);
1234
1235
0
        proto_item_append_text(msg_ti, ": %" PRIu64 " bit/s, RTT %" PRIu64 " ms", bitrate, rtt);
1236
0
        break;
1237
0
    }
1238
1239
0
    case MOQL_MSG_GOAWAY: {
1240
0
        const char *uri = NULL;
1241
1242
0
        body_used += moql_add_string(body_tvb, pinfo, msg_tree, body_used, hf_moql_goaway_uri, &uri);
1243
0
        if (uri != NULL && uri[0] != '\0') {
1244
0
            proto_item_append_text(msg_ti, ": %s", uri);
1245
0
            if (add_col_info) {
1246
0
                col_append_fstr(pinfo->cinfo, COL_INFO, "(%s)", uri);
1247
0
            }
1248
0
        }
1249
0
        break;
1250
0
    }
1251
1252
0
    case MOQL_MSG_GROUP: {
1253
0
        uint64_t subscribe_id, group_sequence;
1254
1255
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_subscribe_id, &subscribe_id);
1256
0
        body_used += moql_add_varint(body_tvb, msg_tree, body_used, hf_moql_group_sequence, &group_sequence);
1257
1258
0
        stream->subscription = (moql_subscription_t *)wmem_map_lookup(session->subscriptions,
1259
0
                                                                     &subscribe_id);
1260
1261
0
        proto_item_append_text(msg_ti, ": Subscribe ID %" PRIu64 ", Group %" PRIu64,
1262
0
                               subscribe_id, group_sequence);
1263
0
        if (add_col_info) {
1264
0
            col_append_fstr(pinfo->cinfo, COL_INFO, "(id=%" PRIu64 " group=%" PRIu64 ")",
1265
0
                            subscribe_id, group_sequence);
1266
0
        }
1267
0
        break;
1268
0
    }
1269
1270
0
    case MOQL_MSG_FRAME:
1271
0
        if (session->version >= 5) {
1272
0
            moql_add_frame_timestamp(body_tvb, pinfo, msg_tree, msg_ti, session, stream, abs_offset,
1273
0
                                     delta, (int)msg_len);
1274
0
        } else {
1275
0
            proto_item_append_text(msg_ti, ": %d byte%s payload", (int)msg_len,
1276
0
                                   plurality(msg_len, "", "s"));
1277
0
        }
1278
0
        if (msg_len > 0) {
1279
0
            proto_tree_add_item(msg_tree, hf_moql_frame_payload, body_tvb, 0, (int)msg_len, ENC_NA);
1280
0
        }
1281
0
        body_fully_used = true;
1282
0
        break;
1283
1284
0
    default:
1285
0
        ws_assert_not_reached();
1286
0
    }
1287
1288
0
    if (!body_fully_used && tvb_reported_length_remaining(body_tvb, body_used) > 0) {
1289
        /* Unexpected trailing bytes; a future version may add fields here. */
1290
0
        proto_tree_add_expert_remaining(msg_tree, pinfo, &ei_moql_extra_data, body_tvb, body_used);
1291
0
    }
1292
1293
    /* Annotate group data with the track it belongs to. */
1294
0
    if (msg_type == MOQL_MSG_GROUP || msg_type == MOQL_MSG_FRAME) {
1295
0
        const char *path = stream->broadcast_path;
1296
0
        const char *name = stream->track_name;
1297
1298
0
        if (stream->subscription != NULL) {
1299
0
            path = stream->subscription->broadcast_path;
1300
0
            name = stream->subscription->track_name;
1301
1302
0
            ti = proto_tree_add_uint(msg_tree, hf_moql_subscribe_frame, tvb, offset, 0,
1303
0
                                     stream->subscription->frame_num);
1304
0
            proto_item_set_generated(ti);
1305
0
        }
1306
0
        if (path != NULL && name != NULL) {
1307
0
            ti = proto_tree_add_string(msg_tree, hf_moql_broadcast_path, tvb, offset, 0, path);
1308
0
            proto_item_set_generated(ti);
1309
0
            ti = proto_tree_add_string(msg_tree, hf_moql_track_name, tvb, offset, 0, name);
1310
0
            proto_item_set_generated(ti);
1311
0
        }
1312
0
    }
1313
1314
0
    if (leading) {
1315
0
        stream->body_offset[dir] = abs_offset + (uint64_t)total_len;
1316
0
        stream->body_offset_known[dir] = true;
1317
0
    }
1318
1319
0
    return total_len;
1320
1321
0
desegment:
1322
0
    if (moql_desegment && pinfo->can_desegment && !stream_info->fin) {
1323
0
        pinfo->desegment_offset = offset;
1324
0
        pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
1325
0
    } else {
1326
0
        proto_tree_add_expert_format_remaining(tree, pinfo, &ei_moql_incomplete_message, tvb, offset,
1327
0
                                               "%s message is incomplete",
1328
0
                                               val_to_str_const(msg_type, moql_message_type_vals,
1329
0
                                                                "Unknown"));
1330
0
        if (add_col_info) {
1331
0
            col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "%s[incomplete]",
1332
0
                                val_to_str_const(msg_type, moql_message_type_vals, "Unknown"));
1333
0
        }
1334
0
    }
1335
0
    return 0;
1336
0
}
1337
1338
static int
1339
dissect_moql(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
1340
0
{
1341
0
    quic_stream_info *stream_info = (quic_stream_info *)data;
1342
0
    moql_session_t   *session;
1343
0
    moql_stream_t    *stream;
1344
0
    proto_item       *ti;
1345
0
    proto_tree       *moql_tree;
1346
0
    int               offset = 0, messages = 0;
1347
0
    bool              opener_is_server, is_bidi, from_opener, saved_from_server;
1348
1349
0
    if (stream_info == NULL) {
1350
0
        return 0;
1351
0
    }
1352
1353
0
    opener_is_server = (stream_info->stream_id & 1) != 0;
1354
0
    is_bidi = (stream_info->stream_id & 2) == 0;
1355
0
    from_opener = (stream_info->from_server == opener_is_server);
1356
1357
0
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "MoQ-Lite");
1358
    /* Only clear the info column for the first moq-lite stream in the packet. */
1359
0
    if (proto_get_layer_num(pinfo, proto_moq_lite) == 1) {
1360
0
        col_clear(pinfo->cinfo, COL_INFO);
1361
0
    }
1362
1363
0
    session = moql_session_lookup_or_create(pinfo);
1364
1365
    /*
1366
     * Both directions of a bidirectional stream share state, since only the
1367
     * endpoint that opened the stream sends the stream type.
1368
     */
1369
0
    saved_from_server = stream_info->from_server;
1370
0
    if (is_bidi) {
1371
0
        stream_info->from_server = opener_is_server;
1372
0
    }
1373
0
    stream = (moql_stream_t *)quic_stream_get_proto_data(pinfo, stream_info);
1374
0
    if (stream == NULL) {
1375
0
        stream = wmem_new0(wmem_file_scope(), moql_stream_t);
1376
0
        stream->stream_type = MOQL_STREAM_TYPE_UNKNOWN;
1377
0
        stream->frame_timestamps = wmem_map_new(wmem_file_scope(), g_int64_hash, g_int64_equal);
1378
0
        quic_stream_add_proto_data(pinfo, stream_info, stream);
1379
0
    }
1380
0
    stream_info->from_server = saved_from_server;
1381
1382
0
    ti = proto_tree_add_item(tree, proto_moq_lite, tvb, 0, -1, ENC_NA);
1383
0
    moql_tree = proto_item_add_subtree(ti, ett_moql);
1384
1385
0
    const value_string *stream_type_vals = is_bidi ? moql_bidi_stream_type_vals
1386
0
                                                   : moql_uni_stream_type_vals;
1387
1388
    /* The stream type is the first thing the opener writes on a stream. */
1389
0
    if (from_opener && stream_info->offset == 0) {
1390
0
        proto_item *type_ti;
1391
0
        uint64_t    stream_type;
1392
0
        uint32_t    lenvar;
1393
1394
0
        if (!moql_try_get_varint(tvb, offset, &stream_type, &lenvar)) {
1395
0
            if (moql_desegment && pinfo->can_desegment && !stream_info->fin) {
1396
0
                pinfo->desegment_offset = offset;
1397
0
                pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
1398
0
            }
1399
0
            return tvb_captured_length(tvb);
1400
0
        }
1401
1402
0
        bool known = stream_type <= UINT32_MAX &&
1403
0
                     try_val_to_str((uint32_t)stream_type, stream_type_vals) != NULL;
1404
1405
0
        type_ti = proto_tree_add_uint64_format_value(moql_tree, hf_moql_stream_type, tvb, offset,
1406
0
                                                     lenvar, stream_type, "%s (0x%" PRIx64 ")",
1407
0
                                                     known ? val_to_str_const((uint32_t)stream_type,
1408
0
                                                                              stream_type_vals, "")
1409
0
                                                           : "Unknown", stream_type);
1410
0
        offset += lenvar;
1411
1412
0
        stream->stream_type = known ? stream_type : MOQL_STREAM_TYPE_UNKNOWN;
1413
0
        stream->data_start = lenvar;
1414
1415
0
        if (!known) {
1416
            /*
1417
             * An endpoint resets streams whose type it does not know, so
1418
             * anything that follows cannot be dissected.
1419
             */
1420
0
            expert_add_info_format(pinfo, type_ti, &ei_moql_unknown_stream_type,
1421
0
                                   "Unknown %s stream type (0x%" PRIx64 ")",
1422
0
                                   is_bidi ? "bidirectional" : "unidirectional", stream_type);
1423
0
        }
1424
0
    }
1425
1426
0
    if (stream->stream_type == MOQL_STREAM_TYPE_UNKNOWN) {
1427
        /*
1428
         * Either the start of the stream was not captured, or its type is not
1429
         * known to this dissector.
1430
         */
1431
0
        if (tvb_reported_length_remaining(tvb, offset) > 0) {
1432
0
            proto_tree_add_expert_remaining(moql_tree, pinfo, &ei_moql_stream_type_missing, tvb,
1433
0
                                            offset);
1434
0
        }
1435
0
        col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", "Undissected stream data");
1436
0
        return tvb_captured_length(tvb);
1437
0
    }
1438
1439
0
    const char *stream_name = val_to_str_const((uint32_t)stream->stream_type, stream_type_vals,
1440
0
                                               "Unknown");
1441
0
    proto_item_append_text(ti, ", %s stream", stream_name);
1442
0
    col_append_sep_fstr(pinfo->cinfo, COL_INFO, ", ", "%s stream", stream_name);
1443
1444
0
    while (tvb_reported_length_remaining(tvb, offset) > 0) {
1445
0
        bool add_col_info = messages < MOQL_MAX_COL_INFO_MESSAGES;
1446
0
        int  consumed;
1447
1448
0
        if (!add_col_info && messages == MOQL_MAX_COL_INFO_MESSAGES) {
1449
0
            col_append_str(pinfo->cinfo, COL_INFO, ", ...");
1450
0
        }
1451
1452
0
        consumed = moql_dissect_message(tvb, pinfo, moql_tree, offset, stream_info, session, stream,
1453
0
                                        from_opener, add_col_info);
1454
0
        messages++;
1455
0
        if (consumed <= 0) {
1456
0
            break;
1457
0
        }
1458
0
        offset += consumed;
1459
0
    }
1460
1461
0
    return tvb_captured_length(tvb);
1462
0
}
1463
1464
static int
1465
dissect_moql_datagram(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
1466
0
{
1467
0
    moql_session_t      *session;
1468
0
    moql_subscription_t *subscription;
1469
0
    proto_item          *ti;
1470
0
    proto_tree          *moql_tree;
1471
0
    uint64_t             subscribe_id, group_sequence, timestamp;
1472
0
    int                  offset = 0;
1473
1474
0
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "MoQ-Lite");
1475
0
    col_set_str(pinfo->cinfo, COL_INFO, "Group datagram");
1476
1477
0
    session = moql_session_lookup_or_create(pinfo);
1478
1479
0
    ti = proto_tree_add_item(tree, proto_moq_lite, tvb, 0, -1, ENC_NA);
1480
0
    proto_item_append_text(ti, ", Group datagram");
1481
0
    moql_tree = proto_item_add_subtree(ti, ett_moql);
1482
1483
0
    offset += moql_add_varint(tvb, moql_tree, offset, hf_moql_subscribe_id, &subscribe_id);
1484
0
    offset += moql_add_varint(tvb, moql_tree, offset, hf_moql_group_sequence, &group_sequence);
1485
0
    offset += moql_add_varint(tvb, moql_tree, offset, hf_moql_datagram_timestamp, &timestamp);
1486
1487
0
    int payload_len = (int)tvb_reported_length_remaining(tvb, offset);
1488
0
    if (payload_len > 0) {
1489
0
        proto_tree_add_item(moql_tree, hf_moql_frame_payload, tvb, offset, payload_len, ENC_NA);
1490
0
    }
1491
1492
0
    subscription = (moql_subscription_t *)wmem_map_lookup(session->subscriptions, &subscribe_id);
1493
0
    if (subscription != NULL) {
1494
0
        ti = proto_tree_add_string(moql_tree, hf_moql_broadcast_path, tvb, 0, 0,
1495
0
                                   subscription->broadcast_path);
1496
0
        proto_item_set_generated(ti);
1497
0
        ti = proto_tree_add_string(moql_tree, hf_moql_track_name, tvb, 0, 0,
1498
0
                                   subscription->track_name);
1499
0
        proto_item_set_generated(ti);
1500
0
        ti = proto_tree_add_uint(moql_tree, hf_moql_subscribe_frame, tvb, 0, 0,
1501
0
                                 subscription->frame_num);
1502
0
        proto_item_set_generated(ti);
1503
0
    }
1504
1505
0
    if (tvb_reported_length(tvb) > MOQL_MAX_DATAGRAM_SIZE) {
1506
0
        proto_tree_add_expert_format_remaining(moql_tree, pinfo, &ei_moql_protocol_violation, tvb, 0,
1507
0
                                     "The datagram body (%u bytes) exceeds the %d byte limit",
1508
0
                                     tvb_reported_length(tvb), MOQL_MAX_DATAGRAM_SIZE);
1509
0
    }
1510
1511
0
    col_append_fstr(pinfo->cinfo, COL_INFO, "(id=%" PRIu64 " group=%" PRIu64
1512
0
                    " ts=%" PRIu64 " %d byte%s)", subscribe_id, group_sequence, timestamp,
1513
0
                    payload_len, plurality(payload_len, "", "s"));
1514
1515
0
    return tvb_captured_length(tvb);
1516
0
}
1517
1518
void
1519
proto_register_moq_lite(void)
1520
16
{
1521
16
    expert_module_t *expert_moql;
1522
16
    module_t        *moql_module;
1523
1524
16
    static hf_register_info hf[] = {
1525
16
        { &hf_moql_stream_type,
1526
16
          { "Stream Type", "moql.stream_type",
1527
16
            FT_UINT64, BASE_HEX, NULL, 0x0,
1528
16
            "The type of transaction carried by this QUIC stream", HFILL }
1529
16
        },
1530
16
        { &hf_moql_message,
1531
16
          { "Message", "moql.message",
1532
16
            FT_NONE, BASE_NONE, NULL, 0x0,
1533
16
            NULL, HFILL }
1534
16
        },
1535
16
        { &hf_moql_message_type,
1536
16
          { "Message Type", "moql.message_type",
1537
16
            FT_UINT32, BASE_DEC, VALS(moql_message_type_vals), 0x0,
1538
16
            "The message, inferred from the stream type, direction and position", HFILL }
1539
16
        },
1540
16
        { &hf_moql_message_length,
1541
16
          { "Message Length", "moql.message_length",
1542
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1543
16
            "Length of the message payload in bytes", HFILL }
1544
16
        },
1545
16
        { &hf_moql_string_length,
1546
16
          { "String Length", "moql.string_length",
1547
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1548
16
            "Length of the following string in bytes", HFILL }
1549
16
        },
1550
16
        { &hf_moql_setup_parameter_count,
1551
16
          { "Parameter Count", "moql.setup.parameter_count",
1552
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1553
16
            NULL, HFILL }
1554
16
        },
1555
16
        { &hf_moql_setup_parameter,
1556
16
          { "Setup Parameter", "moql.setup.parameter",
1557
16
            FT_NONE, BASE_NONE, NULL, 0x0,
1558
16
            NULL, HFILL }
1559
16
        },
1560
16
        { &hf_moql_setup_parameter_id,
1561
16
          { "Parameter ID", "moql.setup.parameter_id",
1562
16
            FT_UINT64, BASE_HEX | BASE_VAL64_STRING, VALS64(moql_setup_parameter_vals), 0x0,
1563
16
            NULL, HFILL }
1564
16
        },
1565
16
        { &hf_moql_setup_parameter_length,
1566
16
          { "Parameter Length", "moql.setup.parameter_length",
1567
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1568
16
            NULL, HFILL }
1569
16
        },
1570
16
        { &hf_moql_setup_parameter_value,
1571
16
          { "Parameter Value", "moql.setup.parameter_value",
1572
16
            FT_BYTES, BASE_NONE, NULL, 0x0,
1573
16
            NULL, HFILL }
1574
16
        },
1575
16
        { &hf_moql_setup_probe_level,
1576
16
          { "Probe Level", "moql.setup.probe_level",
1577
16
            FT_UINT64, BASE_DEC | BASE_VAL64_STRING, VALS64(moql_probe_level_vals), 0x0,
1578
16
            "The sender's bandwidth probing capability as a publisher", HFILL }
1579
16
        },
1580
16
        { &hf_moql_session_parameter,
1581
16
          { "Session Parameter", "moql.session.parameter",
1582
16
            FT_NONE, BASE_NONE, NULL, 0x0,
1583
16
            NULL, HFILL }
1584
16
        },
1585
16
        { &hf_moql_session_parameter_count,
1586
16
          { "Parameter Count", "moql.session.parameter_count",
1587
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1588
16
            NULL, HFILL }
1589
16
        },
1590
16
        { &hf_moql_session_parameter_id,
1591
16
          { "Parameter ID", "moql.session.parameter_id",
1592
16
            FT_UINT64, BASE_HEX, NULL, 0x0,
1593
16
            NULL, HFILL }
1594
16
        },
1595
16
        { &hf_moql_session_parameter_length,
1596
16
          { "Parameter Length", "moql.session.parameter_length",
1597
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1598
16
            NULL, HFILL }
1599
16
        },
1600
16
        { &hf_moql_session_parameter_value,
1601
16
          { "Parameter Value", "moql.session.parameter_value",
1602
16
            FT_BYTES, BASE_NONE, NULL, 0x0,
1603
16
            NULL, HFILL }
1604
16
        },
1605
16
        { &hf_moql_version,
1606
16
          { "Version", "moql.version",
1607
16
            FT_UINT64, BASE_HEX | BASE_VAL64_STRING, VALS64(moql_version_vals), 0x0,
1608
16
            "A moq-lite version, as negotiated by moq-lite-02 and earlier", HFILL }
1609
16
        },
1610
16
        { &hf_moql_version_count,
1611
16
          { "Version Count", "moql.version_count",
1612
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1613
16
            "Number of versions supported by the client", HFILL }
1614
16
        },
1615
16
        { &hf_moql_session_bitrate,
1616
16
          { "Bitrate", "moql.session.bitrate",
1617
16
            FT_UINT64, BASE_DEC | BASE_UNIT_STRING, UNS(&units_bit_sec), 0x0,
1618
16
            "The sender's estimated bitrate, 0 is unknown", HFILL }
1619
16
        },
1620
16
        { &hf_moql_message_body,
1621
16
          { "Message Body", "moql.message_body",
1622
16
            FT_BYTES, BASE_NONE, NULL, 0x0,
1623
16
            "The undissected body of a message", HFILL }
1624
16
        },
1625
16
        { &hf_moql_setup_path,
1626
16
          { "Path", "moql.setup.path",
1627
16
            FT_STRING, BASE_NONE, NULL, 0x0,
1628
16
            "The request path the client wishes to reach", HFILL }
1629
16
        },
1630
16
        { &hf_moql_broadcast_path,
1631
16
          { "Broadcast Path", "moql.broadcast_path",
1632
16
            FT_STRING, BASE_NONE, NULL, 0x0,
1633
16
            NULL, HFILL }
1634
16
        },
1635
16
        { &hf_moql_broadcast_path_prefix,
1636
16
          { "Broadcast Path Prefix", "moql.broadcast_path_prefix",
1637
16
            FT_STRING, BASE_NONE, NULL, 0x0,
1638
16
            "Interest in any broadcast whose path starts with this prefix", HFILL }
1639
16
        },
1640
16
        { &hf_moql_broadcast_path_suffix,
1641
16
          { "Broadcast Path Suffix", "moql.broadcast_path_suffix",
1642
16
            FT_STRING, BASE_NONE, NULL, 0x0,
1643
16
            "Combined with the requested prefix to form the full broadcast path", HFILL }
1644
16
        },
1645
16
        { &hf_moql_exclude_hop,
1646
16
          { "Exclude Hop", "moql.exclude_hop",
1647
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1648
16
            "Skip broadcasts whose path contains this Hop ID", HFILL }
1649
16
        },
1650
16
        { &hf_moql_hop_id,
1651
16
          { "Hop ID", "moql.hop_id",
1652
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1653
16
            "A relay in the path from the origin publisher (0 is unknown)", HFILL }
1654
16
        },
1655
16
        { &hf_moql_hop_count,
1656
16
          { "Hop Count", "moql.hop_count",
1657
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1658
16
            "Number of Hop ID entries that follow", HFILL }
1659
16
        },
1660
16
        { &hf_moql_active_count,
1661
16
          { "Active Count", "moql.active_count",
1662
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1663
16
            "Number of active announcements sent as the initial set", HFILL }
1664
16
        },
1665
16
        { &hf_moql_announce_status,
1666
16
          { "Announce Status", "moql.announce_status",
1667
16
            FT_UINT64, BASE_DEC | BASE_VAL64_STRING, VALS64(moql_announce_status_vals), 0x0,
1668
16
            NULL, HFILL }
1669
16
        },
1670
16
        { &hf_moql_track_name,
1671
16
          { "Track Name", "moql.track_name",
1672
16
            FT_STRING, BASE_NONE, NULL, 0x0,
1673
16
            NULL, HFILL }
1674
16
        },
1675
16
        { &hf_moql_subscribe_id,
1676
16
          { "Subscribe ID", "moql.subscribe_id",
1677
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1678
16
            NULL, HFILL }
1679
16
        },
1680
16
        { &hf_moql_subscriber_priority,
1681
16
          { "Subscriber Priority", "moql.subscriber_priority",
1682
16
            FT_UINT8, BASE_DEC, NULL, 0x0,
1683
16
            "Higher values are transmitted first during congestion", HFILL }
1684
16
        },
1685
16
        { &hf_moql_subscriber_ordered,
1686
16
          { "Subscriber Ordered", "moql.subscriber_ordered",
1687
16
            FT_UINT8, BASE_DEC, VALS(moql_ordered_vals), 0x0,
1688
16
            NULL, HFILL }
1689
16
        },
1690
16
        { &hf_moql_subscriber_max_latency,
1691
16
          { "Subscriber Max Latency", "moql.subscriber_max_latency",
1692
16
            FT_UINT64, BASE_DEC | BASE_UNIT_STRING, UNS(&units_milliseconds), 0x0,
1693
16
            "How long a non-latest group may remain in flight before it is dropped", HFILL }
1694
16
        },
1695
16
        { &hf_moql_group_start,
1696
16
          { "Group Start", "moql.group_start",
1697
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1698
16
            "The first group to deliver: 0 is the latest group, otherwise the absolute "
1699
16
            "group sequence plus one", HFILL }
1700
16
        },
1701
16
        { &hf_moql_group_end,
1702
16
          { "Group End", "moql.group_end",
1703
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1704
16
            "The last group to deliver: 0 is unbounded, otherwise the absolute "
1705
16
            "group sequence plus one", HFILL }
1706
16
        },
1707
16
        { &hf_moql_publisher_priority,
1708
16
          { "Publisher Priority", "moql.publisher_priority",
1709
16
            FT_UINT8, BASE_DEC, NULL, 0x0,
1710
16
            "Used only to resolve ties between equal subscriber priorities", HFILL }
1711
16
        },
1712
16
        { &hf_moql_publisher_ordered,
1713
16
          { "Publisher Ordered", "moql.publisher_ordered",
1714
16
            FT_UINT8, BASE_DEC, VALS(moql_ordered_vals), 0x0,
1715
16
            NULL, HFILL }
1716
16
        },
1717
16
        { &hf_moql_publisher_max_latency,
1718
16
          { "Publisher Max Latency", "moql.publisher_max_latency",
1719
16
            FT_UINT64, BASE_DEC | BASE_UNIT_STRING, UNS(&units_milliseconds), 0x0,
1720
16
            "How long the publisher caches a non-latest group", HFILL }
1721
16
        },
1722
16
        { &hf_moql_timescale,
1723
16
          { "Timescale", "moql.timescale",
1724
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1725
16
            "Frame timestamp units per second", HFILL }
1726
16
        },
1727
16
        { &hf_moql_subscribe_type,
1728
16
          { "Type", "moql.subscribe.type",
1729
16
            FT_UINT64, BASE_HEX | BASE_VAL64_STRING, VALS64(moql_subscribe_type_vals), 0x0,
1730
16
            "The message type, as sent by the publisher on a Subscribe stream", HFILL }
1731
16
        },
1732
16
        { &hf_moql_group,
1733
16
          { "Group", "moql.group",
1734
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1735
16
            "An absolute group sequence number", HFILL }
1736
16
        },
1737
16
        { &hf_moql_drop_group_start,
1738
16
          { "Group Start", "moql.drop.group_start",
1739
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1740
16
            "The first absolute group sequence in the dropped range", HFILL }
1741
16
        },
1742
16
        { &hf_moql_drop_group_end,
1743
16
          { "Group End", "moql.drop.group_end",
1744
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1745
16
            "The last absolute group sequence in the dropped range", HFILL }
1746
16
        },
1747
16
        { &hf_moql_error_code,
1748
16
          { "Error Code", "moql.error_code",
1749
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1750
16
            "An application specific error code (0 is no error)", HFILL }
1751
16
        },
1752
16
        { &hf_moql_group_sequence,
1753
16
          { "Group Sequence", "moql.group_sequence",
1754
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1755
16
            NULL, HFILL }
1756
16
        },
1757
16
        { &hf_moql_probe_bitrate,
1758
16
          { "Bitrate", "moql.probe.bitrate",
1759
16
            FT_UINT64, BASE_DEC | BASE_UNIT_STRING, UNS(&units_bit_sec), 0x0,
1760
16
            "The target bitrate (subscriber) or estimated bitrate (publisher), 0 is unknown",
1761
16
            HFILL }
1762
16
        },
1763
16
        { &hf_moql_probe_rtt,
1764
16
          { "RTT", "moql.probe.rtt",
1765
16
            FT_UINT64, BASE_DEC | BASE_UNIT_STRING, UNS(&units_milliseconds), 0x0,
1766
16
            "The smoothed round-trip time, 0 is unknown", HFILL }
1767
16
        },
1768
16
        { &hf_moql_goaway_uri,
1769
16
          { "New Session URI", "moql.goaway.uri",
1770
16
            FT_STRING, BASE_NONE, NULL, 0x0,
1771
16
            "A URI to reconnect to, empty for no redirect", HFILL }
1772
16
        },
1773
16
        { &hf_moql_frame_timestamp_delta,
1774
16
          { "Timestamp Delta", "moql.frame.timestamp_delta",
1775
16
            FT_INT64, BASE_DEC, NULL, 0x0,
1776
16
            "Zigzag encoded delta from the previous frame's timestamp", HFILL }
1777
16
        },
1778
16
        { &hf_moql_frame_timestamp,
1779
16
          { "Timestamp", "moql.frame.timestamp",
1780
16
            FT_INT64, BASE_DEC, NULL, 0x0,
1781
16
            "The absolute frame timestamp in the track's timescale", HFILL }
1782
16
        },
1783
16
        { &hf_moql_frame_time,
1784
16
          { "Time", "moql.frame.time",
1785
16
            FT_RELATIVE_TIME, BASE_NONE, NULL, 0x0,
1786
16
            "The absolute frame timestamp converted using the track's timescale", HFILL }
1787
16
        },
1788
16
        { &hf_moql_frame_payload,
1789
16
          { "Payload", "moql.frame.payload",
1790
16
            FT_BYTES, BASE_NONE, NULL, 0x0,
1791
16
            "An application specific payload, opaque to moq-lite", HFILL }
1792
16
        },
1793
16
        { &hf_moql_datagram_timestamp,
1794
16
          { "Timestamp", "moql.datagram.timestamp",
1795
16
            FT_UINT64, BASE_DEC, NULL, 0x0,
1796
16
            "The absolute timestamp of the frame in the track's timescale", HFILL }
1797
16
        },
1798
16
        { &hf_moql_subscribe_frame,
1799
16
          { "Subscribe Frame", "moql.subscribe_frame",
1800
16
            FT_FRAMENUM, BASE_NONE, NULL, 0x0,
1801
16
            "The frame containing the SUBSCRIBE for this Subscribe ID", HFILL }
1802
16
        },
1803
16
        { &hf_moql_track_info_frame,
1804
16
          { "Track Info Frame", "moql.track_info_frame",
1805
16
            FT_FRAMENUM, BASE_NONE, NULL, 0x0,
1806
16
            "The frame containing the TRACK_INFO used to interpret timestamps", HFILL }
1807
16
        },
1808
16
    };
1809
1810
16
    static int *ett[] = {
1811
16
        &ett_moql,
1812
16
        &ett_moql_message,
1813
16
        &ett_moql_setup_parameter,
1814
16
        &ett_moql_session_parameter,
1815
16
    };
1816
1817
16
    static ei_register_info ei[] = {
1818
16
        { &ei_moql_unknown_stream_type,
1819
16
          { "moql.unknown_stream_type", PI_PROTOCOL, PI_WARN,
1820
16
            "Unknown stream type", EXPFILL }
1821
16
        },
1822
16
        { &ei_moql_unknown_version,
1823
16
          { "moql.unknown_version", PI_PROTOCOL, PI_WARN,
1824
16
            "Unknown moq-lite version", EXPFILL }
1825
16
        },
1826
16
        { &ei_moql_stream_type_missing,
1827
16
          { "moql.stream_type_missing", PI_UNDECODED, PI_NOTE,
1828
16
            "Undissected stream data: the stream type is unknown", EXPFILL }
1829
16
        },
1830
16
        { &ei_moql_unexpected_message,
1831
16
          { "moql.unexpected_message", PI_MALFORMED, PI_WARN,
1832
16
            "Unexpected message for this stream type and direction", EXPFILL }
1833
16
        },
1834
16
        { &ei_moql_incomplete_message,
1835
16
          { "moql.incomplete_message", PI_UNDECODED, PI_NOTE,
1836
16
            "Incomplete message and reassembly is disabled", EXPFILL }
1837
16
        },
1838
16
        { &ei_moql_extra_data,
1839
16
          { "moql.extra_data", PI_PROTOCOL, PI_NOTE,
1840
16
            "Unexpected trailing data in the message body", EXPFILL }
1841
16
        },
1842
16
        { &ei_moql_protocol_violation,
1843
16
          { "moql.protocol_violation", PI_PROTOCOL, PI_ERROR,
1844
16
            "Protocol violation", EXPFILL }
1845
16
        },
1846
16
    };
1847
1848
16
    proto_moq_lite = proto_register_protocol("Media over QUIC Lite", "MoQ-Lite", "moql");
1849
1850
16
    proto_register_field_array(proto_moq_lite, hf, array_length(hf));
1851
16
    proto_register_subtree_array(ett, array_length(ett));
1852
16
    expert_moql = expert_register_protocol(proto_moq_lite);
1853
16
    expert_register_field_array(expert_moql, ei, array_length(ei));
1854
1855
16
    moql_handle = register_dissector("moq-lite", dissect_moql, proto_moq_lite);
1856
16
    moql_datagram_handle = register_dissector("moq-lite.datagram", dissect_moql_datagram,
1857
16
                                             proto_moq_lite);
1858
1859
16
    moql_module = prefs_register_protocol(proto_moq_lite, NULL);
1860
16
    prefs_register_bool_preference(moql_module, "desegment",
1861
16
                                   "Reassemble MoQ-Lite messages spanning multiple QUIC frames",
1862
16
                                   "Whether the MoQ-Lite dissector should reassemble messages "
1863
16
                                   "spanning multiple QUIC STREAM frames",
1864
16
                                   &moql_desegment);
1865
16
}
1866
1867
void
1868
proto_reg_handoff_moq_lite(void)
1869
16
{
1870
    /* Every draft has its own ALPN token, e.g. "moq-lite-05". */
1871
112
    for (unsigned version = 0; version <= MOQL_VERSION_LATEST; version++) {
1872
96
        char *alpn = ws_strdup_printf("moq-lite-%02u", version);
1873
1874
96
        dissector_add_string("quic.proto", alpn, moql_handle);
1875
96
        dissector_add_string("quic.proto.datagram", alpn, moql_datagram_handle);
1876
96
        g_free(alpn);
1877
96
    }
1878
16
}
1879
1880
/*
1881
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
1882
 *
1883
 * Local variables:
1884
 * c-basic-offset: 4
1885
 * tab-width: 8
1886
 * indent-tabs-mode: nil
1887
 * End:
1888
 *
1889
 * vi: set shiftwidth=4 tabstop=8 expandtab:
1890
 * :indentSize=4:tabSize=8:noTabs=true:
1891
 */