From 24b666a2c205aa1205683e9c0aac6b91012db18b Mon Sep 17 00:00:00 2001 From: John Logan Date: Sat, 17 Jan 2026 20:13:43 -0800 Subject: [PATCH 1/3] Move Bindable to ContainerizationExtras, fix binding bugs. - Bindable is generally useful, so it makes sense to move it out of the netlink library. Removed Equatable conformance from Bindable as this isn't generally necessary (it's only used for unit tests in netlink). - Fix some offset checks in a couple data binding functions and improve the unit tests. --- .../UInt8+DataBinding.swift | 11 +- Sources/ContainerizationNetlink/Types.swift | 32 +- .../UInt8+DataBindingTest.swift | 375 +++++++++++++++++- 3 files changed, 392 insertions(+), 26 deletions(-) diff --git a/Sources/ContainerizationExtras/UInt8+DataBinding.swift b/Sources/ContainerizationExtras/UInt8+DataBinding.swift index ddc8a738..94b59829 100644 --- a/Sources/ContainerizationExtras/UInt8+DataBinding.swift +++ b/Sources/ContainerizationExtras/UInt8+DataBinding.swift @@ -14,6 +14,13 @@ // limitations under the License. //===----------------------------------------------------------------------===// +package protocol Bindable { + 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 { package func hexEncodedString() -> String { self.map { String(format: "%02hhx", $0) }.joined() @@ -35,7 +42,7 @@ extension [UInt8] { package mutating func copyIn(as type: T.Type, value: T, offset: Int = 0, size: Int? = nil) -> Int? { let size = size ?? MemoryLayout.size - guard self.count >= size - offset else { + guard self.count >= size + offset else { return nil } @@ -46,7 +53,7 @@ extension [UInt8] { } package mutating func copyOut(as type: T.Type, offset: Int = 0, size: Int? = nil) -> (Int, T)? { - guard self.count >= (size ?? MemoryLayout.size) - offset else { + guard self.count >= (size ?? MemoryLayout.size) + offset else { return nil } diff --git a/Sources/ContainerizationNetlink/Types.swift b/Sources/ContainerizationNetlink/Types.swift index 5d15c6d8..141b1d9c 100644 --- a/Sources/ContainerizationNetlink/Types.swift +++ b/Sources/ContainerizationNetlink/Types.swift @@ -135,13 +135,7 @@ struct RouteAttributeType { static let PREFSRC: UInt16 = 7 } -protocol Bindable: Equatable { - static var size: Int { get } - func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int - mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int -} - -struct SockaddrNetlink: Bindable { +struct SockaddrNetlink: Bindable, Equatable { static let size = 12 var family: UInt16 @@ -189,7 +183,7 @@ struct SockaddrNetlink: Bindable { } } -struct NetlinkMessageHeader: Bindable { +struct NetlinkMessageHeader: Bindable, Equatable { static let size = 16 var len: UInt32 @@ -262,7 +256,7 @@ struct NetlinkMessageHeader: Bindable { } } -struct InterfaceInfo: Bindable { +struct InterfaceInfo: Bindable, Equatable { static let size = 16 var family: UInt8 @@ -341,7 +335,7 @@ struct InterfaceInfo: Bindable { } } -struct AddressInfo: Bindable { +struct AddressInfo: Bindable, Equatable { static let size = 8 var family: UInt8 @@ -411,7 +405,7 @@ struct AddressInfo: Bindable { } } -struct RouteInfo: Bindable { +struct RouteInfo: Bindable, Equatable { static let size = 12 var family: UInt8 @@ -529,8 +523,8 @@ struct RouteInfo: Bindable { } /// A route information. -public struct RTAttribute: Bindable { - static let size = 4 +public struct RTAttribute: Bindable, Equatable { + package static let size = 4 public var len: UInt16 public var type: UInt16 @@ -541,7 +535,7 @@ public struct RTAttribute: Bindable { self.type = type } - func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + package func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let offset = buffer.copyIn(as: UInt16.self, value: len, offset: offset) else { throw NetlinkDataError.sendMarshalFailure } @@ -552,7 +546,7 @@ public struct RTAttribute: Bindable { return offset } - mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + package mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else { throw NetlinkDataError.sendMarshalFailure } @@ -612,8 +606,8 @@ public struct LinkResponse { } /// Network interface statistics (64-bit version) -public struct LinkStatistics64: Bindable { - static let size = 23 * 8 +public struct LinkStatistics64: Bindable, Equatable { + package static let size = 23 * 8 public var rxPackets: UInt64 public var txPackets: UInt64 @@ -665,7 +659,7 @@ public struct LinkStatistics64: Bindable { self.txCompressed = 0 } - func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + package func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let offset = buffer.copyIn(as: UInt64.self, value: rxPackets, offset: offset) else { throw NetlinkDataError.sendMarshalFailure } @@ -739,7 +733,7 @@ public struct LinkStatistics64: Bindable { return offset } - mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + package mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { throw NetlinkDataError.recvUnmarshalFailure } diff --git a/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift b/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift index e0ae53cb..7bd5c368 100644 --- a/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift +++ b/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift @@ -15,8 +15,51 @@ //===----------------------------------------------------------------------===// import Testing +@testable import ContainerizationExtras struct BufferTest { + // MARK: - hexEncodedString Tests + + @Test func testArrayHexEncodedStringEmpty() { + let buffer: [UInt8] = [] + #expect(buffer.hexEncodedString() == "") + } + + @Test func testArrayHexEncodedStringSingleByte() { + let buffer: [UInt8] = [0xFF] + #expect(buffer.hexEncodedString() == "ff") + } + + @Test func testArrayHexEncodedStringMultipleBytes() { + let buffer: [UInt8] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF] + #expect(buffer.hexEncodedString() == "0123456789abcdef") + } + + @Test func testArrayHexEncodedStringZeroes() { + let buffer: [UInt8] = [0x00, 0x00, 0x00] + #expect(buffer.hexEncodedString() == "000000") + } + + @Test func testArraySliceHexEncodedStringEmpty() { + let buffer: [UInt8] = [0x01, 0x02, 0x03] + let slice = buffer[0..<0] + #expect(slice.hexEncodedString() == "") + } + + @Test func testArraySliceHexEncodedStringSingleByte() { + let buffer: [UInt8] = [0x01, 0x02, 0x03] + let slice = buffer[1..<2] + #expect(slice.hexEncodedString() == "02") + } + + @Test func testArraySliceHexEncodedStringMultipleBytes() { + let buffer: [UInt8] = [0x00, 0xAA, 0xBB, 0xCC, 0x00] + let slice = buffer[1..<4] + #expect(slice.hexEncodedString() == "aabbcc") + } + + // MARK: - bind Tests + @Test func testBufferBind() throws { let expectedValue: UInt64 = 0x0102_0304_0506_0708 let expectedBuffer: [UInt8] = [ @@ -26,11 +69,6 @@ struct BufferTest { ] var buffer = [UInt8](repeating: 0, count: 3 * MemoryLayout.size) guard let ptr = buffer.bind(as: UInt64.self, offset: 2 * MemoryLayout.size) else { - // NOTE: This does not work: - // let ptr: UnsafeMutablePointer = #require(buffer.bind(as: UInt64.self, offset: MemoryLayout.size), "could not bind value to buffer") - // it fails with the error: - // cannot use mutating member on immutable value: '$0' is immutable - // $0.bind(as: $1, offset: $2) #expect(Bool(false), "could not bind value to buffer") return } @@ -39,11 +77,257 @@ struct BufferTest { #expect(buffer == expectedBuffer) } + @Test func testBufferBindZeroOffset() { + let expectedValue: UInt32 = 0x12345678 + var buffer = [UInt8](repeating: 0, count: 8) + guard let ptr = buffer.bind(as: UInt32.self, offset: 0) else { + #expect(Bool(false), "could not bind value to buffer at offset 0") + return + } + + ptr.pointee = expectedValue + #expect(buffer[0] == 0x78) + #expect(buffer[1] == 0x56) + #expect(buffer[2] == 0x34) + #expect(buffer[3] == 0x12) + } + @Test func testBufferBindRangeError() throws { var buffer = [UInt8](repeating: 0, count: 3 * MemoryLayout.size) #expect(buffer.bind(as: UInt64.self, offset: 2 * MemoryLayout.size + 1) == nil) } + @Test func testBufferBindRangeErrorExactBoundary() { + var buffer = [UInt8](repeating: 0, count: 8) + // Trying to bind UInt64 at offset 1 requires 9 bytes total + #expect(buffer.bind(as: UInt64.self, offset: 1) == nil) + } + + @Test func testBufferBindWithCustomSize() { + var buffer = [UInt8](repeating: 0, count: 16) + // Request a size larger than the type + guard let ptr = buffer.bind(as: UInt32.self, offset: 4, size: 8) else { + #expect(Bool(false), "could not bind with custom size") + return + } + + ptr.pointee = 0xAABBCCDD + #expect(buffer[4] == 0xDD) + #expect(buffer[5] == 0xCC) + #expect(buffer[6] == 0xBB) + #expect(buffer[7] == 0xAA) + } + + @Test func testBufferBindWithCustomSizeRangeError() { + var buffer = [UInt8](repeating: 0, count: 10) + // Request size 8 at offset 4 would require 12 bytes total + #expect(buffer.bind(as: UInt32.self, offset: 4, size: 8) == nil) + } + + // MARK: - copyIn Tests + + @Test func testCopyInUInt8() { + var buffer = [UInt8](repeating: 0, count: 4) + let value: UInt8 = 0x42 + + guard let offset = buffer.copyIn(as: UInt8.self, value: value, offset: 2) else { + #expect(Bool(false), "could not copy UInt8 to buffer") + return + } + + #expect(offset == 3) + #expect(buffer[2] == 0x42) + } + + @Test func testCopyInUInt16() { + var buffer = [UInt8](repeating: 0, count: 8) + let value: UInt16 = 0x1234 + + guard let offset = buffer.copyIn(as: UInt16.self, value: value, offset: 3) else { + #expect(Bool(false), "could not copy UInt16 to buffer") + return + } + + #expect(offset == 5) + #expect(buffer[3] == 0x34) + #expect(buffer[4] == 0x12) + } + + @Test func testCopyInUInt32() { + var buffer = [UInt8](repeating: 0, count: 8) + let value: UInt32 = 0x12345678 + + guard let offset = buffer.copyIn(as: UInt32.self, value: value, offset: 0) else { + #expect(Bool(false), "could not copy UInt32 to buffer") + return + } + + #expect(offset == 4) + #expect(buffer[0] == 0x78) + #expect(buffer[1] == 0x56) + #expect(buffer[2] == 0x34) + #expect(buffer[3] == 0x12) + } + + @Test func testCopyInUInt64() { + var buffer = [UInt8](repeating: 0, count: 16) + let value: UInt64 = 0x0102030405060708 + + guard let offset = buffer.copyIn(as: UInt64.self, value: value, offset: 4) else { + #expect(Bool(false), "could not copy UInt64 to buffer") + return + } + + #expect(offset == 12) + #expect(buffer[4] == 0x08) + #expect(buffer[5] == 0x07) + #expect(buffer[6] == 0x06) + #expect(buffer[7] == 0x05) + #expect(buffer[8] == 0x04) + #expect(buffer[9] == 0x03) + #expect(buffer[10] == 0x02) + #expect(buffer[11] == 0x01) + } + + @Test func testCopyInRangeError() { + var buffer = [UInt8](repeating: 0, count: 8) + let value: UInt64 = 0x1234567890ABCDEF + + // Offset 4 + size 8 = 12, but buffer only has 8 bytes + #expect(buffer.copyIn(as: UInt64.self, value: value, offset: 4) == nil) + } + + @Test func testCopyInExactBoundary() { + var buffer = [UInt8](repeating: 0, count: 8) + let value: UInt64 = 0xFEDCBA9876543210 + + guard let offset = buffer.copyIn(as: UInt64.self, value: value, offset: 0) else { + #expect(Bool(false), "could not copy UInt64 at exact boundary") + return + } + + #expect(offset == 8) + } + + @Test func testCopyInWithCustomSize() { + var buffer = [UInt8](repeating: 0, count: 16) + let value: UInt32 = 0xAABBCCDD + + // Copy with custom size of 8 (larger than UInt32's 4 bytes) + guard let offset = buffer.copyIn(as: UInt32.self, value: value, offset: 2, size: 8) else { + #expect(Bool(false), "could not copy with custom size") + return + } + + #expect(offset == 6) // offset + MemoryLayout.size + #expect(buffer[2] == 0xDD) + #expect(buffer[3] == 0xCC) + #expect(buffer[4] == 0xBB) + #expect(buffer[5] == 0xAA) + } + + @Test func testCopyInWithCustomSizeRangeError() { + var buffer = [UInt8](repeating: 0, count: 8) + let value: UInt32 = 0x12345678 + + // Request size 8 at offset 2 would require 10 bytes total + #expect(buffer.copyIn(as: UInt32.self, value: value, offset: 2, size: 8) == nil) + } + + // MARK: - copyOut Tests + + @Test func testCopyOutUInt8() { + var buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33] + + guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: 2) else { + #expect(Bool(false), "could not copy out UInt8") + return + } + + #expect(offset == 3) + #expect(value == 0x22) + } + + @Test func testCopyOutUInt16() { + var buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55] + + guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: 2) else { + #expect(Bool(false), "could not copy out UInt16") + return + } + + #expect(offset == 4) + #expect(value == 0x3322) + } + + @Test func testCopyOutUInt32() { + var buffer: [UInt8] = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] + + guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: 0) else { + #expect(Bool(false), "could not copy out UInt32") + return + } + + #expect(offset == 4) + #expect(value == 0x78563412) + } + + @Test func testCopyOutUInt64() { + var buffer: [UInt8] = [ + 0x00, 0x00, 0x00, 0x00, + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0xFF, 0xFF + ] + + guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: 4) else { + #expect(Bool(false), "could not copy out UInt64") + return + } + + #expect(offset == 12) + #expect(value == 0x8877665544332211) + } + + @Test func testCopyOutRangeError() { + var buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33] + + // Trying to read UInt64 from offset 0 with only 4 bytes + #expect(buffer.copyOut(as: UInt64.self, offset: 0) == nil) + } + + @Test func testCopyOutExactBoundary() { + var buffer: [UInt8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + + guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: 0) else { + #expect(Bool(false), "could not copy out at exact boundary") + return + } + + #expect(offset == 8) + #expect(value == 0x0807060504030201) + } + + @Test func testCopyOutWithCustomSize() { + var buffer: [UInt8] = [0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0xFF, 0xFF, 0xFF, 0xFF] + + guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: 2, size: 8) else { + #expect(Bool(false), "could not copy out with custom size") + return + } + + #expect(offset == 6) // offset + MemoryLayout.size + #expect(value == 0x44332211) + } + + @Test func testCopyOutWithCustomSizeRangeError() { + var buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55] + + // Request size 8 at offset 2 would require 10 bytes total, but buffer only has 6 + #expect(buffer.copyOut(as: UInt32.self, offset: 2, size: 8) == nil) + } + + // MARK: - copyIn(buffer:) and copyOut(buffer:) Tests + @Test func testBufferCopy() throws { let inputBuffer: [UInt8] = [0x01, 0x02, 0x03] var buffer = [UInt8](repeating: 0, count: 9) @@ -78,6 +362,45 @@ struct BufferTest { #expect(expectedOutputBuffer == outputBuffer) } + @Test func testBufferCopyZeroOffset() { + let inputBuffer: [UInt8] = [0xAA, 0xBB, 0xCC] + var buffer = [UInt8](repeating: 0, count: 5) + + guard let offset = buffer.copyIn(buffer: inputBuffer, offset: 0) else { + #expect(Bool(false), "could not copy to buffer at offset 0") + return + } + + #expect(offset == 3) + #expect(buffer[0] == 0xAA) + #expect(buffer[1] == 0xBB) + #expect(buffer[2] == 0xCC) + } + + @Test func testBufferCopyEmptyBuffer() { + let inputBuffer: [UInt8] = [] + var buffer = [UInt8](repeating: 0, count: 5) + + guard let offset = buffer.copyIn(buffer: inputBuffer, offset: 2) else { + #expect(Bool(false), "could not copy empty buffer") + return + } + + #expect(offset == 2) + } + + @Test func testBufferCopyExactFit() { + let inputBuffer: [UInt8] = [0x01, 0x02, 0x03] + var buffer = [UInt8](repeating: 0, count: 6) + + guard let offset = buffer.copyIn(buffer: inputBuffer, offset: 3) else { + #expect(Bool(false), "could not copy to exact fit") + return + } + + #expect(offset == 6) + } + @Test func testBufferCopyRangeError() throws { let inputBuffer: [UInt8] = [0x01, 0x02, 0x03] var buffer = [UInt8](repeating: 0, count: 9) @@ -87,4 +410,46 @@ struct BufferTest { var outputBuffer = [UInt8](repeating: 0, count: 3) #expect(buffer.copyOut(buffer: &outputBuffer, offset: 7) == nil) } + + @Test func testBufferCopyOutZeroOffset() { + var buffer: [UInt8] = [0x11, 0x22, 0x33, 0x44, 0x55] + var outputBuffer = [UInt8](repeating: 0, count: 3) + + guard let offset = buffer.copyOut(buffer: &outputBuffer, offset: 0) else { + #expect(Bool(false), "could not copy out at offset 0") + return + } + + #expect(offset == 3) + #expect(outputBuffer[0] == 0x11) + #expect(outputBuffer[1] == 0x22) + #expect(outputBuffer[2] == 0x33) + } + + @Test func testBufferCopyOutEmptyBuffer() { + var buffer: [UInt8] = [0x11, 0x22, 0x33] + var outputBuffer: [UInt8] = [] + + guard let offset = buffer.copyOut(buffer: &outputBuffer, offset: 1) else { + #expect(Bool(false), "could not copy out to empty buffer") + return + } + + #expect(offset == 1) + } + + @Test func testBufferCopyOutExactFit() { + var buffer: [UInt8] = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] + var outputBuffer = [UInt8](repeating: 0, count: 3) + + guard let offset = buffer.copyOut(buffer: &outputBuffer, offset: 3) else { + #expect(Bool(false), "could not copy out exact fit") + return + } + + #expect(offset == 6) + #expect(outputBuffer[0] == 0xDD) + #expect(outputBuffer[1] == 0xEE) + #expect(outputBuffer[2] == 0xFF) + } } From f160b6dc3dc7f8e97c4a47debb1021b513697661 Mon Sep 17 00:00:00 2001 From: John Logan Date: Mon, 19 Jan 2026 01:05:18 -0800 Subject: [PATCH 2/3] IPAddress enhancements, bind errors, fix SockaddrNetlink. - Adds IPv4Address.init(bytes:). - Adds MACAddress.init(bytes:). - IPv6Address.init(bytes:) throws if not 16 bytes. - Moves BindError to ContainerizationExtras and adds type/field associated data. - Adds associated data to BindError throws in ContainerizationNetlink. - Fix missing pad binding in SockaddrNetlink. --- .../ContainerizationExtras/IPv4Address.swift | 19 ++ .../IPv6Address+Parse.swift | 4 +- .../ContainerizationExtras/IPv6Address.swift | 9 +- .../ContainerizationExtras/MACAddress.swift | 22 +- .../UInt8+DataBinding.swift | 25 +- .../NetlinkSession.swift | 24 +- Sources/ContainerizationNetlink/Types.swift | 230 +++++++++--------- .../TestMACAddress.swift | 5 +- .../UInt8+DataBindingTest.swift | 22 +- .../TypesTest.swift | 110 +++++++++ 10 files changed, 319 insertions(+), 151 deletions(-) diff --git a/Sources/ContainerizationExtras/IPv4Address.swift b/Sources/ContainerizationExtras/IPv4Address.swift index 38e9ac18..b41e7e27 100644 --- a/Sources/ContainerizationExtras/IPv4Address.swift +++ b/Sources/ContainerizationExtras/IPv4Address.swift @@ -18,11 +18,30 @@ 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]) << 16) + | 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") diff --git a/Sources/ContainerizationExtras/IPv6Address+Parse.swift b/Sources/ContainerizationExtras/IPv6Address+Parse.swift index a701fbb8..f6793f7c 100644 --- a/Sources/ContainerizationExtras/IPv6Address+Parse.swift +++ b/Sources/ContainerizationExtras/IPv6Address+Parse.swift @@ -71,12 +71,12 @@ extension IPv6Address { if remainingAddress.isEmpty { // If we have IPv4 suffix, ipBytes already has the IPv4 data, just return if hasIPv4Suffix { - return Self(ipBytes, zone: zone) + return try Self(ipBytes, zone: zone) } // Pure "::" - Return the unspecified address, handling zone identifiers if let zone = zone, !zone.isEmpty { - return Self(ipBytes, zone: zone) + return try Self(ipBytes, zone: zone) } return .unspecified } diff --git a/Sources/ContainerizationExtras/IPv6Address.swift b/Sources/ContainerizationExtras/IPv6Address.swift index e759ba6c..09159cac 100644 --- a/Sources/ContainerizationExtras/IPv6Address.swift +++ b/Sources/ContainerizationExtras/IPv6Address.swift @@ -33,11 +33,14 @@ public struct IPv6Address: Sendable, Hashable, CustomStringConvertible, Equatabl /// Creates an IPv6Address from 16 bytes. /// /// - Parameters: - /// - bytes: 16-byte array representing the IPv6 address + /// - 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) { - precondition(bytes.count == 16, "IPv6 address must be exactly 16 bytes") + 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 = diff --git a/Sources/ContainerizationExtras/MACAddress.swift b/Sources/ContainerizationExtras/MACAddress.swift index cd538fbc..20f6c5be 100644 --- a/Sources/ContainerizationExtras/MACAddress.swift +++ b/Sources/ContainerizationExtras/MACAddress.swift @@ -30,6 +30,24 @@ public struct MACAddress: Sendable, Hashable, CustomStringConvertible, Equatable 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. @@ -225,9 +243,9 @@ public struct MACAddress: Sendable, Hashable, CustomStringConvertible, Equatable /// - 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) -> IPv6Address { + public func ipv6Address(network: IPv6Address) throws -> IPv6Address { let prefixBytes = network.bytes - return IPv6Address([ + 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, diff --git a/Sources/ContainerizationExtras/UInt8+DataBinding.swift b/Sources/ContainerizationExtras/UInt8+DataBinding.swift index 94b59829..40abedd5 100644 --- a/Sources/ContainerizationExtras/UInt8+DataBinding.swift +++ b/Sources/ContainerizationExtras/UInt8+DataBinding.swift @@ -14,13 +14,28 @@ // limitations under the License. //===----------------------------------------------------------------------===// -package protocol Bindable { +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 { package func hexEncodedString() -> String { self.map { String(format: "%02hhx", $0) }.joined() @@ -52,12 +67,12 @@ extension [UInt8] { } } - package mutating func copyOut(as type: T.Type, offset: Int = 0, size: Int? = nil) -> (Int, T)? { + package func copyOut(as type: T.Type, offset: Int = 0, size: Int? = nil) -> (Int, T)? { guard self.count >= (size ?? MemoryLayout.size) + offset else { return nil } - return self.withUnsafeMutableBytes { + return self.withUnsafeBytes { guard let value = $0.baseAddress?.advanced(by: offset).assumingMemoryBound(to: T.self).pointee else { return nil } @@ -74,7 +89,7 @@ extension [UInt8] { return offset + buffer.count } - package mutating func copyOut(buffer: inout [UInt8], offset: Int = 0) -> Int? { + package func copyOut(buffer: inout [UInt8], offset: Int = 0) -> Int? { guard offset + buffer.count <= self.count else { return nil } diff --git a/Sources/ContainerizationNetlink/NetlinkSession.swift b/Sources/ContainerizationNetlink/NetlinkSession.swift index bf04d8b4..6a4cfb90 100644 --- a/Sources/ContainerizationNetlink/NetlinkSession.swift +++ b/Sources/ContainerizationNetlink/NetlinkSession.swift @@ -102,7 +102,7 @@ public struct NetlinkSession { let newRequestOffset = requestBuffer.copyIn(as: UInt32.self, value: m, offset: requestOffset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFLA_MTU") } requestOffset = newRequestOffset } @@ -162,7 +162,7 @@ public struct NetlinkSession { value: filters, offset: requestOffset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFLA_EXT_MASK") } if let interfaceNameAttr { @@ -170,7 +170,7 @@ public struct NetlinkSession { requestOffset = try interfaceNameAttr.appendBuffer(&requestBuffer, offset: requestOffset) guard let updatedRequestOffset = requestBuffer.copyIn(buffer: interfaceName, offset: requestOffset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFLA_IFNAME") } requestOffset = updatedRequestOffset } @@ -239,13 +239,13 @@ public struct NetlinkSession { let ipLocalAttr = RTAttribute(len: UInt16(addressAttrSize), type: AddressAttributeType.IFA_LOCAL) requestOffset = try ipLocalAttr.appendBuffer(&requestBuffer, offset: requestOffset) guard var requestOffset = requestBuffer.copyIn(buffer: ipAddressBytes, offset: requestOffset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFA_LOCAL") } let ipAddressAttr = RTAttribute(len: UInt16(addressAttrSize), type: AddressAttributeType.IFA_ADDRESS) requestOffset = try ipAddressAttr.appendBuffer(&requestBuffer, offset: requestOffset) guard let requestOffset = requestBuffer.copyIn(buffer: ipAddressBytes, offset: requestOffset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFA_ADDRESS") } guard requestOffset == requestSize else { @@ -305,13 +305,13 @@ public struct NetlinkSession { let dstAddrAttr = RTAttribute(len: UInt16(dstAddrAttrSize), type: RouteAttributeType.DST) requestOffset = try dstAddrAttr.appendBuffer(&requestBuffer, offset: requestOffset) guard var requestOffset = requestBuffer.copyIn(buffer: dstAddrBytes, offset: requestOffset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_DST") } let srcAddrAttr = RTAttribute(len: UInt16(dstAddrAttrSize), type: RouteAttributeType.PREFSRC) requestOffset = try srcAddrAttr.appendBuffer(&requestBuffer, offset: requestOffset) guard var requestOffset = requestBuffer.copyIn(buffer: srcAddrBytes, offset: requestOffset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_PREFSRC") } let interfaceAttr = RTAttribute(len: UInt16(interfaceAttrSize), type: RouteAttributeType.OIF) @@ -322,7 +322,7 @@ public struct NetlinkSession { value: UInt32(interfaceIndex), offset: requestOffset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_OIF") } guard requestOffset == requestSize else { @@ -378,7 +378,7 @@ public struct NetlinkSession { let dstAddrAttr = RTAttribute(len: UInt16(dstAddrAttrSize), type: RouteAttributeType.GATEWAY) requestOffset = try dstAddrAttr.appendBuffer(&requestBuffer, offset: requestOffset) guard var requestOffset = requestBuffer.copyIn(buffer: dstAddrBytes, offset: requestOffset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_GATEWAY") } let interfaceAttr = RTAttribute(len: UInt16(interfaceAttrSize), type: RouteAttributeType.OIF) requestOffset = try interfaceAttr.appendBuffer(&requestBuffer, offset: requestOffset) @@ -388,7 +388,7 @@ public struct NetlinkSession { value: UInt32(interfaceIndex), offset: requestOffset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_OIF") } guard requestOffset == requestSize else { @@ -404,7 +404,7 @@ public struct NetlinkSession { private func getInterfaceName(_ interface: String) throws -> [UInt8] { guard let interfaceNameData = interface.data(using: .utf8) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "String", field: "interface") } var interfaceName = [UInt8](interfaceNameData) @@ -503,7 +503,7 @@ public struct NetlinkSession { private func parseErrorCode(buffer: inout [UInt8], offset: Int) throws -> (Int32, Int) { guard let errorPtr = buffer.bind(as: Int32.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "NetlinkErrorMessage", field: "error") } let rc = errorPtr.pointee diff --git a/Sources/ContainerizationNetlink/Types.swift b/Sources/ContainerizationNetlink/Types.swift index 141b1d9c..5faa4024 100644 --- a/Sources/ContainerizationNetlink/Types.swift +++ b/Sources/ContainerizationNetlink/Types.swift @@ -139,7 +139,7 @@ struct SockaddrNetlink: Bindable, Equatable { static let size = 12 var family: UInt16 - var pad: UInt16 = 0 + var _pad: UInt16 = 0 var pid: UInt32 var groups: UInt32 @@ -151,13 +151,16 @@ struct SockaddrNetlink: Bindable, Equatable { func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let offset = buffer.copyIn(as: UInt16.self, value: family, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "SockaddrNetlink", field: "family") + } + guard let offset = buffer.copyIn(as: UInt16.self, value: 0, offset: offset) else { + throw BindError.sendMarshalFailure(type: "SockaddrNetlink", field: "_pad") } guard let offset = buffer.copyIn(as: UInt32.self, value: pid, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "SockaddrNetlink", field: "pid") } guard let offset = buffer.copyIn(as: UInt32.self, value: groups, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "SockaddrNetlink", field: "groups") } return offset @@ -165,21 +168,26 @@ struct SockaddrNetlink: Bindable, Equatable { mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "SockaddrNetlink", field: "family") } family = value + guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "SockaddrNetlink", field: "_pad") + } + _pad = value + guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "SockaddrNetlink", field: "pid") } pid = value guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "SockaddrNetlink", field: "groups") } groups = value - return offset + Self.size + return offset } } @@ -202,19 +210,19 @@ struct NetlinkMessageHeader: Bindable, Equatable { func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let offset = buffer.copyIn(as: UInt32.self, value: len, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "NetlinkMessageHeader", field: "len") } guard let offset = buffer.copyIn(as: UInt16.self, value: type, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "NetlinkMessageHeader", field: "type") } guard let offset = buffer.copyIn(as: UInt16.self, value: flags, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "NetlinkMessageHeader", field: "flags") } guard let offset = buffer.copyIn(as: UInt32.self, value: seq, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "NetlinkMessageHeader", field: "seq") } guard let offset = buffer.copyIn(as: UInt32.self, value: pid, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "NetlinkMessageHeader", field: "pid") } return offset @@ -222,27 +230,27 @@ struct NetlinkMessageHeader: Bindable, Equatable { mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "NetlinkMessageHeader", field: "len") } len = value guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "NetlinkMessageHeader", field: "type") } type = value guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "NetlinkMessageHeader", field: "flags") } flags = value guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "NetlinkMessageHeader", field: "seq") } seq = value guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "NetlinkMessageHeader", field: "pid") } pid = value @@ -279,22 +287,22 @@ struct InterfaceInfo: Bindable, Equatable { func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let offset = buffer.copyIn(as: UInt8.self, value: family, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "family") } guard let offset = buffer.copyIn(as: UInt8.self, value: _pad, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "_pad") } guard let offset = buffer.copyIn(as: UInt16.self, value: type, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "type") } guard let offset = buffer.copyIn(as: Int32.self, value: index, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "index") } guard let offset = buffer.copyIn(as: UInt32.self, value: flags, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "flags") } guard let offset = buffer.copyIn(as: UInt32.self, value: change, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "change") } return offset @@ -302,32 +310,32 @@ struct InterfaceInfo: Bindable, Equatable { mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "family") } family = value guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "_pad") } _pad = value guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "type") } type = value guard let (offset, value) = buffer.copyOut(as: Int32.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "index") } index = value guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "flags") } flags = value guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "change") } change = value @@ -357,19 +365,19 @@ struct AddressInfo: Bindable, Equatable { func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let offset = buffer.copyIn(as: UInt8.self, value: family, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "AddressInfo", field: "family") } guard let offset = buffer.copyIn(as: UInt8.self, value: prefixLength, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "AddressInfo", field: "prefixLength") } guard let offset = buffer.copyIn(as: UInt8.self, value: flags, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "AddressInfo", field: "flags") } guard let offset = buffer.copyIn(as: UInt8.self, value: scope, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "AddressInfo", field: "scope") } guard let offset = buffer.copyIn(as: UInt32.self, value: index, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "AddressInfo", field: "index") } return offset @@ -377,27 +385,27 @@ struct AddressInfo: Bindable, Equatable { mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "AddressInfo", field: "family") } family = value guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "AddressInfo", field: "prefixLength") } prefixLength = value guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "AddressInfo", field: "flags") } flags = value guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "AddressInfo", field: "scope") } scope = value guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "AddressInfo", field: "index") } index = value @@ -442,31 +450,31 @@ struct RouteInfo: Bindable, Equatable { func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let offset = buffer.copyIn(as: UInt8.self, value: family, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RouteInfo", field: "family") } guard let offset = buffer.copyIn(as: UInt8.self, value: dstLen, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RouteInfo", field: "dstLen") } guard let offset = buffer.copyIn(as: UInt8.self, value: srcLen, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RouteInfo", field: "srcLen") } guard let offset = buffer.copyIn(as: UInt8.self, value: tos, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RouteInfo", field: "tos") } guard let offset = buffer.copyIn(as: UInt8.self, value: table, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RouteInfo", field: "table") } guard let offset = buffer.copyIn(as: UInt8.self, value: proto, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RouteInfo", field: "proto") } guard let offset = buffer.copyIn(as: UInt8.self, value: scope, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RouteInfo", field: "scope") } guard let offset = buffer.copyIn(as: UInt8.self, value: type, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RouteInfo", field: "type") } guard let offset = buffer.copyIn(as: UInt32.self, value: flags, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RouteInfo", field: "flags") } return offset @@ -474,47 +482,47 @@ struct RouteInfo: Bindable, Equatable { mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "RouteInfo", field: "family") } family = value guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "RouteInfo", field: "dstLen") } dstLen = value guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "RouteInfo", field: "srcLen") } srcLen = value guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "RouteInfo", field: "tos") } tos = value guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "RouteInfo", field: "table") } table = value guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "RouteInfo", field: "proto") } proto = value guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "RouteInfo", field: "scope") } scope = value guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "RouteInfo", field: "type") } type = value guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "RouteInfo", field: "flags") } flags = value @@ -537,10 +545,10 @@ public struct RTAttribute: Bindable, Equatable { package func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let offset = buffer.copyIn(as: UInt16.self, value: len, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "len") } guard let offset = buffer.copyIn(as: UInt16.self, value: type, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "RTAttribute", field: "type") } return offset @@ -548,12 +556,12 @@ public struct RTAttribute: Bindable, Equatable { package mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "RTAttribute", field: "len") } len = value guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.recvMarshalFailure(type: "RTAttribute", field: "type") } type = value @@ -661,73 +669,73 @@ public struct LinkStatistics64: Bindable, Equatable { package func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let offset = buffer.copyIn(as: UInt64.self, value: rxPackets, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxPackets") } guard let offset = buffer.copyIn(as: UInt64.self, value: txPackets, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txPackets") } guard let offset = buffer.copyIn(as: UInt64.self, value: rxBytes, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxBytes") } guard let offset = buffer.copyIn(as: UInt64.self, value: txBytes, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txBytes") } guard let offset = buffer.copyIn(as: UInt64.self, value: rxErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: txErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: rxDropped, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxDropped") } guard let offset = buffer.copyIn(as: UInt64.self, value: txDropped, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txDropped") } guard let offset = buffer.copyIn(as: UInt64.self, value: multicast, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "multicast") } guard let offset = buffer.copyIn(as: UInt64.self, value: collisions, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "collisions") } guard let offset = buffer.copyIn(as: UInt64.self, value: rxLengthErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxLengthErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: rxOverErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxOverErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: rxCrcErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxCrcErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: rxFrameErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxFrameErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: rxFifoErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxFifoErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: rxMissedErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxMissedErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: txAbortedErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txAbortedErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: txCarrierErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txCarrierErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: txFifoErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txFifoErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: txHeartbeatErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txHeartbeatErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: txWindowErrors, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txWindowErrors") } guard let offset = buffer.copyIn(as: UInt64.self, value: rxCompressed, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxCompressed") } guard let offset = buffer.copyIn(as: UInt64.self, value: txCompressed, offset: offset) else { - throw NetlinkDataError.sendMarshalFailure + throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txCompressed") } return offset @@ -735,117 +743,117 @@ public struct LinkStatistics64: Bindable, Equatable { package mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxPackets") } rxPackets = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txPackets") } txPackets = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxBytes") } rxBytes = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txBytes") } txBytes = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxErrors") } rxErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txErrors") } txErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxDropped") } rxDropped = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txDropped") } txDropped = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "multicast") } multicast = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "collisions") } collisions = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxLengthErrors") } rxLengthErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxOverErrors") } rxOverErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxCrcErrors") } rxCrcErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxFrameErrors") } rxFrameErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxFifoErrors") } rxFifoErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxMissedErrors") } rxMissedErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txAbortedErrors") } txAbortedErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txCarrierErrors") } txCarrierErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txFifoErrors") } txFifoErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txHeartbeatErrors") } txHeartbeatErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txWindowErrors") } txWindowErrors = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxCompressed") } rxCompressed = value guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { - throw NetlinkDataError.recvUnmarshalFailure + throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txCompressed") } txCompressed = value @@ -855,18 +863,12 @@ public struct LinkStatistics64: Bindable, Equatable { /// Errors thrown when parsing netlink data. public enum NetlinkDataError: Swift.Error, CustomStringConvertible, Equatable { - case sendMarshalFailure - case recvUnmarshalFailure case responseError(rc: Int32) case unsupportedPlatform /// The description of the errors. public var description: String { switch self { - case .sendMarshalFailure: - return "could not marshal netlink packet" - case .recvUnmarshalFailure: - return "could not unmarshal netlink packet" case .responseError(let rc): return "netlink response indicates error, rc = \(rc)" case .unsupportedPlatform: diff --git a/Tests/ContainerizationExtrasTests/TestMACAddress.swift b/Tests/ContainerizationExtrasTests/TestMACAddress.swift index aaee901e..e8e99d77 100644 --- a/Tests/ContainerizationExtrasTests/TestMACAddress.swift +++ b/Tests/ContainerizationExtrasTests/TestMACAddress.swift @@ -182,10 +182,11 @@ struct MACAddressTests { (0x5e3b_68d7_e510, 0xfd97_7b15_d62e_75ac_5c3b_68ff_fed7_e510), ] ) - func testLinkLocalAddress(mac: UInt64, ipv6: UInt128) { + func testLinkLocalAddress(mac: UInt64, ipv6: UInt128) throws { let mac = MACAddress(mac) let ipv6Prefix = IPv6Address(ipv6 & 0xffff_ffff_ffff_ffff_0000_0000_0000_0000) - #expect(mac.ipv6Address(network: ipv6Prefix) == IPv6Address(ipv6)) + let ipv6Address = try mac.ipv6Address(network: ipv6Prefix) + #expect(ipv6Address == IPv6Address(ipv6)) } } diff --git a/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift b/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift index 7bd5c368..539cf9e1 100644 --- a/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift +++ b/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift @@ -237,7 +237,7 @@ struct BufferTest { // MARK: - copyOut Tests @Test func testCopyOutUInt8() { - var buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33] + let buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33] guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: 2) else { #expect(Bool(false), "could not copy out UInt8") @@ -249,7 +249,7 @@ struct BufferTest { } @Test func testCopyOutUInt16() { - var buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55] + let buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55] guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: 2) else { #expect(Bool(false), "could not copy out UInt16") @@ -261,7 +261,7 @@ struct BufferTest { } @Test func testCopyOutUInt32() { - var buffer: [UInt8] = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] + let buffer: [UInt8] = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: 0) else { #expect(Bool(false), "could not copy out UInt32") @@ -273,7 +273,7 @@ struct BufferTest { } @Test func testCopyOutUInt64() { - var buffer: [UInt8] = [ + let buffer: [UInt8] = [ 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0xFF, 0xFF @@ -289,14 +289,14 @@ struct BufferTest { } @Test func testCopyOutRangeError() { - var buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33] + let buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33] // Trying to read UInt64 from offset 0 with only 4 bytes #expect(buffer.copyOut(as: UInt64.self, offset: 0) == nil) } @Test func testCopyOutExactBoundary() { - var buffer: [UInt8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + let buffer: [UInt8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: 0) else { #expect(Bool(false), "could not copy out at exact boundary") @@ -308,7 +308,7 @@ struct BufferTest { } @Test func testCopyOutWithCustomSize() { - var buffer: [UInt8] = [0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0xFF, 0xFF, 0xFF, 0xFF] + let buffer: [UInt8] = [0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0xFF, 0xFF, 0xFF, 0xFF] guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: 2, size: 8) else { #expect(Bool(false), "could not copy out with custom size") @@ -320,7 +320,7 @@ struct BufferTest { } @Test func testCopyOutWithCustomSizeRangeError() { - var buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55] + let buffer: [UInt8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55] // Request size 8 at offset 2 would require 10 bytes total, but buffer only has 6 #expect(buffer.copyOut(as: UInt32.self, offset: 2, size: 8) == nil) @@ -412,7 +412,7 @@ struct BufferTest { } @Test func testBufferCopyOutZeroOffset() { - var buffer: [UInt8] = [0x11, 0x22, 0x33, 0x44, 0x55] + let buffer: [UInt8] = [0x11, 0x22, 0x33, 0x44, 0x55] var outputBuffer = [UInt8](repeating: 0, count: 3) guard let offset = buffer.copyOut(buffer: &outputBuffer, offset: 0) else { @@ -427,7 +427,7 @@ struct BufferTest { } @Test func testBufferCopyOutEmptyBuffer() { - var buffer: [UInt8] = [0x11, 0x22, 0x33] + let buffer: [UInt8] = [0x11, 0x22, 0x33] var outputBuffer: [UInt8] = [] guard let offset = buffer.copyOut(buffer: &outputBuffer, offset: 1) else { @@ -439,7 +439,7 @@ struct BufferTest { } @Test func testBufferCopyOutExactFit() { - var buffer: [UInt8] = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] + let buffer: [UInt8] = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF] var outputBuffer = [UInt8](repeating: 0, count: 3) guard let offset = buffer.copyOut(buffer: &outputBuffer, offset: 3) else { diff --git a/Tests/ContainerizationNetlinkTests/TypesTest.swift b/Tests/ContainerizationNetlinkTests/TypesTest.swift index 5ad41fa9..1ca9ef95 100644 --- a/Tests/ContainerizationNetlinkTests/TypesTest.swift +++ b/Tests/ContainerizationNetlinkTests/TypesTest.swift @@ -108,4 +108,114 @@ struct TypesTest { #expect(offset == RTAttribute.size) #expect(expectedValue == value) } + + @Test func testSockaddrNetlink() throws { + let expectedValue = SockaddrNetlink(family: 16, pid: 0x1234_5678, groups: 0x9abc_def0) + let expectedBuffer: [UInt8] = [ + 0x10, 0x00, 0x00, 0x00, + 0x78, 0x56, 0x34, 0x12, + 0xf0, 0xde, 0xbc, 0x9a, + ] + var buffer = [UInt8](repeating: 0, count: SockaddrNetlink.size) + let offset = try expectedValue.appendBuffer(&buffer, offset: 0) + #expect(SockaddrNetlink.size == offset) + #expect(expectedBuffer == buffer) + + var unmarshaledValue = SockaddrNetlink() + let bindOffset = try unmarshaledValue.bindBuffer(&buffer, offset: 0) + #expect(bindOffset == SockaddrNetlink.size) + #expect(expectedValue == unmarshaledValue) + } + + @Test func testRouteInfo() throws { + let expectedValue = RouteInfo( + family: UInt8(AddressFamily.AF_INET), + dstLen: 24, + srcLen: 0, + tos: 0, + table: RouteTable.MAIN, + proto: RouteProtocol.KERNEL, + scope: RouteScope.LINK, + type: RouteType.UNICAST, + flags: 0xdead_beef + ) + let expectedBuffer: [UInt8] = [ + 0x02, 0x18, 0x00, 0x00, + 0xfe, 0x02, 0xfd, 0x01, + 0xef, 0xbe, 0xad, 0xde, + ] + var buffer = [UInt8](repeating: 0, count: RouteInfo.size) + let offset = try expectedValue.appendBuffer(&buffer, offset: 0) + #expect(RouteInfo.size == offset) + #expect(expectedBuffer == buffer) + + var unmarshaledValue = RouteInfo( + dstLen: 0, srcLen: 0, tos: 0, table: 0, proto: 0, scope: 0, type: 0, flags: 0) + let bindOffset = try unmarshaledValue.bindBuffer(&buffer, offset: 0) + #expect(bindOffset == RouteInfo.size) + #expect(expectedValue == unmarshaledValue) + } + + @Test func testLinkStatistics64() throws { + var expectedValue = LinkStatistics64() + expectedValue.rxPackets = 0x0102_0304_0506_0708 + expectedValue.txPackets = 0x090a_0b0c_0d0e_0f10 + expectedValue.rxBytes = 0x1112_1314_1516_1718 + expectedValue.txBytes = 0x191a_1b1c_1d1e_1f20 + expectedValue.rxErrors = 0x2122_2324_2526_2728 + expectedValue.txErrors = 0x292a_2b2c_2d2e_2f30 + expectedValue.rxDropped = 0x3132_3334_3536_3738 + expectedValue.txDropped = 0x393a_3b3c_3d3e_3f40 + expectedValue.multicast = 0x4142_4344_4546_4748 + expectedValue.collisions = 0x494a_4b4c_4d4e_4f50 + expectedValue.rxLengthErrors = 0x5152_5354_5556_5758 + expectedValue.rxOverErrors = 0x595a_5b5c_5d5e_5f60 + expectedValue.rxCrcErrors = 0x6162_6364_6566_6768 + expectedValue.rxFrameErrors = 0x696a_6b6c_6d6e_6f70 + expectedValue.rxFifoErrors = 0x7172_7374_7576_7778 + expectedValue.rxMissedErrors = 0x797a_7b7c_7d7e_7f80 + expectedValue.txAbortedErrors = 0x8182_8384_8586_8788 + expectedValue.txCarrierErrors = 0x898a_8b8c_8d8e_8f90 + expectedValue.txFifoErrors = 0x9192_9394_9596_9798 + expectedValue.txHeartbeatErrors = 0x999a_9b9c_9d9e_9fa0 + expectedValue.txWindowErrors = 0xa1a2_a3a4_a5a6_a7a8 + expectedValue.rxCompressed = 0xa9aa_abac_adae_afb0 + expectedValue.txCompressed = 0xb1b2_b3b4_b5b6_b7b8 + + let expectedBuffer: [UInt8] = [ + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, + 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, + 0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, + 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, + 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, + 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, + 0x40, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, + 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, + 0x50, 0x4f, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, + 0x58, 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, + 0x60, 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, + 0x68, 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, + 0x70, 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, + 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, + 0x80, 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, + 0x88, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, + 0x90, 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, + 0x98, 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, + 0xa0, 0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, 0x99, + 0xa8, 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, + 0xb0, 0xaf, 0xae, 0xad, 0xac, 0xab, 0xaa, 0xa9, + 0xb8, 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, + ] + + var buffer = [UInt8](repeating: 0, count: LinkStatistics64.size) + let offset = try expectedValue.appendBuffer(&buffer, offset: 0) + #expect(LinkStatistics64.size == offset) + #expect(expectedBuffer == buffer) + + var unmarshaledValue = LinkStatistics64() + let bindOffset = try unmarshaledValue.bindBuffer(&buffer, offset: 0) + #expect(bindOffset == LinkStatistics64.size) + #expect(expectedValue == unmarshaledValue) + } } From 891333c3ad8c5e4bc1c35b812ea79ace7e9036d3 Mon Sep 17 00:00:00 2001 From: John Logan Date: Tue, 20 Jan 2026 11:07:39 -0800 Subject: [PATCH 3/3] Fix formatting. --- .../ContainerizationExtras/IPv4Address.swift | 3 ++- .../ContainerizationExtras/MACAddress.swift | 3 ++- .../UInt8+DataBindingTest.swift | 27 ++++++++++--------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Sources/ContainerizationExtras/IPv4Address.swift b/Sources/ContainerizationExtras/IPv4Address.swift index b41e7e27..1aea7a01 100644 --- a/Sources/ContainerizationExtras/IPv4Address.swift +++ b/Sources/ContainerizationExtras/IPv4Address.swift @@ -36,7 +36,8 @@ public struct IPv4Address: Sendable, Hashable, CustomStringConvertible, Equatabl guard bytes.count == 4 else { throw AddressError.unableToParse } - self.value = (UInt32(bytes[0]) << 24) + self.value = + (UInt32(bytes[0]) << 24) | (UInt32(bytes[1]) << 16) | (UInt32(bytes[2]) << 16) | UInt32(bytes[3]) diff --git a/Sources/ContainerizationExtras/MACAddress.swift b/Sources/ContainerizationExtras/MACAddress.swift index 20f6c5be..ea29270b 100644 --- a/Sources/ContainerizationExtras/MACAddress.swift +++ b/Sources/ContainerizationExtras/MACAddress.swift @@ -40,7 +40,8 @@ public struct MACAddress: Sendable, Hashable, CustomStringConvertible, Equatable guard bytes.count == 6 else { throw AddressError.unableToParse } - self.value = (UInt64(bytes[0]) << 40) + self.value = + (UInt64(bytes[0]) << 40) | (UInt64(bytes[1]) << 32) | (UInt64(bytes[2]) << 24) | (UInt64(bytes[3]) << 16) diff --git a/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift b/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift index 539cf9e1..fabc7bf0 100644 --- a/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift +++ b/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift @@ -15,6 +15,7 @@ //===----------------------------------------------------------------------===// import Testing + @testable import ContainerizationExtras struct BufferTest { @@ -78,7 +79,7 @@ struct BufferTest { } @Test func testBufferBindZeroOffset() { - let expectedValue: UInt32 = 0x12345678 + let expectedValue: UInt32 = 0x1234_5678 var buffer = [UInt8](repeating: 0, count: 8) guard let ptr = buffer.bind(as: UInt32.self, offset: 0) else { #expect(Bool(false), "could not bind value to buffer at offset 0") @@ -111,7 +112,7 @@ struct BufferTest { return } - ptr.pointee = 0xAABBCCDD + ptr.pointee = 0xAABB_CCDD #expect(buffer[4] == 0xDD) #expect(buffer[5] == 0xCC) #expect(buffer[6] == 0xBB) @@ -155,7 +156,7 @@ struct BufferTest { @Test func testCopyInUInt32() { var buffer = [UInt8](repeating: 0, count: 8) - let value: UInt32 = 0x12345678 + let value: UInt32 = 0x1234_5678 guard let offset = buffer.copyIn(as: UInt32.self, value: value, offset: 0) else { #expect(Bool(false), "could not copy UInt32 to buffer") @@ -171,7 +172,7 @@ struct BufferTest { @Test func testCopyInUInt64() { var buffer = [UInt8](repeating: 0, count: 16) - let value: UInt64 = 0x0102030405060708 + let value: UInt64 = 0x0102_0304_0506_0708 guard let offset = buffer.copyIn(as: UInt64.self, value: value, offset: 4) else { #expect(Bool(false), "could not copy UInt64 to buffer") @@ -191,7 +192,7 @@ struct BufferTest { @Test func testCopyInRangeError() { var buffer = [UInt8](repeating: 0, count: 8) - let value: UInt64 = 0x1234567890ABCDEF + let value: UInt64 = 0x1234_5678_90AB_CDEF // Offset 4 + size 8 = 12, but buffer only has 8 bytes #expect(buffer.copyIn(as: UInt64.self, value: value, offset: 4) == nil) @@ -199,7 +200,7 @@ struct BufferTest { @Test func testCopyInExactBoundary() { var buffer = [UInt8](repeating: 0, count: 8) - let value: UInt64 = 0xFEDCBA9876543210 + let value: UInt64 = 0xFEDC_BA98_7654_3210 guard let offset = buffer.copyIn(as: UInt64.self, value: value, offset: 0) else { #expect(Bool(false), "could not copy UInt64 at exact boundary") @@ -211,7 +212,7 @@ struct BufferTest { @Test func testCopyInWithCustomSize() { var buffer = [UInt8](repeating: 0, count: 16) - let value: UInt32 = 0xAABBCCDD + let value: UInt32 = 0xAABB_CCDD // Copy with custom size of 8 (larger than UInt32's 4 bytes) guard let offset = buffer.copyIn(as: UInt32.self, value: value, offset: 2, size: 8) else { @@ -228,7 +229,7 @@ struct BufferTest { @Test func testCopyInWithCustomSizeRangeError() { var buffer = [UInt8](repeating: 0, count: 8) - let value: UInt32 = 0x12345678 + let value: UInt32 = 0x1234_5678 // Request size 8 at offset 2 would require 10 bytes total #expect(buffer.copyIn(as: UInt32.self, value: value, offset: 2, size: 8) == nil) @@ -269,14 +270,14 @@ struct BufferTest { } #expect(offset == 4) - #expect(value == 0x78563412) + #expect(value == 0x7856_3412) } @Test func testCopyOutUInt64() { let buffer: [UInt8] = [ 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0xFF, 0xFF + 0xFF, 0xFF, ] guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: 4) else { @@ -285,7 +286,7 @@ struct BufferTest { } #expect(offset == 12) - #expect(value == 0x8877665544332211) + #expect(value == 0x8877_6655_4433_2211) } @Test func testCopyOutRangeError() { @@ -304,7 +305,7 @@ struct BufferTest { } #expect(offset == 8) - #expect(value == 0x0807060504030201) + #expect(value == 0x0807_0605_0403_0201) } @Test func testCopyOutWithCustomSize() { @@ -316,7 +317,7 @@ struct BufferTest { } #expect(offset == 6) // offset + MemoryLayout.size - #expect(value == 0x44332211) + #expect(value == 0x4433_2211) } @Test func testCopyOutWithCustomSizeRangeError() {