chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2026 Apple Inc. and the Containerization project authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// An EUI-48 MAC address as specified by IEEE 802.
|
||||
@frozen
|
||||
public struct MACAddress: Sendable, Hashable, CustomStringConvertible, Equatable, Comparable {
|
||||
public let value: UInt64
|
||||
|
||||
/// Creates an MACAddress from an integer.
|
||||
///
|
||||
/// - Parameter value: The big-endian value of the MAC address.
|
||||
/// The most significant 16 bits of the value are ignored.
|
||||
@inlinable
|
||||
public init(_ value: UInt64) {
|
||||
self.value = value & 0x0000_ffff_ffff_ffff
|
||||
}
|
||||
|
||||
/// Creates an IPv4Address from 6 bytes.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - bytes: 6-byte array in network byte order representing the IPv4 address
|
||||
/// - Throws: `AddressError.unableToParse` if the byte array length is not 6
|
||||
@inlinable
|
||||
public init(_ bytes: [UInt8]) throws {
|
||||
guard bytes.count == 6 else {
|
||||
throw AddressError.unableToParse
|
||||
}
|
||||
self.value =
|
||||
(UInt64(bytes[0]) << 40)
|
||||
| (UInt64(bytes[1]) << 32)
|
||||
| (UInt64(bytes[2]) << 24)
|
||||
| (UInt64(bytes[3]) << 16)
|
||||
| (UInt64(bytes[4]) << 8)
|
||||
| UInt64(bytes[5])
|
||||
}
|
||||
|
||||
/// Creates an MACAddress from a string representation.
|
||||
///
|
||||
/// - Parameter string: The MAC address string with colon or dash delimiters.
|
||||
/// - Throws: `AddressError.unableToParse` if the string is not a valid MAC address
|
||||
@inlinable
|
||||
public init(_ string: String) throws {
|
||||
self.value = try Self.parse(string)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public var bytes: [UInt8] {
|
||||
Self.bytes(value)
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
static func bytes(_ value: UInt64) -> [UInt8] {
|
||||
var result = [UInt8](repeating: 0, count: 6)
|
||||
result[0] = UInt8((value >> 40) & 0xff)
|
||||
result[1] = UInt8((value >> 32) & 0xff)
|
||||
result[2] = UInt8((value >> 24) & 0xff)
|
||||
result[3] = UInt8((value >> 16) & 0xff)
|
||||
result[4] = UInt8((value >> 8) & 0xff)
|
||||
result[5] = UInt8(value & 0xff)
|
||||
return result
|
||||
}
|
||||
|
||||
@available(macOS 26.0, *)
|
||||
@usableFromInline
|
||||
static func bytes(_ value: UInt64) -> InlineArray<6, UInt8> {
|
||||
let result: InlineArray<6, UInt8> = [
|
||||
UInt8((value >> 40) & 0xff),
|
||||
UInt8((value >> 32) & 0xff),
|
||||
UInt8((value >> 24) & 0xff),
|
||||
UInt8((value >> 16) & 0xff),
|
||||
UInt8((value >> 8) & 0xff),
|
||||
UInt8(value & 0xff),
|
||||
]
|
||||
return result
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public var description: String {
|
||||
bytes.map { String(format: "%02x", $0) }.joined(separator: ":")
|
||||
}
|
||||
|
||||
/// Parses an MAC address string into a UInt64 representation.
|
||||
///
|
||||
/// ## Validation Rules
|
||||
/// - Exactly six groups of two hexadecimal digits, separated by colons
|
||||
/// or dashes
|
||||
/// - No whitespace characters
|
||||
/// - Only hexadecimal digits and colons allowed
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```swift
|
||||
/// MACAddress.parse("01:23:45:67:89:ab") // Returns: 0x0000_0123_4567_89ab
|
||||
/// MACAddress.parse("01-23-45-67-89-AB") // Returns: 0x0000_0123_4567_89ab
|
||||
/// MACAddress.parse("00:00:00:00:00:00") // Returns: 0x0000_0000_0000_0000
|
||||
/// MACAddress.parse("ff:ff:ff:ff:ff:ff") // Returns: 0x0000_ffff_ffff_ffff
|
||||
///
|
||||
/// // Invalid examples:
|
||||
/// MACAddress.parse("01:23:45:67:89") // Wrong number of octets
|
||||
/// MACAddress.parse("01:23:45:67:89:a") // Invalid octet length
|
||||
/// MACAddress.parse("01:23:45:67:89:hi") // Invalid octet content
|
||||
/// MACAddress.parse("01:23-45:67-89:ab") // Inconsistent separators
|
||||
/// MACAddress.parse(" 01:23:45:67:89:ab ") // Whitespace
|
||||
/// ```
|
||||
///
|
||||
/// - Parameter s: The MAC address string to parse
|
||||
/// - Returns: The 64-bit representation of the IP address, or `nil` if parsing fails
|
||||
/// - Note: The returned value is in network byte order (big-endian)
|
||||
@usableFromInline
|
||||
internal static func parse(_ s: String) throws -> UInt64 {
|
||||
guard !s.isEmpty, s.count == 17 else {
|
||||
throw AddressError.unableToParse
|
||||
}
|
||||
|
||||
// MAC addresses should only contain ASCII hex digits and dots
|
||||
let utf8 = s.utf8
|
||||
for byte in utf8 {
|
||||
// ASCII whitespace: space(32), tab(9), newline(10), return(13)
|
||||
if byte == 32 || byte == 9 || byte == 10 || byte == 13 {
|
||||
throw AddressError.unableToParse
|
||||
}
|
||||
}
|
||||
|
||||
// accumulator for the 64 bit representation of the MAC address
|
||||
var result: UInt64 = 0
|
||||
|
||||
// tracking octet count, max 6 allowed
|
||||
var octetCount = 0
|
||||
var currentOctet = 0
|
||||
|
||||
// number of digits in the string representation of the octet
|
||||
var digitCount = 0
|
||||
|
||||
// separator character to use
|
||||
var separator: String.UTF8View.Element?
|
||||
|
||||
for byte in utf8 {
|
||||
if byte == 0x3a || byte == 0x2d { // ASCII ':'
|
||||
// Ensure separator is consistent
|
||||
guard separator == nil || byte == separator else {
|
||||
throw AddressError.unableToParse
|
||||
}
|
||||
separator = byte
|
||||
|
||||
// Validate octet before processing
|
||||
guard octetCount < 5, digitCount == 2 else {
|
||||
throw AddressError.unableToParse
|
||||
}
|
||||
|
||||
// Shift result and add current octet
|
||||
result = (result << 8) | UInt64(currentOctet)
|
||||
|
||||
// Reset for next octet
|
||||
octetCount += 1
|
||||
currentOctet = 0
|
||||
digitCount = 0
|
||||
|
||||
} else if byte >= 0x30 && byte <= 0x39 { // ASCII '0'-'9'
|
||||
let digit = Int(byte - 0x30)
|
||||
|
||||
digitCount += 1
|
||||
currentOctet = (currentOctet << 4) + digit
|
||||
|
||||
// Early termination if octet becomes too large
|
||||
guard digitCount <= 2 else {
|
||||
throw AddressError.unableToParse
|
||||
}
|
||||
|
||||
} else if byte >= 0x41 && byte <= 0x46 { // ASCII 'A'-'F'
|
||||
let digit = Int(byte - 0x41 + 10)
|
||||
|
||||
digitCount += 1
|
||||
currentOctet = (currentOctet << 4) + digit
|
||||
|
||||
// Early termination if octet becomes too large
|
||||
guard digitCount <= 2 else {
|
||||
throw AddressError.unableToParse
|
||||
}
|
||||
|
||||
} else if byte >= 0x61 && byte <= 0x66 { // ASCII 'A'-'F'
|
||||
let digit = Int(byte - 0x61 + 10)
|
||||
|
||||
digitCount += 1
|
||||
currentOctet = (currentOctet << 4) + digit
|
||||
|
||||
// Early termination if octet becomes too large
|
||||
guard digitCount <= 2 else {
|
||||
throw AddressError.unableToParse
|
||||
}
|
||||
|
||||
} else {
|
||||
throw AddressError.unableToParse
|
||||
}
|
||||
}
|
||||
|
||||
// Validate final octet
|
||||
guard octetCount == 5, digitCount == 2 else {
|
||||
throw AddressError.unableToParse
|
||||
}
|
||||
|
||||
return (result << 8) | UInt64(currentOctet)
|
||||
}
|
||||
|
||||
// MARK: - Address Classification Methods
|
||||
|
||||
/// Returns `true` if the MAC address is locally administered.
|
||||
///
|
||||
/// IEEE 802 specifies that the second-least-significant bit of
|
||||
/// the first octet of the MAC address determines whether the
|
||||
/// address is globally unique (bit cleared) or locally
|
||||
/// administered (bit set).
|
||||
@inlinable
|
||||
public var isLocallyAdministered: Bool {
|
||||
(value & 0x0000_0200_0000_0000) != 0
|
||||
}
|
||||
|
||||
/// Returns `true` if the MAC address is multicast.
|
||||
///
|
||||
/// IEEE 802 specifies that the least-significant bit of
|
||||
/// the first octet of the MAC address determines whether the
|
||||
/// address is unicast (bit cleared) or multicast (bit set).
|
||||
@inlinable
|
||||
public var isMulticast: Bool {
|
||||
(value & 0x0000_0100_0000_0000) != 0
|
||||
}
|
||||
|
||||
/// Returns the link local IP address based on the EUI-64 version
|
||||
/// of the MAC address.
|
||||
///
|
||||
/// - Parameter network: The IPv6 address to use for the network prefix
|
||||
/// - Returns: The link local IP address for the MAC address
|
||||
@inlinable
|
||||
public func ipv6Address(network: IPv6Address) throws -> IPv6Address {
|
||||
let prefixBytes = network.bytes
|
||||
return try IPv6Address([
|
||||
prefixBytes[0], prefixBytes[1], prefixBytes[2], prefixBytes[3],
|
||||
prefixBytes[4], prefixBytes[5], prefixBytes[6], prefixBytes[7],
|
||||
bytes[0] ^ 0x02, bytes[1], bytes[2], 0xff,
|
||||
0xfe, bytes[3], bytes[4], bytes[5],
|
||||
])
|
||||
}
|
||||
|
||||
/// Compares two IPv4 addresses numerically.
|
||||
@inlinable
|
||||
public static func < (lhs: MACAddress, rhs: MACAddress) -> Bool {
|
||||
lhs.value < rhs.value
|
||||
}
|
||||
}
|
||||
|
||||
extension MACAddress: Codable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let string = try container.decode(String.self)
|
||||
try self.init(string)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(description)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user