IPv6 fixed header encoding and decoding (RFC 8200).

Every IPv6 packet opens with a 40-byte fixed header, followed by zero or more extension headers and then the upper-layer payload:

 0      7 8     15 16    23 24    31
+--------+--------+--------+--------+
|Version | Traffic Class| Flow Label|
+--------+--------+--------+--------+
|  Payload Length | Next Hd|Hop Lim.|
+--------+--------+--------+--------+
|                                   |
+                                   +
|                                   |
+          Source Address          +
|         (16 bytes / 128 bits)     |
+                                   +
|                                   |
+--------+--------+--------+--------+
|                                   |
+                                   +
|                                   |
+        Destination Address       +
|         (16 bytes / 128 bits)     |
+                                   +
|                                   |
+--------+--------+--------+--------+
|        Payload (variable)         |
+-----------------------------------+

version, trafficClass, and flowLabel share the first 32-bit big-endian word of the header (4 + 8 + 20 bits), so they are decoded as a single nested versionClassFlow object via bitStruct rather than three independent fields.

payloadLength counts only the bytes after the 40-byte fixed header — unlike IPv4's totalLength, it excludes the header itself. payload is therefore exactly payloadLength bytes.

This is a v0.0.1, header-only coder: extension headers (Hop-by-Hop Options, Routing, Fragment, Destination Options, ESP, AH, …) are not parsed. nextHeader is surfaced as a raw protocol number and everything after the fixed header — extension headers included, if any are present — is handed back as opaque payload bytes. Callers who need to walk the extension header chain must do so themselves, or await a future version of this package that adds it.

sourceAddress / destinationAddress are raw 16-byte slices, not bigint. Pair this with @hertzg/ip's addressv6FromBytes / addressv6ToBytes for the bigint view and stringifyAddressv6 / parseAddressv6(s).address for the human-readable one; the conversion is left to the caller to keep this package free of that dependency.

Examples

Round-trip a minimal IPv6 packet (no payload)

import { assertEquals } from "@std/assert";
import { ipv6Packet, IPV6_HEADER_SIZE } from "@binstruct/ipv6";

const coder = ipv6Packet();
const packet = {
  versionClassFlow: { version: 6, trafficClass: 0, flowLabel: 0 },
  payloadLength: 0,
  nextHeader: 59,
  hopLimit: 64,
  sourceAddress: new Uint8Array(16),
  destinationAddress: new Uint8Array(16),
  payload: new Uint8Array(0),
};

const buffer = new Uint8Array(IPV6_HEADER_SIZE);
const written = coder.encode(packet, buffer);
const [decoded, read] = coder.decode(buffer);

assertEquals(written, IPV6_HEADER_SIZE);
assertEquals(read, IPV6_HEADER_SIZE);
assertEquals(decoded.versionClassFlow.version, 6);
assertEquals(decoded.hopLimit, 64);