Skip to content

MAC Address Generator — Zig 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 Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

//! mac-address-generator — random EUI-48 MAC address generator. Zig port
//! (canonical TS: src/lib/mac-generator.ts; Go twin: cli/mac-address-generator).
//! The default rng is std.crypto.random (the CSPRNG the Go twin uses); the
//! showcase keeps the rng injectable (a *const fn returning [0, 1)) so the
//! demo asserts exact MAC strings.
const std = @import("std");

const Options = struct {
    separator: ?[]const u8 = null,      // null -> ":"; "" concatenates
    uppercase: bool = false,
    oui: ?[]const u8 = null,            // 6 hex digits (separators tolerated)
    locally_administered: bool = false, // force U/L bit (0x02) of the first octet
    rng: *const fn () -> f64 = &defaultRng,
};

fn defaultRng() f64 { // 32-bit crypto draw mapped to [0, 1)
    return @as(f64, @floatFromInt(std.crypto.random.int(u32))) / 4294967296.0;
}

fn isHexDigit(c: u8) bool {
    return (c >= '0' and c <= '9') or (c >= 'a' and c <= 'f') or (c >= 'A' and c <= 'F');
}

// Strip non-hex chars, require exactly 6 digits, parse the 3 OUI octets.
fn parseOui(oui: []const u8) ![3]u8 {
    var clean: [6]u8 = undefined;
    var n: usize = 0;
    for (oui) |c| {
        if (isHexDigit(c)) {
            if (n == 6) return error.BadOui; // too many hex digits
            clean[n] = c;
            n += 1;
        }
    }
    if (n != 6) return error.BadOui;
    var oct: [3]u8 = undefined;
    for (0..3) |i| oct[i] = try std.fmt.parseInt(u8, clean[i * 2 .. i * 2 + 2], 16);
    return oct;
}

// Fill a slice with rng octets — floor(rng() * 256) like the TS lib.
fn randomOctets(buf: []u8, rng: *const fn () -> f64) void {
    for (buf) |*b| b.* = @intFromFloat(rng() * 256.0);
}

// Build a random EUI-48 MAC: 3 OUI octets (given or random) + 3 random,
// U/L bit forced when asked, joined as 2-digit lowercase hex + separator.
fn generateMac(alloc: std.mem.Allocator, o: Options) ![]const u8 {
    var oct: [6]u8 = undefined;
    // TS `if (opts.oui)` truthiness: null and "" both take the random path.
    const oui = o.oui orelse "";
    if (oui.len > 0) @memcpy(oct[0..3], &(try parseOui(oui))) else randomOctets(oct[0..3], o.rng);
    randomOctets(oct[3..6], o.rng);
    if (o.locally_administered) oct[0] = (oct[0] & 0xFD) | 0x02;
    const sep = o.separator orelse ":";
    const out = try alloc.alloc(u8, 12 + sep.len * 5); // hex pairs + 5 separators
    const hex = "0123456789abcdef";
    var w: usize = 0;
    for (0..6) |i| {
        if (i > 0) {
            @memcpy(out[w .. w + sep.len], sep);
            w += sep.len;
        }
        out[w] = hex[oct[i] >> 4];
        out[w + 1] = hex[oct[i] & 0xF];
        w += 2;
    }
    if (o.uppercase) for (out[0..w]) |*c| c.* = std.ascii.toUpper(c.*);
    return out[0..w];
}

// Deterministic rng sources for the demo assertions below.
fn zero() f64 { return 0; }
fn seven() f64 { return 0.7; }
fn half() f64 { return 0.5; }

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const alloc = arena.allocator();
    std.debug.print("{s}\n", .{try generateMac(alloc, .{ .rng = &zero })}); // 00:00:00:00:00:00
    std.debug.print("{s}\n", .{try generateMac(alloc, .{ .separator = "-" })}); // 6 random pairs, dash-joined
    std.debug.print("{s}\n", .{try generateMac(alloc, .{ .rng = &seven, .uppercase = true })}); // B3:B3:B3:B3:B3:B3
    std.debug.print("{s}\n", .{try generateMac(alloc, .{ .rng = &zero, .oui = "aa-bb-cc" })}); // aa:bb:cc:00:00:00
    std.debug.print("{s}\n", .{try generateMac(alloc, .{ .rng = &half, .locally_administered = true })}); // 82:80:80:80:80:80
}

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 →