from .net_mac_address_types import MacValidation HEX = "0123456789abcdefABCDEF" def _invalid(reason: str) -> MacValidation: return MacValidation( valid=False, canonical=None, reason=reason, multicast=None, locally_administered=None, oui=None ) def validate_mac_address(text: str) -> MacValidation: """Check a MAC (EUI-48) address written in any of the four common forms and normalise it to lower-case colon form. Exactly four shapes are accepted: aa:bb:cc:dd:ee:ff, aa-bb-cc-dd-ee-ff, aabb.ccdd.eeff (Cisco) and aabbccddeeff. Nothing is trimmed and single-digit octets are refused, because "a:b:c:d:e:f" is more often a typo than a MAC. """ if not isinstance(text, str): return _invalid("not text") if len(text) == 0: return _invalid("empty") seps = [s for s in (":", "-", ".") if s in text] if len(seps) > 1: return _invalid("mixed separators") if not seps: parts = [text] shape_ok = len(text) == 12 else: parts = text.split(seps[0]) if seps[0] == ".": shape_ok = len(parts) == 3 and all(len(p) == 4 for p in parts) else: shape_ok = len(parts) == 6 and all(len(p) == 2 for p in parts) if not shape_ok: return _invalid("not a recognised MAC address format") hexdigits = "".join(parts) for ch in hexdigits: if ch not in HEX: return _invalid("not a hex digit") lower = hexdigits.lower() octets = [lower[i : i + 2] for i in range(0, 12, 2)] first = int(octets[0], 16) return MacValidation( valid=True, canonical=":".join(octets), reason=None, # The I/G bit is the least significant bit of the first octet, the U/L bit the next. multicast=(first & 1) == 1, locally_administered=(first & 2) == 2, oui="-".join(octets[:3]).upper(), )