code wiki / (root) / mac48.nx

mac48.nx source

↩ module page · 140 lines · 4327 B

1// mac48.nx -- IEEE 48-bit Ethernet MAC address parse + format. 2// 3// 6-byte unique identifier, printed as six hex pairs separated 4// by ':' (IETF canonical) or '-' (Microsoft) or '.' (Cisco 5// three-group form -- rare, not produced by us). 6// 7// Use cases: NAS monitoring, firewall rule generation, DHCP 8// static leases, Wake-on-LAN packet construction, switch port 9// lookup, Nishi-controller device identity. 10// 11// OUI lookups (first 3 bytes = manufacturer) are a future 12// mac48_oui.nx if Nishi-monitoring needs them -- that's a data 13// table problem, not algorithm. 14// 15// Invariants: 16// M1 Parse accepts both ':' and '-' separators, case-insensitive 17// hex. Three-group Cisco notation (dotted) NOT accepted. 18// M2 Format emits lowercase hex with ':' separators (IETF 19// canonical). A mac48_format_upper / mac48_format_dash 20// can be added later if needed. 21// M3 Output length fixed at 17 bytes (6*2 hex + 5 separators). 22 23import "syscalls.nx" 24 25const MAC_ERR_SHORT: i64 = -1 26const MAC_ERR_FORMAT: i64 = -2 27 28// ASCII hex digit -> value 0..15, or -1. 29func mac_nibble(c: i64) -> i64 { 30 if c >= 0x30 { 31 if c <= 0x39 { return c - 0x30 } 32 } 33 if c >= 0x61 { 34 if c <= 0x66 { return c - 0x61 + 10 } 35 } 36 if c >= 0x41 { 37 if c <= 0x46 { return c - 0x41 + 10 } 38 } 39 return -1 40} 41 42// Parse a MAC address string into 6 output bytes. Accepts 43// 17-char aa:bb:cc:dd:ee:ff or aa-bb-cc-dd-ee-ff. 44func mac48_parse(buf: *u8, n: i64, out: *u8) -> i64 { 45 if n != 17 { return MAC_ERR_SHORT } 46 var i: i64 = 0 47 while i < 6 { 48 let hi: i64 = mac_nibble(buf[i * 3]) 49 let lo: i64 = mac_nibble(buf[i * 3 + 1]) 50 if hi < 0 { return MAC_ERR_FORMAT } 51 if lo < 0 { return MAC_ERR_FORMAT } 52 out[i] = (hi << 4) | lo 53 if i < 5 { 54 let sep: i64 = buf[i * 3 + 2] 55 if sep != 0x3A { // ':' 56 if sep != 0x2D { // '-' 57 return MAC_ERR_FORMAT 58 } 59 } 60 } 61 i = i + 1 62 } 63 return 0 64} 65 66// Format 6 bytes as lowercase "aa:bb:cc:dd:ee:ff". out must 67// have 17 bytes of room. 68func mac48_format(mac: *u8, out: *u8) -> i64 { 69 let lut: *u8 = "0123456789abcdef" 70 var i: i64 = 0 71 while i < 6 { 72 let b: i64 = mac[i] 73 out[i * 3] = lut[(b >> 4) & 0xF] 74 out[i * 3 + 1] = lut[b & 0xF] 75 if i < 5 { out[i * 3 + 2] = 0x3A } // ':' 76 i = i + 1 77 } 78 return 17 79} 80 81// Is this MAC the broadcast address (FF:FF:FF:FF:FF:FF)? 82func mac48_is_broadcast(mac: *u8) -> i64 { 83 var i: i64 = 0 84 while i < 6 { 85 if mac[i] != 0xFF { return 0 } 86 i = i + 1 87 } 88 return 1 89} 90 91// Is this MAC a multicast (bit 0 of first byte set)? 92func mac48_is_multicast(mac: *u8) -> i64 { 93 return mac[0] & 0x1 94} 95 96// Is this MAC locally administered (bit 1 of first byte set)? 97// OUI-assigned ones have this bit clear; randomised / private 98// MACs have it set. 99func mac48_is_local(mac: *u8) -> i64 { 100 return (mac[0] >> 1) & 0x1 101} 102 103// Compile-only smoke. 104func main() -> i64 { 105 let bytes: *u8 = sys_mmap(16) 106 107 // Parse lower-case. 108 if mac48_parse("aa:bb:cc:dd:ee:ff", 17, bytes) != 0 { return 1 } 109 if bytes[0] != 0xAA { return 2 } 110 if bytes[5] != 0xFF { return 3 } 111 112 // Parse upper-case with dashes. 113 if mac48_parse("00-1A-2B-3C-4D-5E", 17, bytes) != 0 { return 4 } 114 if bytes[0] != 0x00 { return 5 } 115 if bytes[1] != 0x1A { return 6 } 116 if bytes[5] != 0x5E { return 7 } 117 118 // Format round-trip. 119 let out: *u8 = sys_mmap(32) 120 mac48_format(bytes, out) 121 if out[0] != 0x30 { return 8 } // '0' 122 if out[1] != 0x30 { return 9 } 123 if out[2] != 0x3A { return 10 } // ':' 124 if out[3] != 0x31 { return 11 } // '1' 125 126 // Broadcast detection. 127 var i: i64 = 0 128 while i < 6 { bytes[i] = 0xFF; i = i + 1 } 129 if mac48_is_broadcast(bytes) != 1 { return 12 } 130 131 // Multicast bit (first byte 01:...). 132 bytes[0] = 0x01 133 if mac48_is_multicast(bytes) != 1 { return 13 } 134 135 // Bad separator rejected. 136 if mac48_parse("aa.bb.cc.dd.ee.ff", 17, bytes) != MAC_ERR_FORMAT { 137 return 14 138 } 139 return 0 140}