IEEE 802.1Q VLAN tag encoding and decoding.
A VLAN tag is the 4 bytes that follow the TPID_8021Q (0x8100)
EtherType in a tagged Ethernet II frame — a 2-byte Tag Control Information
(TCI) field, itself three bit-packed subfields, followed by the 2-byte
EtherType of the encapsulated payload:
0 1 2 3 4 15 16 31 +-+-+-+-+-----------------+-----------------+ | PCP|D| VLAN ID (VID) | EtherType | +-+-+-+-+-----------------+-----------------+ | | | Payload (variable) | +---------------------------------------------+
Field breakdown of the first two bytes (the TCI):
pcp(3 bits) — Priority Code Point, a class-of-service value (0–7).dei(1 bit) — Drop Eligible Indicator (formerly CFI).vlanId(12 bits) — VLAN Identifier (VID), 0–4095.
This coder starts at the TCI, not at the TPID — the caller is expected to
have already consumed a 0x8100 EtherType (for example via
@binstruct/ethernet's
ethernet2Frame with a ref()'d etherType) and hand the remaining bytes
to vlanTag.
Scope for v0.0.1: a single tag only. Double-tagging (QinQ, EtherType
0x88a8) is out of scope — decode a stacked frame by feeding a vlanTag()
decode's payload back into another vlanTag() when its etherType is
0x8100 or 0x88a8.
Round-trip a tagged frame carrying an IPv4 payload
Round-trip a tagged frame carrying an IPv4 payload
import { assertEquals } from "@std/assert"; import { vlanTag, VLAN_TAG_SIZE } from "@binstruct/vlan"; const coder = vlanTag(); const tag = { tci: { pcp: 5, dei: 0, vlanId: 100 }, etherType: 0x0800, payload: new Uint8Array([0x45, 0x00, 0x00, 0x14]), }; const buffer = new Uint8Array(VLAN_TAG_SIZE + tag.payload.length); const written = coder.encode(tag, buffer); const [decoded, read] = coder.decode(buffer); assertEquals(written, read); assertEquals(decoded.tci, tag.tci); assertEquals(decoded.etherType, tag.etherType); assertEquals(decoded.payload, tag.payload);