""" Packet interleaving for consecutive packet-erasure FEC blocks. The interleaver is deliberately independent from GF(256). It accepts fully encoded outer symbols from ``packet_erasure_fec`` and only changes their wire order. A group contains up to ``depth`` consecutive blocks. Symbols are emitted by symbol index across the blocks: block0[0], block1[0], ..., block0[1], block1[1], ... If a block is shorter than the others, its missing symbol index is skipped. No packet is added, removed, or modified. """ from __future__ import annotations from collections.abc import Sequence from protocol.packet_erasure_fec import ( OuterSymbol, decode_outer_symbol, ) class InterleaverError(ValueError): """An interleaving group or outer-symbol sequence is inconsistent.""" def _decode_block( wire_block: Sequence[bytes], ) -> tuple[int, tuple[bytes, ...], tuple[OuterSymbol, ...]]: if not wire_block: raise InterleaverError("FEC block is empty") packets = tuple(wire_block) symbols = tuple(decode_outer_symbol(packet) for packet in packets) first = symbols[0] expected_count = first.source_count + first.parity_count if len(symbols) != expected_count: raise InterleaverError( f"block {first.block_id} has {len(symbols)} symbols, " f"expected {expected_count}" ) metadata = ( first.block_id, first.source_count, first.parity_count, first.symbol_size, ) for expected_index, symbol in enumerate(symbols): current = ( symbol.block_id, symbol.source_count, symbol.parity_count, symbol.symbol_size, ) if current != metadata: raise InterleaverError("outer symbols have inconsistent metadata") if symbol.symbol_index != expected_index: raise InterleaverError( f"block {first.block_id} is not in symbol-index order" ) return first.block_id, packets, symbols def interleave_group( wire_blocks: Sequence[Sequence[bytes]], ) -> tuple[bytes, ...]: """ Interleave one non-empty group of complete consecutive FEC blocks. Source symbols remain before parity symbols inside every individual block because each block is traversed by increasing ``symbol_index``. """ if not wire_blocks: raise InterleaverError("interleaving group is empty") decoded = tuple(_decode_block(block) for block in wire_blocks) block_ids = tuple(item[0] for item in decoded) if len(set(block_ids)) != len(block_ids): raise InterleaverError("interleaving group repeats a block_id") if any( right != left + 1 for left, right in zip(block_ids, block_ids[1:]) ): raise InterleaverError("FEC blocks are not consecutive") packets_by_block = tuple(item[1] for item in decoded) maximum_symbols = max(len(block) for block in packets_by_block) return tuple( block[symbol_index] for symbol_index in range(maximum_symbols) for block in packets_by_block if symbol_index < len(block) ) def interleave_blocks( wire_blocks: Sequence[Sequence[bytes]], depth: int, ) -> tuple[bytes, ...]: """ Interleave a stream in groups of ``depth`` blocks. The final group may contain fewer than ``depth`` blocks and is emitted without synthetic padding. """ if not isinstance(depth, int) or isinstance(depth, bool) or depth < 1: raise ValueError("interleaving depth must be a positive integer") blocks = tuple(tuple(block) for block in wire_blocks) if not blocks: return () block_ids = tuple(_decode_block(block)[0] for block in blocks) if any( right != left + 1 for left, right in zip(block_ids, block_ids[1:]) ): raise InterleaverError("FEC block stream is not consecutive") result = [] for start in range(0, len(blocks), depth): group = blocks[start:start + depth] result.extend(interleave_group(group)) return tuple(result) def deinterleave_symbols( wire_symbols: Sequence[bytes], ) -> tuple[tuple[bytes, ...], ...]: """ Group received symbols by ``block_id`` and restore symbol-index order. Lost symbols are simply absent, so the returned blocks may be incomplete. Blocks are returned in first-seen order. A conflicting duplicate symbol is rejected; a byte-identical duplicate is retained only once. """ block_order = [] grouped: dict[int, dict[int, bytes]] = {} metadata: dict[int, tuple[int, int, int]] = {} for wire_packet in wire_symbols: symbol = decode_outer_symbol(wire_packet) if symbol.block_id not in grouped: block_order.append(symbol.block_id) grouped[symbol.block_id] = {} metadata[symbol.block_id] = ( symbol.source_count, symbol.parity_count, symbol.symbol_size, ) current_metadata = ( symbol.source_count, symbol.parity_count, symbol.symbol_size, ) if current_metadata != metadata[symbol.block_id]: raise InterleaverError( f"block {symbol.block_id} metadata changed" ) existing = grouped[symbol.block_id].get(symbol.symbol_index) if existing is not None and existing != wire_packet: raise InterleaverError( f"conflicting duplicate block={symbol.block_id}, " f"symbol={symbol.symbol_index}" ) grouped[symbol.block_id][symbol.symbol_index] = wire_packet return tuple( tuple( symbols[index] for index in sorted(symbols) ) for block_id in block_order for symbols in (grouped[block_id],) )