Files
wehub-resource-sync 680845cb1c
Linux build / Determine Swift version (push) Waiting to run
Linux build / Linux compile check (push) Blocked by required conditions
Build containerization / Verify commit signatures (push) Has been skipped
Build containerization / containerization (push) Successful in 0s
chore: import upstream snapshot with attribution
2026-07-13 12:25:30 +08:00

271 lines
9.2 KiB
Swift

//===----------------------------------------------------------------------===//
// 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)
}
}