Skip to content

Python — Header & Packet

Header and packet types are defined in the nnrp.core module.

Import

python
from nnrp.core import (
    NnrpHeader, NnrpPacket, TensorSectionData,
    HEADER_MAGIC, HEADER_LENGTH,
)

Constants

NameTypeValueDescription
HEADER_MAGICbytesb"NNRP"Fixed 4-byte magic prefix for every packet
HEADER_LENGTHint40Fixed header byte length

NnrpHeader

Fixed 40-byte packet header (@dataclass(frozen=True, slots=True)). Wire format: struct.Struct("<4sBBBBIIIIIHHQ").

Fields

FieldTypeDescription
wire_formatWireFormatWire format version (currently CURRENT=0)
version_majorintProtocol major version (currently 1)
msg_typeMessageTypeMessage type
flagsHeaderFlagsHeader flags
meta_lenintMetadata byte count
body_lenintBody byte count
session_idintSession ID
frame_idintFrame ID
view_idintView/viewport ID
route_idintRoute ID (uint16)
trace_idintTrace ID (uint16)
header_lenintFixed HEADER_LENGTH=40

Methods

python
def pack(self) -> bytes:
    """Serialize header to 40 bytes (including 4-byte magic prefix)."""

@classmethod
def unpack(
    cls,
    payload: bytes,
    *,
    expected_wire_format: WireFormat | None = None,
) -> "NnrpHeader":
    """Parse header from bytes. Raises ValueError if expected_wire_format doesn't match."""

NnrpPacket

Complete NNRP packet containing header, metadata, and body (@dataclass(frozen=True, slots=True)).

Fields

FieldTypeDescription
headerNnrpHeaderPacket header
metadatabytesRaw metadata bytes
bodybytesRaw body bytes

Methods

python
def pack(self) -> bytes:
    """Serialize complete packet to bytes."""

@classmethod
def build(
    cls,
    msg_type: MessageType,
    *,
    wire_format: WireFormat = WireFormat.CURRENT,
    version_major: int = 1,
    flags: HeaderFlags = HeaderFlags.NONE,
    session_id: int = 0,
    frame_id: int = 0,
    view_id: int = 0,
    route_id: int = 0,
    trace_id: int = 0,
    metadata: bytes = b"",
    body: bytes = b"",
) -> "NnrpPacket":
    """Construct and return a new NnrpPacket."""

@classmethod
def unpack(cls, payload: bytes) -> "NnrpPacket":
    """Parse complete packet (header + metadata + body) from bytes."""

TensorSectionData

Describes a single tensor section (@dataclass(frozen=True, slots=True)).

Fields

FieldTypeDescription
role_idintTensor role ID
default_codec_idintDefault codec ID
dtype_idintData type (see TensorDType)
tile_payloadstuple[bytes, ...]Raw payload bytes per tile
codec_idstuple[int, ...]Per-tile codec IDs (may be empty)
layout_idintMemory layout (see TensorLayout)
scale_policyintQuantization scale policy (see ScalePolicy)
payload_stride_bytesintFixed stride in bytes; 0 means variable
element_count_per_tileintElement count per tile

Methods

python
def normalized_tile_payloads(self) -> tuple[bytes, ...]: ...
def normalized_codec_ids(self) -> tuple[int, ...]: ...

Example: Build and parse a PING packet

python
from nnrp.core import NnrpPacket
from nnrp.core.enums import MessageType

packet = NnrpPacket.build(MessageType.PING, session_id=42, frame_id=1)
raw = packet.pack()

restored = NnrpPacket.unpack(raw)
assert restored.header.msg_type is MessageType.PING
assert restored.header.session_id == 42

Typical Use Cases

When to use the low-level packet API

Most application code does not need NnrpPacket or NnrpHeader directlyClientSession / ServerSession covers all common operations. Use the low-level API when:

  • Implementing a custom transport adapter
  • Writing protocol conformance tests or fuzz tooling
  • Debugging: capturing and replaying raw packet bytes
  • Adding custom ControlExtensionEntry fields to the handshake

Measuring RTT with a raw PING

python
import time
from nnrp.core import NnrpPacket
from nnrp.core.enums import MessageType

async def measure_rtt(connection) -> float:
    seq = int(time.monotonic() * 1000) & 0xFFFF
    ping = NnrpPacket.build(MessageType.PING, session_id=connection.session_id,
                             frame_id=seq)
    t0 = time.monotonic()
    await connection.send_packet(ping)
    pong = await connection.receive_packet(timeout=2.0)
    assert pong.header.msg_type is MessageType.PONG
    return time.monotonic() - t0

Parsing an incoming raw packet

python
from nnrp.core import NnrpPacket
from nnrp.core.messages import unpack_body
from nnrp.core.enums import MessageType

raw_bytes = await raw_transport.recv()
packet = NnrpPacket.unpack(raw_bytes)
match packet.header.msg_type:
    case MessageType.FRAME_SUBMIT:
        body = unpack_body(packet)
        process_submit(body)
    case MessageType.RESULT_PUSH:
        body = unpack_body(packet)
        process_result(body)

Common Pitfalls

WARNING

  1. Header length is fixed by the protocol version. Do not append custom bytes to the header; put extension fields in metadata via ControlExtensionEntry.

  2. Magic bytes are validated by unpack(). An off-by-one read offset from a TCP length-prefix framing bug will cause ValueError: bad magic.

  3. pack() returns immutable bytes. Do not attempt in-place mutation; reconstruct via NnrpPacket.build() instead.

  4. session_id and frame_id are unsigned integers. Passing a negative value does not raise an error in Python, but the serialized result is silently truncated, causing parse errors on the receiver.

NNRP Documentation