@blockcast/mmt-base 0.1.0-main.85c1beb6aa0e → 0.1.0-main.8fd67100e40b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,9 +57,23 @@ MPU `payload_length` is interpreted strictly as ISO's length-after-field value:
57
57
  | `extractFecPayloadId` | function | `{ ssId }` from a source packet's FEC payload id (or `null`) |
58
58
  | `extractRepairFecPayloadId` | function | Repair-flow FEC payload id (13-byte, `REPAIR_FEC_PID_SIZE`) |
59
59
  | `parseMoqRepairFrame` | function | Strictly parse a mode-0 MMTP Annex C repair frame → `MoqRepairFrame` |
60
- | `parseAlFecMessage` | function | Parse an AL-FEC signaling message → `AlFecConfig` |
61
- | `MoqRepairFrame`, `AlFecConfig` | type | Repair-frame / AL-FEC config shapes |
60
+ | `MoqRepairFrame` | type | Repair-frame shape |
62
61
  | `ALFEC_MESSAGE_ID` (0x0203), `FEC_CODE_XOR` (1), `FEC_CODE_RAPTORQ` (2), `REPAIR_FEC_PID_SIZE` (13) | const | AL-FEC signaling + payload-id constants |
62
+ | `MMTP_SIGNALING_PACKET_ID` (0), `MMTP_PACKET_ID_MAX` (0xffff) | const | Signaling-flow `packet_id` (never a track's) and the 16-bit `packet_id` ceiling; a track's `packet_id` is 1..65535 |
63
+
64
+ ### Signalling messages (`signalling.ts`, packet_id 0)
65
+
66
+ Total parsers (malformed or unsupported input → `null`, never a throw), per
67
+ ISO/IEC 23008-1:2023 9.3.4.2, 10.2-10.3, 10.6 and Annex C Table C.3 as
68
+ replaced by Amd 1:2025.
69
+
70
+ | Symbol | Kind | Purpose |
71
+ |--------|------|---------|
72
+ | `parseSignallingPayload` | function | Split a signalling packet into complete messages; reports the payload form: `iso-9.3.4.2` (Figure 15 header) or `legacy-bare-alfec` (message 0x0203 directly after the MMTP header, recognised by its exact `0x02 0x03` peek) |
73
+ | `parseSignallingPacket` | function | Decode every message of a signalling packet → `al-fec` / `pa` / `mpt` / `other` |
74
+ | `parseAlFecMessage`, `parseAlFecMessageBody` | function | Every FEC flow of an AL-FEC message: all asset packet_ids, one-stage / two-stage / LA-FEC repair flows, private fields → `AlFecMessage` |
75
+ | `alFecRaptorQFlowConfig` | function | One flow's decoder geometry under the Blockcast one-stage RaptorQ profile (private field = OTI + interleave depth) → `AlFecRaptorQFlowConfig` |
76
+ | `parsePaMessageBody`, `parseMptMessageBody`, `parseMpTable`, `mpAssetPacketIds` | function | PA / MPT messages and the MP table: asset → packet_id mapping |
63
77
 
64
78
  ### ALTA trailer (re-exported from `@blockcast/mmt-alta-parse`)
65
79
 
package/dist/index.d.ts CHANGED
@@ -1,2 +1,6 @@
1
1
  export * from './mmtp.js';
2
+ export * from './net-constants.js';
3
+ export * from './ntp-short.js';
4
+ export * from './signalling.js';
5
+ export * from './serial-number.js';
2
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA"}
package/dist/index.js CHANGED
@@ -1,2 +1,6 @@
1
1
  export * from './mmtp.js';
2
+ export * from './net-constants.js';
3
+ export * from './ntp-short.js';
4
+ export * from './signalling.js';
5
+ export * from './serial-number.js';
2
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA"}
package/dist/mmtp.d.ts CHANGED
@@ -74,12 +74,32 @@ export declare const PACKET_TYPE_MPU = 0;
74
74
  export declare const PACKET_TYPE_SIGNALING = 2;
75
75
  export declare const PACKET_TYPE_REPAIR = 3;
76
76
  export declare const ALFEC_MESSAGE_ID = 515;
77
+ /**
78
+ * MMTP `packet_id` of the signaling flow, which carries PACKET_TYPE_SIGNALING
79
+ * packets such as the AL-FEC message (ALFEC_MESSAGE_ID). The FFmpeg `moq_mmt`
80
+ * muxer sends that message on this packet_id into the same multicast group as
81
+ * the media, and moqcast-draft (PR #44) makes advertising it for a track a
82
+ * MUST NOT. A track's packet_id is therefore 1..MMTP_PACKET_ID_MAX.
83
+ */
84
+ export declare const MMTP_SIGNALING_PACKET_ID = 0;
85
+ /** Largest value of the 16-bit MMTP `packet_id` header field. */
86
+ export declare const MMTP_PACKET_ID_MAX = 65535;
77
87
  export declare const FEC_CODE_XOR = 1;
78
88
  export declare const FEC_CODE_RAPTORQ = 2;
79
89
  /**
80
90
  * Parse MMTP header from buffer
81
91
  */
82
92
  export declare function parseMMTPHeader(buffer: Uint8Array): MMTPHeader;
93
+ /**
94
+ * The MMTP `packet_id` of `buffer`, or null exactly when parseMMTPHeader would
95
+ * throw for it (short buffer, V != 0, truncated packet_counter or header
96
+ * extension).
97
+ *
98
+ * This is the per-packet filter a receiver runs before any other work, so it
99
+ * builds no header object, DataView or BigInt timestamp (BLO-36599). Use
100
+ * parseMMTPHeader when any other header field is needed.
101
+ */
102
+ export declare function readMMTPPacketId(buffer: Uint8Array): number | null;
83
103
  /**
84
104
  * Unwrap MMTP packet to extract payload
85
105
  */
@@ -147,6 +167,16 @@ export declare function explainMFURejection(mmtpPacket: Uint8Array, options?: {
147
167
  export declare function extractMFUPayload(mmtpPacket: Uint8Array, options?: {
148
168
  skipFecId?: boolean;
149
169
  }): MFUExtraction | null;
170
+ /**
171
+ * extractMFUPayload for a caller that already holds this packet's parsed MMTP
172
+ * header, so the header is not parsed a second time (BLO-36599).
173
+ *
174
+ * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet; the
175
+ * result is then identical to `extractMFUPayload(mmtpPacket, options)`.
176
+ */
177
+ export declare function extractMFUPayloadWithHeader(mmtpPacket: Uint8Array, header: MMTPHeader, options?: {
178
+ skipFecId?: boolean;
179
+ }): MFUExtraction | null;
150
180
  /**
151
181
  * Extract timestamp from MMTP packet
152
182
  */
@@ -161,9 +191,10 @@ export declare function isFecRepairPacket(mmtpPacket: Uint8Array): boolean;
161
191
  * source packet per ISO/IEC 23008-1:2023 Section C.5.2 Figure C.10.
162
192
  *
163
193
  * SS_ID is a flat 32-bit monotonic counter (incremented by 1 per packet
164
- * for ssbg_mode0 and ssbg_mode1). Block membership and ESI are derived
165
- * at recovery time from repair packet SS_Start and SSB_length:
166
- * SBN = floor(SS_ID / K), ESI = SS_ID % K
194
+ * for ssbg_mode0 and ssbg_mode1). Block membership and ESI use the
195
+ * configured K; repair SS_Start identifies the first SS_ID of the block and
196
+ * SSB_length records the authored source-symbol count:
197
+ * SBN = floor(SS_ID / K), ESI = SS_ID % K
167
198
  * SS_Start = SBN * K (first SS_ID of the block)
168
199
  *
169
200
  * This matches FFmpeg moqenc_mmt.c, the drafts (draft-ramadan-moq-fec §6.2),
@@ -177,6 +208,18 @@ export declare function extractFecPayloadId(mmtpPacket: Uint8Array): {
177
208
  * payload contains a four-byte Source FEC Payload ID.
178
209
  */
179
210
  export declare function hasSourceFecPayloadId(mmtpPacket: Uint8Array): boolean;
211
+ /**
212
+ * The Source FEC Payload ID of a packet whose MMTP header the caller already
213
+ * parsed, or null when the packet carries none: present only when FEC_type=1
214
+ * and the exact suffix after the ISO-declared MPU payload holds the four-byte
215
+ * ID (ISO/IEC 23008-1 §C.5.2). Same answer as extractFecPayloadId, without a
216
+ * second header parse (BLO-36599).
217
+ *
218
+ * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet.
219
+ */
220
+ export declare function sourceFecPayloadIdOf(mmtpPacket: Uint8Array, header: MMTPHeader): {
221
+ ssId: number;
222
+ } | null;
180
223
  /**
181
224
  * MMTP header-extension ext_type assigned to the detached ALTA authenticator
182
225
  * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`
@@ -216,7 +259,7 @@ export interface MoqRepairFrame {
216
259
  rsbLength: number;
217
260
  /** RS_ID (24 bits): repair symbol index (0-based) */
218
261
  rsId: number;
219
- /** SSB_length (24 bits): number of source symbols (K) */
262
+ /** SSB_length (24 bits): authored source-symbol count (1..=configured K) */
220
263
  ssbLength: number;
221
264
  /** Repair symbol data after the dynamic MMTP header and 13-byte FEC payload ID. */
222
265
  symbolData: Uint8Array;
@@ -229,7 +272,7 @@ export interface MoqRepairFrame {
229
272
  * then SS_Start (4 bytes) → block identifier
230
273
  * then RSB_length (3 bytes) → P
231
274
  * then RS_ID (3 bytes) → ESI = K + RS_ID
232
- * then SSB_length (3 bytes) → K
275
+ * then SSB_length (3 bytes) → authored source-symbol count (1..=K)
233
276
  * then Repair Symbol Data (T bytes)
234
277
  *
235
278
  * @param frame - Raw MoQ repair frame bytes
@@ -237,39 +280,13 @@ export interface MoqRepairFrame {
237
280
  */
238
281
  export declare function parseMoqRepairFrame(frame: Uint8Array): MoqRepairFrame | null;
239
282
  /**
240
- * AL-FEC signaling configuration parsed from ISO/IEC 23008-1:2023 Table C.3 message.
241
- */
242
- export interface AlFecConfig {
243
- fecCodeId: number;
244
- fecPayloadIdMode: 0;
245
- repairSymbolSize: number;
246
- maximumK: number;
247
- maximumP: number;
248
- interleaveDepth: number;
249
- oti: Uint8Array;
250
- }
251
- /**
252
- * Parse AL-FEC signaling message from a full MMTP signaling packet.
253
- *
254
- * Wire format (ISO/IEC 23008-1:2023 Table C.3, Amendment 1:2025):
255
- * [12-byte MMTP header (packet_type=0x02)]
256
- * message_id(2) = 0x0203
257
- * version(1)
258
- * message_length(2) = length of remaining payload
259
- * flags(1) = fec_flag(1) | private_fec_flag(1) | reserved(6)
260
- * fec_flow_descriptor:
261
- * length(2)
262
- * num_flows(1)
263
- * per flow:
264
- * fec_flow_id(1), source_flow_id(1), num_assets(1), packet_ids(2*N),
265
- * coding_params(1), repair_symbol_size(2),
266
- * [one-stage]: repair_flow_id(2), fec_code_id(1),
267
- * [private_fec_flag]: private_header(1){flag(1)|len(7)},
268
- * current RaptorQ profile OTI(12) + interleave_depth(1)
269
- * max_k(3), max_p(3), protection_window_time(4), protection_window_size(4)
283
+ * parseMoqRepairFrame for a caller that already holds this frame's parsed MMTP
284
+ * header, so the header is not parsed a second time (BLO-36599). The router's
285
+ * multicast repair branch uses it: one repair packet in every K + P otherwise
286
+ * paid two header parses.
270
287
  *
271
- * @param mmtpPacket Full MMTP packet including 12-byte header
272
- * @returns Parsed config or null if not an AL-FEC signaling message
288
+ * `header` must be `parseMMTPHeader(frame)` of this same frame; the result is
289
+ * then identical to `parseMoqRepairFrame(frame)`.
273
290
  */
274
- export declare function parseAlFecMessage(mmtpPacket: Uint8Array): AlFecConfig | null;
291
+ export declare function parseMoqRepairFrameWithHeader(frame: Uint8Array, header: MMTPHeader): MoqRepairFrame | null;
275
292
  //# sourceMappingURL=mmtp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mmtp.d.ts","sourceRoot":"","sources":["../src/mmtp.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,kBAAkB,IAAI,uBAAuB,EAC7C,KAAK,WAAW,IAAI,gBAAgB,EACrC,MAAM,2BAA2B,CAAA;AAElC;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,iBAAiB,EAAE,OAAO,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,4DAA4D;IAC5D,aAAa,EAAE,OAAO,CAAA;IACtB,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,CAAA;IAC9C,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,sBAAsB,EAAE,MAAM,CAAA;IAC9B,8CAA8C;IAC9C,eAAe,EAAE,MAAM,CAAA;IACvB,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,eAAe,EAAE,OAAO,CAAA;IACxB;sEACkE;IAClE,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,2BAA2B,EAAE,MAAM,CAAA;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAA;IAClB,OAAO,EAAE,UAAU,CAAA;CACpB;AAED,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,eAAe,IAAI,CAAA;AAChC,eAAO,MAAM,wBAAwB,KAAK,CAAA;AAC1C,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAO5C,eAAO,MAAM,eAAe,IAAO,CAAA;AACnC,eAAO,MAAM,qBAAqB,IAAO,CAAA;AACzC,eAAO,MAAM,kBAAkB,IAAO,CAAA;AAGtC,eAAO,MAAM,gBAAgB,MAAS,CAAA;AAGtC,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,eAAO,MAAM,gBAAgB,IAAI,CAAA;AAEjC;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAuE9D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAKzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CA+B7D;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAU7F;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GACxB,0CAA0C,GAC1C,yBAAyB,GACzB,mBAAmB,GACnB,iCAAiC,GACjC,sBAAsB,GACtB,2CAA2C,GAC3C,kCAAkC,GAClC,oCAAoC,GACpC,sCAAsC,GACtC,8BAA8B,CAAA;AAElC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,gBAAgB,CAAA;IACtB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAA;CACf;AA4GD;;GAEG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAIjE;AAED,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,yBAAyB;IACzB,MAAM,EAAE,UAAU,CAAA;IAClB,wBAAwB;IACxB,GAAG,EAAE,SAAS,CAAA;IACd,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAA;IACpB,8EAA8E;IAC9E,IAAI,EAAE,UAAU,CAAA;IAChB,8DAA8D;IAC9D,WAAW,EAAE,WAAW,GAAG,IAAI,CAAA;IAC/B,kEAAkE;IAClE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,kEAAkE;IAClE,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CACtC;AAqID;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,UAAU,EACtB,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAChC,YAAY,GAAG,IAAI,CAGrB;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,UAAU,EACtB,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAChC,aAAa,GAAG,IAAI,CA+BtB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAG/D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAIjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAUnF;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAiBrE;AAED;;;;;;;GAOG;AAIH,eAAO,MAAM,uBAAuB,QAAgC,CAAA;AACpE,eAAO,MAAM,4BAA4B,IAAqC,CAAA;AAC9E,MAAM,MAAM,WAAW,GAAG,gBAAgB,CAAA;AAC1C,eAAO,MAAM,kBAAkB,gCAA0B,CAAA;AAEzD,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,KAAK,CAAA;AA+BrC;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,UAAU,GAAG;IACjE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CACpE,GAAG,IAAI,CASP;AAED,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAA;IAChB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAA;IACf,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,mFAAmF;IACnF,UAAU,EAAE,UAAU,CAAA;CACvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAoB5E;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,CAAC,CAAA;IACnB,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,GAAG,EAAE,UAAU,CAAA;CAChB;AAoBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI,CA2E5E"}
1
+ {"version":3,"file":"mmtp.d.ts","sourceRoot":"","sources":["../src/mmtp.ts"],"names":[],"mappings":"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"}
package/dist/mmtp.js CHANGED
@@ -12,6 +12,16 @@ export const PACKET_TYPE_SIGNALING = 0x02;
12
12
  export const PACKET_TYPE_REPAIR = 0x03;
13
13
  // AL-FEC Signaling Message ID (ISO/IEC 23008-1:2023 Amendment 1:2025)
14
14
  export const ALFEC_MESSAGE_ID = 0x0203;
15
+ /**
16
+ * MMTP `packet_id` of the signaling flow, which carries PACKET_TYPE_SIGNALING
17
+ * packets such as the AL-FEC message (ALFEC_MESSAGE_ID). The FFmpeg `moq_mmt`
18
+ * muxer sends that message on this packet_id into the same multicast group as
19
+ * the media, and moqcast-draft (PR #44) makes advertising it for a track a
20
+ * MUST NOT. A track's packet_id is therefore 1..MMTP_PACKET_ID_MAX.
21
+ */
22
+ export const MMTP_SIGNALING_PACKET_ID = 0;
23
+ /** Largest value of the 16-bit MMTP `packet_id` header field. */
24
+ export const MMTP_PACKET_ID_MAX = 0xffff;
15
25
  // FEC Code IDs
16
26
  export const FEC_CODE_XOR = 1;
17
27
  export const FEC_CODE_RAPTORQ = 2;
@@ -81,6 +91,36 @@ export function parseMMTPHeader(buffer) {
81
91
  rapFlag,
82
92
  };
83
93
  }
94
+ /**
95
+ * The MMTP `packet_id` of `buffer`, or null exactly when parseMMTPHeader would
96
+ * throw for it (short buffer, V != 0, truncated packet_counter or header
97
+ * extension).
98
+ *
99
+ * This is the per-packet filter a receiver runs before any other work, so it
100
+ * builds no header object, DataView or BigInt timestamp (BLO-36599). Use
101
+ * parseMMTPHeader when any other header field is needed.
102
+ */
103
+ export function readMMTPPacketId(buffer) {
104
+ if (buffer.length < MMTP_HEADER_SIZE)
105
+ return null;
106
+ const flags = buffer[0];
107
+ if (((flags >> 6) & 0x03) !== 0)
108
+ return null;
109
+ let offset = MMTP_HEADER_SIZE;
110
+ if ((flags & 0x20) !== 0) {
111
+ if (offset + 4 > buffer.length)
112
+ return null;
113
+ offset += 4;
114
+ }
115
+ if ((flags & 0x02) !== 0) {
116
+ if (offset + 4 > buffer.length)
117
+ return null;
118
+ const length = (buffer[offset + 2] << 8) | buffer[offset + 3];
119
+ if (offset + 4 + length > buffer.length)
120
+ return null;
121
+ }
122
+ return (buffer[2] << 8) | buffer[3];
123
+ }
84
124
  /**
85
125
  * Unwrap MMTP packet to extract payload
86
126
  */
@@ -227,7 +267,7 @@ export function extractNALUnit(mmtpPacket) {
227
267
  * reason reported to operators can never drift from the decision the parser
228
268
  * actually made.
229
269
  */
230
- function planMfuExtraction(mmtpPacket, options) {
270
+ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
231
271
  if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {
232
272
  return {
233
273
  rejection: {
@@ -237,16 +277,21 @@ function planMfuExtraction(mmtpPacket, options) {
237
277
  };
238
278
  }
239
279
  let header;
240
- try {
241
- header = parseMMTPHeader(mmtpPacket);
280
+ if (parsedHeader) {
281
+ header = parsedHeader;
242
282
  }
243
- catch (error) {
244
- return {
245
- rejection: {
246
- gate: 'mmtp-header-unparseable',
247
- detail: error instanceof Error ? error.message : String(error),
248
- },
249
- };
283
+ else {
284
+ try {
285
+ header = parseMMTPHeader(mmtpPacket);
286
+ }
287
+ catch (error) {
288
+ return {
289
+ rejection: {
290
+ gate: 'mmtp-header-unparseable',
291
+ detail: error instanceof Error ? error.message : String(error),
292
+ },
293
+ };
294
+ }
250
295
  }
251
296
  if (header.packetType !== PACKET_TYPE_MPU) {
252
297
  return {
@@ -320,7 +365,19 @@ export function explainMFURejection(mmtpPacket, options) {
320
365
  return 'rejection' in result ? result.rejection : null;
321
366
  }
322
367
  export function extractMFUPayload(mmtpPacket, options) {
323
- const result = planMfuExtraction(mmtpPacket, options);
368
+ return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options));
369
+ }
370
+ /**
371
+ * extractMFUPayload for a caller that already holds this packet's parsed MMTP
372
+ * header, so the header is not parsed a second time (BLO-36599).
373
+ *
374
+ * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet; the
375
+ * result is then identical to `extractMFUPayload(mmtpPacket, options)`.
376
+ */
377
+ export function extractMFUPayloadWithHeader(mmtpPacket, header, options) {
378
+ return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options, header));
379
+ }
380
+ function extractPlannedMfu(mmtpPacket, result) {
324
381
  if ('rejection' in result)
325
382
  return null;
326
383
  const { header, payload, mpu, fragmentType, layout, trailerLayout } = result.plan;
@@ -363,46 +420,69 @@ export function isFecRepairPacket(mmtpPacket) {
363
420
  * source packet per ISO/IEC 23008-1:2023 Section C.5.2 Figure C.10.
364
421
  *
365
422
  * SS_ID is a flat 32-bit monotonic counter (incremented by 1 per packet
366
- * for ssbg_mode0 and ssbg_mode1). Block membership and ESI are derived
367
- * at recovery time from repair packet SS_Start and SSB_length:
368
- * SBN = floor(SS_ID / K), ESI = SS_ID % K
423
+ * for ssbg_mode0 and ssbg_mode1). Block membership and ESI use the
424
+ * configured K; repair SS_Start identifies the first SS_ID of the block and
425
+ * SSB_length records the authored source-symbol count:
426
+ * SBN = floor(SS_ID / K), ESI = SS_ID % K
369
427
  * SS_Start = SBN * K (first SS_ID of the block)
370
428
  *
371
429
  * This matches FFmpeg moqenc_mmt.c, the drafts (draft-ramadan-moq-fec §6.2),
372
430
  * and ATSC A/331 §8.1.2.5 which defers to ISO 23008-1 Annex C.
373
431
  */
374
432
  export function extractFecPayloadId(mmtpPacket) {
375
- if (!hasSourceFecPayloadId(mmtpPacket))
376
- return null;
377
- // Flat 4-byte SS_ID: encoder writes block_id * K + symbol_id
378
- const off = mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
379
- const view = new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + off, SOURCE_FEC_PAYLOAD_ID_SIZE);
380
- return { ssId: view.getUint32(0, false) };
433
+ const header = parseHeaderForSourceFecId(mmtpPacket);
434
+ return header ? sourceFecPayloadIdOf(mmtpPacket, header) : null;
381
435
  }
382
436
  /**
383
437
  * Return true when FEC_type=1 and the exact suffix after the ISO-declared MPU
384
438
  * payload contains a four-byte Source FEC Payload ID.
385
439
  */
386
440
  export function hasSourceFecPayloadId(mmtpPacket) {
441
+ const header = parseHeaderForSourceFecId(mmtpPacket);
442
+ return header !== null && sourceFecPayloadIdOf(mmtpPacket, header) !== null;
443
+ }
444
+ function parseHeaderForSourceFecId(mmtpPacket) {
387
445
  if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE)
388
- return false;
389
- let header;
446
+ return null;
390
447
  try {
391
- header = parseMMTPHeader(mmtpPacket);
448
+ return parseMMTPHeader(mmtpPacket);
392
449
  }
393
450
  catch {
394
- return false;
451
+ return null;
395
452
  }
453
+ }
454
+ /**
455
+ * The Source FEC Payload ID of a packet whose MMTP header the caller already
456
+ * parsed, or null when the packet carries none: present only when FEC_type=1
457
+ * and the exact suffix after the ISO-declared MPU payload holds the four-byte
458
+ * ID (ISO/IEC 23008-1 §C.5.2). Same answer as extractFecPayloadId, without a
459
+ * second header parse (BLO-36599).
460
+ *
461
+ * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet.
462
+ */
463
+ export function sourceFecPayloadIdOf(mmtpPacket, header) {
464
+ if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE)
465
+ return null;
396
466
  if (header.packetType !== PACKET_TYPE_MPU)
397
- return false;
467
+ return null;
398
468
  const payload = mmtpPacket.subarray(header.headerLength);
399
469
  if (payload.length < MPU_HEADER_SIZE)
400
- return false;
401
- const mpu = parseMPUHeader(payload);
402
- if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE)
403
- return false;
404
- const declaredEnd = MPU_LENGTH_FIELD_SIZE + mpu.payloadLength;
405
- return resolveDeclaredTrailers(payload, declaredEnd, header.fecType)?.hasFecId ?? false;
470
+ return null;
471
+ const payloadLength = (payload[0] << 8) | payload[1];
472
+ if (payloadLength < MPU_HEADER_REMAINDER_SIZE)
473
+ return null;
474
+ const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength;
475
+ if (!resolveDeclaredTrailers(payload, declaredEnd, header.fecType)?.hasFecId)
476
+ return null;
477
+ // Flat 4-byte SS_ID: encoder writes block_id * K + symbol_id
478
+ const off = mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
479
+ return {
480
+ ssId: ((mmtpPacket[off] << 24) |
481
+ (mmtpPacket[off + 1] << 16) |
482
+ (mmtpPacket[off + 2] << 8) |
483
+ mmtpPacket[off + 3]) >>>
484
+ 0,
485
+ };
406
486
  }
407
487
  /**
408
488
  * MMTP header-extension ext_type assigned to the detached ALTA authenticator
@@ -420,22 +500,34 @@ export const MMTP_EXT_TRAILER_HEADER_SIZE = _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE;
420
500
  export const extractAltaTrailer = _leafExtractAltaTrailer;
421
501
  /** Size of Repair FEC Payload ID per ISO/IEC 23008-1 Section C.5.3 */
422
502
  export const REPAIR_FEC_PID_SIZE = 13;
423
- /** Parse the 13-byte Repair FEC Payload ID after the complete dynamic MMTP header. */
424
- function readRepairFecPayloadIdFields(mmtpPacket) {
503
+ /**
504
+ * Parse the 13-byte Repair FEC Payload ID after the complete dynamic MMTP header.
505
+ * `parsedHeader`, when given, must be `parseMMTPHeader(mmtpPacket)` of this
506
+ * same packet; it spares the second header parse (BLO-36599).
507
+ */
508
+ function readRepairFecPayloadIdFields(mmtpPacket, parsedHeader) {
425
509
  let header;
426
- try {
427
- header = parseMMTPHeader(mmtpPacket);
510
+ if (parsedHeader) {
511
+ header = parsedHeader;
428
512
  }
429
- catch {
430
- return null;
513
+ else {
514
+ try {
515
+ header = parseMMTPHeader(mmtpPacket);
516
+ }
517
+ catch {
518
+ return null;
519
+ }
431
520
  }
432
521
  if (header.packetType !== PACKET_TYPE_REPAIR || header.fecType !== 2)
433
522
  return null;
434
523
  const off = header.headerLength;
435
524
  if (mmtpPacket.length < off + REPAIR_FEC_PID_SIZE)
436
525
  return null;
437
- const view = new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + off, REPAIR_FEC_PID_SIZE);
438
- const ssStart = view.getUint32(0, false); // 32 bits
526
+ const ssStart = ((mmtpPacket[off] << 24) |
527
+ (mmtpPacket[off + 1] << 16) |
528
+ (mmtpPacket[off + 2] << 8) |
529
+ mmtpPacket[off + 3]) >>>
530
+ 0; // 32 bits
439
531
  const rsbLength = (mmtpPacket[off + 4] << 16) | (mmtpPacket[off + 5] << 8) | mmtpPacket[off + 6]; // 24 bits
440
532
  const rsId = (mmtpPacket[off + 7] << 16) | (mmtpPacket[off + 8] << 8) | mmtpPacket[off + 9]; // 24 bits
441
533
  const ssbLength = (mmtpPacket[off + 10] << 16) | (mmtpPacket[off + 11] << 8) | mmtpPacket[off + 12]; // 24 bits
@@ -476,14 +568,28 @@ export function extractRepairFecPayloadId(mmtpPacket) {
476
568
  * then SS_Start (4 bytes) → block identifier
477
569
  * then RSB_length (3 bytes) → P
478
570
  * then RS_ID (3 bytes) → ESI = K + RS_ID
479
- * then SSB_length (3 bytes) → K
571
+ * then SSB_length (3 bytes) → authored source-symbol count (1..=K)
480
572
  * then Repair Symbol Data (T bytes)
481
573
  *
482
574
  * @param frame - Raw MoQ repair frame bytes
483
575
  * @returns Parsed frame or null if invalid
484
576
  */
485
577
  export function parseMoqRepairFrame(frame) {
486
- const repairId = readRepairFecPayloadIdFields(frame);
578
+ return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame));
579
+ }
580
+ /**
581
+ * parseMoqRepairFrame for a caller that already holds this frame's parsed MMTP
582
+ * header, so the header is not parsed a second time (BLO-36599). The router's
583
+ * multicast repair branch uses it: one repair packet in every K + P otherwise
584
+ * paid two header parses.
585
+ *
586
+ * `header` must be `parseMMTPHeader(frame)` of this same frame; the result is
587
+ * then identical to `parseMoqRepairFrame(frame)`.
588
+ */
589
+ export function parseMoqRepairFrameWithHeader(frame, header) {
590
+ return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame, header));
591
+ }
592
+ function moqRepairFrameFrom(frame, repairId) {
487
593
  if (!repairId)
488
594
  return null;
489
595
  const symbolData = frame.subarray(repairId.dataOffset);
@@ -501,144 +607,6 @@ export function parseMoqRepairFrame(frame) {
501
607
  symbolData,
502
608
  };
503
609
  }
504
- function canonicalRaptorQOtiK(oti, repairSymbolSize) {
505
- if (oti.length !== 12 || oti[5] !== 0)
506
- return null;
507
- const transferLength = (oti[0] * 0x1_0000_0000) +
508
- (((oti[1] << 24) | (oti[2] << 16) | (oti[3] << 8) | oti[4]) >>> 0);
509
- const symbolSize = (oti[6] << 8) | oti[7];
510
- const sourceBlocks = oti[8];
511
- const subBlocks = (oti[9] << 8) | oti[10];
512
- const alignment = oti[11];
513
- if (!Number.isSafeInteger(transferLength) || transferLength <= 0 ||
514
- symbolSize !== repairSymbolSize || symbolSize === 0 || symbolSize % 8 !== 0 ||
515
- sourceBlocks !== 1 || subBlocks !== 1 || alignment !== 8 ||
516
- transferLength % symbolSize !== 0) {
517
- return null;
518
- }
519
- const k = transferLength / symbolSize;
520
- return Number.isInteger(k) && k >= 1 && k <= 56_403 ? k : null;
521
- }
522
- /**
523
- * Parse AL-FEC signaling message from a full MMTP signaling packet.
524
- *
525
- * Wire format (ISO/IEC 23008-1:2023 Table C.3, Amendment 1:2025):
526
- * [12-byte MMTP header (packet_type=0x02)]
527
- * message_id(2) = 0x0203
528
- * version(1)
529
- * message_length(2) = length of remaining payload
530
- * flags(1) = fec_flag(1) | private_fec_flag(1) | reserved(6)
531
- * fec_flow_descriptor:
532
- * length(2)
533
- * num_flows(1)
534
- * per flow:
535
- * fec_flow_id(1), source_flow_id(1), num_assets(1), packet_ids(2*N),
536
- * coding_params(1), repair_symbol_size(2),
537
- * [one-stage]: repair_flow_id(2), fec_code_id(1),
538
- * [private_fec_flag]: private_header(1){flag(1)|len(7)},
539
- * current RaptorQ profile OTI(12) + interleave_depth(1)
540
- * max_k(3), max_p(3), protection_window_time(4), protection_window_size(4)
541
- *
542
- * @param mmtpPacket Full MMTP packet including 12-byte header
543
- * @returns Parsed config or null if not an AL-FEC signaling message
544
- */
545
- export function parseAlFecMessage(mmtpPacket) {
546
- if (mmtpPacket.length < MMTP_HEADER_SIZE + 9)
547
- return null;
548
- let header;
549
- try {
550
- header = parseMMTPHeader(mmtpPacket);
551
- }
552
- catch {
553
- return null;
554
- }
555
- if (header.packetType !== PACKET_TYPE_SIGNALING || header.fecType !== 0)
556
- return null;
557
- const payload = mmtpPacket.subarray(header.headerLength);
558
- if (payload.length < 9)
559
- return null;
560
- const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
561
- let off = 0;
562
- const messageId = view.getUint16(off, false);
563
- off += 2;
564
- if (messageId !== ALFEC_MESSAGE_ID)
565
- return null;
566
- const version = payload[off];
567
- off += 1;
568
- const messageLength = view.getUint16(off, false);
569
- off += 2;
570
- if (version !== 1 || messageLength === 0 || messageLength !== payload.length - off)
571
- return null;
572
- const flags = payload[off];
573
- off += 1;
574
- const fecFlag = (flags >> 7) & 1;
575
- const privateFecFlag = (flags >> 6) & 1;
576
- if (fecFlag !== 1 || privateFecFlag !== 1 || (flags & 0x3f) !== 0x3f)
577
- return null;
578
- if (off + 2 > payload.length)
579
- return null;
580
- const descriptorLength = view.getUint16(off, false);
581
- off += 2;
582
- const descriptorEnd = off + descriptorLength;
583
- if (descriptorLength === 0 || descriptorEnd !== payload.length)
584
- return null;
585
- const numFlows = payload[off];
586
- off += 1;
587
- if (numFlows !== 1 || off + 3 > descriptorEnd)
588
- return null;
589
- off += 2; // fec_flow_id, source_flow_id
590
- const numAssets = payload[off];
591
- off += 1;
592
- if (numAssets === 0 || off + (numAssets * 2) > descriptorEnd)
593
- return null;
594
- off += numAssets * 2;
595
- if (off + 3 > descriptorEnd)
596
- return null;
597
- const codingParams = payload[off];
598
- off += 1;
599
- const fecCodingStructure = (codingParams >> 4) & 0x0f;
600
- const ssbgMode = (codingParams >> 2) & 0x03;
601
- const ffsrptsFlag = (codingParams >> 1) & 0x01;
602
- const fecPayloadIdMode = codingParams & 0x01;
603
- if (fecCodingStructure !== 1 || ssbgMode !== 0 || ffsrptsFlag !== 0 || fecPayloadIdMode !== 0) {
604
- return null;
605
- }
606
- const repairSymbolSize = view.getUint16(off, false);
607
- off += 2;
608
- if (repairSymbolSize === 0 || repairSymbolSize % 8 !== 0)
609
- return null;
610
- if (off + 4 > descriptorEnd)
611
- return null;
612
- off += 2; // Amendment 1:2025 replaces Table C.3 with a 16-bit repair_flow_id
613
- const fecCodeId = payload[off];
614
- off += 1;
615
- if (fecCodeId !== FEC_CODE_RAPTORQ)
616
- return null;
617
- const privateHeader = payload[off];
618
- off += 1;
619
- const privateFlag = privateHeader >> 7;
620
- const privateFieldLength = privateHeader & 0x7f;
621
- if (privateFlag !== 1 || privateFieldLength !== 13 || off + 13 > descriptorEnd)
622
- return null;
623
- const oti = payload.slice(off, off + 12);
624
- off += 12;
625
- const interleaveDepth = payload[off];
626
- off += 1;
627
- const otiK = canonicalRaptorQOtiK(oti, repairSymbolSize);
628
- if (otiK === null || interleaveDepth === 0)
629
- return null;
630
- if (off + 14 !== descriptorEnd)
631
- return null;
632
- const maximumK = (payload[off] << 16) | (payload[off + 1] << 8) | payload[off + 2];
633
- off += 3;
634
- const maximumP = (payload[off] << 16) | (payload[off + 1] << 8) | payload[off + 2];
635
- off += 3;
636
- if (maximumK !== otiK || maximumP === 0 || maximumK + maximumP - 1 > 0x00ff_ffff) {
637
- return null;
638
- }
639
- off += 8; // protection_window_time, protection_window_size
640
- if (off !== descriptorEnd)
641
- return null;
642
- return { fecCodeId, fecPayloadIdMode, repairSymbolSize, maximumK, maximumP, interleaveDepth, oti };
643
- }
610
+ // The signalling-message parsers (9.3.4.2 payload header, AL-FEC message,
611
+ // PA/MPT) live in ./signalling.ts.
644
612
  //# sourceMappingURL=mmtp.js.map