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 →