chore: import upstream snapshot with attribution
Build containerization / Verify commit signatures (push) Has been skipped
Build containerization / containerization (push) Successful in 0s
Linux build / Linux compile check (push) Has been cancelled
Linux build / Determine Swift version (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:25:30 +08:00
commit 680845cb1c
445 changed files with 103779 additions and 0 deletions
@@ -0,0 +1,65 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
/// Conforming objects can allocate and free various address types.
public protocol AddressAllocator<AddressType>: Sendable {
associatedtype AddressType: Sendable
/// Allocate a new address.
func allocate() throws -> AddressType
/// Attempt to reserve a specific address.
func reserve(_ address: AddressType) throws
/// Free an allocated address.
func release(_ address: AddressType) throws
/// If no addresses are allocated, prevent future allocations and return true.
func disableAllocator() -> Bool
}
/// Errors that a type implementing AddressAllocator should throw.
public enum AllocatorError: Swift.Error, CustomStringConvertible, Equatable {
case allocatorDisabled
case allocatorFull
case alreadyAllocated(_ address: String)
case invalidAddress(_ index: String)
case invalidArgument(_ msg: String)
case invalidIndex(_ index: Int)
case notAllocated(_ address: String)
case rangeExceeded
public var description: String {
switch self {
case .allocatorDisabled:
return "the allocator is shutting down"
case .allocatorFull:
return "no free indices are available for allocation"
case .alreadyAllocated(let address):
return "cannot choose already-allocated address \(address)"
case .invalidAddress(let address):
return "cannot create index using address \(address)"
case .invalidArgument(let msg):
return "invalid argument: \(msg)"
case .invalidIndex(let index):
return "cannot create address using index \(index)"
case .notAllocated(let address):
return "cannot free unallocated address \(address)"
case .rangeExceeded:
return "cannot create allocator that overflows maximum address value"
}
}
}
@@ -0,0 +1,66 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
public struct AddressError: Error, Equatable, Hashable, CustomStringConvertible {
public var description: String {
String(describing: self.base)
}
@usableFromInline
enum Base: Equatable, Hashable, Sendable {
case unableToParse
case invalidZoneIdentifier
case invalidIPv4Suffix
case multipleEllipsis
case invalidHexGroup
case malformedAddress
case incompleteAddress
}
@usableFromInline
let base: Base
@inlinable
init(_ base: Base) { self.base = base }
public static var unableToParse: Self {
Self(.unableToParse)
}
public static var invalidZoneIdentifier: Self {
Self(.invalidZoneIdentifier)
}
public static var invalidIPv4SuffixInIPv6Address: Self {
Self(.invalidIPv4Suffix)
}
public static var multipleEllipsis: Self {
Self(.multipleEllipsis)
}
public static var invalidHexGroup: Self {
Self(.invalidHexGroup)
}
public static var malformedAddress: Self {
Self(.malformedAddress)
}
public static var incompleteAddress: Self {
Self(.incompleteAddress)
}
}
@@ -0,0 +1,61 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
import Logging
/// `AsyncLock` provides a familiar locking API, with the main benefit being that it
/// is safe to call async methods while holding the lock. This is primarily used in spots
/// where an actor makes sense, but we may need to ensure we don't fall victim to actor
/// reentrancy issues.
public actor AsyncLock {
private var busy = false
private var queue: ArraySlice<CheckedContinuation<(), Never>> = []
private var log: Logger?
public struct Context: Sendable {
fileprivate init() {}
}
public init(log: Logger? = nil) {
self.log = log
}
/// withLock provides a scoped locking API to run a function while holding the lock.
public func withLock<T: Sendable>(logMetadata: Logger.Metadata? = nil, _ body: @Sendable @escaping (Context) async throws -> T) async rethrows -> T {
log?.debug("acquiring lock", metadata: logMetadata)
while self.busy {
await withCheckedContinuation { cc in
self.queue.append(cc)
}
}
self.busy = true
defer {
self.busy = false
if let next = self.queue.popFirst() {
next.resume(returning: ())
} else {
self.queue = []
}
}
log?.debug("holding lock", metadata: logMetadata)
defer { log?.debug("releasing lock", metadata: logMetadata) }
let context = Context()
return try await body(context)
}
}
@@ -0,0 +1,59 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
/// `AsyncMutex` provides a mutex that protects a piece of data, with the main benefit being that it
/// is safe to call async methods while holding the lock. This is primarily used in spots
/// where an actor makes sense, but we may need to ensure we don't fall victim to actor
/// reentrancy issues.
public actor AsyncMutex<T: Sendable> {
private final class Box: @unchecked Sendable {
var value: T
init(_ value: T) {
self.value = value
}
}
private var busy = false
private var queue: ArraySlice<CheckedContinuation<(), Never>> = []
private let box: Box
public init(_ initialValue: T) {
self.box = Box(initialValue)
}
/// withLock provides a scoped locking API to run a function while holding the lock.
/// The protected value is passed to the closure for safe access.
public func withLock<R: Sendable>(_ body: @Sendable @escaping (inout T) async throws -> R) async rethrows -> R {
while self.busy {
await withCheckedContinuation { cc in
self.queue.append(cc)
}
}
self.busy = true
defer {
self.busy = false
if let next = self.queue.popFirst() {
next.resume(returning: ())
} else {
self.queue = []
}
}
return try await body(&self.box.value)
}
}
+148
View File
@@ -0,0 +1,148 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
/// Describes an IPv4 or IPv6 CIDR address block.
@frozen
public enum CIDR: CustomStringConvertible, Equatable, Sendable, Hashable {
case v4(IPv4Address, Prefix)
case v6(IPv6Address, Prefix)
/// Create a CIDR address block.
public init(_ cidr: String) throws {
if let cidrV4 = try? CIDRv4(cidr) {
self = .v4(cidrV4.address, cidrV4.prefix)
} else if let cidrV6 = try? CIDRv6(cidr) {
self = .v6(cidrV6.address, cidrV6.prefix)
} else {
throw Error.invalidCIDR(cidr: cidr)
}
}
/// Create a CIDR address from a member IP and a prefix length.
public init(_ address: IPAddress, prefix: Prefix) throws {
switch address {
case .v4(let addr):
guard prefix.length <= 32 else {
throw Error.invalidCIDR(cidr: "\(address)/\(prefix)")
}
self = .v4(addr, prefix)
case .v6(let addr):
guard prefix.length <= 128 else {
throw Error.invalidCIDR(cidr: "\(address)/\(prefix)")
}
self = .v6(addr, prefix)
}
}
/// Create the smallest CIDR block that includes the lower and upper bounds.
///
/// For type-safe construction, prefer `v4Range(lower:upper:)` or `v6Range(lower:upper:)`.
public init(lower: IPAddress, upper: IPAddress) throws {
switch (lower, upper) {
case (.v4(let lowerAddr), .v4(let upperAddr)):
let cidr = try CIDRv4(lower: lowerAddr, upper: upperAddr)
self = .v4(cidr.address, cidr.prefix)
case (.v6(let lowerAddr), .v6(let upperAddr)):
let cidr = try CIDRv6(lower: lowerAddr, upper: upperAddr)
self = .v6(cidr.address, cidr.prefix)
default:
throw Error.invalidAddressRange(lower: lower.description, upper: upper.description)
}
}
/// The IP component of this CIDR address.
@inlinable
public var address: IPAddress {
switch self {
case .v4(let addr, _):
return .v4(addr)
case .v6(let addr, _):
return .v6(addr)
}
}
/// The prefix length of this CIDR address.
@inlinable
public var prefix: Prefix {
switch self {
case .v4(_, let prefix), .v6(_, let prefix):
return prefix
}
}
/// The lowest address in this CIDR block
@inlinable
public var lower: IPAddress {
switch self {
case (.v4(let addr, let prefix)):
return .v4(IPv4Address(addr.value & prefix.prefixMask32))
case (.v6(let addr, let prefix)):
return .v6(IPv6Address(addr.value & prefix.prefixMask128))
}
}
/// The highest address in this CIDR block (broadcast address).
@inlinable
public var upper: IPAddress {
switch self {
case .v4(let addr, let prefix):
return .v4(IPv4Address(addr.value | prefix.suffixMask32))
case .v6(let addr, let prefix):
return .v6(IPv6Address(addr.value | prefix.suffixMask128, zone: addr.zone))
}
}
/// Return true if the CIDR block contains the specified address.
///
/// Compares network portion of the given IP address.
@inlinable
public func contains(_ ip: IPAddress) -> Bool {
switch (self, ip) {
case (.v4(let network, let prefix), .v4(let ip)):
return network.value == (ip.value & prefix.prefixMask32)
case (.v6(let network, let prefix), .v6(let ip)):
return (network.zone == ip.zone) && (network.value == (ip.value & prefix.prefixMask128))
default:
return false
}
}
/// Retrieve the text representation of the CIDR block.
public var description: String {
"\(address)/\(prefix)"
}
}
extension CIDR {
public enum Error: Swift.Error {
case invalidCIDR(cidr: String)
case invalidAddressRange(lower: String, upper: String)
}
}
extension CIDR: 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)
}
}
+122
View File
@@ -0,0 +1,122 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
/// Describes an IPv4 CIDR address block.
@frozen
public struct CIDRv4: CustomStringConvertible, Equatable, Sendable, Hashable {
/// The IP component of this CIDR address.
public let address: IPv4Address
/// The prefix length of this CIDR address.
public let prefix: Prefix
/// Create a CIDR address block.
public init(_ cidr: String) throws {
let split = cidr.split(separator: "/")
guard split.count == 2 else {
throw CIDR.Error.invalidCIDR(cidr: cidr)
}
guard let prefixLength = UInt8(split[1]), let prefix = Prefix(length: prefixLength) else {
throw CIDR.Error.invalidCIDR(cidr: cidr)
}
let address = try IPv4Address(String(split[0]))
try self.init(address, prefix: prefix)
}
/// Create a CIDR address from a member IP and a prefix length.
public init(_ address: IPv4Address, prefix: Prefix) throws {
guard prefix.length <= 32 else {
throw CIDR.Error.invalidCIDR(cidr: "\(address)/\(prefix)")
}
self.address = address
self.prefix = prefix
}
/// Create the smallest IPv4 CIDR block that includes the lower and upper bounds.
///
/// - Parameters:
/// - lower: The lower bound IPv4 address
/// - upper: The upper bound IPv4 address
/// - Returns: The smallest CIDR block containing both addresses
/// - Throws: If lower > upper
public init(lower: IPv4Address, upper: IPv4Address) throws {
guard lower.value <= upper.value else {
throw CIDR.Error.invalidAddressRange(lower: lower.description, upper: upper.description)
}
for length in 1...32 {
let prefixLength = Prefix(unchecked: UInt8(length))
let mask = prefixLength.prefixMask32
if (lower.value & mask) != (upper.value & mask) {
let prefix = Prefix(unchecked: UInt8(length - 1))
let networkAddr = IPv4Address(lower.value & prefix.prefixMask32)
try self.init(networkAddr, prefix: prefix)
return
}
}
// Same address - /32 block
let prefix = Prefix(unchecked: 32)
let networkAddr = IPv4Address(lower.value & prefix.prefixMask32)
try self.init(networkAddr, prefix: prefix)
}
/// The lowest address in this CIDR block
@inlinable
public var lower: IPv4Address {
IPv4Address(address.value & prefix.prefixMask32)
}
/// The highest address in this CIDR block (broadcast address).
@inlinable
public var upper: IPv4Address {
IPv4Address(address.value | prefix.suffixMask32)
}
/// Return true if the CIDR block contains the specified address.
///
/// Compares network portion of the given IP address.
@inlinable
public func contains(_ ip: IPv4Address) -> Bool {
(address.value & prefix.prefixMask32) == (ip.value & prefix.prefixMask32)
}
/// Retrieve the text representation of the CIDR block.
public var description: String {
"\(address)/\(prefix)"
}
}
extension CIDRv4: 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)
}
}
extension CIDRv4 {
/// The gateway address of the network. Conventionally the first usable
/// address in the subnet (`lower + 1`).
public var gateway: IPv4Address {
IPv4Address(self.lower.value + 1)
}
}
+115
View File
@@ -0,0 +1,115 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
/// Describes an IPv4 or IPv6 CIDR address block.
@frozen
public struct CIDRv6: CustomStringConvertible, Equatable, Sendable, Hashable {
/// The IP component of this CIDR address.
public let address: IPv6Address
/// The prefix length of this CIDR address.
public let prefix: Prefix
/// Create a CIDR address block.
public init(_ cidr: String) throws {
let split = cidr.split(separator: "/")
guard split.count == 2 else {
throw CIDR.Error.invalidCIDR(cidr: cidr)
}
guard let prefixLength = UInt8(split[1]), let prefix = Prefix(length: prefixLength) else {
throw CIDR.Error.invalidCIDR(cidr: cidr)
}
let address = try IPv6Address(String(split[0]))
try self.init(address, prefix: prefix)
}
/// Create a CIDR address from a member IP and a prefix length.
public init(_ address: IPv6Address, prefix: Prefix) throws {
guard prefix.length <= 128 else {
throw CIDR.Error.invalidCIDR(cidr: "\(address)/\(prefix)")
}
self.address = address
self.prefix = prefix
}
/// Create the smallest IPv6 CIDR block that includes the lower and upper bounds.
///
/// - Parameters:
/// - lower: The lower bound IPv6 address
/// - upper: The upper bound IPv6 address
/// - Returns: The smallest CIDR block containing both addresses
/// - Throws: If lower > upper or zones don't match
public init(lower: IPv6Address, upper: IPv6Address) throws {
guard lower.value <= upper.value && lower.zone == upper.zone else {
throw CIDR.Error.invalidAddressRange(lower: lower.description, upper: upper.description)
}
for length in 1...128 {
let prefixLength = Prefix(unchecked: UInt8(length))
let mask = prefixLength.prefixMask128
if (lower.value & mask) != (upper.value & mask) {
let prefix = Prefix(unchecked: UInt8(length - 1))
let networkAddr = IPv6Address(lower.value & prefix.prefixMask128, zone: lower.zone)
try self.init(networkAddr, prefix: prefix)
return
}
}
// Same address - /128 block
let prefix = Prefix(unchecked: 128)
let networkAddr = IPv6Address(lower.value & prefix.prefixMask128, zone: lower.zone)
try self.init(networkAddr, prefix: prefix)
}
/// The lowest address in this CIDR block
@inlinable
public var lower: IPv6Address {
IPv6Address(address.value & prefix.prefixMask128)
}
/// The highest address in this CIDR block (broadcast address).
@inlinable
public var upper: IPv6Address {
IPv6Address(address.value | prefix.suffixMask128, zone: address.zone)
}
/// Return true if the CIDR block contains the specified address.
///
/// Compares network portion of the given IP address.
@inlinable
public func contains(_ ip: IPv6Address) -> Bool {
(address.zone == ip.zone) && ((address.value & prefix.prefixMask128) == (ip.value & prefix.prefixMask128))
}
/// Retrieve the text representation of the CIDR block.
public var description: String {
"\(address)/\(prefix)"
}
}
extension CIDRv6: 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)
}
}
@@ -0,0 +1,29 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
import Foundation
extension FileManager {
/// Returns a unique temporary directory to use.
public func uniqueTemporaryDirectory(create: Bool = true) -> URL {
let tempDirectoryURL = temporaryDirectory
let uniqueDirectoryURL = tempDirectoryURL.appendingPathComponent(UUID().uuidString)
if create {
try? createDirectory(at: uniqueDirectoryURL, withIntermediateDirectories: true, attributes: nil)
}
return uniqueDirectoryURL
}
}
@@ -0,0 +1,148 @@
//===----------------------------------------------------------------------===//
// 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 IP address that can be either IPv4 or IPv6.
@frozen
public enum IPAddress: Sendable, Hashable, CustomStringConvertible, Equatable {
/// An IPv4 address
case v4(IPv4Address)
/// An IPv6 address
case v6(IPv6Address)
/// Parses an IP address string, automatically detecting IPv4 or IPv6 format.
///
/// - Parameter string: IP address string to parse
/// - Returns: An `IPAddress` containing either an IPv4 or IPv6 address
/// - Throws: `AddressError.unableToParse` if invalid
public init(_ string: String) throws {
let utf8 = string.utf8
var hasColon = false
var hasDot = false
for byte in utf8 {
if byte == 58 { // ASCII ':'
hasColon = true
break
}
if byte == 46 { // ASCII '.'
hasDot = true
}
}
if hasColon {
let ipv6 = try IPv6Address.parse(string)
self = .v6(ipv6)
} else if hasDot {
let ipv4 = try IPv4Address(string)
self = .v4(ipv4)
} else {
throw AddressError.unableToParse
}
}
/// String representation of the IP address.
public var description: String {
switch self {
case .v4(let addr):
return addr.description
case .v6(let addr):
return addr.description
}
}
/// Returns `true` if this is an IPv4 address.
@inlinable
public var isV4: Bool {
if case .v4 = self {
return true
}
return false
}
/// Returns `true` if this is an IPv6 address.
@inlinable
public var isV6: Bool {
if case .v6 = self {
return true
}
return false
}
/// Returns the underlying IPv4 address if this is an IPv4 address, otherwise `nil`.
@inlinable
public var ipv4: IPv4Address? {
if case .v4(let addr) = self {
return addr
}
return nil
}
/// Returns the underlying IPv6 address if this is an IPv6 address, otherwise `nil`.
@inlinable
public var ipv6: IPv6Address? {
if case .v6(let addr) = self {
return addr
}
return nil
}
/// Returns `true` if this is a loopback address (127.0.0.0/8 or ::1).
@inlinable
public var isLoopback: Bool {
switch self {
case .v4(let addr):
return addr.isLoopback
case .v6(let addr):
return addr.isLoopback
}
}
/// Returns `true` if this is a multicast address.
@inlinable
public var isMulticast: Bool {
switch self {
case .v4(let addr):
return addr.isMulticast
case .v6(let addr):
return addr.isMulticast
}
}
/// Returns `true` if this is an unspecified address (0.0.0.0 or ::).
@inlinable
public var isUnspecified: Bool {
switch self {
case .v4(let addr):
return addr.isUnspecified
case .v6(let addr):
return addr.isUnspecified
}
}
}
extension IPAddress: 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)
}
}
@@ -0,0 +1,250 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
@frozen
public struct IPv4Address: Sendable, Hashable, CustomStringConvertible, Equatable, Comparable {
public let value: UInt32
/// Creates an IPv4Address from an unsigned integer.
///
/// - Parameter string: The integer representation of the address.
@inlinable
public init(_ value: UInt32) {
self.value = value
}
/// Creates an IPv4Address from 4 bytes.
///
/// - Parameters:
/// - bytes: 4-byte array in network byte order representing the IPv4 address
/// - Throws: `AddressError.unableToParse` if the byte array length is not 4
@inlinable
public init(_ bytes: [UInt8]) throws {
guard bytes.count == 4 else {
throw AddressError.unableToParse
}
self.value =
(UInt32(bytes[0]) << 24)
| (UInt32(bytes[1]) << 16)
| (UInt32(bytes[2]) << 8)
| UInt32(bytes[3])
}
/// Creates an IPv4Address from a string representation.
///
/// - Parameter string: The IPv4 address string in dotted decimal notation (e.g., "192.168.1.1")
/// - Throws: `AddressError.unableToParse` if the string is not a valid IPv4 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: UInt32) -> [UInt8] {
var result = [UInt8](repeating: 0, count: 4)
result[0] = UInt8((value >> 24) & 0xff)
result[1] = UInt8((value >> 16) & 0xff)
result[2] = UInt8((value >> 8) & 0xff)
result[3] = UInt8(value & 0xff)
return result
}
// TODO: spans?
@available(macOS 26.0, *)
@usableFromInline
static func bytes(_ value: UInt32) -> InlineArray<4, UInt8> {
let result: InlineArray<4, UInt8> = [
UInt8((value >> 24) & 0xff),
UInt8((value >> 16) & 0xff),
UInt8((value >> 8) & 0xff),
UInt8(value & 0xff),
]
return result
}
@inlinable
public var description: String {
"\(bytes[0]).\(bytes[1]).\(bytes[2]).\(bytes[3])"
}
/// Parses an IPv4 address string in dotted decimal notation into a UInt32 representation.
///
/// ## Validation Rules
/// - Exactly 4 octets separated by dots
/// - Each octet must be 0-255
/// - No leading zeros (except for "0" itself)
/// - No whitespace characters
/// - Only digits and dots allowed
///
/// ## Examples
/// ```swift
/// IPv4Address.parse("192.168.1.1") // Returns: 3232235777
/// IPv4Address.parse("127.0.0.1") // Returns: 2130706433
/// IPv4Address.parse("0.0.0.0") // Returns: 0
/// IPv4Address.parse("255.255.255.255") // Returns: 4294967295
///
/// // Invalid examples:
/// IPv4Address.parse("192.168.1") // Wrong number of octets
/// IPv4Address.parse("192.168.1.256") // Octet out of range
/// IPv4Address.parse("192.168.001.1") // Leading zeros
/// IPv4Address.parse(" 192.168.1.1 ") // Whitespace
/// ```
///
/// - Parameter s: The IPv4 address string to parse
/// - Returns: The 32-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 -> UInt32 {
guard !s.isEmpty, s.count >= 7, s.count <= 15 else {
throw AddressError.unableToParse
}
// IP addresses should only contain ASCII 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 32bit representation of the IPv4 address
var result: UInt32 = 0
// tracking octet count, max 4 allowed
var octetCount = 0
var currentOctet = 0
// number of digits in the string representation of the octet, max 3
var digitCount = 0
for byte in utf8 {
if byte == 46 { // ASCII '.'
// Validate octet before processing
guard octetCount < 3, digitCount > 0, digitCount <= 3, currentOctet <= 255 else {
throw AddressError.unableToParse
}
// Shift result and add current octet
result = (result << 8) | UInt32(currentOctet)
// Reset for next octet
octetCount += 1
currentOctet = 0
digitCount = 0
} else if byte >= 48 && byte <= 57 { // ASCII '0'-'9'
let digit = Int(byte - 48)
digitCount += 1
// Check for invalid leading zeros: "01", "001", etc.
// Allow single "0" but reject multi-digit numbers starting with 0
if digitCount == 1 && digit == 0 {
// First digit is 0 - this is only valid if it's the only digit
currentOctet = 0
} else if digitCount > 1 && currentOctet == 0 {
// We had a leading zero and now have more digits - invalid
throw AddressError.unableToParse
} else {
// Normal case: build the octet value
currentOctet = currentOctet * 10 + digit
}
// Early termination if octet becomes too large
guard currentOctet <= 255, digitCount <= 3 else {
throw AddressError.unableToParse
}
} else {
throw AddressError.unableToParse
}
}
// Validate final octet
guard octetCount == 3, digitCount > 0, digitCount <= 3, currentOctet <= 255 else {
throw AddressError.unableToParse
}
return (result << 8) | UInt32(currentOctet)
}
// MARK: - Address Classification Methods
/// Returns `true` if this is the unspecified address (0.0.0.0).
///
/// Per RFC 791, 0.0.0.0 is the "this network" address.
@inlinable
public var isUnspecified: Bool {
value == 0
}
/// Returns `true` if this is a loopback address (127.0.0.0/8).
///
/// Per RFC 1122 Section 3.2.1.3, the entire 127.0.0.0/8 block is reserved for loopback.
@inlinable
public var isLoopback: Bool {
(value & 0xFF00_0000) == 0x7F00_0000
}
/// Returns `true` if this is a multicast address (224.0.0.0/4).
///
/// Per RFC 1112, addresses in the range 224.0.0.0 to 239.255.255.255 are multicast addresses.
@inlinable
public var isMulticast: Bool {
(value & 0xF000_0000) == 0xE000_0000
}
/// Returns `true` if this is a link-local address (169.254.0.0/16).
///
/// Per RFC 3927, 169.254.0.0/16 is reserved for link-local addresses (APIPA/Auto-IP).
@inlinable
public var isLinkLocal: Bool {
(value & 0xFFFF_0000) == 0xA9FE_0000
}
/// Returns `true` if this is the limited broadcast address (255.255.255.255).
///
/// Per RFC 919/922, 255.255.255.255 is the limited broadcast address.
@inlinable
public var isBroadcast: Bool {
value == 0xFFFF_FFFF
}
/// Compares two IPv4 addresses numerically.
@inlinable
public static func < (lhs: IPv4Address, rhs: IPv4Address) -> Bool {
lhs.value < rhs.value
}
}
extension IPv4Address: 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)
}
}
@@ -0,0 +1,333 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
extension IPv6Address {
/// Parses an IPv6 address string into an IPv6Address instance.
///
/// Follows RFC 4291 and RFC 5952.
///
/// This function supports standard IPv6 notation including:
/// - Full addresses: `2001:0db8:0000:0042:0000:8a2e:0370:7334`
/// - Zero compression: `2001:db8::8a2e:370:7334`
/// - Leading zero omission: `2001:db8:0:42:0:8a2e:370:7334`
/// - Unspecified address: `::`
/// - Zone identifiers: `fe80::1%eth0`
///
/// - Parameter input: IPv6 address string (with optional zone identifier after %)
/// - Returns: An `IPv6Address` instance representing the parsed address
///
/// ## Example Usage
/// ```swift
/// let addr1 = try IPv6Address.parse("2001:db8::1")
/// let addr2 = try IPv6Address.parse("::") // Unspecified address
/// let addr3 = try IPv6Address.parse("fe80::1%eth0") // With zone identifier
/// ```
static func parse(_ input: String) throws -> IPv6Address {
var ipBytes = [UInt8](repeating: 0, count: 16)
var ellipsisPosition: Int?
// Extract zone identifier
let (address, zone) = try extractZoneIdentifier(from: input)
// RFC 4291 Section 2.2.3: IPv4 suffix must be at the end (last 32 bits)
var remainingAddress = address
var ipv6ByteLimit = 16 // Maximum bytes available for IPv6 hex groups
var hasIPv4Suffix = false
// check if the IPv6 address has IPv4 address in it.
if let (ipv6Part, ipv4Bytes) = try extractIPv4Suffix(from: address) {
// If IPv4 present, save directly to last 4 bytes.
ipBytes[12] = ipv4Bytes[0]
ipBytes[13] = ipv4Bytes[1]
ipBytes[14] = ipv4Bytes[2]
ipBytes[15] = ipv4Bytes[3]
// Update address and limit IPv6 parsing to first 12 bytes (6 groups max)
remainingAddress = ipv6Part
ipv6ByteLimit = 12
hasIPv4Suffix = true
}
// Handle leading ellipsis in the IPv6 part
if remainingAddress.utf8.starts(with: [58, 58]) { // "::"
ellipsisPosition = 0
remainingAddress = String(remainingAddress.dropFirst(2))
// Special case: "::" represents the unspecified address (all zeros)
// But if we have IPv4 suffix, the IPv4 bytes are already set correctly
if remainingAddress.isEmpty {
// If we have IPv4 suffix, ipBytes already has the IPv4 data, just return
if hasIPv4Suffix {
return try Self(ipBytes, zone: zone)
}
// Pure "::" - Return the unspecified address, handling zone identifiers
if let zone = zone, !zone.isEmpty {
return try Self(ipBytes, zone: zone)
}
return .unspecified
}
}
// Parse IPv6 hex groups up to the byte limit
var byteIndex = 0
let utf8 = remainingAddress.utf8
var currentPosition = utf8.startIndex
while byteIndex < ipv6ByteLimit && currentPosition < utf8.endIndex {
let (hexValue, nextPosition) = try parseHexadecimal(
from: utf8,
startingAt: currentPosition
)
// Store the UInt16 in network-byte order
ipBytes[byteIndex] = UInt8(hexValue >> 8)
ipBytes[byteIndex + 1] = UInt8(hexValue & 0xFF)
byteIndex += 2
currentPosition = nextPosition
// Terminate early if we have consumed the whole string
if currentPosition == utf8.endIndex {
break
}
// Parse separator and handle ellipsis detection
currentPosition = try skipColonSeparator(
from: utf8,
at: currentPosition,
currentByteIndex: byteIndex,
ellipsisPosition: &ellipsisPosition
)
}
// Validate complete consumption of input
guard currentPosition >= utf8.endIndex else {
throw AddressError.malformedAddress
}
// Apply ellipsis expansion for the IPv6 portion
try expandEllipsis(
in: &ipBytes,
parsedBytes: byteIndex,
ellipsisPosition: ellipsisPosition,
byteLimit: ipv6ByteLimit
)
let value = ipBytes.reduce(UInt128(0)) { ($0 << 8) | UInt128($1) }
return Self(value, zone: zone)
}
// MARK: - Helper Functions
/// Extracts IPv4 suffix if present at the end of the address
///
/// Follows: RFC 4291 Section 2.2.3: Alternative form x:x:x:x:x:x:d.d.d.d
/// IPv4 must be the last 32 bits and preceded by a colon
///
/// - Parameter input: The IPv6 address string to check
/// - Returns: Optional tuple of (IPv6 part without IPv4, IPv4 bytes array) if IPv4 found, nil otherwise
/// - Throws: `AddressError.invalidIPv4Suffix` for invalid IPv4 addresses
internal static func extractIPv4Suffix(from input: String) throws -> (String, [UInt8])? {
// must contain a dot to be IPv4
guard input.utf8.contains(46) else { // ASCII '.'
return nil
}
// IPv4 address must be present after last colon
guard let lastColonIndex = input.lastIndex(of: ":") else {
return nil
}
// TODO: maybe refactor for performance
let afterColon = input.index(after: lastColonIndex)
guard afterColon < input.endIndex else {
return nil
}
let possibleIPv4 = String(input[afterColon...])
guard let ipv4Value = try? IPv4Address.parse(possibleIPv4) else {
throw AddressError.invalidIPv4SuffixInIPv6Address
}
// Check if lastColonIndex is the second ':' of '::'. If so, ensure to include it.
let isDoubleColon = lastColonIndex > input.startIndex && input[input.index(before: lastColonIndex)] == ":"
let ipv6Part = isDoubleColon ? String(input[...lastColonIndex]) : String(input[..<lastColonIndex])
return (ipv6Part, IPv4Address.bytes(ipv4Value))
}
/// Extracts zone identifier
///
/// - Parameter input: The full IPv6 address string with potential zone identifier
/// - Returns: Tuple of (address part, optional zone identifier)
/// - Throws: `AddressError.invalidZoneIdentifier` for malformed zone identifiers
private static func extractZoneIdentifier(from input: String) throws -> (String, String?) {
guard let percentIndex = input.lastIndex(of: "%") else {
return (input, nil)
}
let zoneStartIndex = input.index(after: percentIndex)
guard zoneStartIndex < input.endIndex else {
throw AddressError.invalidZoneIdentifier
}
let addressPart = String(input[..<percentIndex])
let zoneIdentifier = String(input[zoneStartIndex...])
return (addressPart, zoneIdentifier)
}
/// Parses a hexadecimal group from an IPv6 address component.
///
/// - Parameters:
/// - utf8: The UTF-8 view to parse from
/// - startIndex: Starting position in the UTF-8 view
/// - Returns: Tuple of (parsed hex value as UInt16, next position after parsed digits)
/// - Throws: `AddressError.invalidHexGroup` if no valid hex digits are found
///
/// ## Example
/// ```swift
/// let utf8 = "2001:db8::1".utf8
/// let (value, nextPos) = try parseHexadecimal(from: utf8, startingAt: utf8.startIndex)
/// // value = 0x2001, nextPos points to ':'
/// ```
@inlinable
internal static func parseHexadecimal(
from group: String.UTF8View,
startingAt startIndex: String.UTF8View.Index
) throws -> (UInt16, String.UTF8View.Index) {
var accumulator: UInt16 = 0
var digitCount = 0
var currentIndex = startIndex
while currentIndex < group.endIndex && digitCount < 4 {
let byte = group[currentIndex]
// Fast hex digit parsing using ASCII values
let hexValue: UInt16
if byte >= 48 && byte <= 57 { // '0'-'9'
hexValue = UInt16(byte - 48)
} else if byte >= 65 && byte <= 70 { // 'A'-'F'
hexValue = UInt16(byte - 65 + 10)
} else if byte >= 97 && byte <= 102 { // 'a'-'f'
hexValue = UInt16(byte - 97 + 10)
} else {
break // Not a hex digit
}
accumulator = (accumulator << 4) + hexValue
digitCount += 1
currentIndex = group.index(after: currentIndex)
}
guard digitCount > 0 else {
// No hex digits found
throw AddressError.invalidHexGroup
}
return (accumulator, currentIndex)
}
/// Parses a colon separator between IPv6 groups and detects ellipsis notation (::).
///
/// - Parameters:
/// - utf8: The UTF-8 view being parsed
/// - position: Current position in the UTF-8 view (must point to a colon)
/// - currentByteIndex: Current byte index in the IP array
/// - ellipsisPosition: Inout parameter tracking ellipsis position
/// - Returns: Next position in the UTF-8 view after parsing separator
///
/// ## Example
/// ```swift
/// let utf8 = "2001:db8::1".utf8
/// var ellipsisPos: Int? = nil
/// // After parsing "2001", position points to first ':'
/// let nextPos = try skipColonSeparator(from: utf8, at: position,
/// currentByteIndex: 2,
/// ellipsisPosition: &ellipsisPos)
/// // For single colon: nextPos points to 'd' in 'db8'
/// // For double colon (::): ellipsisPos = 2, nextPos points to '1'
/// ```
private static func skipColonSeparator(
from group: String.UTF8View,
at position: String.UTF8View.Index,
currentByteIndex: Int,
ellipsisPosition: inout Int?
) throws -> String.UTF8View.Index {
// Expect colon separator
guard group[position] == 58 else { // ASCII ':'
throw AddressError.malformedAddress
}
let afterFirstColon = group.index(after: position)
guard afterFirstColon < group.endIndex else {
// Trailing colon not allowed
throw AddressError.malformedAddress
}
// Check for double colon, return position after that
if group[afterFirstColon] == 58 { // ASCII ':'
guard ellipsisPosition == nil else {
// Multiple :: not allowed
throw AddressError.multipleEllipsis
}
ellipsisPosition = currentByteIndex
let afterSecondColon = group.index(after: afterFirstColon)
return afterSecondColon
}
return afterFirstColon
}
/// Expands ellipsis for IPv6 addresses
///
/// - Parameters:
/// - ipBytes: Inout array of IP bytes to modify
/// - parsedBytes: Number of bytes already parsed for IPv6 groups
/// - ellipsisPosition: Optional position where ellipsis was found
/// - byteLimit: Maximum bytes available for IPv6 (16 for pure IPv6, 12 if IPv4 suffix present)
/// - Throws: `AddressError.incompleteAddress` for invalid address lengths
private static func expandEllipsis(
in ipBytes: inout [UInt8],
parsedBytes: Int,
ellipsisPosition: Int?,
byteLimit: Int = 16
) throws {
guard let ellipsisPosition = ellipsisPosition else {
// No ellipsis - validate we have exactly filled the available bytes
guard parsedBytes == byteLimit else {
throw AddressError.incompleteAddress // Incomplete address without ellipsis
}
return
}
// Calculate expansion within the byte limit
let bytesToExpand = byteLimit - parsedBytes
guard bytesToExpand > 0 else {
throw AddressError.malformedAddress // No room for ellipsis expansion
}
let suffixBytes = Array(ipBytes[ellipsisPosition..<parsedBytes])
let targetStartIndex = byteLimit - suffixBytes.count
// Clear the expansion area using Swift's range-based assignment
for i in ellipsisPosition..<targetStartIndex {
ipBytes[i] = 0
}
// Place suffix at the end of the IPv6 section using Swift's collection assignment
for (offset, byte) in suffixBytes.enumerated() {
ipBytes[targetStartIndex + offset] = byte
}
}
}
@@ -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)
}
}
@@ -0,0 +1,127 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
import Collections
import Synchronization
/// Maps a network address to an array index value, or nil in the case of a domain error.
package typealias AddressToIndexTransform<AddressType> = @Sendable (AddressType) -> Int?
/// Maps an array index value to a network address, or nil in the case of a domain error.
package typealias IndexToAddressTransform<AddressType> = @Sendable (Int) -> AddressType?
package final class IndexedAddressAllocator<AddressType: CustomStringConvertible & Sendable>: AddressAllocator {
private class State {
var allocations: BitArray
var enabled: Bool
var allocationCount: Int
let addressToIndex: AddressToIndexTransform<AddressType>
let indexToAddress: IndexToAddressTransform<AddressType>
init(
size: Int,
addressToIndex: @escaping AddressToIndexTransform<AddressType>,
indexToAddress: @escaping IndexToAddressTransform<AddressType>
) {
self.allocations = BitArray.init(repeating: false, count: size)
self.enabled = true
self.allocationCount = 0
self.addressToIndex = addressToIndex
self.indexToAddress = indexToAddress
}
}
private let state: Mutex<State>
/// Create an allocator with specified size and index mappings.
package init(
size: Int,
addressToIndex: @escaping AddressToIndexTransform<AddressType>,
indexToAddress: @escaping IndexToAddressTransform<AddressType>
) {
let state = State(
size: size,
addressToIndex: addressToIndex,
indexToAddress: indexToAddress
)
self.state = Mutex(state)
}
public func allocate() throws -> AddressType {
try self.state.withLock { state in
guard state.enabled else {
throw AllocatorError.allocatorDisabled
}
guard let index = state.allocations.firstIndex(of: false) else {
throw AllocatorError.allocatorFull
}
guard let address = state.indexToAddress(index) else {
throw AllocatorError.invalidIndex(index)
}
state.allocations[index] = true
state.allocationCount += 1
return address
}
}
package func reserve(_ address: AddressType) throws {
try self.state.withLock { state in
guard state.enabled else {
throw AllocatorError.allocatorDisabled
}
guard let index = state.addressToIndex(address) else {
throw AllocatorError.invalidAddress(address.description)
}
guard !state.allocations[index] else {
throw AllocatorError.alreadyAllocated("\(address.description)")
}
state.allocations[index] = true
state.allocationCount += 1
}
}
package func release(_ address: AddressType) throws {
try self.state.withLock { state in
guard let index = state.addressToIndex(address) else {
throw AllocatorError.invalidAddress(address.description)
}
guard state.allocations[index] else {
throw AllocatorError.notAllocated("\(address.description)")
}
state.allocations[index] = false
state.allocationCount -= 1
}
}
package func disableAllocator() -> Bool {
self.state.withLock { state in
guard state.allocationCount == 0 else {
return false
}
state.enabled = false
return true
}
}
}
@@ -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)
}
}
@@ -0,0 +1,108 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
extension IPv4Address {
/// Creates an allocator for IPv4 addresses.
public static func allocator(lower: UInt32, size: Int) throws -> any AddressAllocator<IPv4Address> {
// NOTE: 2^31 - 1 size limit in the very improbable case that we run on 32-bit.
guard size > 0 && size < Int.max && 0xffff_ffff - lower >= size - 1 else {
throw AllocatorError.rangeExceeded
}
return IndexedAddressAllocator(
size: size,
addressToIndex: { address in
guard address.value >= lower && address.value - lower <= UInt32(size) else {
return nil
}
return Int(address.value - lower)
},
indexToAddress: { IPv4Address(lower + UInt32($0)) }
)
}
}
extension UInt16 {
/// Creates an allocator for TCP/UDP ports and other UInt16 values.
public static func allocator(lower: UInt16, size: Int) throws -> any AddressAllocator<UInt16> {
guard 0xffff - lower + 1 >= size else {
throw AllocatorError.rangeExceeded
}
return IndexedAddressAllocator(
size: size,
addressToIndex: { address in
guard address >= lower && address <= lower + UInt16(size) else {
return nil
}
return Int(address - lower)
},
indexToAddress: { lower + UInt16($0) }
)
}
}
extension UInt32 {
/// Creates an allocator for vsock ports, or any UInt32 values.
public static func allocator(lower: UInt32, size: Int) throws -> any AddressAllocator<UInt32> {
guard 0xffff_ffff - lower + 1 >= size else {
throw AllocatorError.rangeExceeded
}
return IndexedAddressAllocator(
size: size,
addressToIndex: { address in
guard address >= lower && address <= lower + UInt32(size) else {
return nil
}
return Int(address - lower)
},
indexToAddress: { lower + UInt32($0) }
)
}
/// Creates a rotating allocator for vsock ports, or any UInt32 values.
public static func rotatingAllocator(lower: UInt32, size: UInt32) throws -> any AddressAllocator<UInt32> {
guard 0xffff_ffff - lower + 1 >= size else {
throw AllocatorError.rangeExceeded
}
return RotatingAddressAllocator(
size: size,
addressToIndex: { address in
guard address >= lower && address <= lower + UInt32(size) else {
return nil
}
return Int(address - lower)
},
indexToAddress: { lower + UInt32($0) }
)
}
}
extension Character {
private static let deviceLetters = Array("abcdefghijklmnopqrstuvwxyz")
/// Creates an allocator for block device tags, or any character values.
public static func blockDeviceTagAllocator() -> any AddressAllocator<Character> {
IndexedAddressAllocator(
size: Self.deviceLetters.count,
addressToIndex: { address in
Self.deviceLetters.firstIndex(of: address)
},
indexToAddress: { Self.deviceLetters[$0] }
)
}
}
@@ -0,0 +1,57 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
/// A network interface's addresses.
public struct InterfaceAddress: Sendable, Hashable {
public var ipv4Address: CIDRv4
public var ipv6Address: CIDRv6?
public init(ipv4Address: CIDRv4, ipv6Address: CIDRv6? = nil) {
self.ipv4Address = ipv4Address
self.ipv6Address = ipv6Address
}
}
/// A link-scoped route a destination directly reachable on an interface.
public struct LinkRoute: Sendable, Hashable {
public var ipv4Destination: IPv4Address?
public var ipv4Source: IPv4Address?
public var ipv6Destination: IPv6Address?
public var ipv6Source: IPv6Address?
public init(
ipv4Destination: IPv4Address? = nil,
ipv4Source: IPv4Address? = nil,
ipv6Destination: IPv6Address? = nil,
ipv6Source: IPv6Address? = nil
) {
self.ipv4Destination = ipv4Destination
self.ipv4Source = ipv4Source
self.ipv6Destination = ipv6Destination
self.ipv6Source = ipv6Source
}
}
/// The default-route gateway for a network interface.
public struct DefaultRoute: Sendable, Hashable {
public var ipv4Gateway: IPv4Address?
public var ipv6Gateway: IPv6Address?
public init(ipv4Gateway: IPv4Address? = nil, ipv6Gateway: IPv6Address? = nil) {
self.ipv4Gateway = ipv4Gateway
self.ipv6Gateway = ipv6Gateway
}
}
@@ -0,0 +1,88 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
/// CIDR prefix length (e.g., `/24` for a 24-bit network mask).
@frozen
public struct Prefix: Sendable, CustomStringConvertible, Hashable, Codable {
public let length: UInt8
/// Create a prefix (0-128). Use `ipv4(_:)` or `ipv6(_:)` for version-specific validation.
public init?(length: UInt8) {
guard length <= 128 else { return nil }
self.length = length
}
/// Create an IPv4 prefix (0-32). Returns `nil` if length > 32.
public static func ipv4(_ length: UInt8) -> Prefix? {
guard length <= 32 else { return nil }
return Prefix(unchecked: length)
}
/// Create an IPv6 prefix (0-128). Returns `nil` if length > 128.
public static func ipv6(_ length: UInt8) -> Prefix? {
guard length <= 128 else { return nil }
return Prefix(unchecked: length)
}
/// Internal unchecked initializer for known-valid values.
internal init(unchecked length: UInt8) {
self.length = length
}
public var description: String {
"\(length)"
}
}
extension Prefix {
/// Computes a 32-bit mask for the suffix (host) portion of an IPv4 address.
///
/// Example: Prefix `/24` `0x0000_00FF` (255 host addresses)
@inlinable
public var suffixMask32: UInt32 {
if self.length <= 0 {
return 0xffff_ffff
}
return self.length >= 32 ? 0x0000_0000 : (1 << (32 - self.length)) - 1
}
/// Network portion mask (high-order bits) for IPv4.
///
/// Example: Prefix `/24` `0xFFFF_FF00` (255.255.255.0)
@inlinable
public var prefixMask32: UInt32 {
~self.suffixMask32
}
/// Computes a 128-bit mask for the suffix (host) portion of an IPv6 address.
///
/// Example: Prefix `/64` `0x0000_0000_0000_0000_FFFF_FFFF_FFFF_FFFF`
@inlinable
public var suffixMask128: UInt128 {
if self.length <= 0 {
return UInt128.max
}
return self.length >= 128 ? 0 : (1 << (128 - self.length)) - 1
}
/// Network portion mask (high-order bits) for IPv6.
///
/// Example: Prefix `/64` `0xFFFF_FFFF_FFFF_FFFF_0000_0000_0000_0000`
@inlinable
public var prefixMask128: UInt128 {
~self.suffixMask128
}
}
@@ -0,0 +1,51 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
/// A progress update event.
public enum ProgressEvent: Sendable {
/// The possible values:
/// - `add-items`: Increment the number of processed items by `value`.
/// - `add-total-items`: Increment the total number of items to process by `value`.
/// - `add-size`: Increment the size of processed items by `value`.
/// - `add-total-size`: Increment the total size of items to process by `value`.
case addItems(Int)
case addTotalItems(Int)
case addSize(Int64)
case addTotalSize(Int64)
/// The event name.
public var event: String {
switch self {
case .addItems: "add-items"
case .addTotalItems: "add-total-items"
case .addSize: "add-size"
case .addTotalSize: "add-total-size"
}
}
/// The event value.
public var value: any Sendable {
switch self {
case .addItems(let value): value
case .addTotalItems(let value): value
case .addSize(let value): value
case .addTotalSize(let value): value
}
}
}
/// The progress update handler.
public typealias ProgressHandler = @Sendable (_ events: [ProgressEvent]) async -> Void
@@ -0,0 +1,77 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
import ContainerizationError
import Foundation
/// A small utility to resolve proxy settings (HTTP(S)_PROXY / NO_PROXY).
public enum ProxyUtils {
/// Resolves the proxy URL for a given host based on environment variables.
/// Malformed http_proxy or https_proxy URLs are ignored.
/// Uses Go-style handling rules:
/// - Uppercase environment variables take priority over lowercase counterparts.
/// - Leading dot on no_proxy component implies prefix matching.
///
/// - Parameters:
/// - scheme: The request scheme.
/// - host: The request hostname.
/// - env: Environment variables to check, dafaulting to the process environment.
///
/// - Returns: The proxy URL to use, or `nil` for transparent connection.
public static func proxyFromEnvironment(
scheme: String?,
host: String,
env: [String: String] = ProcessInfo.processInfo.environment
) -> URL? {
guard let scheme else {
return nil
}
let httpProxy = env["HTTP_PROXY"] ?? env["http_proxy"]
let httpsProxy = env["HTTPS_PROXY"] ?? env["https_proxy"]
let noProxy = env["NO_PROXY"] ?? env["no_proxy"]
// If NO_PROXY matches skip proxy
if let noProxy, shouldBypassProxy(host: host, noProxy: noProxy) {
return nil
}
// Select proxy based on scheme, defaulting to http.
let proxy = scheme == "https" ? httpsProxy : httpProxy
guard let proxy, let proxyUrl = URL(string: proxy) else {
return nil
}
return proxyUrl
}
/// Check if a host should bypass proxy according to NO_PROXY.
/// - Example: NO_PROXY=".example.com,localhost,127.0.0.1"
private static func shouldBypassProxy(host: String, noProxy: String) -> Bool {
let entries = noProxy.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) }
for entry in entries {
if entry.isEmpty { continue }
if entry == "*" { return true }
if host == entry { return true }
if entry.hasPrefix("*.") {
let suffix = String(entry.dropFirst())
if host.hasSuffix(suffix) { return true }
}
if entry.hasPrefix(".") && host.hasSuffix(entry) { return true }
}
return false
}
}
@@ -0,0 +1,124 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
import Synchronization
package final class RotatingAddressAllocator: AddressAllocator {
package typealias AddressType = UInt32
private struct State {
var allocations: [AddressType]
var enabled: Bool
var allocationCount: Int
let addressToIndex: AddressToIndexTransform<AddressType>
let indexToAddress: IndexToAddressTransform<AddressType>
init(
size: UInt32,
addressToIndex: @escaping AddressToIndexTransform<AddressType>,
indexToAddress: @escaping IndexToAddressTransform<AddressType>
) {
self.allocations = [UInt32](0..<size)
self.enabled = true
self.allocationCount = 0
self.addressToIndex = addressToIndex
self.indexToAddress = indexToAddress
}
}
private let state: Mutex<State>
/// Create an allocator with specified size and index mappings.
package init(
size: UInt32,
addressToIndex: @escaping AddressToIndexTransform<AddressType>,
indexToAddress: @escaping IndexToAddressTransform<AddressType>
) {
let state = State(
size: size,
addressToIndex: addressToIndex,
indexToAddress: indexToAddress
)
self.state = Mutex(state)
}
public func allocate() throws -> AddressType {
try self.state.withLock { state in
guard state.enabled else {
throw AllocatorError.allocatorDisabled
}
guard state.allocations.count > 0 else {
throw AllocatorError.allocatorFull
}
let value = state.allocations.removeFirst()
guard let address = state.indexToAddress(Int(value)) else {
throw AllocatorError.invalidIndex(Int(value))
}
state.allocationCount += 1
return address
}
}
package func reserve(_ address: AddressType) throws {
try self.state.withLock { state in
guard state.enabled else {
throw AllocatorError.allocatorDisabled
}
guard let index = state.addressToIndex(address) else {
throw AllocatorError.invalidAddress(address.description)
}
let i = state.allocations.firstIndex(of: UInt32(index))
guard let i else {
throw AllocatorError.alreadyAllocated("\(address.description)")
}
_ = state.allocations.remove(at: i)
state.allocationCount += 1
}
}
package func release(_ address: AddressType) throws {
try self.state.withLock { state in
guard let index = (state.addressToIndex(address)) else {
throw AllocatorError.invalidAddress(address.description)
}
let value = UInt32(index)
guard !state.allocations.contains(value) else {
throw AllocatorError.notAllocated("\(address.description)")
}
state.allocations.append(value)
state.allocationCount -= 1
}
}
package func disableAllocator() -> Bool {
self.state.withLock { state in
guard state.allocationCount == 0 else {
return false
}
state.enabled = false
return true
}
}
}
@@ -0,0 +1,39 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
import Foundation
import NIO
import NIOSSL
public enum TLSUtils {
public static func makeEnvironmentAwareTLSConfiguration() -> TLSConfiguration {
var tlsConfig = TLSConfiguration.makeClientConfiguration()
// Check standard SSL environment variables in priority order
let customCAPath =
ProcessInfo.processInfo.environment["SSL_CERT_FILE"]
?? ProcessInfo.processInfo.environment["CURL_CA_BUNDLE"]
?? ProcessInfo.processInfo.environment["REQUESTS_CA_BUNDLE"]
if let caPath = customCAPath {
tlsConfig.trustRoots = .file(caPath)
}
// else: use .default
return tlsConfig
}
}
@@ -0,0 +1,67 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
/// `Timeout` contains helpers to run an operation and error out if
/// the operation does not finish within a provided time.
public struct Timeout {
/// Performs the passed in `operation` and throws a `CancellationError` if the operation
/// doesn't finish in the provided `seconds` amount.
public static func run<T: Sendable>(
seconds: UInt32,
operation: @escaping @Sendable () async throws -> T
) async throws -> T {
try await withThrowingTaskGroup(of: T.self) { group in
group.addTask {
try await operation()
}
group.addTask {
try await Task.sleep(for: .seconds(seconds))
throw CancellationError()
}
guard let result = try await group.next() else {
fatalError()
}
group.cancelAll()
return result
}
}
public static func run<T: Sendable>(
for duration: Duration,
operation: @escaping @Sendable () async throws -> T
) async throws -> T {
try await withThrowingTaskGroup(of: T.self) { group in
group.addTask {
try await operation()
}
group.addTask {
try await Task.sleep(for: duration)
throw CancellationError()
}
guard let result = try await group.next() else {
fatalError()
}
group.cancelAll()
return result
}
}
}
@@ -0,0 +1,100 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
import Foundation
package enum BindError: Error, CustomStringConvertible {
case recvMarshalFailure(type: String, field: String)
case sendMarshalFailure(type: String, field: String)
package var description: String {
switch self {
case .recvMarshalFailure(let type, let field):
return "failed to unmarshal \(type).\(field)"
case .sendMarshalFailure(let type, let field):
return "failed to marshal \(type).\(field)"
}
}
}
package protocol Bindable: Sendable {
static var size: Int { get }
func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int
mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int
}
extension ArraySlice<UInt8> {
package func hexEncodedString() -> String {
self.map { String(format: "%02hhx", $0) }.joined()
}
}
extension [UInt8] {
package func hexEncodedString() -> String {
self.map { String(format: "%02hhx", $0) }.joined()
}
package mutating func bind<T>(as type: T.Type, offset: Int = 0, size: Int? = nil) -> UnsafeMutablePointer<T>? {
guard self.count >= (size ?? MemoryLayout<T>.size) + offset else {
return nil
}
return self.withUnsafeMutableBytes { $0.baseAddress?.advanced(by: offset).assumingMemoryBound(to: T.self) }
}
package mutating func copyIn<T>(as type: T.Type, value: T, offset: Int = 0, size: Int? = nil) -> Int? {
let size = size ?? MemoryLayout<T>.size
guard self.count >= size + offset else {
return nil
}
return self.withUnsafeMutableBytes {
$0.baseAddress?.advanced(by: offset).assumingMemoryBound(to: T.self).pointee = value
return offset + MemoryLayout<T>.size
}
}
package func copyOut<T>(as type: T.Type, offset: Int = 0, size: Int? = nil) -> (Int, T)? {
guard self.count >= (size ?? MemoryLayout<T>.size) + offset else {
return nil
}
return self.withUnsafeBytes {
guard let value = $0.baseAddress?.advanced(by: offset).assumingMemoryBound(to: T.self).pointee else {
return nil
}
return (offset + MemoryLayout<T>.size, value)
}
}
package mutating func copyIn(buffer: [UInt8], offset: Int = 0) -> Int? {
guard offset + buffer.count <= self.count else {
return nil
}
self[offset..<offset + buffer.count] = buffer[0..<buffer.count]
return offset + buffer.count
}
package func copyOut(buffer: inout [UInt8], offset: Int = 0) -> Int? {
guard offset + buffer.count <= self.count else {
return nil
}
buffer[0..<buffer.count] = self[offset..<offset + buffer.count]
return offset + buffer.count
}
}