Skip to content

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

// Package macaddr is the Go twin of CosmoDev's src/lib/mac-generator.ts (dual
// source: the web lib is TypeScript, the CLI lib is Go — kept in lock-step).
// Pure + deterministic, never panics. Table-driven tests share vectors with
// the TS suite.
package macaddr

import (
	"crypto/rand"
	"fmt"
	"strconv"
	"strings"
)

// Options configures Generate. The zero value (Options{}) matches the TS
// default (generateMac() with no options): colon separator, lowercase, no OUI,
// globally unique (U/L bit not set).
//
// Separator is a *string so the zero value means "default colon" while a
// non-nil empty string still means "concatenate" — exactly like the TS lib's
// distinction between omitted (→ ':') and '' (→ concatenate). This mirrors
// the pattern established by cli/slugify for the same reason.
type Options struct {
	Separator           *string // nil → ":" (default); non-nil, including "", used verbatim
	Uppercase           bool
	OUI                 string // optional 6-hex-digit OUI prefix (separators tolerated, stripped before validation)
	LocallyAdministered bool   // force the U/L bit (0x02) of the first octet
}

// Generate builds a random EUI-48 MAC address. It is the Go twin of
// generateMac() in src/lib/mac-generator.ts and must agree with it on every
// shared vector.
//
// Pipeline mirrors the TS lib exactly: if an OUI is supplied, strip non-hex
// characters and require exactly 6 hex digits (else return an error) for the
// first 3 octets; otherwise draw 3 random octets from crypto/rand. Always draw
// the last 3 octets from crypto/rand. When LocallyAdministered is set, force
// the U/L bit: octets[0] = (octets[0] & 0xFD) | 0x02. Join the 6 octets as
// lowercase %02x pairs with the separator; uppercase the result if requested.
// Never panics — an invalid OUI yields ("", error).
func Generate(opts Options) (string, error) {
	sep := ":"
	if opts.Separator != nil {
		sep = *opts.Separator
	}

	octets := make([]byte, 0, 6)

	if opts.OUI != "" {
		clean := stripNonHex(opts.OUI)
		if len(clean) != 6 {
			return "", fmt.Errorf("OUI must be 6 hex digits, got %q", opts.OUI)
		}
		for i := 0; i < 3; i++ {
			v, err := strconv.ParseUint(clean[i*2:i*2+2], 16, 8)
			if err != nil {
				// Unreachable after stripNonHex + len==6, but stay panic-free.
				return "", fmt.Errorf("OUI must be 6 hex digits, got %q", opts.OUI)
			}
			octets = append(octets, byte(v))
		}
	} else {
		head := make([]byte, 3)
		if _, err := rand.Read(head); err != nil {
			return "", fmt.Errorf("crypto/rand failed: %w", err)
		}
		octets = append(octets, head...)
	}

	tail := make([]byte, 3)
	if _, err := rand.Read(tail); err != nil {
		return "", fmt.Errorf("crypto/rand failed: %w", err)
	}
	octets = append(octets, tail...)

	if opts.LocallyAdministered {
		octets[0] = (octets[0] & 0xFD) | 0x02 // set U/L bit (0x02)
	}

	parts := make([]string, 6)
	for i, b := range octets {
		parts[i] = fmt.Sprintf("%02x", b)
	}
	mac := strings.Join(parts, sep)
	if opts.Uppercase {
		mac = strings.ToUpper(mac)
	}
	return mac, nil
}

// stripNonHex removes every character that is not a hex digit, mirroring
// opts.oui.replace(/[^0-9a-fA-F]/g, '') in the TS lib.
func stripNonHex(s string) string {
	var b strings.Builder
	for i := 0; i < len(s); i++ {
		c := s[i]
		if (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') {
			b.WriteByte(c)
		}
	}
	return b.String()
}

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 →