Functional Weave
Code in TypeScript

net.mac-address@1.0.0

impl/typescript.ts

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.
 */
export function 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(),
  };
}