Skip to content

MAC Address Generator — PHP source

Generate random EUI-48 MAC addresses with a chosen separator, optional OUI prefix, uppercase formatting, and a locally-administered flag. Runs entirely in your browser with crypto-grade randomness.

This is the PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.

<?php
/**
 * mac-address-generator — random EUI-48 MAC address generator.
 *
 * Language: PHP (8.1+, standard library only)
 * Source:   CosmoDev polyglot showcase port of the MAC Address Generator tool,
 *           ported from src/lib/mac-generator.ts (the canonical TypeScript
 *           implementation), kept in lock-step with the Go twin at
 *           cli/mac-address-generator/mac-address-generator.go.
 * License:  display source — part of CosmoDev's polyglot tool pages.
 *
 * Design goals:
 *   - Pure + deterministic; throws only on a malformed OUI (never on randomness).
 *   - Functionally equivalent to the TS reference: same inputs -> same outputs.
 *   - Self-contained: stdlib only (no Composer packages). The default RNG uses
 *     random_bytes — PHP's cryptographic random source.
 *
 * API note: this port mirrors the TS canonical lib's public API, the
 * deterministic, RNG-injectable superset of the Go twin's. The Go twin draws
 * from crypto/rand directly (non-injectable), so its own suite asserts only
 * structural properties (regex shape, OUI prefix, U/L bit). Like the TS lib,
 * this port accepts an injected rng (a callable returning a float in [0, 1)),
 * letting the showcase tests below assert exact MAC strings.
 *
 * Pipeline: (optional OUI -> 3 octets) -> 3 random octets -> (force U/L bit) ->
 * join 6 octets as %02x with the separator -> (uppercase). A malformed OUI (not
 * 6 hex digits after stripping non-hex chars) throws InvalidArgumentException.
 */

declare(strict_types=1);

/**
 * Default RNG: a 32-bit draw from random_bytes mapped to [0, 1) — mirrors the
 * TS default crypto.getRandomValues(new Uint32Array(1))[0] / 2**32.
 *
 * @return float
 */
function mac_default_rng(): float
{
    // random_bytes(4) is a 4-byte cryptographically secure string; 'N' unpacks
    // it as an unsigned 32-bit big-endian integer. PHP ints are 64-bit on
    // 64-bit builds, so 2**32 fits and the division stays exact.
    return unpack('N', random_bytes(4))[1] / 4294967296.0;
}

/**
 * Two-digit lowercase hex, mirroring TS n.toString(16).padStart(2, '0').
 *
 * @param int $n
 * @return string
 */
function mac_hex(int $n): string
{
    return sprintf('%02x', $n);
}

/**
 * Options for generate_mac(). Each key is optional; defaults match the TS source.
 *
 *   - separator:           string   default ':'    joining chars; '' concatenates
 *   - uppercase:           bool     default false  uppercase hex letters
 *   - oui:                 string   default null   6-hex-digit OUI prefix (separators tolerated)
 *   - locallyAdministered: bool     default false  force the U/L bit (0x02)
 *   - rng:                 callable default mac_default_rng  returns a float in [0, 1)
 *
 * @return array
 */
function mac_default_options(): array
{
    return [
        'separator'           => ':',
        'uppercase'           => false,
        'oui'                 => null,
        'locallyAdministered' => false,
        'rng'                 => 'mac_default_rng',
    ];
}

/**
 * Build a random EUI-48 MAC address.
 *
 * The PHP twin of generateMac() in src/lib/mac-generator.ts. Throws
 * InvalidArgumentException on a malformed OUI; never throws otherwise.
 *
 * @param array $options
 * @return string
 * @throws \InvalidArgumentException
 */
function generate_mac(array $options = []): string
{
    $options = array_merge(mac_default_options(), $options);

    // Distinguish "not provided"/null (-> ':') from "provided as ''" (-> concat),
    // exactly like the TS `?? ':'` and Go's nil-vs-non-nil Separator.
    $sep = $options['separator'] ?? ':';
    $rng = $options['rng'];

    $octets = [];

    // Octets 0..2 — OUI (validated) or random. TS `if (opts.oui)` truthiness:
    // null and '' both take the random path.
    if (!empty($options['oui'])) {
        $clean = preg_replace('/[^0-9a-fA-F]/', '', $options['oui']);
        if (strlen($clean) !== 6) {
            throw new \InvalidArgumentException(
                'OUI must be 6 hex digits, got "' . $options['oui'] . '"'
            );
        }
        $octets[] = hexdec(substr($clean, 0, 2));
        $octets[] = hexdec(substr($clean, 2, 2));
        $octets[] = hexdec(substr($clean, 4, 2));
    } else {
        $octets[] = (int) floor($rng() * 256);
        $octets[] = (int) floor($rng() * 256);
        $octets[] = (int) floor($rng() * 256);
    }

    // Octets 3..5 — always random.
    $octets[] = (int) floor($rng() * 256);
    $octets[] = (int) floor($rng() * 256);
    $octets[] = (int) floor($rng() * 256);

    if ($options['locallyAdministered']) {
        $octets[0] = ($octets[0] & 0xFD) | 0x02; // set U/L bit (0x02), clear multicast
    }

    $mac = implode($sep, array_map('mac_hex', $octets));
    if ($options['uppercase']) {
        $mac = strtoupper($mac);
    }
    return $mac;
}

// ---------- showcase tests (mirror src/lib/mac-generator.test.ts) ----------
// Run: php php.php  (guarded so the file is still importable as a module).
if (!debug_backtrace()) {
    $check = function (string $label, bool $ok): void {
        if (!$ok) {
            throw new \AssertionError("mac-address-generator showcase failed: {$label}");
        }
    };
    $zero = static fn () => 0.0;

    $check('zero rng', generate_mac(['rng' => $zero]) === '00:00:00:00:00:00');
    $check('dash', generate_mac(['rng' => $zero, 'separator' => '-']) === '00-00-00-00-00-00');
    $check('concat', generate_mac(['rng' => $zero, 'separator' => '']) === '000000000000');
    // floor(0.7 * 256) == 179 == 0xb3
    $check('uppercase', generate_mac(['rng' => static fn () => 0.7, 'uppercase' => true]) === 'B3:B3:B3:B3:B3:B3');
    // OUI prefix (embedded separators stripped)
    $check('oui', generate_mac(['rng' => $zero, 'oui' => 'aa-bb-cc']) === 'aa:bb:cc:00:00:00');
    // locally administered: floor(0.5 * 256) == 0x80 -> 0x82
    $check('local', generate_mac(['rng' => static fn () => 0.5, 'locallyAdministered' => true]) === '82:80:80:80:80:80');
    // malformed OUI -> InvalidArgumentException
    try {
        generate_mac(['rng' => $zero, 'oui' => 'abc']);
        throw new \Exception('expected InvalidArgumentException');
    } catch (\InvalidArgumentException $e) {
        // expected
    }

    echo "ok\n";
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →