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}