diff --git a/Sources/ContainerizationExtras/IPv4Address.swift b/Sources/ContainerizationExtras/IPv4Address.swift index 38e9ac18..1aea7a01 100644 --- a/Sources/ContainerizationExtras/IPv4Address.swift +++ b/Sources/ContainerizationExtras/IPv4Address.swift @@ -18,11 +18,31 @@ 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..ea29270b 100644 --- a/Sources/ContainerizationExtras/MACAddress.swift +++ b/Sources/ContainerizationExtras/MACAddress.swift @@ -30,6 +30,25 @@ 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 +244,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 ddc8a738..40abedd5 100644 --- a/Sources/ContainerizationExtras/UInt8+DataBinding.swift +++ b/Sources/ContainerizationExtras/UInt8+DataBinding.swift @@ -14,6 +14,28 @@ // 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 { package func hexEncodedString() -> String { self.map { String(format: "%02hhx", $0) }.joined() @@ -35,7 +57,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 } @@ -45,12 +67,12 @@ 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 { + 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 } @@ -67,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 5d15c6d8..5faa4024 100644 --- a/Sources/ContainerizationNetlink/Types.swift +++ b/Sources/ContainerizationNetlink/Types.swift @@ -135,17 +135,11 @@ 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 - var pad: UInt16 = 0 + var _pad: UInt16 = 0 var pid: UInt32 var groups: UInt32 @@ -157,13 +151,16 @@ struct SockaddrNetlink: Bindable { 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 @@ -171,25 +168,30 @@ struct SockaddrNetlink: Bindable { 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 } } -struct NetlinkMessageHeader: Bindable { +struct NetlinkMessageHeader: Bindable, Equatable { static let size = 16 var len: UInt32 @@ -208,19 +210,19 @@ struct NetlinkMessageHeader: Bindable { 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 @@ -228,27 +230,27 @@ struct NetlinkMessageHeader: Bindable { 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 @@ -262,7 +264,7 @@ struct NetlinkMessageHeader: Bindable { } } -struct InterfaceInfo: Bindable { +struct InterfaceInfo: Bindable, Equatable { static let size = 16 var family: UInt8 @@ -285,22 +287,22 @@ struct InterfaceInfo: Bindable { 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 @@ -308,32 +310,32 @@ struct InterfaceInfo: Bindable { 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 @@ -341,7 +343,7 @@ struct InterfaceInfo: Bindable { } } -struct AddressInfo: Bindable { +struct AddressInfo: Bindable, Equatable { static let size = 8 var family: UInt8 @@ -363,19 +365,19 @@ struct AddressInfo: Bindable { 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 @@ -383,27 +385,27 @@ struct AddressInfo: Bindable { 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 @@ -411,7 +413,7 @@ struct AddressInfo: Bindable { } } -struct RouteInfo: Bindable { +struct RouteInfo: Bindable, Equatable { static let size = 12 var family: UInt8 @@ -448,31 +450,31 @@ struct RouteInfo: Bindable { 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 @@ -480,47 +482,47 @@ struct RouteInfo: Bindable { 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 @@ -529,8 +531,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,25 +543,25 @@ 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 + 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 } - 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 + 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 @@ -612,8 +614,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,193 +667,193 @@ 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 + 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 } - 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 + 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 @@ -861,18 +863,12 @@ public struct LinkStatistics64: Bindable { /// 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 e0ae53cb..fabc7bf0 100644 --- a/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift +++ b/Tests/ContainerizationExtrasTests/UInt8+DataBindingTest.swift @@ -16,7 +16,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 +70,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 +78,257 @@ struct BufferTest { #expect(buffer == expectedBuffer) } + @Test func testBufferBindZeroOffset() { + 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") + 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 = 0xAABB_CCDD + #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 = 0x1234_5678 + + 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 = 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") + 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 = 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) + } + + @Test func testCopyInExactBoundary() { + var buffer = [UInt8](repeating: 0, count: 8) + 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") + return + } + + #expect(offset == 8) + } + + @Test func testCopyInWithCustomSize() { + var buffer = [UInt8](repeating: 0, count: 16) + 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 { + #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 = 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) + } + + // MARK: - copyOut Tests + + @Test func testCopyOutUInt8() { + 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") + return + } + + #expect(offset == 3) + #expect(value == 0x22) + } + + @Test func testCopyOutUInt16() { + 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") + return + } + + #expect(offset == 4) + #expect(value == 0x3322) + } + + @Test func testCopyOutUInt32() { + 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") + return + } + + #expect(offset == 4) + #expect(value == 0x7856_3412) + } + + @Test func testCopyOutUInt64() { + let 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 == 0x8877_6655_4433_2211) + } + + @Test func testCopyOutRangeError() { + 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() { + 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") + return + } + + #expect(offset == 8) + #expect(value == 0x0807_0605_0403_0201) + } + + @Test func testCopyOutWithCustomSize() { + 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") + return + } + + #expect(offset == 6) // offset + MemoryLayout.size + #expect(value == 0x4433_2211) + } + + @Test func testCopyOutWithCustomSizeRangeError() { + 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) + } + + // 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 +363,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 +411,46 @@ struct BufferTest { var outputBuffer = [UInt8](repeating: 0, count: 3) #expect(buffer.copyOut(buffer: &outputBuffer, offset: 7) == nil) } + + @Test func testBufferCopyOutZeroOffset() { + 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 { + #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() { + let 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() { + 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 { + #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) + } } 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) + } }