# @blockcast/mmt-base

The shared MMTP packet-parsing leaf for libmmt's TypeScript packages. It hosts the low-level MMTP / MPU / MFU header parsers, and FEC payload-id predicates — everything that operates on raw MMTP packet bytes **with zero FEC-decode dependency**. RFC 6330 OTI geometry belongs to `@blockcast/mmt-fec`.

It exists for one structural reason. Both `@blockcast/mmt-fec` and `@blockcast/mmt-container` need these primitives. Before this package they lived in `mmt-container`, so `mmt-fec` had to import `mmt-container` for them — while `mmt-container`'s `FecManager` already imports `mmt-fec`. That is a circular dependency. Extracting the primitives into this leaf breaks the cycle: both packages now import **down** into `mmt-base` rather than across into each other. It has no runtime dependencies.

```bash
pnpm add @blockcast/mmt-base
```

## Quick start

```ts
import {
  unwrapMMTP, extractMFUPayload, extractTimestamp,
  isFecRepairPacket, hasSourceFecPayloadId,
  PACKET_TYPE_MPU,
} from "@blockcast/mmt-base"

const pkt = unwrapMMTP(datagram)            // { header, payload }
if (pkt.header.packetType === PACKET_TYPE_MPU) {
  const mfu = extractMFUPayload(datagram)   // { data, fragmentationIndicator, mpuSequenceNumber, ... }
  const ts  = extractTimestamp(datagram)    // bigint MPU timestamp
}

if (isFecRepairPacket(datagram)) {
  // repair flow — hand the payload to @blockcast/mmt-fec
}
```

## Exports

Single entry point — `package.json#exports` declares only `.`, and `src/index.ts` re-exports the packet primitives. Group them as three families:

### Header, MPU & MFU parsing

| Symbol | Kind | Purpose |
|--------|------|---------|
| `parseMMTPHeader` | function | Parse the 12-byte MMTP header → `MMTPHeader` |
| `unwrapMMTP` | function | Header + payload split → `MMTPPacket` |
| `parseMPUHeader` | function | Parse the 8-byte MPU header → `MPUHeader` |
| `extractMFUPayload` | function | Pull the MFU data unit out of an MMTP packet → `MFUExtraction` (handles timed `T=1` and non-timed `T=0` DU headers) |
| `extractNALUnit` | function | Extract the NAL unit bytes from an MMTP packet |
| `extractTimestamp` | function | MPU presentation timestamp as `bigint` |
| `MMTPHeader`, `MPUHeader`, `MMTPPacket`, `MFUExtraction` | type | Parsed-structure shapes |
| `MMTP_HEADER_SIZE` (12), `MPU_HEADER_SIZE` (8), `MFU_DU_HEADER_SIZE_TIMED` (14), `MFU_DU_HEADER_SIZE_NONTIMED` (4) | const | Fixed header sizes |
| `PACKET_TYPE_MPU` (0x00), `PACKET_TYPE_SIGNALING` (0x02), `PACKET_TYPE_REPAIR` (0x03) | const | MMTP `packet_type` values |

MPU `payload_length` is interpreted strictly as ISO's length-after-field value: six remaining MPU-header bytes plus the data-unit header and media. The legacy `0xf0a1` authenticator trailer (skipped, never verified — provenance is `@blockcast/mmt-manifest-verify`) and the Source FEC Payload ID are outside that span; malformed, zero-length, media-only, and ambiguous declarations are rejected.

### FEC payload-id, repair & AL-FEC signaling

