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.

Examples

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);