@blockcast/mmt-base 0.1.0 → 0.3.0-main.ef68de90edb3
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 +16 -2
- package/dist/fec-block-profile.d.ts +55 -0
- package/dist/fec-block-profile.d.ts.map +1 -0
- package/dist/fec-block-profile.js +128 -0
- package/dist/fec-block-profile.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/mmtp.d.ts +77 -56
- package/dist/mmtp.d.ts.map +1 -1
- package/dist/mmtp.js +260 -216
- package/dist/mmtp.js.map +1 -1
- package/dist/net-constants.d.ts +18 -0
- package/dist/net-constants.d.ts.map +1 -0
- package/dist/net-constants.js +18 -0
- package/dist/net-constants.js.map +1 -0
- package/dist/ntp-short.d.ts +47 -0
- package/dist/ntp-short.d.ts.map +1 -0
- package/dist/ntp-short.js +54 -0
- package/dist/ntp-short.js.map +1 -0
- package/dist/serial-number.d.ts +57 -0
- package/dist/serial-number.d.ts.map +1 -0
- package/dist/serial-number.js +98 -0
- package/dist/serial-number.js.map +1 -0
- package/dist/signalling.d.ts +312 -0
- package/dist/signalling.d.ts.map +1 -0
- package/dist/signalling.js +637 -0
- package/dist/signalling.js.map +1 -0
- package/package.json +9 -4
- package/src/fec-block-profile.ts +145 -0
- package/src/index.ts +5 -0
- package/src/mmtp.ts +334 -188
- package/src/net-constants.ts +17 -0
- package/src/ntp-short.ts +67 -0
- package/src/serial-number.ts +107 -0
- package/src/signalling.ts +862 -0
- package/src/alfec.test.ts +0 -176
- package/src/mmtp.test.ts +0 -769
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockcast/mmt-base",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared MMTP
|
|
3
|
+
"version": "0.3.0-main.ef68de90edb3",
|
|
4
|
+
"description": "Shared MMTP parsing primitives and protocol constants (zero FEC dependency). Hosts MMTP_HEADER_SIZE, parseMMTPHeader, extractMFUPayload and friends, plus wire constants such as DEFAULT_AMT_RELAY_PORT, so @blockcast/mmt-fec, @blockcast/mmt-container and @blockcast/mmt-transport can share one definition without a circular dep.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -13,7 +13,12 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
|
-
"src"
|
|
16
|
+
"src",
|
|
17
|
+
"!src/**/*.test.ts",
|
|
18
|
+
"!src/**/*.spec.ts",
|
|
19
|
+
"!src/**/__tests__",
|
|
20
|
+
"!dist/**/*.test.*",
|
|
21
|
+
"!dist/**/*.spec.*"
|
|
17
22
|
],
|
|
18
23
|
"scripts": {
|
|
19
24
|
"build": "tsc",
|
|
@@ -24,7 +29,7 @@
|
|
|
24
29
|
"test:watch": "vitest"
|
|
25
30
|
},
|
|
26
31
|
"dependencies": {
|
|
27
|
-
"@blockcast/mmt-alta-parse": "
|
|
32
|
+
"@blockcast/mmt-alta-parse": "0.3.0-main.ef68de90edb3"
|
|
28
33
|
},
|
|
29
34
|
"devDependencies": {
|
|
30
35
|
"typescript": "^5.3.0",
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FEC block profile (BLO-35597, ISO/IEC 23008-1 Annex C).
|
|
3
|
+
*
|
|
4
|
+
* Every catalog `fec` object that declares an FEC instance names exactly one
|
|
5
|
+
* block profile in `fec.blockProfile`. A receiver applies all of a profile's
|
|
6
|
+
* rules or none of them, so the token is the one explicit per-stream
|
|
7
|
+
* discriminator: it is never defaulted and never inferred from the wire.
|
|
8
|
+
*
|
|
9
|
+
* - `k-padded-v1` (legacy, Blockcast-private): a source symbol is the
|
|
10
|
+
* protected MMTP packet zero-padded to T with no length field; every block
|
|
11
|
+
* is a K-symbol RaptorQ block whose ESIs `SSB_length..K-1` are implicit
|
|
12
|
+
* zero padding; `SS_ID = SBN*K + ESI`, so `SS_Start = SBN*K`; repair
|
|
13
|
+
* ESI = K + RS_ID; the MoQ repair group id is the SBN.
|
|
14
|
+
* - `iso-ssbg1-v1`: ssbg_mode1 (C.3.3): a source symbol is
|
|
15
|
+
* `u16 length || protected packet || zero padding` to T; a block is a
|
|
16
|
+
* K' = SSB_length RaptorQ block (C.5.3) and repair ESI = SSB_length + RS_ID;
|
|
17
|
+
* SS_ID increments by one per FEC source packet and wraps at 2^32 (C.5.2),
|
|
18
|
+
* so a block is located by membership in `[SS_Start, SS_Start + SSB_length)`
|
|
19
|
+
* under RFC 1982 serial arithmetic; the MoQ repair group id is the 62-bit
|
|
20
|
+
* wrap-epoch-extended SS_Start. The AL-FEC coding byte for this profile is
|
|
21
|
+
* 0x14 (one-stage, ssbg_mode1, fec_payload_id_mode 0, Amd 1:2025 Table C.3).
|
|
22
|
+
*/
|
|
23
|
+
import { serial32Add, serial32Distance, serial32Precedes } from './serial-number.js'
|
|
24
|
+
|
|
25
|
+
/** The two block profiles a catalog `fec.blockProfile` may name. */
|
|
26
|
+
export type FecBlockProfile = 'k-padded-v1' | 'iso-ssbg1-v1'
|
|
27
|
+
|
|
28
|
+
/** Every accepted `fec.blockProfile` token, legacy first. */
|
|
29
|
+
export const FEC_BLOCK_PROFILES: readonly FecBlockProfile[] = Object.freeze([
|
|
30
|
+
'k-padded-v1',
|
|
31
|
+
'iso-ssbg1-v1',
|
|
32
|
+
])
|
|
33
|
+
|
|
34
|
+
/** True for a known block-profile token. */
|
|
35
|
+
export function isFecBlockProfile(value: unknown): value is FecBlockProfile {
|
|
36
|
+
return value === 'k-padded-v1' || value === 'iso-ssbg1-v1'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Read a required `fec.blockProfile`. Throws on an absent or unknown token:
|
|
41
|
+
* there is no default and nothing is inferred, because the two profiles carry
|
|
42
|
+
* identical wire tuples whose repair math differs.
|
|
43
|
+
*
|
|
44
|
+
* @param where the catalog path of the field, for the error message.
|
|
45
|
+
*/
|
|
46
|
+
export function parseFecBlockProfile(value: unknown, where: string): FecBlockProfile {
|
|
47
|
+
if (isFecBlockProfile(value)) return value
|
|
48
|
+
if (value === undefined) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`${where} is required: name the FEC block profile ("k-padded-v1" or "iso-ssbg1-v1")`,
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
throw new Error(
|
|
54
|
+
`${where} must be "k-padded-v1" or "iso-ssbg1-v1" (got ${JSON.stringify(value)})`,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** ISO/IEC 23008-1 C.3.3 ssbg_mode1: the two-octet packet length field. */
|
|
59
|
+
export const SSBG1_LENGTH_FIELD_BYTES = 2
|
|
60
|
+
|
|
61
|
+
/** The largest protected packet an ssbg_mode1 symbol of size T can carry. */
|
|
62
|
+
export function ssbg1MaxPacketLength(symbolSize: number): number {
|
|
63
|
+
return symbolSize - SSBG1_LENGTH_FIELD_BYTES
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Whether a protected packet of `packetLength` bytes fits one T-byte ssbg_mode1 symbol. */
|
|
67
|
+
export function ssbg1FitsSymbol(packetLength: number, symbolSize: number): boolean {
|
|
68
|
+
return (
|
|
69
|
+
Number.isSafeInteger(packetLength) &&
|
|
70
|
+
packetLength >= 0 &&
|
|
71
|
+
packetLength <= ssbg1MaxPacketLength(symbolSize) &&
|
|
72
|
+
packetLength <= 0xffff
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build one ssbg_mode1 source symbol (C.3.3): the protected packet's length
|
|
78
|
+
* in two octets, network byte order, then the packet, then zero octets up to
|
|
79
|
+
* T. The protected packet is the MMTP packet before its Source FEC Payload ID
|
|
80
|
+
* is appended (C.4.2).
|
|
81
|
+
*
|
|
82
|
+
* @throws when `length + 2 > T`: the packet cannot be protected under this
|
|
83
|
+
* profile, and silently truncating it would corrupt every repair symbol.
|
|
84
|
+
*/
|
|
85
|
+
export function buildSsbg1SourceSymbol(packet: Uint8Array, symbolSize: number): Uint8Array {
|
|
86
|
+
if (!Number.isSafeInteger(symbolSize) || symbolSize <= SSBG1_LENGTH_FIELD_BYTES) {
|
|
87
|
+
throw new Error(`ssbg_mode1 symbol size must exceed ${SSBG1_LENGTH_FIELD_BYTES} (got ${symbolSize})`)
|
|
88
|
+
}
|
|
89
|
+
if (!ssbg1FitsSymbol(packet.byteLength, symbolSize)) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`ssbg_mode1 source symbol needs ${packet.byteLength} + ${SSBG1_LENGTH_FIELD_BYTES} bytes but T is ${symbolSize}`,
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
const symbol = new Uint8Array(symbolSize)
|
|
95
|
+
symbol[0] = (packet.byteLength >>> 8) & 0xff
|
|
96
|
+
symbol[1] = packet.byteLength & 0xff
|
|
97
|
+
symbol.set(packet, SSBG1_LENGTH_FIELD_BYTES)
|
|
98
|
+
return symbol
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Recover the protected packet from one ssbg_mode1 source symbol: the bytes
|
|
103
|
+
* the length field names. Returns null, never a guess, when the length field
|
|
104
|
+
* points past the symbol or any padding octet is non-zero (C.3.3 padding is
|
|
105
|
+
* all 00h), since either means the recovered symbol is not what was sent.
|
|
106
|
+
*/
|
|
107
|
+
export function trimSsbg1SourceSymbol(symbol: Uint8Array): Uint8Array | null {
|
|
108
|
+
if (symbol.byteLength < SSBG1_LENGTH_FIELD_BYTES) return null
|
|
109
|
+
const length = (symbol[0]! << 8) | symbol[1]!
|
|
110
|
+
const end = SSBG1_LENGTH_FIELD_BYTES + length
|
|
111
|
+
if (end > symbol.byteLength) return null
|
|
112
|
+
for (let i = end; i < symbol.byteLength; i++) {
|
|
113
|
+
if (symbol[i] !== 0) return null
|
|
114
|
+
}
|
|
115
|
+
return symbol.subarray(SSBG1_LENGTH_FIELD_BYTES, end)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The SS_ID after the last source symbol of `[ssStart, ssStart + ssbLength)`. */
|
|
119
|
+
export function isoBlockEnd(ssStart: number, ssbLength: number): number {
|
|
120
|
+
return serial32Add(ssStart, ssbLength)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Range membership (C.5.2, C.5.3): whether `ssId` is one of the
|
|
125
|
+
* `ssbLength` consecutive SS_IDs starting at `ssStart`, modulo 2^32.
|
|
126
|
+
*/
|
|
127
|
+
export function isoBlockContains(ssStart: number, ssbLength: number, ssId: number): boolean {
|
|
128
|
+
return serial32Distance(ssStart, ssId) < ssbLength
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* A lower bound on how many blocks the block starting at `a` lies behind the
|
|
133
|
+
* one starting at `b` (0 when `a` does not precede `b`). An iso-ssbg1-v1
|
|
134
|
+
* block holds at most `kMax` source symbols (the catalog `sourceSymbols`, the
|
|
135
|
+
* AL-FEC maximum_k_for_repair_flow), so `n * kMax` SS_IDs always span at least
|
|
136
|
+
* `n` blocks. Every interleave horizon in the receiver (decoder retention,
|
|
137
|
+
* worker retirement, ordered-relay settlement) uses this one bound, so the
|
|
138
|
+
* manager's horizon can never fall inside the decoder's.
|
|
139
|
+
*/
|
|
140
|
+
export function isoBlocksBehindLowerBound(a: number, b: number, kMax: number): number {
|
|
141
|
+
if (!Number.isSafeInteger(kMax) || kMax <= 0) {
|
|
142
|
+
throw new Error(`iso-ssbg1-v1 block horizon needs a positive maximum K (got ${kMax})`)
|
|
143
|
+
}
|
|
144
|
+
return serial32Precedes(a, b) ? Math.floor(serial32Distance(a, b) / kMax) : 0
|
|
145
|
+
}
|