@blockcast/mmt-base 0.1.0 → 0.3.0
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/src/ntp-short.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NTP short format (RFC 5905 §6): an unsigned 32-bit, 16.16 fixed-point count
|
|
3
|
+
* of seconds. The MMTP header `timestamp` field (ISO/IEC 23008-1 §9.2.2)
|
|
4
|
+
* carries each packet's UTC sending instant in this format, so the value wraps
|
|
5
|
+
* to 0 every 2^16 s (~18.2 h).
|
|
6
|
+
*
|
|
7
|
+
* Plain numeric order on these values breaks at every wrap: a retained
|
|
8
|
+
* `Math.max` stays at the pre-wrap value and every later instant compares as
|
|
9
|
+
* older. Order send times only with {@link compareNtpShort}, which applies
|
|
10
|
+
* RFC 1982 serial-number arithmetic over the field's width.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Width in bits of the MMTP `timestamp` field (NTP short). */
|
|
14
|
+
export const NTP_SHORT_BITS = 32
|
|
15
|
+
|
|
16
|
+
/** Number of distinct NTP-short values; the field wraps to 0 here. */
|
|
17
|
+
export const NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* RFC 1982 §3.2's `2^(SERIAL_BITS - 1)`. Two values whose forward distance is
|
|
21
|
+
* below this are ordered; exactly this far apart they are not ordered at all.
|
|
22
|
+
* Two instants must be less than this far apart (2^15 s, ~9.1 h of send time)
|
|
23
|
+
* for the comparison to give their true order.
|
|
24
|
+
*/
|
|
25
|
+
export const NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1)
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Serial order of one NTP-short value relative to another.
|
|
29
|
+
*
|
|
30
|
+
* `'unordered'` is RFC 1982's undefined case: the values are exactly
|
|
31
|
+
* {@link NTP_SHORT_HALF_RANGE} apart, so neither precedes the other. It is
|
|
32
|
+
* never folded into `'before'` or `'after'`; each caller decides what an
|
|
33
|
+
* unorderable pair means for it.
|
|
34
|
+
*/
|
|
35
|
+
export type NtpShortOrder = 'before' | 'equal' | 'after' | 'unordered'
|
|
36
|
+
|
|
37
|
+
/** True when `value` is an NTP-short value: an integer in [0, NTP_SHORT_MODULUS). */
|
|
38
|
+
export function isNtpShort(value: number): boolean {
|
|
39
|
+
return Number.isInteger(value) && value >= 0 && value < NTP_SHORT_MODULUS
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* RFC 1982 serial comparison of NTP-short `a` against `b`: `'after'` when `a`
|
|
44
|
+
* is the later instant, `'before'` when it is the earlier one.
|
|
45
|
+
*
|
|
46
|
+
* The answer is the true order only while the two instants are less than
|
|
47
|
+
* {@link NTP_SHORT_HALF_RANGE} apart. Past that a 32-bit field cannot tell
|
|
48
|
+
* later from earlier, and the result names whichever direction is nearer.
|
|
49
|
+
* This is not a total order (it is not transitive across the whole space), so
|
|
50
|
+
* it must never be used to sort.
|
|
51
|
+
*
|
|
52
|
+
* @throws {RangeError} if either operand is not an NTP-short value
|
|
53
|
+
*/
|
|
54
|
+
export function compareNtpShort(a: number, b: number): NtpShortOrder {
|
|
55
|
+
if (!isNtpShort(a) || !isNtpShort(b)) {
|
|
56
|
+
throw new RangeError(
|
|
57
|
+
`NTP-short values must be integers in [0, 2^${NTP_SHORT_BITS}), got ${String(a)} and ${String(b)}`,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
if (a === b) return 'equal'
|
|
61
|
+
// Forward distance from b to a, reduced into [1, NTP_SHORT_MODULUS). Both
|
|
62
|
+
// operands are below 2^32, so every step is exact in a double.
|
|
63
|
+
const forward = (a - b + NTP_SHORT_MODULUS) % NTP_SHORT_MODULUS
|
|
64
|
+
if (forward < NTP_SHORT_HALF_RANGE) return 'after'
|
|
65
|
+
if (forward > NTP_SHORT_HALF_RANGE) return 'before'
|
|
66
|
+
return 'unordered'
|
|
67
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 1982 serial-number arithmetic for the 32-bit MMTP AL-FEC symbol
|
|
3
|
+
* counters (ISO/IEC 23008-1:2023 C.5.2 SS_ID, C.5.3 SS_Start).
|
|
4
|
+
*
|
|
5
|
+
* SS_ID is a 32-bit field, so a stream that runs long enough wraps it to 0.
|
|
6
|
+
* Ordering two SS_IDs (or two blocks by their first SS_ID) with `<` reads
|
|
7
|
+
* every post-wrap value as older than every pre-wrap one. Serial order
|
|
8
|
+
* agrees with `<` whenever the two values are less than 2^31 apart, so for
|
|
9
|
+
* every stream that has not reached the wrap the result is unchanged; only
|
|
10
|
+
* the order across the wrap point differs (BLO-35597 iso:5).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** 2^SERIAL_BITS for SERIAL_BITS = 32. */
|
|
14
|
+
export const SERIAL32_SPAN = 0x1_0000_0000
|
|
15
|
+
|
|
16
|
+
/** RFC 1982 §3.2's 2^(SERIAL_BITS - 1). */
|
|
17
|
+
export const SERIAL32_HALF = 0x8000_0000
|
|
18
|
+
|
|
19
|
+
function assertUint32(value: number, name: string): void {
|
|
20
|
+
if (!Number.isInteger(value) || value < 0 || value >= SERIAL32_SPAN) {
|
|
21
|
+
throw new RangeError(`${name} must be a uint32 (got ${value})`)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* RFC 1982 §3.1 addition, extended to negative `n` for the matching
|
|
27
|
+
* subtraction: `(s + n) mod 2^32`. `n` must lie in (-2^31, 2^31).
|
|
28
|
+
*/
|
|
29
|
+
export function serial32Add(s: number, n: number): number {
|
|
30
|
+
assertUint32(s, 's')
|
|
31
|
+
if (!Number.isInteger(n) || n <= -SERIAL32_HALF || n >= SERIAL32_HALF) {
|
|
32
|
+
throw new RangeError(`serial addend must lie in (-2^31, 2^31) (got ${n})`)
|
|
33
|
+
}
|
|
34
|
+
return (s + n + SERIAL32_SPAN) % SERIAL32_SPAN
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Forward distance from `from` to `to`: `(to - from) mod 2^32`. */
|
|
38
|
+
export function serial32Distance(from: number, to: number): number {
|
|
39
|
+
return (to - from) >>> 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* RFC 1982 §3.2: `a` precedes `b`. Values exactly 2^31 apart are unordered
|
|
44
|
+
* (neither precedes the other).
|
|
45
|
+
*/
|
|
46
|
+
export function serial32Precedes(a: number, b: number): boolean {
|
|
47
|
+
const distance = serial32Distance(a, b)
|
|
48
|
+
return distance !== 0 && distance < SERIAL32_HALF
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* First SS_ID of block `sbn` under the K-stride numbering every current
|
|
53
|
+
* sender uses (`SS_ID = SBN * K + ESI`, C.5.2), reduced mod 2^32.
|
|
54
|
+
*/
|
|
55
|
+
export function kStrideBlockStart(sbn: number, k: number): number {
|
|
56
|
+
assertUint32(sbn, 'sbn')
|
|
57
|
+
if (!Number.isInteger(k) || k < 1 || k >= SERIAL32_HALF) {
|
|
58
|
+
throw new RangeError(`k must be a positive integer below 2^31 (got ${k})`)
|
|
59
|
+
}
|
|
60
|
+
// sbn < 2^32 and k < 2^31 keep the product below 2^63, but a double is
|
|
61
|
+
// exact only to 2^53; split the product so every step is exact.
|
|
62
|
+
const high = Math.floor(sbn / 0x1_0000) // < 2^16
|
|
63
|
+
const low = sbn % 0x1_0000 // < 2^16
|
|
64
|
+
return (((high * k) % SERIAL32_SPAN) * 0x1_0000 + low * k) % SERIAL32_SPAN
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether block `a` precedes block `b`, ordering the blocks by their first
|
|
69
|
+
* SS_ID in serial arithmetic. Identical to `a < b` for any two blocks of a
|
|
70
|
+
* stream that has not wrapped SS_ID.
|
|
71
|
+
*/
|
|
72
|
+
export function kStrideBlockPrecedes(a: number, b: number, k: number): boolean {
|
|
73
|
+
return serial32Precedes(kStrideBlockStart(a, k), kStrideBlockStart(b, k))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* How many blocks `a` lies behind `b`: 0 when `a` does not precede `b`,
|
|
78
|
+
* otherwise `ceil(distance / K)` over the SS_ID distance between their first
|
|
79
|
+
* symbols. Without a wrap that is exactly `b - a`; across the wrap the one
|
|
80
|
+
* short pre-wrap block (2^32 mod K symbols) still counts as one block.
|
|
81
|
+
*/
|
|
82
|
+
export function kStrideBlocksBehind(a: number, b: number, k: number): number {
|
|
83
|
+
const from = kStrideBlockStart(a, k)
|
|
84
|
+
const to = kStrideBlockStart(b, k)
|
|
85
|
+
if (!serial32Precedes(from, to)) return 0
|
|
86
|
+
return Math.ceil(serial32Distance(from, to) / k)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Whether `sbn` and `k` describe a block of the K-stride numbering:
|
|
91
|
+
* `sbn` a uint32, `k` an integer in 1..2^31-1.
|
|
92
|
+
*/
|
|
93
|
+
export function isKStrideBlock(sbn: number, k: number): boolean {
|
|
94
|
+
return Number.isInteger(sbn) && sbn >= 0 && sbn < SERIAL32_SPAN &&
|
|
95
|
+
Number.isInteger(k) && k >= 1 && k < SERIAL32_HALF
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The block after `sbn`: `sbn + 1`, or 0 once `(sbn + 1) * K` reaches 2^32
|
|
100
|
+
* and the next SS_ID wraps (C.5.2). There are `ceil(2^32 / K)` blocks per
|
|
101
|
+
* wrap, the last of them short when K does not divide 2^32.
|
|
102
|
+
*/
|
|
103
|
+
export function kStrideNextBlock(sbn: number, k: number): number {
|
|
104
|
+
assertUint32(sbn, 'sbn')
|
|
105
|
+
kStrideBlockStart(sbn, k) // validates k
|
|
106
|
+
return (sbn + 1) * k >= SERIAL32_SPAN ? 0 : sbn + 1
|
|
107
|
+
}
|