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 →