| Symbol | Kind | Purpose |
|--------|------|---------|
| `isFecRepairPacket` | function | Is this a `PACKET_TYPE_REPAIR` packet? |
| `hasSourceFecPayloadId` | function | Strict predicate for a source packet's declared four-byte FEC payload ID |
| `extractFecPayloadId` | function | `{ ssId }` from a source packet's FEC payload id (or `null`) |
| `extractRepairFecPayloadId` | function | Repair-flow FEC payload id (13-byte, `REPAIR_FEC_PID_SIZE`) |
| `parseMoqRepairFrame` | function | Strictly parse a mode-0 MMTP Annex C repair frame → `MoqRepairFrame` |
| `MoqRepairFrame` | type | Repair-frame shape |
| `ALFEC_MESSAGE_ID` (0x0203), `FEC_CODE_XOR` (1), `FEC_CODE_RAPTORQ` (2), `REPAIR_FEC_PID_SIZE` (13) | const | AL-FEC signaling + payload-id constants |
| `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 |

### Signalling messages (`signalling.ts`, packet_id 0)

Total parsers (malformed or unsupported input → `null`, never a throw), per
ISO/IEC 23008-1:2023 9.3.4.2, 10.2-10.3, 10.6 and Annex C Table C.3 as
replaced by Amd 1:2025.

| Symbol | Kind | Purpose |
|--------|------|---------|
| `parseSignallingPayload` | function | Split a signalling packet into complete messages behind the 9.3.4.2 header (Figure 15); the payload form is always `iso-9.3.4.2`. A message straight after the MMTP header (the retired bare AL-FEC form) is refused |
| `parseSignallingPacket` | function | Decode every message of a signalling packet → `al-fec` / `pa` / `mpt` / `other` |
| `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` |
| `alFecRaptorQFlowConfig` | function | One flow's decoder geometry under the Blockcast one-stage RaptorQ profile: coding byte 0x14 (`iso-ssbg1-v1`) or 0x15 (its low bit, reserved "1" in the unamended 2023 Table C.3, is reported, not interpreted), private field = OTI + interleave depth → `AlFecRaptorQFlowConfig`; any other coding byte (0x10 ssbg_mode0 included) → `null` |
| `parsePaMessageBody`, `parseMptMessageBody`, `parseMpTable`, `mpAssetPacketIds` | function | PA / MPT messages and the MP table: asset → packet_id mapping |

## Why this package exists

```
        before                                   after
   ┌──────────────┐                       ┌──────────────┐
   │ mmt-fec      │──needs primitives──►  │ mmt-fec      │─┐
   └──────────────┘                       └──────────────┘ │ import down
   ┌──────────────┐                       ┌──────────────┐ │
   │ mmt-container│◄─FecManager needs─┐   │ mmt-container│─┤
   │  (hosts the  │   mmt-fec         │   └──────────────┘ │
   │  primitives) │───────────────────┘                    ▼
   └──────────────┘   ⟲ CYCLE              ┌──────────────────────────┐
                                           │ @blockcast/mmt-base      │
                                           │ (MMTP/MPU/MFU/PID)       │
                                           └──────────────────────────┘
```

- `@blockcast/mmt-fec` imports the parsers in `src/block-processor.ts` (splitting a decoded FEC block back into MFU fragments).
- `@blockcast/mmt-container` imports these primitives directly. The obsolete
  container re-export shim and `@blockcast/mmt-container/mmtp` subpath were
  removed so there is one parser authority.

Keep this package dependency-light. OTI geometry and anything that needs the WASM FEC decoder belong in `@blockcast/mmt-fec`; anything that needs catalog/CMAF/reassembly state belongs in `@blockcast/mmt-container`. `mmt-base` stays pure MMTP packet parsing so both can depend on it without pulling in the other.

## Build & test

```bash
npm run build       # tsc → dist/
npm run typecheck   # tsc --noEmit
npm test            # vitest run
```

The direct suite owns header parsing, MFU extraction, payload-ID predicates,
declared-boundary rejection, C/X optional headers, and
legacy-authenticator/FEC suffix coverage. Downstream suites exercise integration only.

## Related packages

| Package | Relationship |
|---------|-------------|
| [`@blockcast/mmt-fec`](../fec) | Imports these parsers in `block-processor` to turn decoded FEC blocks back into MFU fragments |
| [`@blockcast/mmt-container`](../container) | Imports these primitives directly and builds the `FecManager`, MFU reassembler, and CMAF demuxer on top |

## License

Apache-2.0 — see `LICENSE`.
