IPv4 datagram encoding and decoding (RFC 791).
A datagram is a 20-byte fixed header, a variable-length options trailer
(0–40 bytes per IHL), and the transport-layer payload:
0 7 8 15 16 23 24 31 +--------+--------+--------+--------+ |Ver/IHL | ToS | Total Length | +--------+--------+--------+--------+ | Identification |Flags + FragOffs | +--------+--------+--------+--------+ | TTL |Protocol| Header Cksum | +--------+--------+--------+--------+ | Source Address | +--------+--------+--------+--------+ | Destination Address | +--------+--------+--------+--------+ | Options (0-40 bytes) | +-----------------------------------+ | Payload (variable) | +-----------------------------------+
Provides a single-pass coder for the full datagram — fixed header, optional
options trailer, and the transport payload sized via totalLength. Bit-packed
fields (version/IHL, flags/fragment offset) are exposed as nested objects via
bitStruct, keeping the on-wire layout faithful while preserving named-field
access.
IPv4 addresses are surfaced as raw 32-bit unsigned integers, mirroring how
ARP exposes the same field. Use
@hertzg/ip's parseAddressv4 / stringifyAddressv4
for human-readable conversion.
Design notes:
- The header checksum is not computed automatically. Callers are expected
to set the
headerChecksumfield to a valid RFC 1071 16-bit one's complement sum before encoding (or zero when the receiver tolerates it, e.g. for testing). This matches the@hertzg/binstruct"no defensive programming" stance — the coder describes layout, not semantics. - Options are encoded as a raw byte slice whose length is derived from
versionIhl.ihl:(ihl - 5) * 4. For a datagram with no options passihl = 5andoptions = new Uint8Array(0). - The payload size is derived from
totalLength - ihl * 4. Callers are responsible for settingtotalLengthto matchihl * 4 + payload.length.
Round-trip a minimal IPv4 datagram (no options, empty payload)
Round-trip a minimal IPv4 datagram (no options, empty payload)
import { assertEquals } from "@std/assert"; import { parseAddressv4 } from "@hertzg/ip/addressv4"; import { ipv4Packet } from "@binstruct/ipv4"; const coder = ipv4Packet(); const datagram = { versionIhl: { version: 4, ihl: 5 }, typeOfService: 0, totalLength: 20, identification: 0x1234, flagsFragmentOffset: { reserved: 0, dontFragment: 1, moreFragments: 0, fragmentOffset: 0, }, timeToLive: 64, protocol: 6, headerChecksum: 0, sourceAddress: parseAddressv4("192.168.1.100").address, destinationAddress: parseAddressv4("10.0.0.50").address, options: new Uint8Array(0), payload: new Uint8Array(0), }; const buffer = new Uint8Array(20); const bytesWritten = coder.encode(datagram, buffer); const [decoded, bytesRead] = coder.decode(buffer); assertEquals(bytesWritten, 20); assertEquals(bytesRead, 20); assertEquals(decoded.sourceAddress, parseAddressv4("192.168.1.100").address); assertEquals(decoded.destinationAddress, parseAddressv4("10.0.0.50").address); assertEquals(decoded.flagsFragmentOffset.dontFragment, 1);