chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025-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.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// Represents an IPv6 network address conforming to RFC 5952 and RFC 4291.
|
||||
public struct IPv6Address: Sendable, Hashable, CustomStringConvertible, Equatable, Comparable {
|
||||
public let value: UInt128
|
||||
|
||||
public let zone: String?
|
||||
|
||||
/// Creates an IPv6Address by parsing a string representation.
|
||||
///
|
||||
/// Supports standard IPv6 formats including compressed notation (::), mixed IPv4 notation, and zone identifiers.
|
||||
///
|
||||
/// - Parameter address: String representation of an IPv6 address
|
||||
/// - Throws: `AddressError` if the string is not a valid IPv6 address
|
||||
public init(_ address: String) throws {
|
||||
self = try Self.parse(address)
|
||||
}
|
||||
|
||||
/// Creates an IPv6Address from 16 bytes.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - bytes: 16-byte array in network byte order representing the IPv6 address
|
||||
/// - zone: Optional zone identifier (e.g., "eth0")
|
||||
/// - Throws: `AddressError.unableToParse` if the byte array length is not 16
|
||||
@inlinable
|
||||
public init(_ bytes: [UInt8], zone: String? = nil) throws {
|
||||
guard bytes.count == 16 else {
|
||||
throw AddressError.unableToParse
|
||||
}
|
||||
|
||||
// Build UInt128 value in chunks to avoid compiler complexity
|
||||
let hh =
|
||||
(UInt128(bytes[0]) << 120) | (UInt128(bytes[1]) << 112) | (UInt128(bytes[2]) << 104)
|
||||
| (UInt128(bytes[3]) << 96)
|
||||
let hl =
|
||||
(UInt128(bytes[4]) << 88) | (UInt128(bytes[5]) << 80) | (UInt128(bytes[6]) << 72)
|
||||
| (UInt128(bytes[7]) << 64)
|
||||
let lh =
|
||||
(UInt128(bytes[8]) << 56) | (UInt128(bytes[9]) << 48) | (UInt128(bytes[10]) << 40)
|
||||
| (UInt128(bytes[11]) << 32)
|
||||
let ll =
|
||||
(UInt128(bytes[12]) << 24) | (UInt128(bytes[13]) << 16) | (UInt128(bytes[14]) << 8) | UInt128(bytes[15])
|
||||
|
||||
self.value = hh | hl | lh | ll
|
||||
self.zone = zone
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public init(_ value: UInt128, zone: String? = nil) {
|
||||
self.value = value
|
||||
self.zone = zone
|
||||
}
|
||||
|
||||
/// Canonical string representation following RFC 5952.
|
||||
public var description: String {
|
||||
// Convert UInt128 value to 16-bit groups
|
||||
let groups: [UInt16] = [
|
||||
UInt16((value >> 112) & 0xFFFF),
|
||||
UInt16((value >> 96) & 0xFFFF),
|
||||
UInt16((value >> 80) & 0xFFFF),
|
||||
UInt16((value >> 64) & 0xFFFF),
|
||||
UInt16((value >> 48) & 0xFFFF),
|
||||
UInt16((value >> 32) & 0xFFFF),
|
||||
UInt16((value >> 16) & 0xFFFF),
|
||||
UInt16(value & 0xFFFF),
|
||||
]
|
||||
|
||||
// Find the longest run of consecutive zeros for :: compression
|
||||
var longestZeroStart = -1
|
||||
var longestZeroLength = 0
|
||||
var currentZeroStart = -1
|
||||
var currentZeroLength = 0
|
||||
|
||||
for (index, group) in groups.enumerated() {
|
||||
if group == 0 {
|
||||
if currentZeroStart == -1 {
|
||||
currentZeroStart = index
|
||||
currentZeroLength = 1
|
||||
} else {
|
||||
currentZeroLength += 1
|
||||
}
|
||||
} else {
|
||||
if currentZeroLength > longestZeroLength {
|
||||
longestZeroStart = currentZeroStart
|
||||
longestZeroLength = currentZeroLength
|
||||
}
|
||||
currentZeroStart = -1
|
||||
currentZeroLength = 0
|
||||
}
|
||||
}
|
||||
if currentZeroLength > longestZeroLength {
|
||||
longestZeroStart = currentZeroStart
|
||||
longestZeroLength = currentZeroLength
|
||||
}
|
||||
|
||||
let useCompression = longestZeroLength >= 2
|
||||
|
||||
var result = ""
|
||||
var index = 0
|
||||
|
||||
while index < 8 {
|
||||
if useCompression && index == longestZeroStart {
|
||||
if index == 0 {
|
||||
result += "::"
|
||||
} else {
|
||||
result += ":"
|
||||
}
|
||||
// Skip the compressed zeros
|
||||
index += longestZeroLength
|
||||
|
||||
// If we compressed to the end, we're done
|
||||
if index >= 8 {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// Add the group in lowercase hex without leading zeros
|
||||
result += String(groups[index], radix: 16, uppercase: false)
|
||||
index += 1
|
||||
|
||||
// Add colon if not at the end
|
||||
if index < 8 {
|
||||
result += ":"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let zone = zone {
|
||||
result += "%" + zone
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public var bytes: [UInt8] {
|
||||
Self.bytes(self.value)
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
internal static func bytes(_ value: UInt128) -> [UInt8] {
|
||||
var result = [UInt8](repeating: 0, count: 16)
|
||||
result[0] = UInt8((value >> 120) & 0xff)
|
||||
result[1] = UInt8((value >> 112) & 0xff)
|
||||
result[2] = UInt8((value >> 104) & 0xff)
|
||||
result[3] = UInt8((value >> 96) & 0xff)
|
||||
result[4] = UInt8((value >> 88) & 0xff)
|
||||
result[5] = UInt8((value >> 80) & 0xff)
|
||||
result[6] = UInt8((value >> 72) & 0xff)
|
||||
result[7] = UInt8((value >> 64) & 0xff)
|
||||
result[8] = UInt8((value >> 56) & 0xff)
|
||||
result[9] = UInt8((value >> 48) & 0xff)
|
||||
result[10] = UInt8((value >> 40) & 0xff)
|
||||
result[11] = UInt8((value >> 32) & 0xff)
|
||||
result[12] = UInt8((value >> 24) & 0xff)
|
||||
result[13] = UInt8((value >> 16) & 0xff)
|
||||
result[14] = UInt8((value >> 8) & 0xff)
|
||||
result[15] = UInt8(value & 0xff)
|
||||
return result
|
||||
}
|
||||
|
||||
@available(macOS 26.0, *)
|
||||
@usableFromInline
|
||||
internal static func bytes(_ value: UInt128) -> InlineArray<16, UInt8> {
|
||||
let result: InlineArray<16, UInt8> = [
|
||||
UInt8((value >> 120) & 0xff),
|
||||
UInt8((value >> 112) & 0xff),
|
||||
UInt8((value >> 104) & 0xff),
|
||||
UInt8((value >> 96) & 0xff),
|
||||
UInt8((value >> 88) & 0xff),
|
||||
UInt8((value >> 80) & 0xff),
|
||||
UInt8((value >> 72) & 0xff),
|
||||
UInt8((value >> 64) & 0xff),
|
||||
UInt8((value >> 56) & 0xff),
|
||||
UInt8((value >> 48) & 0xff),
|
||||
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
|
||||
}
|
||||
|
||||
/// The unspecified IPv6 address (::)
|
||||
public static let unspecified = IPv6Address(0)
|
||||
|
||||
/// The loopback IPv6 address (::1)
|
||||
public static let loopback = IPv6Address(1)
|
||||
|
||||
// MARK: - Address Classification Methods
|
||||
|
||||
/// Returns `true` if this is the unspecified address (::).
|
||||
@inlinable
|
||||
public var isUnspecified: Bool {
|
||||
value == 0
|
||||
}
|
||||
|
||||
/// Returns `true` if this is the loopback address (::1).
|
||||
@inlinable
|
||||
public var isLoopback: Bool {
|
||||
value == 1
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a multicast address (ff00::/8).
|
||||
@inlinable
|
||||
public var isMulticast: Bool {
|
||||
(value >> 120) == 0xFF
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a link-local unicast address (fe80::/10).
|
||||
@inlinable
|
||||
public var isLinkLocal: Bool {
|
||||
(value >> 118) == 0x3FA // fe80::/10 = top 10 bits are 1111111010
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a unique local address (fc00::/7).
|
||||
@inlinable
|
||||
public var isUniqueLocal: Bool {
|
||||
(value >> 121) == 0x7E // fc00::/7 = top 7 bits are 1111110
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a global unicast address.
|
||||
@inlinable
|
||||
public var isGlobalUnicast: Bool {
|
||||
!isUnspecified && !isLoopback && !isMulticast && !isLinkLocal && !isUniqueLocal
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a documentation address (2001:db8::/32).
|
||||
@inlinable
|
||||
public var isDocumentation: Bool {
|
||||
(value >> 96) == 0x2001_0DB8 // 2001:db8::/32
|
||||
}
|
||||
/// Compares two IPv6 addresses numerically, then by zone if values are equal.
|
||||
@inlinable
|
||||
public static func < (lhs: IPv6Address, rhs: IPv6Address) -> Bool {
|
||||
if lhs.value != rhs.value {
|
||||
return lhs.value < rhs.value
|
||||
}
|
||||
// Same value, compare zones lexicographically
|
||||
return (lhs.zone ?? "") < (rhs.zone ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
extension IPv6Address: 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