2,043 bytes · the TypeScript implementation · view raw
import { type MacValidation } from"./net_mac_address_types.ts";
const HEX = "0123456789abcdefABCDEF";
function invalid(reason: string): MacValidation {
return { valid: false, canonical: null, reason, multicast: null, locallyAdministered: null, oui: null };
}
/** * Check a MAC (EUI-48) address written in any of the four common forms and * normalise it to lower-case colon form. * * Exactly four shapes are accepted: aa:bb:cc:dd:ee:ff, aa-bb-cc-dd-ee-ff, * aabb.ccdd.eeff (Cisco) and aabbccddeeff. Nothing is trimmed and single-digit * octets are refused, because "a:b:c:d:e:f" is more often a typo than a MAC. */exportfunction validateMacAddress(text: string): MacValidation {
if (typeof text !== "string") return invalid("not text");
if (text.length === 0) return invalid("empty");
const seps = [":", "-", "."].filter((s) => text.includes(s));
if (seps.length > 1) return invalid("mixed separators");
// Count characters, not UTF-16 units or bytes, so every language agrees.const chars = (s: string) => Array.from(s).length;
const sep = seps[0];
const parts = sep === undefined ? [text] : text.split(sep);
const shapeOk =
sep === undefined
? chars(text) === 12
: sep === "."
? parts.length === 3 && parts.every((p) => chars(p) === 4)
: parts.length === 6 && parts.every((p) => chars(p) === 2);
if (!shapeOk) return invalid("not a recognised MAC address format");
const hex = parts.join("");
for (const ch of hex) {
if (!HEX.includes(ch)) return invalid("not a hex digit");
}
const lower = hex.toLowerCase();
const octets: string[] = [];
for (let i = 0; i < 12; i += 2) octets.push(lower.slice(i, i + 2));
const first = parseInt(octets[0], 16);
return {
valid: true,
canonical: octets.join(":"),
reason: null,
// The I/G bit is the least significant bit of the first octet, the U/L bit the next.
multicast: (first & 1) === 1,
locallyAdministered: (first & 2) === 2,
oui: octets.slice(0, 3).join("-").toUpperCase(),
};
}