commit 78c2a37ae10eb72701b708c600d107481467cc52 Author: anlicheng <244108715@qq.com> Date: Mon Jul 14 15:33:40 2025 +0800 init project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..0821caa --- /dev/null +++ b/Package.resolved @@ -0,0 +1,51 @@ +{ + "originHash" : "c6ae4911d55046c288e0ecdc9de28a95dcb591d3daaa03914664a3d2e069977a", + "pins" : [ + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "ad6b5f17270a7008f60d35ec5378e6144a575162", + "version" : "2.84.0" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "102a647b573f60f73afdce5613a51d71349fe507", + "version" : "1.30.0" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "61e4ca4b81b9e09e2ec863b00c340eb13497dac6", + "version" : "1.5.0" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..a7ea93c --- /dev/null +++ b/Package.swift @@ -0,0 +1,38 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "sdlan", + platforms: [ + .iOS(.v17), + .macOS(.v10_13) + ], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "sdlan", + targets: ["sdlan"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-nio.git", from: "2.84.0"), + .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.26.0") + + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "sdlan", + dependencies: [ + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "SwiftProtobuf", package: "swift-protobuf") + ] + ), + .testTarget( + name: "sdlanTests", + dependencies: ["sdlan"] + ), + ] +) diff --git a/Sources/sdlan/ARPPacket.swift b/Sources/sdlan/ARPPacket.swift new file mode 100644 index 0000000..ac3e9f6 --- /dev/null +++ b/Sources/sdlan/ARPPacket.swift @@ -0,0 +1,113 @@ +// +// ARPPacket.swift +// Tun +// +// Created by 安礼成 on 2024/8/25. +// +import Foundation + +struct ARPPacket { + // ARP操作码 + enum Opcode: UInt16 { + case request = 0x01 + case response = 0x02 + + func isRequest() -> Bool { + return self == .request + } + + func isResponse() -> Bool { + return self == .response + } + } + + var hardwareType: UInt16 + var protocolType: UInt16 + var hardwareSize: UInt8 + var protocolSize: UInt8 + var opcode: Opcode + var senderMAC: Data + var senderIP: UInt32 + var targetMAC: Data + var targetIP: UInt32 + + init(hardwareType: UInt16, protocolType: UInt16, hardwareSize: UInt8, protocolSize: UInt8, opcode: Opcode, + senderMAC: Data, senderIP: UInt32, targetMAC: Data, targetIP: UInt32) { + + self.hardwareType = hardwareType + self.protocolType = protocolType + self.hardwareSize = hardwareSize + self.protocolSize = protocolSize + self.opcode = opcode + self.senderMAC = senderMAC + self.senderIP = senderIP + self.targetMAC = targetMAC + self.targetIP = targetIP + } + + init?(data: Data) { + guard data.count >= 28 else { + NSLog("length < 28: len: \(data.count)") + return nil + } + + self.hardwareType = UInt16(data[0]) << 8 | UInt16(data[1]) + self.protocolType = UInt16(data[2]) << 8 | UInt16(data[3]) + self.hardwareSize = data[4] + self.protocolSize = data[5] + guard let opcode = Opcode(rawValue: UInt16(data[6]) << 8 | UInt16(data[7])) else { + NSLog("opcode error") + return nil + } + + self.opcode = opcode + self.senderMAC = Data(data[8..<14]) + self.senderIP = UInt32(data: Data(data[14..<18])) + self.targetMAC = Data(data[18..<24]) + self.targetIP = UInt32(data: Data(data[24..<28])) + } + + func marshal() -> Data { + var data = Data() + data.append(self.hardwareType.data()) + data.append(self.protocolType.data()) + data.append(self.hardwareSize) + data.append(self.protocolSize) + data.append(self.opcode.rawValue.data()) + data.append(self.senderMAC) + data.append(Data(uint32: self.senderIP)) + data.append(self.targetMAC) + data.append(Data(uint32: self.targetIP)) + + return data + } + +} + +extension ARPPacket { + + static func arpRequest(senderIP: UInt32, senderMAC: Data, targetIP: UInt32) -> ARPPacket { + return ARPPacket(hardwareType: 0x01, + protocolType: 0x0800, + hardwareSize: 0x06, + protocolSize: 0x04, + opcode: .request, + senderMAC: senderMAC, + senderIP: senderIP, + targetMAC: Data([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), + targetIP: targetIP) + } + + static func arpResponse(for arp: ARPPacket, mac: Data, ip: UInt32) -> ARPPacket { + return ARPPacket(hardwareType: arp.hardwareType, + protocolType: arp.protocolType, + hardwareSize: arp.hardwareSize, + protocolSize: arp.protocolSize, + opcode: .response, + senderMAC: mac, + senderIP: ip, + targetMAC: arp.senderMAC, + targetIP: arp.senderIP) + } + +} diff --git a/Sources/sdlan/DataExtension.swift b/Sources/sdlan/DataExtension.swift new file mode 100644 index 0000000..e52843b --- /dev/null +++ b/Sources/sdlan/DataExtension.swift @@ -0,0 +1,39 @@ +// +// DataExtension.swift +// Tun +// +// Created by 安礼成 on 2024/2/29. +// + +import Foundation + +extension Data { + + init(uint32: UInt32) { + var bytes: [UInt8] = [UInt8](repeating: 0, count: 4) + bytes[0] = (UInt8)(uint32 >> 24 & 0xFF) + bytes[1] = (UInt8)(uint32 >> 16 & 0xFF) + bytes[2] = (UInt8)(uint32 >> 8 & 0xFF) + bytes[3] = (UInt8)(uint32 & 0xFF) + + self.init(bytes) + } + + init(uint16: UInt16) { + var bytes: [UInt8] = [UInt8](repeating: 0, count: 2) + bytes[0] = (UInt8)(uint16 >> 8 & 0xFF) + bytes[1] = (UInt8)(uint16 & 0xFF) + + self.init(bytes) + } + + init(components: Data...) { + var data = Data() + for component in components { + data.append(component) + } + self = data + } + +} + diff --git a/Sources/sdlan/IPPacket.swift b/Sources/sdlan/IPPacket.swift new file mode 100644 index 0000000..e136525 --- /dev/null +++ b/Sources/sdlan/IPPacket.swift @@ -0,0 +1,83 @@ +// +// IPPacket.swift +// Tun +// +// Created by 安礼成 on 2024/1/18. +// + +import Foundation + +struct IPHeader { + let version: UInt8 + let headerLength: UInt8 + let typeOfService: UInt8 + let totalLength: UInt16 + let id: UInt16 + let offset: UInt16 + let timeToLive: UInt8 + let proto:UInt8 + let checksum: UInt16 + let source: UInt32 + let destination: UInt32 + + var source_ip: String { + return intToIp(source) + } + + var destination_ip: String { + return intToIp(destination) + } + + private func intToIp(_ num: UInt32) -> String { + let ip0 = (UInt8) (num >> 24 & 0xFF) + let ip1 = (UInt8) (num >> 16 & 0xFF) + let ip2 = (UInt8) (num >> 8 & 0xFF) + let ip3 = (UInt8) (num & 0xFF) + + return "\(ip0).\(ip1).\(ip2).\(ip3)" + } + + public var description: String { + """ + IPHeader version: \(version), header length: \(headerLength), type of service: \(typeOfService), total length: \(totalLength), + id: \(id), offset: \(offset), time ot live: \(timeToLive), proto: \(proto), checksum: \(checksum), source ip: \(source_ip), destination ip:\(destination_ip) + """ + } +} + +enum IPVersion: UInt8 { + case ipv4 = 4 + case ipv6 = 6 +} + +enum TransportProtocol: UInt8 { + case icmp = 1 + case tcp = 6 + case udp = 17 + +} + +struct IPPacket { + let header: IPHeader + let data: Data + + init?(_ data: Data) { + guard data.count >= 20 else { + return nil + } + + self.header = IPHeader(version: data[0] >> 4, + headerLength: (data[0] & 0b1111) * 4, + typeOfService: data[1], + totalLength: UInt16(bytes: (data[2], data[3])), + id: UInt16(bytes: (data[4], data[5])), + offset: 1, + timeToLive: data[8], + proto: data[9], + checksum: UInt16(bytes: (data[10], data[11])), + source: UInt32(bytes: (data[12], data[13], data[14], data[15])), + destination: UInt32(bytes: (data[16], data[17], data[18], data[19]))) + self.data = data + } + +} diff --git a/Sources/sdlan/LayerPacket.swift b/Sources/sdlan/LayerPacket.swift new file mode 100644 index 0000000..07860c7 --- /dev/null +++ b/Sources/sdlan/LayerPacket.swift @@ -0,0 +1,117 @@ +// +// LayerPacket.swift +// Tun +// +// Created by 安礼成 on 2024/8/21. +// + +import Foundation +import zlib + +struct LayerPacket { + // 数据包类型 + enum PacketType: UInt16 { + case arp = 0x0806 + case ipv4 = 0x0800 + case ipv6 = 0x86DD + case taggedFrame = 0x8100 + } + + enum LayerPacketError: Error { + case invalidLength + case crcError + case invaldPacketType + } + + struct MacAddress { + let data: Data + + init(data: Data) { + self.data = data + } + + func isBroadcast() -> Bool { + return data.count == 6 && self.data.allSatisfy { $0 == 0xFF} + } + + func isMulticast() -> Bool { + return data.count == 6 && (data[0] == 0x01 && data[1] == 0x00 && data[2] == 0x5E) + } + + func format() -> String { + // 将mac地址转换成字符串 + let bytes = [UInt8](data) + return bytes.map { String(format: "%02X", $0) }.joined(separator: ":").lowercased() + } + + static func description(data: Data) -> String { + // 将mac地址转换成字符串 + let bytes = [UInt8](data) + return bytes.map { String(format: "%02X", $0) }.joined(separator: ":").lowercased() + } + + } + + let dstMac: Data + let srcMac: Data + let type: PacketType + let data: Data + + init(dstMac: Data, srcMac: Data, type: PacketType, data: Data) { + self.dstMac = dstMac + self.srcMac = srcMac + self.type = type + self.data = data + } + + init(layerData playload: Data) throws { + guard playload.count >= 14 else { + throw LayerPacketError.invalidLength + } + + /* + let idx = layerData.count - 4 + let playload = layerData.subdata(in: 0.. Data { + var packet = Data() + packet.append(dstMac) + packet.append(srcMac) + packet.append(self.type.rawValue.data()) + packet.append(self.data) + + // 计算crc32的值 + //let crc32 = data.crc32() + //packet.append(Data(uint32: Self.crc32(data: packet))) + + return packet + } + + private static func crc32(data: Data) -> UInt32 { + let crc = data.withUnsafeBytes { buffer in + return zlib.crc32(0, buffer.bindMemory(to: UInt8.self).baseAddress, uInt(buffer.count)) + } + + return UInt32(crc) + } + +} diff --git a/Sources/sdlan/NetworkInterface.swift b/Sources/sdlan/NetworkInterface.swift new file mode 100644 index 0000000..b6ac7fa --- /dev/null +++ b/Sources/sdlan/NetworkInterface.swift @@ -0,0 +1,66 @@ +// +// NetworkInterface.swift +// Tun +// +// Created by 安礼成 on 2024/1/19. +// + +import Foundation + +struct NetworkInterface { + let name: String + let ip: String + let netmask: String +} + +struct NetworkInterfaceManager { + /** + 获取网卡信息, (let name: String let ip: String let netmask: String) + */ + public static func getInterfaces() -> [NetworkInterface] { + var interfaces: [NetworkInterface] = [] + + // Get list of all interfaces on the local machine: + var ifaddr : UnsafeMutablePointer? = nil + if getifaddrs(&ifaddr) == 0 { + + // For each interface ... + var ptr = ifaddr + while( ptr != nil) { + + let flags = Int32(ptr!.pointee.ifa_flags) + var addr = ptr!.pointee.ifa_addr.pointee + + // Check for running IPv4, IPv6 interfaces. Skip the loopback interface. + if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) { + if addr.sa_family == UInt8(AF_INET) || addr.sa_family == UInt8(AF_INET6) { + + var mask = ptr!.pointee.ifa_netmask.pointee + + // Convert interface address to a human readable string: + let zero = CChar(0) + var hostname = [CChar](repeating: zero, count: Int(NI_MAXHOST)) + var netmask = [CChar](repeating: zero, count: Int(NI_MAXHOST)) + if (getnameinfo(&addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count), + nil, socklen_t(0), NI_NUMERICHOST) == 0) { + let address = String(cString: hostname) + let name = ptr!.pointee.ifa_name! + let ifname = String(cString: name) + + if (getnameinfo(&mask, socklen_t(mask.sa_len), &netmask, socklen_t(netmask.count), nil, socklen_t(0), NI_NUMERICHOST) == 0) { + let netmaskIP = String(cString: netmask) + + interfaces.append(NetworkInterface(name: ifname, ip: address, netmask: netmaskIP)) + } + } + } + } + ptr = ptr!.pointee.ifa_next + } + freeifaddrs(ifaddr) + } + + return interfaces + } + +} diff --git a/Sources/sdlan/PacketTunnelProvider.swift b/Sources/sdlan/PacketTunnelProvider.swift new file mode 100644 index 0000000..dd82fde --- /dev/null +++ b/Sources/sdlan/PacketTunnelProvider.swift @@ -0,0 +1,81 @@ +// +// PacketTunnelProvider.swift +// Tun +// +// Created by 安礼成 on 2024/1/17. +// + +import NetworkExtension + +class PacketTunnelProvider: NEPacketTunnelProvider { + var context: SDLContext? + + override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { + // host: "192.168.0.101", port: 1265 + guard let options else { + return + } + + let token = options["token"] as! String + //let version = options["version"] as! Int + let installed_channel = options["installed_channel"] as! String + + let superIp = options["super_ip"] as! String + Task { + SDLLogger.logLevel = .debug + do { + self.context = try SDLContext(provider: self, config: .init( + version: 1, + installedChannel: installed_channel, + //superHost: "118.178.229.213", + superHost: superIp, + superPort: 18083, + stunServers: [.init(host: superIp, ports: [1265, 1266]), .init(host: "118.178.229.213", ports: [1265, 1266])], + clientId: SDLContext.getUUID(), + token: "" + //token: token + )) + + try await self.context?.start() + completionHandler(nil) + } catch let err { + NSLog("SDLContext start get error: \(err)") + + completionHandler(err) + } + } + } + + override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { + // Add code here to start the process of stopping the tunnel. + completionHandler() + } + + override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { + // Add code here to handle the message. + if let handler = completionHandler { + handler(messageData) + } + } + + override func sleep(completionHandler: @escaping () -> Void) { + // Add code here to get ready to sleep. + completionHandler() + } + + override func wake() { + // Add code here to wake up. + } + +} + +// 获取物理网卡ip地址 +extension PacketTunnelProvider { + + public static var viaInterface: NetworkInterface? = { + let interfaces = NetworkInterfaceManager.getInterfaces() + + return interfaces.first {$0.name == "en0"} + }() + +} diff --git a/Sources/sdlan/SDLContext.swift b/Sources/sdlan/SDLContext.swift new file mode 100644 index 0000000..685d22f --- /dev/null +++ b/Sources/sdlan/SDLContext.swift @@ -0,0 +1,833 @@ +// +// SDLContext.swift +// Tun +// +// Created by 安礼成 on 2024/2/29. +// + +import Foundation +import NetworkExtension +import NIOCore +import Combine + +// 上下文环境变量,全局共享 +/* +1. 处理rsa的加解密逻辑 + */ + +class SDLContext { + + // 路由信息 + struct Route { + let dstAddress: String + let subnetMask: String + + var debugInfo: String { + return "\(dstAddress):\(subnetMask)" + } + } + + // 配置项目 + final class Configuration { + + struct StunServer { + let host: String + let ports: [Int] + } + + // 当前的客户端版本 + let version: UInt8 + + // 安装渠道 + let installedChannel: String + + let superHost: String + let superPort: Int + + let stunServers: [StunServer] + + lazy var stunSocketAddress: SocketAddress = { + let stunServer = stunServers[0] + return try! SocketAddress.makeAddressResolvingHost(stunServer.host, port: stunServer.ports[0]) + }() + + // 网络探测地址信息 + lazy var stunProbeSocketAddressArray: [[SocketAddress]] = { + return stunServers.map { stunServer in + [ + try! SocketAddress.makeAddressResolvingHost(stunServer.host, port: stunServer.ports[0]), + try! SocketAddress.makeAddressResolvingHost(stunServer.host, port: stunServer.ports[1]) + ] + } + }() + + let clientId: String + let token: String + + init(version: UInt8, installedChannel: String, superHost: String, superPort: Int, stunServers: [StunServer], clientId: String, token: String) { + self.version = version + self.installedChannel = installedChannel + self.superHost = superHost + self.superPort = superPort + self.stunServers = stunServers + self.clientId = clientId + self.token = token + } + + } + + let config: Configuration + + // tun网络地址信息 + var devAddr: SDLDevAddr + + // nat映射的相关信息, 暂时没有用处 + //var natAddress: SDLNatAddress? + // nat的网络类型 + var natType: NatType = .blocked + + // AES加密,授权通过后,对象才会被创建 + var aesCipher: AESCipher? + // rsa的相关配置, public_key是本地生成的 + let rsaCipher: RSACipher + + // 依赖的变量 + var udpHole: SDLUDPHole? + private var udpCancel: AnyCancellable? + + var superClient: SDLSuperClient? + private var superCancel: AnyCancellable? + + // 数据包读取任务 + private var readTask: Task<(), Never>? + + let provider: PacketTunnelProvider + + private var sessionManager: SessionManager + private var holerManager: HolerManager + private var arpServer: ArpServer + + // 记录最后发送的stunRequest的cookie + private var lastCookie: UInt32? = 0 + + // 定时器 + private var stunCancel: AnyCancellable? + + // 网络状态变化的健康 + private var monitor = SDLNetworkMonitor() + private var monitorCancel: AnyCancellable? + + // 内部socket通讯 + private var noticeClient: SDLNoticeClient + + // 流量统计 + private var flowTracer = SDLFlowTracerActor() + private var flowTracerCancel: AnyCancellable? + + init(provider: PacketTunnelProvider, config: Configuration) throws { + self.config = config + self.rsaCipher = try RSACipher(keySize: 1024) + + // 生成mac地址 + var devAddr = SDLDevAddr() + devAddr.mac = Self.getMacAddress() + self.devAddr = devAddr + + self.provider = provider + self.sessionManager = SessionManager() + self.holerManager = HolerManager() + self.arpServer = ArpServer(known_macs: [:]) + self.noticeClient = SDLNoticeClient() + } + + func start() async throws { + try await self.startSuperClient() + try await self.startUDPHole() + self.noticeClient.start() + + // 启动网络监控 + self.monitorCancel = self.monitor.eventFlow.sink { event in + switch event { + case .changed: + // 需要重新探测网络的nat类型 + Task { + self.natType = await self.getNatType() + NSLog("didNetworkPathChanged, nat type is: \(self.natType)") + } + case .unreachable: + NSLog("didNetworkPathUnreachable") + } + } + self.monitor.start() + } + + private func startSuperClient() async throws { + self.superClient = SDLSuperClient(host: config.superHost, port: config.superPort) + // 建立super的绑定关系 + self.superCancel?.cancel() + self.superCancel = self.superClient?.eventFlow.sink { event in + Task.detached { + await self.handleSuperEvent(event: event) + } + } + try await self.superClient?.start() + } + + private func handleSuperEvent(event: SDLSuperClient.SuperEvent) async { + switch event { + case .ready: + NSLog("[SDLContext] get registerSuper, mac address: \(Self.formatMacAddress(mac: self.devAddr.mac))") + guard let message = await self.superClient?.registerSuper(context: self) else { + return + } + + switch message.packet { + case .registerSuperAck(let registerSuperAck): + // 需要对数据通过rsa的私钥解码 + let aesKey = try! self.rsaCipher.decode(data: Data(registerSuperAck.aesKey)) + let upgradeType = SDLUpgradeType(rawValue: registerSuperAck.upgradeType) + + NSLog("[SDLContext] get registerSuperAck, aes_key len: \(aesKey.count)") + self.devAddr = registerSuperAck.devAddr + + if upgradeType == .force { + let forceUpgrade = NoticeMessage.UpgradeMessage(prompt: registerSuperAck.upgradePrompt, address: registerSuperAck.upgradeAddress) + self.noticeClient.send(data: forceUpgrade.binaryData) + + exit(-1) + } + + // 服务器分配的tun网卡信息 + await self.didNetworkConfigChanged(devAddr: self.devAddr) + self.aesCipher = AESCipher(aesKey: aesKey) + if upgradeType == .normal { + let normalUpgrade = NoticeMessage.UpgradeMessage(prompt: registerSuperAck.upgradePrompt, address: registerSuperAck.upgradeAddress) + self.noticeClient.send(data: normalUpgrade.binaryData) + } + + case .registerSuperNak(let nakPacket): + let errorMessage = nakPacket.errorMessage + guard let errorCode = SDLNAKErrorCode(rawValue: UInt8(nakPacket.errorCode)) else { + return + } + + switch errorCode { + case .invalidToken, .nodeDisabled: + let alertNotice = NoticeMessage.AlertMessage(alert: errorMessage) + self.noticeClient.send(data: alertNotice.binaryData) + exit(-1) + case .noIpAddress, .networkFault, .internalFault: + let alertNotice = NoticeMessage.AlertMessage(alert: errorMessage) + self.noticeClient.send(data: alertNotice.binaryData) + } + SDLLogger.log("Get a SuperNak message exit", level: .error) + default: + () + } + + case .closed: + SDLLogger.log("[SDLContext] super client closed", level: .debug) + await self.arpServer.clear() + DispatchQueue.global().asyncAfter(deadline: .now() + 5) { + Task { + try await self.startSuperClient() + } + } + case .event(let evt): + switch evt { + case .natChanged(let natChangedEvent): + let dstMac = natChangedEvent.mac + NSLog("natChangedEvent, dstMac: \(dstMac)") + await sessionManager.removeSession(dstMac: dstMac) + case .sendRegister(let sendRegisterEvent): + NSLog("sendRegisterEvent, ip: \(sendRegisterEvent)") + let address = SDLUtil.int32ToIp(sendRegisterEvent.natIp) + if let remoteAddress = try? SocketAddress.makeAddressResolvingHost(address, port: Int(sendRegisterEvent.natPort)) { + // 发送register包 + self.udpHole?.sendRegister(context: self, remoteAddress: remoteAddress, dst_mac: sendRegisterEvent.dstMac) + } + + case .networkShutdown(let shutdownEvent): + let alertNotice = NoticeMessage.AlertMessage(alert: shutdownEvent.message) + self.noticeClient.send(data: alertNotice.binaryData) + exit(-1) + } + case .command(let packetId, let command): + switch command { + case .changeNetwork(let changeNetworkCommand): + // 需要对数据通过rsa的私钥解码 + let aesKey = try! self.rsaCipher.decode(data: Data(changeNetworkCommand.aesKey)) + NSLog("[SDLContext] change network command get aes_key len: \(aesKey.count)") + self.devAddr = changeNetworkCommand.devAddr + + // 服务器分配的tun网卡信息 + await self.didNetworkConfigChanged(devAddr: self.devAddr) + self.aesCipher = AESCipher(aesKey: aesKey) + + var commandAck = SDLCommandAck() + commandAck.status = true + + self.superClient?.commandAck(packetId: packetId, ack: commandAck) + } + } + + } + + private func startUDPHole() async throws { + self.udpHole = SDLUDPHole() + + self.udpCancel?.cancel() + self.udpCancel = self.udpHole?.eventFlow.sink { event in + Task.detached { + await self.handleUDPEvent(event: event) + } + } + + try await self.udpHole?.start() + } + + private func handleUDPEvent(event: SDLUDPHole.UDPEvent) async { + switch event { + case .ready: + // 获取当前网络的类型 + self.natType = await self.getNatType() + SDLLogger.log("[SDLContext] nat type is: \(self.natType)", level: .debug) + + let timer = Timer.publish(every: 5.0, on: .main, in: .common).autoconnect() + self.stunCancel = Just(Date()).merge(with: timer).sink { _ in + self.lastCookie = self.udpHole?.stunRequest(context: self) + } + + case .closed: + DispatchQueue.global().asyncAfter(deadline: .now() + 5) { + Task { + try await self.startUDPHole() + } + } + + case .message(let remoteAddress, let message): + switch message { + case .register(let register): + NSLog("register packet: \(register), dev_addr: \(self.devAddr)") + // 判断目标地址是否是tun的网卡地址, 并且是在同一个网络下 + if register.dstMac == self.devAddr.mac && register.networkID == self.devAddr.networkID { + // 回复ack包 + self.udpHole?.sendRegisterAck(context: self, remoteAddress: remoteAddress, dst_mac: register.srcMac) + // 这里需要建立到来源的会话, 在复杂网络下,通过super-node查询到的nat地址不一定靠谱,需要通过udp包的来源地址作为nat地址 + let session = Session(dstMac: register.srcMac, natAddress: remoteAddress) + await self.sessionManager.addSession(session: session) + } else { + SDLLogger.log("SDLContext didReadRegister get a invalid packet, because dst_ip not matched: \(register.dstMac)", level: .warning) + } + case .registerAck(let registerAck): + // 判断目标地址是否是tun的网卡地址, 并且是在同一个网络下 + if registerAck.dstMac == self.devAddr.mac && registerAck.networkID == self.devAddr.networkID { + let session = Session(dstMac: registerAck.srcMac, natAddress: remoteAddress) + await self.sessionManager.addSession(session: session) + } else { + SDLLogger.log("SDLContext didReadRegisterAck get a invalid packet, because dst_mac not matched: \(registerAck.dstMac)", level: .warning) + } + case .stunReply(let stunReply): + let cookie = stunReply.cookie + if cookie == self.lastCookie { + // 记录下当前在nat上的映射信息,暂时没有用;后续会用来判断网络类型 + //self.natAddress = stunReply.natAddress + SDLLogger.log("[SDLContext] get a stunReply: \(try! stunReply.jsonString())") + } + default: + () + } + + case .data(let data): + let mac = LayerPacket.MacAddress(data: data.dstMac) + guard (data.dstMac == self.devAddr.mac || mac.isBroadcast() || mac.isMulticast()) else { + NSLog("[SDLContext] didReadData 1") + return + } + + guard let decyptedData = try? self.aesCipher?.decypt(data: Data(data.data)) else { + NSLog("[SDLContext] didReadData 2") + return + } + + do { + let layerPacket = try LayerPacket(layerData: decyptedData) + + await self.flowTracer.inc(num: decyptedData.count, type: .inbound) + // 处理arp请求 + switch layerPacket.type { + case .arp: + // 判断如果收到的是arp请求 + if let arpPacket = ARPPacket(data: layerPacket.data) { + if arpPacket.targetIP == self.devAddr.netAddr { + switch arpPacket.opcode { + case .request: + NSLog("[SDLContext] get arp request packet") + let response = ARPPacket.arpResponse(for: arpPacket, mac: self.devAddr.mac, ip: self.devAddr.netAddr) + await self.routeLayerPacket(dstMac: arpPacket.senderMAC, type: .arp, data: response.marshal()) + case .response: + NSLog("[SDLContext] get arp response packet") + await self.arpServer.append(ip: arpPacket.senderIP, mac: arpPacket.senderMAC) + } + } else { + NSLog("[SDLContext] get invalid arp packet, target_ip: \(arpPacket)") + } + } else { + NSLog("[SDLContext] get invalid arp packet") + } + case .ipv4: + NSLog("[SDLContext] get ipv4 packet") + guard let ipPacket = IPPacket(layerPacket.data), ipPacket.header.destination == self.devAddr.netAddr else { + return + } + + let packet = NEPacket(data: ipPacket.data, protocolFamily: 2) + self.provider.packetFlow.writePacketObjects([packet]) + default: + NSLog("[SDLContext] get invalid packet") + } + } catch let err { + NSLog("[SDLContext] didReadData err: \(err)") + } + } + + } + + // 流量统计 + func flowReportTask() { + Task { + // 每分钟汇报一次 + self.flowTracerCancel = Timer.publish(every: 60.0, on: .main, in: .common).autoconnect() + .sink { _ in + Task { + let (forwardNum, p2pNum, inboundNum) = await self.flowTracer.reset() + self.superClient?.flowReport(forwardNum: forwardNum, p2pNum: p2pNum, inboundNum: inboundNum) + } + } + } + } + + // 网络改变时需要重新配置网络信息 + private func didNetworkConfigChanged(devAddr: SDLDevAddr, dnsServers: [String]? = nil) async { + let netAddress = SDLNetAddress(ip: devAddr.netAddr, maskLen: UInt8(devAddr.netBitLen)) + let routes = [Route(dstAddress: netAddress.networkAddress, subnetMask: netAddress.maskAddress)] + + // Add code here to start the process of connecting the tunnel. + let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "8.8.8.8") + networkSettings.mtu = 1460 + + // 设置网卡的DNS解析 + if let dnsServers { + networkSettings.dnsSettings = NEDNSSettings(servers: dnsServers) + } else { + networkSettings.dnsSettings = NEDNSSettings(servers: ["8.8.8.8", "114.114.114.114"]) + } + + let ipv4Settings = NEIPv4Settings(addresses: [netAddress.ipAddress], subnetMasks: [netAddress.maskAddress]) + // 设置路由表 + //NEIPv4Route.default() + ipv4Settings.includedRoutes = routes.map { route in + NEIPv4Route(destinationAddress: route.dstAddress, subnetMask: route.subnetMask) + } + networkSettings.ipv4Settings = ipv4Settings + // 网卡配置设置必须成功 + do { + try await self.provider.setTunnelNetworkSettings(networkSettings) + + await self.holerManager.cleanup() + + self.startReader() + } catch let err { + SDLLogger.log("[SDLContext] setTunnelNetworkSettings get error: \(err)", level: .error) + exit(-1) + } + } + + // 开始读取数据, 用单独的线程处理packetFlow + private func startReader() { + // 停止之前的任务 + self.readTask?.cancel() + + // 开启新的任务 + self.readTask = Task(priority: .high) { + repeat { + if Task.isCancelled { + break + } + + let (packets, numbers) = await self.provider.packetFlow.readPackets() + for (data, number) in zip(packets, numbers) where number == 2 { + if let packet = IPPacket(data) { + Task.detached { + let dstIp = packet.header.destination + // 本地通讯, 目标地址是本地服务器的ip地址 + if dstIp == self.devAddr.netAddr { + let nePacket = NEPacket(data: packet.data, protocolFamily: 2) + self.provider.packetFlow.writePacketObjects([nePacket]) + return + } + + // 查找arp缓存中是否有目标mac地址 + if let dstMac = await self.arpServer.query(ip: dstIp) { + await self.routeLayerPacket(dstMac: dstMac, type: .ipv4, data: packet.data) + } + else { + // 构造arp请求 + let broadcastMac = Data([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) + let arpReqeust: ARPPacket = ARPPacket.arpRequest(senderIP: self.devAddr.netAddr, senderMAC: self.devAddr.mac, targetIP: dstIp) + await self.routeLayerPacket(dstMac: broadcastMac, type: .arp, data: arpReqeust.marshal()) + + NSLog("[SDLContext] dstIp: \(dstIp) arp query not found") + } + } + } + } + + } while true + } + + } + + private func routeLayerPacket(dstMac: Data, type: LayerPacket.PacketType, data: Data) async { + // 将数据封装层2层的数据包 + let layerPacket = LayerPacket(dstMac: dstMac, srcMac: self.devAddr.mac, type: type, data: data) + guard let encodedPacket = try? self.aesCipher?.encrypt(data: layerPacket.marshal()) else { + return + } + + // 通过session发送到对端 + if let session = await self.sessionManager.getSession(toAddress: dstMac) { + NSLog("[SDLContext] send packet by session: \(session)") + self.udpHole?.sendPacket(context: self, session: session, data: encodedPacket) + + await self.flowTracer.inc(num: data.count, type: .p2p) + } else { + // 通过super_node进行转发 + self.udpHole?.forwardPacket(context: self, dst_mac: dstMac, data: encodedPacket) + // 流量统计 + await self.flowTracer.inc(num: data.count, type: .forward) + + // 尝试打洞 + await self.holerManager.addHoler(dstMac: dstMac) { + self.holerTask(dstMac: dstMac) + } + } + } + + deinit { + self.stunCancel?.cancel() + self.udpHole = nil + self.superClient = nil + } + +} + +//--MARK: 处理RSA加密算法 +extension SDLContext { + + struct RSACipher { + let pubKey: String + let privateKeyDER: Data + + init(keySize: Int) throws { + let (privateKey, publicKey) = try Self.loadKeys(keySize: keySize) + let privKeyStr = SwKeyConvert.PrivateKey.derToPKCS1PEM(privateKey) + + self.pubKey = SwKeyConvert.PublicKey.derToPKCS8PEM(publicKey) + self.privateKeyDER = try SwKeyConvert.PrivateKey.pemToPKCS1DER(privKeyStr) + } + + public func decode(data: Data) throws -> Data { + let tag = Data() + let (decryptedData, _) = try CC.RSA.decrypt(data, derKey: self.privateKeyDER, tag: tag, padding: .pkcs1, digest: .none) + + return decryptedData + } + + private static func loadKeys(keySize: Int) throws -> (Data, Data) { + if let privateKey = UserDefaults.standard.data(forKey: "privateKey"), + let publicKey = UserDefaults.standard.data(forKey: "publicKey") { + + return (privateKey, publicKey) + } else { + let (privateKey, publicKey) = try CC.RSA.generateKeyPair(keySize) + UserDefaults.standard.setValue(privateKey, forKey: "privateKey") + UserDefaults.standard.setValue(publicKey, forKey: "publicKey") + + return (privateKey, publicKey) + } + } + + } +} + +// --MARK: 处理AES加密, AES256 +extension SDLContext { + + struct AESCipher { + let aesKey: Data + let ivData: Data + + init(aesKey: Data) { + self.aesKey = aesKey + self.ivData = Data(aesKey.prefix(16)) + } + + func decypt(data: Data) throws -> Data { + return try CC.crypt(.decrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: aesKey, iv: ivData) + } + + func encrypt(data: Data) throws -> Data { + return try CC.crypt(.encrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, data: data, key: aesKey, iv: ivData) + } + } + +} + +// --MARK: session管理, session的有效时间为10s,没次使用后更新最后使用时间 +extension SDLContext { + + struct Session { + // 在内部的通讯的ip地址, 整数格式 + let dstMac: Data + // 对端的主机在nat上映射的端口信息 + let natAddress: SocketAddress + + // 最后使用时间 + var lastTimestamp: Int32 + + init(dstMac: Data, natAddress: SocketAddress) { + self.dstMac = dstMac + self.natAddress = natAddress + self.lastTimestamp = Int32(Date().timeIntervalSince1970) + } + + mutating func updateLastTimestamp(_ lastTimestamp: Int32) { + self.lastTimestamp = lastTimestamp + } + } + + actor SessionManager { + private var sessions: [Data:Session] = [:] + + // session的有效时间 + private let ttl: Int32 = 10 + + func getSession(toAddress: Data) -> Session? { + let timestamp = Int32(Date().timeIntervalSince1970) + if let session = self.sessions[toAddress] { + if session.lastTimestamp >= timestamp + ttl { + self.sessions[toAddress]?.updateLastTimestamp(timestamp) + return session + } else { + self.sessions.removeValue(forKey: toAddress) + } + } + return nil + } + + func addSession(session: Session) { + self.sessions[session.dstMac] = session + } + + func removeSession(dstMac: Data) { + self.sessions.removeValue(forKey: dstMac) + } + + } + +} + +// --MARK: known_ips管理 +extension SDLContext { + + actor ArpServer { + private var known_macs: [UInt32:Data] = [:] + + init(known_macs: [UInt32:Data]) { + self.known_macs = known_macs + } + + func query(ip: UInt32) -> Data? { + return self.known_macs[ip] + } + + func append(ip: UInt32, mac: Data) { + self.known_macs[ip] = mac + } + + func remove(ip: UInt32) { + self.known_macs.removeValue(forKey: ip) + } + + func clear() { + self.known_macs = [:] + } + } + +} + +// --MARK: 打洞流程管理 +extension SDLContext { + + actor HolerManager { + private var holers: [Data:Task<(), Never>] = [:] + + func addHoler(dstMac: Data, creator: @escaping () -> Task<(), Never>) { + if let task = self.holers[dstMac] { + if task.isCancelled { + self.holers[dstMac] = creator() + } + } else { + self.holers[dstMac] = creator() + } + } + + func cleanup() { + for holer in holers.values { + holer.cancel() + } + self.holers.removeAll() + } + + } + + func holerTask(dstMac: Data) -> Task<(), Never> { + return Task { + guard let message = try? await self.superClient?.queryInfo(context: self, dst_mac: dstMac) else { + return + } + + switch message.packet { + case .empty: + SDLLogger.log("[SDLContext] hole query_info get empty: \(message)", level: .debug) + case .peerInfo(let peerInfo): + if let remoteAddress = peerInfo.v4Info.socketAddress() { + SDLLogger.log("[SDLContext] hole sock address: \(remoteAddress)", level: .warning) + // 发送register包 + self.udpHole?.sendRegister(context: self, remoteAddress: remoteAddress, dst_mac: dstMac) + } else { + SDLLogger.log("[SDLContext] hole sock address is invalid: \(peerInfo.v4Info)", level: .warning) + } + default: + SDLLogger.log("[SDLContext] hole query_info is packet: \(message)", level: .warning) + } + } + } + +} + +//--MARK: 网络类型探测 +extension SDLContext { + + // 定义nat类型 + enum NatType: UInt8, Encodable { + case blocked = 0 + case noNat = 1 + case fullCone = 2 + case portRestricted = 3 + case coneRestricted = 4 + case symmetric = 5 + } + + private func getNatAddress(remoteAddress: SocketAddress, attr: SDLProbeAttr) async -> SocketAddress? { + let stunProbeReply = await self.udpHole?.stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: 5) + + return stunProbeReply?.socketAddress() + } + + // 获取当前所处的网络的nat类型 + func getNatType() async -> NatType { + let addressArray = config.stunProbeSocketAddressArray + // step1: ip1:port1 <---- ip1:port1 + guard let natAddress1 = await getNatAddress(remoteAddress: addressArray[0][0], attr: .none) else { + return .blocked + } + + // 网络没有在nat下 + if natAddress1 == self.udpHole?.localAddress { + return .noNat + } + + // step2: ip2:port2 <---- ip2:port2 + guard let natAddress2 = await getNatAddress(remoteAddress: addressArray[1][1], attr: .none) else { + return .blocked + } + + // 如果natAddress2 的IP地址与上次回来的IP是不一样的,它就是对称型NAT; 这次的包也一定能发成功并收到 + // 如果ip地址变了,这说明{dstIp, dstPort, srcIp, srcPort}, 其中有一个变了;则用新的ip地址 + NSLog("nat_address1: \(natAddress1), nat_address2: \(natAddress2)") + if let ipAddress1 = natAddress1.ipAddress, let ipAddress2 = natAddress2.ipAddress, ipAddress1 != ipAddress2 { + return .symmetric + } + + // step3: ip1:port1 <---- ip2:port2 (ip地址和port都变的情况) + // 如果能收到的,说明是完全锥形 说明是IP地址限制锥型NAT,如果不能收到说明是端口限制锥型。 + if let natAddress3 = await getNatAddress(remoteAddress: addressArray[0][0], attr: .peer) { + NSLog("nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address3: \(natAddress3)") + return .fullCone + } + + // step3: ip1:port1 <---- ip1:port2 (port改变情况) + // 如果能收到的说明是IP地址限制锥型NAT,如果不能收到说明是端口限制锥型。 + if let natAddress4 = await getNatAddress(remoteAddress: addressArray[0][0], attr: .port) { + NSLog("nat_address1: \(natAddress1), nat_address2: \(natAddress2), nat_address4: \(natAddress4)") + return .coneRestricted + } else { + return .portRestricted + } + } + +} + + +//--MARK: 获取设备的UUID + +extension SDLContext { + + static func getUUID() -> String { + let userDefaults = UserDefaults.standard + if let uuid = userDefaults.value(forKey: "gClientId") as? String { + return uuid + } else { + let uuid = UUID().uuidString.replacingOccurrences(of: "-", with: "").lowercased() + userDefaults.setValue(uuid, forKey: "gClientId") + + return uuid + } + } + + // 获取mac地址 + static func getMacAddress() -> Data { + let key = "gMacAddress2" + + let userDefaults = UserDefaults.standard + if let mac = userDefaults.value(forKey: key) as? Data { + return mac + } + else { + let mac = generateMacAddress() + userDefaults.setValue(mac, forKey: key) + + return mac + } + } + + // 随机生成mac地址 + private static func generateMacAddress() -> Data { + var macAddress = [UInt8](repeating: 0, count: 6) + for i in 0..<6 { + macAddress[i] = UInt8.random(in: 0...255) + } + + return Data(macAddress) + } + + // 将mac地址转换成字符串 + private static func formatMacAddress(mac: Data) -> String { + let bytes = [UInt8](mac) + + return bytes.map { String(format: "%02X", $0) }.joined(separator: ":").lowercased() + } + +} diff --git a/Sources/sdlan/SDLFlowTracerActor.swift b/Sources/sdlan/SDLFlowTracerActor.swift new file mode 100644 index 0000000..0b0afbe --- /dev/null +++ b/Sources/sdlan/SDLFlowTracerActor.swift @@ -0,0 +1,43 @@ +// +// SDLFlowTracer.swift +// Tun +// +// Created by 安礼成 on 2024/5/27. +// + +import Foundation + +// 流量统计器 +actor SDLFlowTracerActor { + enum FlowType { + case forward + case p2p + case inbound + } + + private var forwardFlowBytes: UInt32 = 0 + private var p2pFlowBytes: UInt32 = 0 + private var inFlowBytes: UInt32 = 0 + + func inc(num: Int, type: FlowType) { + switch type { + case .inbound: + self.inFlowBytes += UInt32(num) + case .forward: + self.forwardFlowBytes += UInt32(num) + case .p2p: + self.p2pFlowBytes += UInt32(num) + } + } + + func reset() -> (UInt32, UInt32, UInt32) { + defer { + self.forwardFlowBytes = 0 + self.inFlowBytes = 0 + self.p2pFlowBytes = 0 + } + + return (forwardFlowBytes, p2pFlowBytes, inFlowBytes) + } + +} diff --git a/Sources/sdlan/SDLLogger.swift b/Sources/sdlan/SDLLogger.swift new file mode 100644 index 0000000..e66e49a --- /dev/null +++ b/Sources/sdlan/SDLLogger.swift @@ -0,0 +1,39 @@ +// +// SDLLogger.swift +// Tun +// +// Created by 安礼成 on 2024/3/13. +// + +import Foundation + +struct SDLLogger { + enum Level { + case debug + case info + case warning + case error + } + + static var logLevel: Level = .debug + + static func log(_ message: String, level: Level = .debug) { + switch logLevel { + case .debug: + NSLog(message) + case .info: + if level == .info || level == .warning || level == .error { + NSLog(message) + } + case .warning: + if level == .warning || level == .error { + NSLog(message) + } + case .error: + if level == .error { + NSLog(message) + } + } + } + +} diff --git a/Sources/sdlan/SDLMessage.pb.swift b/Sources/sdlan/SDLMessage.pb.swift new file mode 100644 index 0000000..60cfbd7 --- /dev/null +++ b/Sources/sdlan/SDLMessage.pb.swift @@ -0,0 +1,1482 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: sdlan_pb.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +struct SDLV4Info { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var port: UInt32 = 0 + + var v4: Data = Data() + + var natType: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLV6Info { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var port: UInt32 = 0 + + var v6: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// 设备网络地址信息 +struct SDLDevAddr { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var networkID: UInt32 = 0 + + var mac: Data = Data() + + var netAddr: UInt32 = 0 + + var netBitLen: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// tcp通讯消息 +struct SDLEmpty { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLRegisterSuper { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var version: UInt32 = 0 + + var installedChannel: String = String() + + var clientID: String = String() + + var devAddr: SDLDevAddr { + get {return _devAddr ?? SDLDevAddr()} + set {_devAddr = newValue} + } + /// Returns true if `devAddr` has been explicitly set. + var hasDevAddr: Bool {return self._devAddr != nil} + /// Clears the value of `devAddr`. Subsequent reads from it will return its default value. + mutating func clearDevAddr() {self._devAddr = nil} + + var pubKey: String = String() + + var token: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _devAddr: SDLDevAddr? = nil +} + +struct SDLRegisterSuperAck { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var devAddr: SDLDevAddr { + get {return _devAddr ?? SDLDevAddr()} + set {_devAddr = newValue} + } + /// Returns true if `devAddr` has been explicitly set. + var hasDevAddr: Bool {return self._devAddr != nil} + /// Clears the value of `devAddr`. Subsequent reads from it will return its default value. + mutating func clearDevAddr() {self._devAddr = nil} + + var aesKey: Data = Data() + + var upgradeType: UInt32 = 0 + + var upgradePrompt: String { + get {return _upgradePrompt ?? String()} + set {_upgradePrompt = newValue} + } + /// Returns true if `upgradePrompt` has been explicitly set. + var hasUpgradePrompt: Bool {return self._upgradePrompt != nil} + /// Clears the value of `upgradePrompt`. Subsequent reads from it will return its default value. + mutating func clearUpgradePrompt() {self._upgradePrompt = nil} + + var upgradeAddress: String { + get {return _upgradeAddress ?? String()} + set {_upgradeAddress = newValue} + } + /// Returns true if `upgradeAddress` has been explicitly set. + var hasUpgradeAddress: Bool {return self._upgradeAddress != nil} + /// Clears the value of `upgradeAddress`. Subsequent reads from it will return its default value. + mutating func clearUpgradeAddress() {self._upgradeAddress = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _devAddr: SDLDevAddr? = nil + fileprivate var _upgradePrompt: String? = nil + fileprivate var _upgradeAddress: String? = nil +} + +struct SDLRegisterSuperNak { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var errorCode: UInt32 = 0 + + var errorMessage: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLQueryInfo { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var dstMac: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLPeerInfo { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var dstMac: Data = Data() + + var v4Info: SDLV4Info { + get {return _v4Info ?? SDLV4Info()} + set {_v4Info = newValue} + } + /// Returns true if `v4Info` has been explicitly set. + var hasV4Info: Bool {return self._v4Info != nil} + /// Clears the value of `v4Info`. Subsequent reads from it will return its default value. + mutating func clearV4Info() {self._v4Info = nil} + + var natType: UInt32 = 0 + + var v6Info: SDLV6Info { + get {return _v6Info ?? SDLV6Info()} + set {_v6Info = newValue} + } + /// Returns true if `v6Info` has been explicitly set. + var hasV6Info: Bool {return self._v6Info != nil} + /// Clears the value of `v6Info`. Subsequent reads from it will return its default value. + mutating func clearV6Info() {self._v6Info = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _v4Info: SDLV4Info? = nil + fileprivate var _v6Info: SDLV6Info? = nil +} + +struct SDLNatChangedEvent { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var mac: Data = Data() + + var ip: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLSendRegisterEvent { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var dstMac: Data = Data() + + var natIp: UInt32 = 0 + + var natPort: UInt32 = 0 + + var v6Info: SDLV6Info { + get {return _v6Info ?? SDLV6Info()} + set {_v6Info = newValue} + } + /// Returns true if `v6Info` has been explicitly set. + var hasV6Info: Bool {return self._v6Info != nil} + /// Clears the value of `v6Info`. Subsequent reads from it will return its default value. + mutating func clearV6Info() {self._v6Info = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _v6Info: SDLV6Info? = nil +} + +struct SDLNetworkShutdownEvent { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var message: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLChangeNetworkCommand { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var devAddr: SDLDevAddr { + get {return _devAddr ?? SDLDevAddr()} + set {_devAddr = newValue} + } + /// Returns true if `devAddr` has been explicitly set. + var hasDevAddr: Bool {return self._devAddr != nil} + /// Clears the value of `devAddr`. Subsequent reads from it will return its default value. + mutating func clearDevAddr() {self._devAddr = nil} + + var aesKey: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _devAddr: SDLDevAddr? = nil +} + +struct SDLCommandAck { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// status = true, 表示成功;status = false 表示失败,message是失败原因描述 + var status: Bool = false + + var message: String { + get {return _message ?? String()} + set {_message = newValue} + } + /// Returns true if `message` has been explicitly set. + var hasMessage: Bool {return self._message != nil} + /// Clears the value of `message`. Subsequent reads from it will return its default value. + mutating func clearMessage() {self._message = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _message: String? = nil +} + +struct SDLFlows { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// 服务器转发流量 + var forwardNum: UInt32 = 0 + + /// p2p直接流量 + var p2PNum: UInt32 = 0 + + /// 接收的流量 + var inboundNum: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLStunRequest { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var cookie: UInt32 = 0 + + var clientID: String = String() + + var networkID: UInt32 = 0 + + var mac: Data = Data() + + var ip: UInt32 = 0 + + var natType: UInt32 = 0 + + var v6Info: SDLV6Info { + get {return _v6Info ?? SDLV6Info()} + set {_v6Info = newValue} + } + /// Returns true if `v6Info` has been explicitly set. + var hasV6Info: Bool {return self._v6Info != nil} + /// Clears the value of `v6Info`. Subsequent reads from it will return its default value. + mutating func clearV6Info() {self._v6Info = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _v6Info: SDLV6Info? = nil +} + +struct SDLStunReply { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var cookie: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLData { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var networkID: UInt32 = 0 + + var srcMac: Data = Data() + + var dstMac: Data = Data() + + var isP2P: Bool = false + + var ttl: UInt32 = 0 + + var data: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLRegister { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var networkID: UInt32 = 0 + + var srcMac: Data = Data() + + var dstMac: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLRegisterAck { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var networkID: UInt32 = 0 + + var srcMac: Data = Data() + + var dstMac: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLStunProbe { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var cookie: UInt32 = 0 + + var attr: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +struct SDLStunProbeReply { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var cookie: UInt32 = 0 + + var port: UInt32 = 0 + + var ip: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +#if swift(>=5.5) && canImport(_Concurrency) +extension SDLV4Info: @unchecked Sendable {} +extension SDLV6Info: @unchecked Sendable {} +extension SDLDevAddr: @unchecked Sendable {} +extension SDLEmpty: @unchecked Sendable {} +extension SDLRegisterSuper: @unchecked Sendable {} +extension SDLRegisterSuperAck: @unchecked Sendable {} +extension SDLRegisterSuperNak: @unchecked Sendable {} +extension SDLQueryInfo: @unchecked Sendable {} +extension SDLPeerInfo: @unchecked Sendable {} +extension SDLNatChangedEvent: @unchecked Sendable {} +extension SDLSendRegisterEvent: @unchecked Sendable {} +extension SDLNetworkShutdownEvent: @unchecked Sendable {} +extension SDLChangeNetworkCommand: @unchecked Sendable {} +extension SDLCommandAck: @unchecked Sendable {} +extension SDLFlows: @unchecked Sendable {} +extension SDLStunRequest: @unchecked Sendable {} +extension SDLStunReply: @unchecked Sendable {} +extension SDLData: @unchecked Sendable {} +extension SDLRegister: @unchecked Sendable {} +extension SDLRegisterAck: @unchecked Sendable {} +extension SDLStunProbe: @unchecked Sendable {} +extension SDLStunProbeReply: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension SDLV4Info: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLV4Info" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "port"), + 2: .same(proto: "v4"), + 3: .standard(proto: "nat_type"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.port) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.v4) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.natType) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.port != 0 { + try visitor.visitSingularUInt32Field(value: self.port, fieldNumber: 1) + } + if !self.v4.isEmpty { + try visitor.visitSingularBytesField(value: self.v4, fieldNumber: 2) + } + if self.natType != 0 { + try visitor.visitSingularUInt32Field(value: self.natType, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLV4Info, rhs: SDLV4Info) -> Bool { + if lhs.port != rhs.port {return false} + if lhs.v4 != rhs.v4 {return false} + if lhs.natType != rhs.natType {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLV6Info: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLV6Info" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "port"), + 2: .same(proto: "v6"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.port) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.v6) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.port != 0 { + try visitor.visitSingularUInt32Field(value: self.port, fieldNumber: 1) + } + if !self.v6.isEmpty { + try visitor.visitSingularBytesField(value: self.v6, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLV6Info, rhs: SDLV6Info) -> Bool { + if lhs.port != rhs.port {return false} + if lhs.v6 != rhs.v6 {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLDevAddr: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLDevAddr" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "network_id"), + 2: .same(proto: "mac"), + 3: .standard(proto: "net_addr"), + 4: .standard(proto: "net_bit_len"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.mac) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.netAddr) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.netBitLen) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.networkID != 0 { + try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 1) + } + if !self.mac.isEmpty { + try visitor.visitSingularBytesField(value: self.mac, fieldNumber: 2) + } + if self.netAddr != 0 { + try visitor.visitSingularUInt32Field(value: self.netAddr, fieldNumber: 3) + } + if self.netBitLen != 0 { + try visitor.visitSingularUInt32Field(value: self.netBitLen, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLDevAddr, rhs: SDLDevAddr) -> Bool { + if lhs.networkID != rhs.networkID {return false} + if lhs.mac != rhs.mac {return false} + if lhs.netAddr != rhs.netAddr {return false} + if lhs.netBitLen != rhs.netBitLen {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLEmpty: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLEmpty" + static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + mutating func decodeMessage(decoder: inout D) throws { + while let _ = try decoder.nextFieldNumber() { + } + } + + func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLEmpty, rhs: SDLEmpty) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLRegisterSuper: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLRegisterSuper" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "version"), + 2: .standard(proto: "installed_channel"), + 3: .standard(proto: "client_id"), + 4: .standard(proto: "dev_addr"), + 5: .standard(proto: "pub_key"), + 6: .same(proto: "token"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.version) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.installedChannel) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.clientID) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._devAddr) }() + case 5: try { try decoder.decodeSingularStringField(value: &self.pubKey) }() + case 6: try { try decoder.decodeSingularStringField(value: &self.token) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.version != 0 { + try visitor.visitSingularUInt32Field(value: self.version, fieldNumber: 1) + } + if !self.installedChannel.isEmpty { + try visitor.visitSingularStringField(value: self.installedChannel, fieldNumber: 2) + } + if !self.clientID.isEmpty { + try visitor.visitSingularStringField(value: self.clientID, fieldNumber: 3) + } + try { if let v = self._devAddr { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + if !self.pubKey.isEmpty { + try visitor.visitSingularStringField(value: self.pubKey, fieldNumber: 5) + } + if !self.token.isEmpty { + try visitor.visitSingularStringField(value: self.token, fieldNumber: 6) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLRegisterSuper, rhs: SDLRegisterSuper) -> Bool { + if lhs.version != rhs.version {return false} + if lhs.installedChannel != rhs.installedChannel {return false} + if lhs.clientID != rhs.clientID {return false} + if lhs._devAddr != rhs._devAddr {return false} + if lhs.pubKey != rhs.pubKey {return false} + if lhs.token != rhs.token {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLRegisterSuperAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLRegisterSuperAck" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "dev_addr"), + 2: .standard(proto: "aes_key"), + 3: .standard(proto: "upgrade_type"), + 4: .standard(proto: "upgrade_prompt"), + 5: .standard(proto: "upgrade_address"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._devAddr) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.aesKey) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.upgradeType) }() + case 4: try { try decoder.decodeSingularStringField(value: &self._upgradePrompt) }() + case 5: try { try decoder.decodeSingularStringField(value: &self._upgradeAddress) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._devAddr { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !self.aesKey.isEmpty { + try visitor.visitSingularBytesField(value: self.aesKey, fieldNumber: 2) + } + if self.upgradeType != 0 { + try visitor.visitSingularUInt32Field(value: self.upgradeType, fieldNumber: 3) + } + try { if let v = self._upgradePrompt { + try visitor.visitSingularStringField(value: v, fieldNumber: 4) + } }() + try { if let v = self._upgradeAddress { + try visitor.visitSingularStringField(value: v, fieldNumber: 5) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLRegisterSuperAck, rhs: SDLRegisterSuperAck) -> Bool { + if lhs._devAddr != rhs._devAddr {return false} + if lhs.aesKey != rhs.aesKey {return false} + if lhs.upgradeType != rhs.upgradeType {return false} + if lhs._upgradePrompt != rhs._upgradePrompt {return false} + if lhs._upgradeAddress != rhs._upgradeAddress {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLRegisterSuperNak: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLRegisterSuperNak" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "error_code"), + 2: .standard(proto: "error_message"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.errorCode) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.errorMessage) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.errorCode != 0 { + try visitor.visitSingularUInt32Field(value: self.errorCode, fieldNumber: 1) + } + if !self.errorMessage.isEmpty { + try visitor.visitSingularStringField(value: self.errorMessage, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLRegisterSuperNak, rhs: SDLRegisterSuperNak) -> Bool { + if lhs.errorCode != rhs.errorCode {return false} + if lhs.errorMessage != rhs.errorMessage {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLQueryInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLQueryInfo" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "dst_mac"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLQueryInfo, rhs: SDLQueryInfo) -> Bool { + if lhs.dstMac != rhs.dstMac {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLPeerInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLPeerInfo" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "dst_mac"), + 2: .standard(proto: "v4_info"), + 3: .standard(proto: "nat_type"), + 4: .standard(proto: "v6_info"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._v4Info) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.natType) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._v6Info) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 1) + } + try { if let v = self._v4Info { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + if self.natType != 0 { + try visitor.visitSingularUInt32Field(value: self.natType, fieldNumber: 3) + } + try { if let v = self._v6Info { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLPeerInfo, rhs: SDLPeerInfo) -> Bool { + if lhs.dstMac != rhs.dstMac {return false} + if lhs._v4Info != rhs._v4Info {return false} + if lhs.natType != rhs.natType {return false} + if lhs._v6Info != rhs._v6Info {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLNatChangedEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLNatChangedEvent" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "mac"), + 2: .same(proto: "ip"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.mac) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.ip) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.mac.isEmpty { + try visitor.visitSingularBytesField(value: self.mac, fieldNumber: 1) + } + if self.ip != 0 { + try visitor.visitSingularUInt32Field(value: self.ip, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLNatChangedEvent, rhs: SDLNatChangedEvent) -> Bool { + if lhs.mac != rhs.mac {return false} + if lhs.ip != rhs.ip {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLSendRegisterEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLSendRegisterEvent" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "dst_mac"), + 2: .standard(proto: "nat_ip"), + 3: .standard(proto: "nat_port"), + 4: .standard(proto: "v6_info"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.natIp) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.natPort) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._v6Info) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 1) + } + if self.natIp != 0 { + try visitor.visitSingularUInt32Field(value: self.natIp, fieldNumber: 2) + } + if self.natPort != 0 { + try visitor.visitSingularUInt32Field(value: self.natPort, fieldNumber: 3) + } + try { if let v = self._v6Info { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLSendRegisterEvent, rhs: SDLSendRegisterEvent) -> Bool { + if lhs.dstMac != rhs.dstMac {return false} + if lhs.natIp != rhs.natIp {return false} + if lhs.natPort != rhs.natPort {return false} + if lhs._v6Info != rhs._v6Info {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLNetworkShutdownEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLNetworkShutdownEvent" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "message"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.message) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.message.isEmpty { + try visitor.visitSingularStringField(value: self.message, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLNetworkShutdownEvent, rhs: SDLNetworkShutdownEvent) -> Bool { + if lhs.message != rhs.message {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLChangeNetworkCommand: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLChangeNetworkCommand" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "dev_addr"), + 2: .standard(proto: "aes_key"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._devAddr) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.aesKey) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._devAddr { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !self.aesKey.isEmpty { + try visitor.visitSingularBytesField(value: self.aesKey, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLChangeNetworkCommand, rhs: SDLChangeNetworkCommand) -> Bool { + if lhs._devAddr != rhs._devAddr {return false} + if lhs.aesKey != rhs.aesKey {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLCommandAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLCommandAck" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "status"), + 2: .same(proto: "message"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBoolField(value: &self.status) }() + case 2: try { try decoder.decodeSingularStringField(value: &self._message) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.status != false { + try visitor.visitSingularBoolField(value: self.status, fieldNumber: 1) + } + try { if let v = self._message { + try visitor.visitSingularStringField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLCommandAck, rhs: SDLCommandAck) -> Bool { + if lhs.status != rhs.status {return false} + if lhs._message != rhs._message {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLFlows: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLFlows" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "forward_num"), + 2: .standard(proto: "p2p_num"), + 3: .standard(proto: "inbound_num"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.forwardNum) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.p2PNum) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.inboundNum) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.forwardNum != 0 { + try visitor.visitSingularUInt32Field(value: self.forwardNum, fieldNumber: 1) + } + if self.p2PNum != 0 { + try visitor.visitSingularUInt32Field(value: self.p2PNum, fieldNumber: 2) + } + if self.inboundNum != 0 { + try visitor.visitSingularUInt32Field(value: self.inboundNum, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLFlows, rhs: SDLFlows) -> Bool { + if lhs.forwardNum != rhs.forwardNum {return false} + if lhs.p2PNum != rhs.p2PNum {return false} + if lhs.inboundNum != rhs.inboundNum {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLStunRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLStunRequest" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "cookie"), + 2: .standard(proto: "client_id"), + 3: .standard(proto: "network_id"), + 4: .same(proto: "mac"), + 5: .same(proto: "ip"), + 6: .standard(proto: "nat_type"), + 7: .standard(proto: "v6_info"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.cookie) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.clientID) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }() + case 4: try { try decoder.decodeSingularBytesField(value: &self.mac) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self.ip) }() + case 6: try { try decoder.decodeSingularUInt32Field(value: &self.natType) }() + case 7: try { try decoder.decodeSingularMessageField(value: &self._v6Info) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.cookie != 0 { + try visitor.visitSingularUInt32Field(value: self.cookie, fieldNumber: 1) + } + if !self.clientID.isEmpty { + try visitor.visitSingularStringField(value: self.clientID, fieldNumber: 2) + } + if self.networkID != 0 { + try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 3) + } + if !self.mac.isEmpty { + try visitor.visitSingularBytesField(value: self.mac, fieldNumber: 4) + } + if self.ip != 0 { + try visitor.visitSingularUInt32Field(value: self.ip, fieldNumber: 5) + } + if self.natType != 0 { + try visitor.visitSingularUInt32Field(value: self.natType, fieldNumber: 6) + } + try { if let v = self._v6Info { + try visitor.visitSingularMessageField(value: v, fieldNumber: 7) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLStunRequest, rhs: SDLStunRequest) -> Bool { + if lhs.cookie != rhs.cookie {return false} + if lhs.clientID != rhs.clientID {return false} + if lhs.networkID != rhs.networkID {return false} + if lhs.mac != rhs.mac {return false} + if lhs.ip != rhs.ip {return false} + if lhs.natType != rhs.natType {return false} + if lhs._v6Info != rhs._v6Info {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLStunReply: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLStunReply" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "cookie"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.cookie) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.cookie != 0 { + try visitor.visitSingularUInt32Field(value: self.cookie, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLStunReply, rhs: SDLStunReply) -> Bool { + if lhs.cookie != rhs.cookie {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLData: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLData" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "network_id"), + 2: .standard(proto: "src_mac"), + 3: .standard(proto: "dst_mac"), + 4: .standard(proto: "is_p2p"), + 5: .same(proto: "ttl"), + 6: .same(proto: "data"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.srcMac) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self.isP2P) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self.ttl) }() + case 6: try { try decoder.decodeSingularBytesField(value: &self.data) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.networkID != 0 { + try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 1) + } + if !self.srcMac.isEmpty { + try visitor.visitSingularBytesField(value: self.srcMac, fieldNumber: 2) + } + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 3) + } + if self.isP2P != false { + try visitor.visitSingularBoolField(value: self.isP2P, fieldNumber: 4) + } + if self.ttl != 0 { + try visitor.visitSingularUInt32Field(value: self.ttl, fieldNumber: 5) + } + if !self.data.isEmpty { + try visitor.visitSingularBytesField(value: self.data, fieldNumber: 6) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLData, rhs: SDLData) -> Bool { + if lhs.networkID != rhs.networkID {return false} + if lhs.srcMac != rhs.srcMac {return false} + if lhs.dstMac != rhs.dstMac {return false} + if lhs.isP2P != rhs.isP2P {return false} + if lhs.ttl != rhs.ttl {return false} + if lhs.data != rhs.data {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLRegister: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLRegister" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "network_id"), + 2: .standard(proto: "src_mac"), + 3: .standard(proto: "dst_mac"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.srcMac) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.networkID != 0 { + try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 1) + } + if !self.srcMac.isEmpty { + try visitor.visitSingularBytesField(value: self.srcMac, fieldNumber: 2) + } + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLRegister, rhs: SDLRegister) -> Bool { + if lhs.networkID != rhs.networkID {return false} + if lhs.srcMac != rhs.srcMac {return false} + if lhs.dstMac != rhs.dstMac {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLRegisterAck: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLRegisterAck" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "network_id"), + 2: .standard(proto: "src_mac"), + 3: .standard(proto: "dst_mac"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.networkID) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.srcMac) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.dstMac) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.networkID != 0 { + try visitor.visitSingularUInt32Field(value: self.networkID, fieldNumber: 1) + } + if !self.srcMac.isEmpty { + try visitor.visitSingularBytesField(value: self.srcMac, fieldNumber: 2) + } + if !self.dstMac.isEmpty { + try visitor.visitSingularBytesField(value: self.dstMac, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLRegisterAck, rhs: SDLRegisterAck) -> Bool { + if lhs.networkID != rhs.networkID {return false} + if lhs.srcMac != rhs.srcMac {return false} + if lhs.dstMac != rhs.dstMac {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLStunProbe: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLStunProbe" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "cookie"), + 2: .same(proto: "attr"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.cookie) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.attr) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.cookie != 0 { + try visitor.visitSingularUInt32Field(value: self.cookie, fieldNumber: 1) + } + if self.attr != 0 { + try visitor.visitSingularUInt32Field(value: self.attr, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLStunProbe, rhs: SDLStunProbe) -> Bool { + if lhs.cookie != rhs.cookie {return false} + if lhs.attr != rhs.attr {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SDLStunProbeReply: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "SDLStunProbeReply" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "cookie"), + 2: .same(proto: "port"), + 3: .same(proto: "ip"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.cookie) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.port) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.ip) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.cookie != 0 { + try visitor.visitSingularUInt32Field(value: self.cookie, fieldNumber: 1) + } + if self.port != 0 { + try visitor.visitSingularUInt32Field(value: self.port, fieldNumber: 2) + } + if self.ip != 0 { + try visitor.visitSingularUInt32Field(value: self.ip, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SDLStunProbeReply, rhs: SDLStunProbeReply) -> Bool { + if lhs.cookie != rhs.cookie {return false} + if lhs.port != rhs.port {return false} + if lhs.ip != rhs.ip {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Sources/sdlan/SDLMessage.swift b/Sources/sdlan/SDLMessage.swift new file mode 100644 index 0000000..f5c1f35 --- /dev/null +++ b/Sources/sdlan/SDLMessage.swift @@ -0,0 +1,156 @@ +// +// SDLPacketType.swift +// Tun +// +// Created by 安礼成 on 2024/4/10. +// + +import Foundation +import NIOCore + +// 消息类型定义 +enum SDLPacketType: UInt8 { + case empty = 0x00 + + case registerSuper = 0x01 + case registerSuperAck = 0x02 + case registerSuperNak = 0x04 + + case unregisterSuper = 0x05 + + case queryInfo = 0x06 + case peerInfo = 0x07 + + case ping = 0x08 + case pong = 0x09 + + // 事件类型 + case event = 0x10 + + // 推送命令消息, 需要返回值 + case command = 0x11 + case commandAck = 0x12 + + // 流量统计 + case flowTracer = 0x15 + + case register = 0x20 + case registerAck = 0x21 + + case stunRequest = 0x30 + case stunReply = 0x31 + + case stunProbe = 0x32 + case stunProbeReply = 0x33 + + case data = 0xFF +} + +// 升级策略 +enum SDLUpgradeType: UInt32 { + case none = 0 + case normal = 1 + case force = 2 +} + +// Id生成器 +struct SDLIdGenerator { + // 消息体id + private var packetId: UInt32 + + init(seed packetId: UInt32) { + self.packetId = packetId + } + + mutating func nextId() -> UInt32 { + let packetId = self.packetId + self.packetId = packetId + 1 + return packetId + } +} + +// 定义事件类型 + +// 命令类型 +enum SDLEventType: UInt8 { + case natChanged = 0x03 + case sendRegister = 0x04 + case networkShutdown = 0xFF +} + +enum SDLEvent { + case natChanged(SDLNatChangedEvent) + case sendRegister(SDLSendRegisterEvent) + case networkShutdown(SDLNetworkShutdownEvent) +} + +// --MARK: 定义命令类型 + +enum SDLCommandType: UInt8 { + case changeNetwork = 0x01 +} + +enum SDLCommand { + case changeNetwork(SDLChangeNetworkCommand) +} + +// --MARK: 网络类型探测 +// 探测的Attr属性 +enum SDLProbeAttr: UInt8 { + case none = 0 + case port = 1 + case peer = 2 +} + +// Nak的错误类型,不同的错误客户端的处理逻辑不一样 +enum SDLNAKErrorCode: UInt8 { + case invalidToken = 1 + case nodeDisabled = 2 + case noIpAddress = 3 + case networkFault = 4 + case internalFault = 5 +} + +extension SDLV4Info { + func socketAddress() -> SocketAddress? { + let address = "\(v4[0]).\(v4[1]).\(v4[2]).\(v4[3])" + + return try? SocketAddress.makeAddressResolvingHost(address, port: Int(port)) + } +} + +extension SDLStunProbeReply { + func socketAddress() -> SocketAddress? { + let address = SDLUtil.int32ToIp(self.ip) + + return try? SocketAddress.makeAddressResolvingHost(address, port: Int(port)) + } +} + +// --MARK: 进来的消息, 这里需要采用代数类型来表示 + +enum SDLHoleInboundMessage { + case stunReply(SDLStunReply) + case stunProbeReply(SDLStunProbeReply) + + case data(SDLData) + case register(SDLRegister) + case registerAck(SDLRegisterAck) +} + +// --MARK: 定义消息类型 + +struct SDLSuperInboundMessage { + let msgId: UInt32 + let packet: InboundPacket + + enum InboundPacket { + case empty + case registerSuperAck(SDLRegisterSuperAck) + case registerSuperNak(SDLRegisterSuperNak) + case peerInfo(SDLPeerInfo) + case pong + case event(SDLEvent) + case command(SDLCommand) + } +} diff --git a/Sources/sdlan/SDLNetAddress.swift b/Sources/sdlan/SDLNetAddress.swift new file mode 100644 index 0000000..5ab8a56 --- /dev/null +++ b/Sources/sdlan/SDLNetAddress.swift @@ -0,0 +1,49 @@ +// +// SDLIPAddress.swift +// Tun +// +// Created by 安礼成 on 2024/3/4. +// + +import Foundation + +struct SDLNetAddress { + let ip: UInt32 + let maskLen: UInt8 + + // ip地址 + var ipAddress: String { + return intToIpAddress(self.ip) + } + + // 掩码 + var maskAddress: String { + let len0 = 32 - maskLen + let num: UInt32 = (0xFFFFFFFF >> len0) << len0 + + return intToIpAddress(num) + } + + // 网络地址 + var networkAddress: String { + let len0 = 32 - maskLen + let mask: UInt32 = (0xFFFFFFFF >> len0) << len0 + + return intToIpAddress(self.ip & mask) + } + + init(ip: UInt32, maskLen: UInt8) { + self.ip = ip + self.maskLen = maskLen + } + + private func intToIpAddress(_ num: UInt32) -> String { + let ip0 = (UInt8) (num >> 24 & 0xFF) + let ip1 = (UInt8) (num >> 16 & 0xFF) + let ip2 = (UInt8) (num >> 8 & 0xFF) + let ip3 = (UInt8) (num & 0xFF) + + return "\(ip0).\(ip1).\(ip2).\(ip3)" + } + +} diff --git a/Sources/sdlan/SDLNetworkMonitor.swift b/Sources/sdlan/SDLNetworkMonitor.swift new file mode 100644 index 0000000..2e13336 --- /dev/null +++ b/Sources/sdlan/SDLNetworkMonitor.swift @@ -0,0 +1,62 @@ +// +// SDLNetworkMonitor.swift +// Tun +// +// Created by 安礼成 on 2024/5/16. +// + +import Foundation +import Network +import Combine + +// 监控网络的变化 +class SDLNetworkMonitor { + private var monitor: NWPathMonitor + private var interfaceType: NWInterface.InterfaceType? + private let publisher = PassthroughSubject() + private var cancel: AnyCancellable? + private let queue = DispatchQueue(label: "networkMonitorQueue") + + public let eventFlow = PassthroughSubject() + + enum MonitorEvent { + case changed + case unreachable + } + + init() { + self.monitor = NWPathMonitor() + } + + func start() { + self.monitor.pathUpdateHandler = { path in + if path.status == .satisfied { + if path.usesInterfaceType(.wifi) { + self.publisher.send(.wifi) + } else if path.usesInterfaceType(.cellular) { + self.publisher.send(.cellular) + } else if path.usesInterfaceType(.wiredEthernet) { + self.publisher.send(.wiredEthernet) + } + } else { + self.eventFlow.send(.unreachable) + self.interfaceType = nil + } + } + self.monitor.start(queue: self.queue) + + self.cancel = publisher.throttle(for: 5.0, scheduler: self.queue, latest: true) + .sink { type in + if self.interfaceType != nil && self.interfaceType != type { + self.eventFlow.send(.changed) + } + self.interfaceType = type + } + } + + deinit { + self.monitor.cancel() + self.cancel?.cancel() + } + +} diff --git a/Sources/sdlan/SDLNoticeClient.swift b/Sources/sdlan/SDLNoticeClient.swift new file mode 100644 index 0000000..71a12de --- /dev/null +++ b/Sources/sdlan/SDLNoticeClient.swift @@ -0,0 +1,95 @@ +// +// SDLNoticeClient.swift +// Tun +// +// Created by 安礼成 on 2024/5/20. +// + +import Foundation + +// +// SDLanServer.swift +// Tun +// +// Created by 安礼成 on 2024/1/31. +// + +import Foundation +import NIOCore +import NIOPosix + +// 处理和sn-server服务器之间的通讯 +class SDLNoticeClient: ChannelInboundHandler { + public typealias InboundIn = AddressedEnvelope + public typealias OutboundOut = AddressedEnvelope + + private var thread: Thread? + var context: ChannelHandlerContext? + private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + private let remoteAddress: SocketAddress + + init() { + self.remoteAddress = try! SocketAddress(ipAddress: "127.0.0.1", port: 50195) + } + + // 启动函数 + func start() { + self.thread = Thread { + let bootstrap = DatagramBootstrap(group: self.group) + .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + .channelInitializer { channel in + // 接收缓冲区 + channel.pipeline.addHandler(self) + } + + let channel = try! bootstrap.bind(host: "0.0.0.0", port: 0).wait() + SDLLogger.log("[SDLNoticeClient] started and listening on: \(channel.localAddress!)", level: .debug) + + // This will never unblock as we don't close the channel + try! channel.closeFuture.wait() + } + self.thread?.start() + } + + // -- MARK: ChannelInboundHandler Methods + + public func channelActive(context: ChannelHandlerContext) { + self.context = context + } + + // 接收到的消息, 消息需要根据类型分流 + public func channelRead(context: ChannelHandlerContext, data: NIOAny) { + context.fireChannelRead(data) + } + + public func errorCaught(context: ChannelHandlerContext, error: Error) { + // As we are not really interested getting notified on success or failure we just pass nil as promise to + // reduce allocations. + context.close(promise: nil) + self.context = nil + } + + public func channelInactive(context: ChannelHandlerContext) { + self.context = nil + context.close(promise: nil) + } + + // 处理写入逻辑 + func send(data: Data) { + guard let context = self.context else { + return + } + + context.eventLoop.execute { + let buffer = context.channel.allocator.buffer(bytes: data) + + let envelope = AddressedEnvelope(remoteAddress: self.remoteAddress, data: buffer) + context.writeAndFlush(self.wrapOutboundOut(envelope), promise: nil) + } + } + + deinit { + self.thread?.cancel() + try? self.group.syncShutdownGracefully() + } +} diff --git a/Sources/sdlan/SDLProtoMessageExtension.swift b/Sources/sdlan/SDLProtoMessageExtension.swift new file mode 100644 index 0000000..89ca77e --- /dev/null +++ b/Sources/sdlan/SDLProtoMessageExtension.swift @@ -0,0 +1,16 @@ +// +// SDLProtoMessageExtension.swift +// Tun +// +// Created by 安礼成 on 2024/10/24. +// + +import Foundation + +extension SDLData { + + func format() -> String { + return "network_id: \(self.networkID), src_mac: \(LayerPacket.MacAddress.description(data: self.srcMac)), dst_mac: \(LayerPacket.MacAddress.description(data: self.dstMac)), data: \([UInt8](self.data))" + } + +} diff --git a/Sources/sdlan/SDLQPSCounter.swift b/Sources/sdlan/SDLQPSCounter.swift new file mode 100644 index 0000000..57311fd --- /dev/null +++ b/Sources/sdlan/SDLQPSCounter.swift @@ -0,0 +1,37 @@ +// +// SDLQPSCounter.swift +// Tun +// +// Created by 安礼成 on 2024/4/16. +// + +import Foundation + +// 计数器,用来统计qps +class SDLQPSCounter { + private var count = 0 + private let timer: DispatchSourceTimer + private let label: String + + init(label: String) { + self.label = label + timer = DispatchSource.makeTimerSource(queue: DispatchQueue(label: "com.yourapp.qps")) + timer.schedule(deadline: .now(), repeating: .seconds(1), leeway: .milliseconds(100)) + timer.setEventHandler { [weak self] in + guard let self = self else { return } + NSLog("[\(self.label)] QPS: \(self.count)") + self.count = 0 + } + timer.resume() + } + + func increment(num: Int = 1) { + DispatchQueue(label: "com.yourapp.qps").async { + self.count += num + } + } + + deinit { + timer.cancel() + } +} diff --git a/Sources/sdlan/SDLSuperClient.swift b/Sources/sdlan/SDLSuperClient.swift new file mode 100644 index 0000000..e673619 --- /dev/null +++ b/Sources/sdlan/SDLSuperClient.swift @@ -0,0 +1,373 @@ +// +// SDLWebsocketClient.swift +// Tun +// +// Created by 安礼成 on 2024/3/28. +// + +import Foundation +import NIOCore +import NIOPosix +import Combine + +// --MARK: 和SuperNode的客户端 +class SDLSuperClient: ChannelInboundHandler { + public typealias InboundIn = ByteBuffer + public typealias OutboundOut = ByteBuffer + + public typealias CallbackFun = (SDLSuperInboundMessage?) -> Void + + private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + private var channel: Channel? + + // id生成器 + var idGenerator = SDLIdGenerator(seed: 1) + private let callbackManager = SuperCallbackManager() + + let host: String + let port: Int + + private var pingCancel: AnyCancellable? + + public var eventFlow = PassthroughSubject() + + // 定义事件类型 + enum SuperEvent { + case ready + case closed + case event(SDLEvent) + case command(UInt32, SDLCommand) + } + + init(host: String, port: Int) { + self.host = host + self.port = port + } + + func start() async throws { + let bootstrap = ClientBootstrap(group: self.group) + .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + .channelInitializer { channel in + return channel.pipeline.addHandlers([ + ByteToMessageHandler(FixedHeaderDelimiterCoder()), + MessageToByteHandler(FixedHeaderDelimiterCoder()), + self + ]) + } + + do { + NSLog("super client connect: \(self.host):\(self.port)") + self.channel = try await bootstrap.connect(host: self.host, port: self.port).get() + } catch let err { + NSLog("super client get error: \(err)") + self.eventFlow.send(.closed) + } + } + + // -- MARK: apis + + func commandAck(packetId: UInt32, ack: SDLCommandAck) { + guard let data = try? ack.serializedData() else { + return + } + + self.send(type: .commandAck, packetId: packetId, data: data) + } + + func registerSuper(context ctx: SDLContext) async -> SDLSuperInboundMessage? { + return await withCheckedContinuation { c in + self.registerSuper(context: ctx) { message in + c.resume(returning: message) + } + } + } + + func registerSuper(context ctx: SDLContext, callback: @escaping CallbackFun) { + var registerSuper = SDLRegisterSuper() + registerSuper.version = UInt32(ctx.config.version) + registerSuper.clientID = ctx.config.clientId + registerSuper.devAddr = ctx.devAddr + registerSuper.pubKey = ctx.rsaCipher.pubKey + registerSuper.token = ctx.config.token + + let data = try! registerSuper.serializedData() + + self.write(type: .registerSuper, data: data, callback: callback) + } + + func queryInfo(context ctx: SDLContext, dst_mac: Data) async throws -> SDLSuperInboundMessage? { + return await withCheckedContinuation { c in + self.queryInfo(context: ctx, dst_mac: dst_mac) { message in + c.resume(returning: message) + } + } + } + + // 查询目标服务器的相关信息 + func queryInfo(context ctx: SDLContext, dst_mac: Data, callback: @escaping CallbackFun) { + var queryInfo = SDLQueryInfo() + queryInfo.dstMac = dst_mac + + self.write(type: .queryInfo, data: try! queryInfo.serializedData(), callback: callback) + } + + func unregister(context ctx: SDLContext) throws { + self.send(type: .unregisterSuper, packetId: 0, data: Data()) + } + + func ping() { + self.send(type: .ping, packetId: 0, data: Data()) + } + + func flowReport(forwardNum: UInt32, p2pNum: UInt32, inboundNum: UInt32) { + var flow = SDLFlows() + flow.forwardNum = forwardNum + flow.p2PNum = p2pNum + flow.inboundNum = inboundNum + + self.send(type: .flowTracer, packetId: 0, data: try! flow.serializedData()) + } + + // --MARK: ChannelInboundHandler + + public func channelActive(context: ChannelHandlerContext) { + self.startPingTicker() + self.eventFlow.send(.ready) + } + + public func channelRead(context: ChannelHandlerContext, data: NIOAny) { + var buffer = self.unwrapInboundIn(data) + if let message = decode(buffer: &buffer) { + SDLLogger.log("[SDLSuperTransport] read message: \(message)", level: .warning) + + switch message.packet { + case .event(let event): + self.eventFlow.send(.event(event)) + case .command(let command): + self.eventFlow.send(.command(message.msgId, command)) + default: + self.callbackManager.fireCallback(message: message) + } + } + } + + public func errorCaught(context: ChannelHandlerContext, error: Error) { + SDLLogger.log("[SDLSuperTransport] error: \(error)", level: .warning) + self.channel = nil + self.eventFlow.send(.closed) + context.close(promise: nil) + } + + public func channelInactive(context: ChannelHandlerContext) { + SDLLogger.log("[SDLSuperTransport] channelInactive", level: .warning) + self.channel = nil + context.close(promise: nil) + } + + func write(type: SDLPacketType, data: Data, callback: @escaping CallbackFun) { + guard let channel = self.channel else { + return + } + + SDLLogger.log("[SDLSuperTransport] will write data: \(data)", level: .debug) + + let packetId = idGenerator.nextId() + self.callbackManager.addCallback(id: packetId, callback: callback) + + channel.eventLoop.execute { + var buffer = channel.allocator.buffer(capacity: data.count + 5) + buffer.writeInteger(packetId, as: UInt32.self) + buffer.writeBytes([type.rawValue]) + buffer.writeBytes(data) + + channel.writeAndFlush(self.wrapOutboundOut(buffer), promise: nil) + } + } + + func send(type: SDLPacketType, packetId: UInt32, data: Data) { + guard let channel = self.channel else { + return + } + + channel.eventLoop.execute { + var buffer = channel.allocator.buffer(capacity: data.count + 5) + buffer.writeInteger(packetId, as: UInt32.self) + buffer.writeBytes([type.rawValue]) + buffer.writeBytes(data) + + channel.writeAndFlush(self.wrapOutboundOut(buffer), promise: nil) + } + } + + // --MARK: 心跳机制 + + private func startPingTicker() { + self.pingCancel = Timer.publish(every: 5.0, on: .main, in: .common).autoconnect() + .sink { _ in + // 保持和super-node的心跳机制 + self.ping() + } + } + + deinit { + self.pingCancel?.cancel() + try! group.syncShutdownGracefully() + } + +} + +/// 基于2字节固定长度的分包协议 +extension SDLSuperClient { + private final class FixedHeaderDelimiterCoder: ByteToMessageDecoder, MessageToByteEncoder { + typealias InboundIn = ByteBuffer + typealias InboundOut = ByteBuffer + + func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState { + guard let len = buffer.getInteger(at: buffer.readerIndex, endianness: .big, as: UInt16.self) else { + return .needMoreData + } + + if buffer.readableBytes >= len + 2 { + buffer.moveReaderIndex(forwardBy: 2) + if let bytes = buffer.readBytes(length: Int(len)) { + context.fireChannelRead(self.wrapInboundOut(ByteBuffer(bytes: bytes))) + } + return .continue + } else { + return .needMoreData + } + } + + func encode(data: ByteBuffer, out: inout ByteBuffer) throws { + let len = data.readableBytes + out.writeInteger(UInt16(len)) + out.writeBytes(data.readableBytesView) + } + } +} + +// 回调函数管理器 +extension SDLSuperClient { + private final class SuperCallbackManager { + // 对应请求体和相应的关系 + private var callbacks: [UInt32:CallbackFun] = [:] + private let locker = NSLock() + + func addCallback(id: UInt32, callback: @escaping CallbackFun) { + locker.lock() + defer { + locker.unlock() + } + self.callbacks[id] = callback + } + + func fireCallback(message: SDLSuperInboundMessage) { + locker.lock() + defer { + locker.unlock() + } + + if let callback = self.callbacks[message.msgId] { + callback(message) + self.callbacks.removeValue(forKey: message.msgId) + } + } + + func fireAllCallbacks(message: SDLSuperInboundMessage) { + locker.lock() + defer { + locker.unlock() + } + + for (_, callback) in self.callbacks { + callback(nil) + } + self.callbacks.removeAll() + } + } +} + +// --MARK: 编解码器 +extension SDLSuperClient { + // 消息格式为: <> + func decode(buffer: inout ByteBuffer) -> SDLSuperInboundMessage? { + guard let msgId = buffer.readInteger(as: UInt32.self), + let type = buffer.readInteger(as: UInt8.self), + let messageType = SDLPacketType(rawValue: type) else { + return nil + } + + switch messageType { + case .empty: + return .init(msgId: msgId, packet: .empty) + case .registerSuperAck: + guard let bytes = buffer.readBytes(length: buffer.readableBytes), + let registerSuperAck = try? SDLRegisterSuperAck(serializedData: Data(bytes)) else { + return nil + } + return .init(msgId: msgId, packet: .registerSuperAck(registerSuperAck)) + + case .registerSuperNak: + guard let bytes = buffer.readBytes(length: buffer.readableBytes), + let registerSuperNak = try? SDLRegisterSuperNak(serializedData: Data(bytes)) else { + return nil + } + return .init(msgId: msgId, packet: .registerSuperNak(registerSuperNak)) + + case .peerInfo: + guard let bytes = buffer.readBytes(length: buffer.readableBytes), + let peerInfo = try? SDLPeerInfo(serializedData: Data(bytes)) else { + return nil + } + + return .init(msgId: msgId, packet: .peerInfo(peerInfo)) + case .pong: + return .init(msgId: msgId, packet: .pong) + + case .command: + guard let commandVal = buffer.readInteger(as: UInt8.self), + let command = SDLCommandType(rawValue: commandVal), + let bytes = buffer.readBytes(length: buffer.readableBytes) else { + return nil + } + + switch command { + case .changeNetwork: + guard let changeNetworkCommand = try? SDLChangeNetworkCommand(serializedData: Data(bytes)) else { + return nil + } + + return .init(msgId: msgId, packet: .command(.changeNetwork(changeNetworkCommand))) + } + + case .event: + guard let eventVal = buffer.readInteger(as: UInt8.self), + let event = SDLEventType(rawValue: eventVal), + let bytes = buffer.readBytes(length: buffer.readableBytes) else { + return nil + } + + switch event { + case .natChanged: + guard let natChangedEvent = try? SDLNatChangedEvent(serializedData: Data(bytes)) else { + return nil + } + return .init(msgId: msgId, packet: .event(.natChanged(natChangedEvent))) + case .sendRegister: + guard let sendRegisterEvent = try? SDLSendRegisterEvent(serializedData: Data(bytes)) else { + return nil + } + return .init(msgId: msgId, packet: .event(.sendRegister(sendRegisterEvent))) + case .networkShutdown: + guard let networkShutdownEvent = try? SDLNetworkShutdownEvent(serializedData: Data(bytes)) else { + return nil + } + return .init(msgId: msgId, packet: .event(.networkShutdown(networkShutdownEvent))) + } + + default: + return nil + } + } + +} diff --git a/Sources/sdlan/SDLThrottler.swift b/Sources/sdlan/SDLThrottler.swift new file mode 100644 index 0000000..4e32217 --- /dev/null +++ b/Sources/sdlan/SDLThrottler.swift @@ -0,0 +1,45 @@ +// +// SDLThrottler.swift +// Tun +// +// Created by 安礼成 on 2024/6/3. +// + +import Foundation +import Combine + +// 限流器 +actor SDLThrottler { + private var limit: Int + private var token: Int + private var cancel: AnyCancellable? + + init(limit: Int) { + self.limit = limit + self.token = limit + } + + func start() { + self.cancel?.cancel() + self.cancel = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect() + .sink { _ in + Task { + self.token = self.limit + } + } + } + + func setRateLimit(limit: Int) { + self.limit = limit + } + + func getToken(num: Int) -> Bool { + if token > 0 { + self.token = self.token - num + return true + } else { + return false + } + } + +} diff --git a/Sources/sdlan/SDLUDPHole.swift b/Sources/sdlan/SDLUDPHole.swift new file mode 100644 index 0000000..3c941c1 --- /dev/null +++ b/Sources/sdlan/SDLUDPHole.swift @@ -0,0 +1,337 @@ +// +// SDLanServer.swift +// Tun +// +// Created by 安礼成 on 2024/1/31. +// + +import Foundation +import NIOCore +import NIOPosix +import Combine + +// 处理和sn-server服务器之间的通讯 +class SDLUDPHole: ChannelInboundHandler { + public typealias InboundIn = AddressedEnvelope + public typealias OutboundOut = AddressedEnvelope + + // 回调函数 + public typealias CallbackFun = (SDLStunProbeReply?) -> Void + + private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + + private var cookieGenerator = SDLIdGenerator(seed: 1) + private let callbackManager = HoleCallbackManager() + + public var localAddress: SocketAddress? + public var channel: Channel? + + public var eventFlow = PassthroughSubject() + + // 定义事件类型 + enum UDPEvent { + case ready + case closed + case message(SocketAddress, SDLHoleInboundMessage) + case data(SDLData) + } + + init() { + + } + + // MARK: super_node apis + + func stunRequest(context ctx: SDLContext) -> UInt32 { + let cookie = self.cookieGenerator.nextId() + let remoteAddress = ctx.config.stunSocketAddress + + var stunRequest = SDLStunRequest() + stunRequest.cookie = cookie + stunRequest.clientID = ctx.config.clientId + stunRequest.networkID = ctx.devAddr.networkID + stunRequest.ip = ctx.devAddr.netAddr + stunRequest.mac = ctx.devAddr.mac + stunRequest.natType = UInt32(ctx.natType.rawValue) + + SDLLogger.log("[SDLUDPHole] stunRequest: \(remoteAddress), host: \(ctx.config.stunServers[0].host):\(ctx.config.stunServers[0].ports[0])", level: .warning) + + self.send(remoteAddress: remoteAddress, type: .stunRequest, data: try! stunRequest.serializedData()) + + return cookie + } + + // 探测tun信息 + func stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int = 5) async -> SDLStunProbeReply? { + return await withCheckedContinuation { continuation in + self.stunProbe(remoteAddress: remoteAddress, attr: attr, timeout: timeout) { probeReply in + continuation.resume(returning: probeReply) + } + } + } + + private func stunProbe(remoteAddress: SocketAddress, attr: SDLProbeAttr = .none, timeout: Int, callback: @escaping CallbackFun) { + let cookie = self.cookieGenerator.nextId() + + var stunProbe = SDLStunProbe() + stunProbe.cookie = cookie + stunProbe.attr = UInt32(attr.rawValue) + + self.send(remoteAddress: remoteAddress, type: .stunProbe, data: try! stunProbe.serializedData()) + + SDLLogger.log("[SDLUDPHole] stunProbe: \(remoteAddress)", level: .warning) + + self.callbackManager.addCallback(id: cookie, timeout: timeout, callback: callback) + } + + // MARK: client-client apis + + // 发送数据包到其他session + func sendPacket(context ctx: SDLContext, session: SDLContext.Session, data: Data) { + let remoteAddress = session.natAddress + + var dataPacket = SDLData() + dataPacket.networkID = ctx.devAddr.networkID + dataPacket.srcMac = ctx.devAddr.mac + dataPacket.dstMac = session.dstMac + dataPacket.ttl = 255 + dataPacket.data = data + let packet = try! dataPacket.serializedData() + + SDLLogger.log("[SDLUDPHole] sendPacket: \(remoteAddress), count: \(packet.count)", level: .debug) + + self.send(remoteAddress: remoteAddress, type: .data, data: packet) + } + + // 通过sn服务器转发数据包, data已经是加密过后的数据 + func forwardPacket(context ctx: SDLContext, dst_mac: Data, data: Data) { + let remoteAddress = ctx.config.stunSocketAddress + + var dataPacket = SDLData() + dataPacket.networkID = ctx.devAddr.networkID + dataPacket.srcMac = ctx.devAddr.mac + dataPacket.dstMac = dst_mac + dataPacket.ttl = 255 + dataPacket.data = data + + let packet = try! dataPacket.serializedData() + + NSLog("[SDLContext] forward packet, remoteAddress: \(remoteAddress), data size: \(packet.count)") + + self.send(remoteAddress: remoteAddress, type: .data, data: packet) + } + + // 发送register包 + func sendRegister(context ctx: SDLContext, remoteAddress: SocketAddress, dst_mac: Data) { + var register = SDLRegister() + register.networkID = ctx.devAddr.networkID + register.srcMac = ctx.devAddr.mac + register.dstMac = dst_mac + + SDLLogger.log("[SDLUDPHole] SendRegister: \(remoteAddress), src_mac: \(LayerPacket.MacAddress.description(data: ctx.devAddr.mac)), dst_mac: \(LayerPacket.MacAddress.description(data: dst_mac))", level: .debug) + + self.send(remoteAddress: remoteAddress, type: .register, data: try! register.serializedData()) + } + + // 回复registerAck + func sendRegisterAck(context ctx: SDLContext, remoteAddress: SocketAddress, dst_mac: Data) { + var registerAck = SDLRegisterAck() + registerAck.networkID = ctx.devAddr.networkID + registerAck.srcMac = ctx.devAddr.mac + registerAck.dstMac = dst_mac + + SDLLogger.log("[SDLUDPHole] SendRegisterAck: \(remoteAddress), \(registerAck)", level: .debug) + + self.send(remoteAddress: remoteAddress, type: .registerAck, data: try! registerAck.serializedData()) + } + + // 启动函数 + func start() async throws { + let bootstrap = DatagramBootstrap(group: self.group) + .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + .channelInitializer { channel in + // 接收缓冲区 + return channel.setOption(ChannelOptions.socketOption(.so_rcvbuf), value: 5 * 1024 * 1024) + .flatMap { + channel.setOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_SNDBUF), value: 5 * 1024 * 1024) + }.flatMap { + channel.pipeline.addHandler(self) + } + } + + let channel = try await bootstrap.bind(host: "0.0.0.0", port: 0).get() + + SDLLogger.log("[UDPHole] started and listening on: \(channel.localAddress!)", level: .debug) + self.localAddress = channel.localAddress + self.channel = channel + } + + // -- MARK: ChannelInboundHandler Methods + + public func channelActive(context: ChannelHandlerContext) { + self.eventFlow.send(.ready) + } + + // 接收到的消息, 消息需要根据类型分流 + public func channelRead(context: ChannelHandlerContext, data: NIOAny) { + let envelope = self.unwrapInboundIn(data) + var buffer = envelope.data + let remoteAddress = envelope.remoteAddress + + do { + if let message = try decode(buffer: &buffer) { + Task { + switch message { + case .data(let data): + SDLLogger.log("[SDLUDPHole] read data: \(data.format()), from: \(remoteAddress)", level: .debug) + self.eventFlow.send(.data(data)) + case .stunProbeReply(let probeReply): + self.callbackManager.fireCallback(message: probeReply) + default: + self.eventFlow.send(.message(remoteAddress, message)) + } + } + } else { + SDLLogger.log("[SDLUDPHole] decode message, get null", level: .warning) + } + } catch let err { + SDLLogger.log("[SDLUDPHole] decode message, get error: \(err)", level: .debug) + } + } + + public func errorCaught(context: ChannelHandlerContext, error: Error) { + SDLLogger.log("[SDLUDPHole] get error: \(error)", level: .error) + // As we are not really interested getting notified on success or failure we just pass nil as promise to + // reduce allocations. + context.close(promise: nil) + self.channel = nil + self.eventFlow.send(.closed) + } + + public func channelInactive(context: ChannelHandlerContext) { + self.channel = nil + context.close(promise: nil) + } + + // 处理写入逻辑 + func send(remoteAddress: SocketAddress, type: SDLPacketType, data: Data) { + guard let channel = self.channel else { + return + } + + // 在Eventloop中时直接写入数据避免线程切换 + if channel.eventLoop.inEventLoop { + var buffer = channel.allocator.buffer(capacity: data.count + 1) + buffer.writeBytes([type.rawValue]) + buffer.writeBytes(data) + + let envelope = AddressedEnvelope(remoteAddress: remoteAddress, data: buffer) + channel.writeAndFlush(self.wrapOutboundOut(envelope), promise: nil) + } else { + channel.eventLoop.execute { + var buffer = channel.allocator.buffer(capacity: data.count + 1) + buffer.writeBytes([type.rawValue]) + buffer.writeBytes(data) + + let envelope = AddressedEnvelope(remoteAddress: remoteAddress, data: buffer) + channel.writeAndFlush(self.wrapOutboundOut(envelope), promise: nil) + } + } + } + + deinit { + try? self.group.syncShutdownGracefully() + } +} + +//--MARK: 编解码器 +extension SDLUDPHole { + + func decode(buffer: inout ByteBuffer) throws -> SDLHoleInboundMessage? { + guard let type = buffer.readInteger(as: UInt8.self), + let packetType = SDLPacketType(rawValue: type), + let bytes = buffer.readBytes(length: buffer.readableBytes) else { + SDLLogger.log("[SDLUDPHole] decode error", level: .error) + return nil + } + + switch packetType { + case .data: + let dataPacket = try SDLData(serializedData: Data(bytes)) + return .data(dataPacket) + case .register: + let registerPacket = try SDLRegister(serializedData: Data(bytes)) + return .register(registerPacket) + case .registerAck: + let registerAck = try SDLRegisterAck(serializedData: Data(bytes)) + return .registerAck(registerAck) + case .stunReply: + let stunReply = try SDLStunReply(serializedData: Data(bytes)) + return .stunReply(stunReply) + case .stunProbeReply: + let stunProbeReply = try SDLStunProbeReply(serializedData: Data(bytes)) + return .stunProbeReply(stunProbeReply) + default: + return nil + } + } +} + +// --MARK: 回调函数管理器 +extension SDLUDPHole { + private final class HoleCallbackManager { + // 对应请求体和相应的关系 + private var callbacks: [UInt32:CallbackFun] = [:] + private let locker = NSLock() + + func addCallback(id: UInt32, timeout: Int, callback: @escaping CallbackFun) { + locker.lock() + defer { + locker.unlock() + } + + DispatchQueue.global().asyncAfter(deadline: .now() + Double(timeout)) { + self.fireCallback(cookie: id) + } + + self.callbacks[id] = callback + } + + func fireCallback(message: SDLStunProbeReply) { + locker.lock() + defer { + locker.unlock() + } + + if let callback = self.callbacks[message.cookie] { + callback(message) + self.callbacks.removeValue(forKey: message.cookie) + } + } + + func fireAllCallbacks(message: SDLSuperInboundMessage) { + locker.lock() + defer { + locker.unlock() + } + + for (_, callback) in self.callbacks { + callback(nil) + } + self.callbacks.removeAll() + } + + private func fireCallback(cookie: UInt32) { + locker.lock() + defer { + locker.unlock() + } + + if let callback = self.callbacks[cookie] { + callback(nil) + self.callbacks.removeValue(forKey: cookie) + } + } + + } +} diff --git a/Sources/sdlan/SDLUtil.swift b/Sources/sdlan/SDLUtil.swift new file mode 100644 index 0000000..2307a3c --- /dev/null +++ b/Sources/sdlan/SDLUtil.swift @@ -0,0 +1,46 @@ +// +// Util.swift +// Tun +// +// Created by 安礼成 on 2024/1/19. +// + +import Foundation + +struct SDLUtil { + + public static func int32ToIp(_ num: UInt32) -> String { + let ip0 = (UInt8) (num >> 24 & 0xFF) + let ip1 = (UInt8) (num >> 16 & 0xFF) + let ip2 = (UInt8) (num >> 8 & 0xFF) + let ip3 = (UInt8) (num & 0xFF) + + return "\(ip0).\(ip1).\(ip2).\(ip3)" + } + + public static func netMaskIp(maskLen: UInt8) -> String { + let len0 = 32 - maskLen + let num: UInt32 = (0xFFFFFFFF >> len0) << len0 + + let ip0 = (UInt8) (num >> 24 & 0xFF) + let ip1 = (UInt8) (num >> 16 & 0xFF) + let ip2 = (UInt8) (num >> 8 & 0xFF) + let ip3 = (UInt8) (num & 0xFF) + + return "\(ip0).\(ip1).\(ip2).\(ip3)" + } + + // 判断ip地址是否在同一个网络 + public static func inSameNetwork(ip: UInt32, compareIp: UInt32, maskLen: UInt8) -> Bool { + if ip == compareIp { + return true + } + + let len0 = 32 - maskLen + // 掩码值 + let mask: UInt32 = (0xFFFFFFFF >> len0) << len0 + + return ip & mask == compareIp & mask + } + +} diff --git a/Sources/sdlan/SwCrypt.swift b/Sources/sdlan/SwCrypt.swift new file mode 100644 index 0000000..af6f633 --- /dev/null +++ b/Sources/sdlan/SwCrypt.swift @@ -0,0 +1,2423 @@ +import Foundation + +open class SwKeyStore { + + public enum SecError: OSStatus, Error { + case unimplemented = -4 + case param = -50 + case allocate = -108 + case notAvailable = -25291 + case authFailed = -25293 + case duplicateItem = -25299 + case itemNotFound = -25300 + case interactionNotAllowed = -25308 + case decode = -26275 + case missingEntitlement = -34018 + + public static var debugLevel = 1 + + init(_ status: OSStatus, function: String = #function, file: String = #file, line: Int = #line) { + self = SecError(rawValue: status)! + if SecError.debugLevel > 0 { + print("\(file):\(line): [\(function)] \(self._domain): \(self) (\(self.rawValue))") + } + } + init(_ type: SecError, function: String = #function, file: String = #file, line: Int = #line) { + self = type + if SecError.debugLevel > 0 { + print("\(file):\(line): [\(function)] \(self._domain): \(self) (\(self.rawValue))") + } + } + } + + public static func upsertKey(_ pemKey: String, keyTag: String, + options: [NSString : AnyObject] = [:]) throws { + let pemKeyAsData = pemKey.data(using: String.Encoding.utf8)! + + var parameters: [NSString : AnyObject] = [ + kSecClass: kSecClassKey, + kSecAttrKeyType: kSecAttrKeyTypeRSA, + kSecAttrIsPermanent: true as AnyObject, + kSecAttrApplicationTag: keyTag as AnyObject, + kSecValueData: pemKeyAsData as AnyObject + ] + options.forEach { k, v in + parameters[k] = v + } + + var status = SecItemAdd(parameters as CFDictionary, nil) + if status == errSecDuplicateItem { + try delKey(keyTag) + status = SecItemAdd(parameters as CFDictionary, nil) + } + guard status == errSecSuccess else { throw SecError(status) } + } + + public static func getKey(_ keyTag: String) throws -> String { + let parameters: [NSString : AnyObject] = [ + kSecClass : kSecClassKey, + kSecAttrKeyType : kSecAttrKeyTypeRSA, + kSecAttrApplicationTag : keyTag as AnyObject, + kSecReturnData : true as AnyObject + ] + var data: AnyObject? + let status = SecItemCopyMatching(parameters as CFDictionary, &data) + guard status == errSecSuccess else { throw SecError(status) } + + guard let pemKeyAsData = data as? Data else { + throw SecError(.decode) + } + guard let result = String(data: pemKeyAsData, encoding: String.Encoding.utf8) else { + throw SecError(.decode) + } + return result + } + + public static func delKey(_ keyTag: String) throws { + let parameters: [NSString : AnyObject] = [ + kSecClass : kSecClassKey, + kSecAttrApplicationTag: keyTag as AnyObject + ] + let status = SecItemDelete(parameters as CFDictionary) + guard status == errSecSuccess else { throw SecError(status) } + } +} + +open class SwKeyConvert { + + public enum SwError: Error { + case invalidKey + case badPassphrase + case keyNotEncrypted + + public static var debugLevel = 1 + + init(_ type: SwError, function: String = #function, file: String = #file, line: Int = #line) { + self = type + if SwError.debugLevel > 0 { + print("\(file):\(line): [\(function)] \(self._domain): \(self)") + } + } + } + + open class PrivateKey { + + public static func pemToPKCS1DER(_ pemKey: String) throws -> Data { + guard let derKey = try? PEM.PrivateKey.toDER(pemKey) else { + throw SwError(.invalidKey) + } + guard let pkcs1DERKey = PKCS8.PrivateKey.stripHeaderIfAny(derKey) else { + throw SwError(.invalidKey) + } + return pkcs1DERKey + } + + public static func derToPKCS1PEM(_ derKey: Data) -> String { + return PEM.PrivateKey.toPEM(derKey) + } + + public typealias EncMode = PEM.EncryptedPrivateKey.EncMode + + public static func encryptPEM(_ pemKey: String, passphrase: String, + mode: EncMode) throws -> String { + do { + let derKey = try PEM.PrivateKey.toDER(pemKey) + return PEM.EncryptedPrivateKey.toPEM(derKey, passphrase: passphrase, mode: mode) + } catch { + throw SwError(.invalidKey) + } + } + + public static func decryptPEM(_ pemKey: String, passphrase: String) throws -> String { + do { + let derKey = try PEM.EncryptedPrivateKey.toDER(pemKey, passphrase: passphrase) + return PEM.PrivateKey.toPEM(derKey) + } catch PEM.SwError.badPassphrase { + throw SwError(.badPassphrase) + } catch PEM.SwError.keyNotEncrypted { + throw SwError(.keyNotEncrypted) + } catch { + throw SwError(.invalidKey) + } + } + } + + open class PublicKey { + + public static func pemToPKCS1DER(_ pemKey: String) throws -> Data { + guard let derKey = try? PEM.PublicKey.toDER(pemKey) else { + throw SwError(.invalidKey) + } + guard let pkcs1DERKey = PKCS8.PublicKey.stripHeaderIfAny(derKey) else { + throw SwError(.invalidKey) + } + return pkcs1DERKey + } + + public static func pemToPKCS8DER(_ pemKey: String) throws -> Data { + guard let derKey = try? PEM.PublicKey.toDER(pemKey) else { + throw SwError(.invalidKey) + } + return derKey + } + + public static func derToPKCS1PEM(_ derKey: Data) -> String { + return PEM.PublicKey.toPEM(derKey) + } + + public static func derToPKCS8PEM(_ derKey: Data) -> String { + let pkcs8Key = PKCS8.PublicKey.addHeader(derKey) + return PEM.PublicKey.toPEM(pkcs8Key) + } + + } + +} + +open class PKCS8 { + + open class PrivateKey { + + // https://lapo.it/asn1js/ + public static func getPKCS1DEROffset(_ derKey: Data) -> Int? { + let bytes = derKey.bytesView + + var offset = 0 + guard bytes.length > offset else { return nil } + guard bytes[offset] == 0x30 else { return nil } + + offset += 1 + + guard bytes.length > offset else { return nil } + if bytes[offset] > 0x80 { + offset += Int(bytes[offset]) - 0x80 + } + offset += 1 + + guard bytes.length > offset else { return nil } + guard bytes[offset] == 0x02 else { return nil } + + offset += 3 + + // without PKCS8 header + guard bytes.length > offset else { return nil } + if bytes[offset] == 0x02 { + return 0 + } + + let OID: [UInt8] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00] + + guard bytes.length > offset + OID.count else { return nil } + let slice = derKey.bytesViewRange(NSRange(location: offset, length: OID.count)) + + guard OID.elementsEqual(slice) else { return nil } + + offset += OID.count + + guard bytes.length > offset else { return nil } + guard bytes[offset] == 0x04 else { return nil } + + offset += 1 + + guard bytes.length > offset else { return nil } + if bytes[offset] > 0x80 { + offset += Int(bytes[offset]) - 0x80 + } + offset += 1 + + guard bytes.length > offset else { return nil } + guard bytes[offset] == 0x30 else { return nil } + + return offset + } + + public static func stripHeaderIfAny(_ derKey: Data) -> Data? { + guard let offset = getPKCS1DEROffset(derKey) else { + return nil + } + return derKey.subdata(in: offset.. Bool { + return getPKCS1DEROffset(derKey) != nil + } + + } + + open class PublicKey { + + public static func addHeader(_ derKey: Data) -> Data { + var result = Data() + + let encodingLength: Int = encodedOctets(derKey.count + 1).count + let OID: [UInt8] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00] + + var builder: [UInt8] = [] + + // ASN.1 SEQUENCE + builder.append(0x30) + + // Overall size, made of OID + bitstring encoding + actual key + let size = OID.count + 2 + encodingLength + derKey.count + let encodedSize = encodedOctets(size) + builder.append(contentsOf: encodedSize) + result.append(builder, count: builder.count) + result.append(OID, count: OID.count) + builder.removeAll(keepingCapacity: false) + + builder.append(0x03) + builder.append(contentsOf: encodedOctets(derKey.count + 1)) + builder.append(0x00) + result.append(builder, count: builder.count) + + // Actual key bytes + result.append(derKey) + + return result + } + + // https://lapo.it/asn1js/ + public static func getPKCS1DEROffset(_ derKey: Data) -> Int? { + let bytes = derKey.bytesView + + var offset = 0 + guard bytes.length > offset else { return nil } + guard bytes[offset] == 0x30 else { return nil } + + offset += 1 + + guard bytes.length > offset else { return nil } + if bytes[offset] > 0x80 { + offset += Int(bytes[offset]) - 0x80 + } + offset += 1 + + // without PKCS8 header + guard bytes.length > offset else { return nil } + if bytes[offset] == 0x02 { + return 0 + } + + let OID: [UInt8] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00] + + guard bytes.length > offset + OID.count else { return nil } + let slice = derKey.bytesViewRange(NSRange(location: offset, length: OID.count)) + + guard OID.elementsEqual(slice) else { return nil } + offset += OID.count + + // Type + guard bytes.length > offset else { return nil } + guard bytes[offset] == 0x03 else { return nil } + + offset += 1 + + guard bytes.length > offset else { return nil } + if bytes[offset] > 0x80 { + offset += Int(bytes[offset]) - 0x80 + } + offset += 1 + + // Contents should be separated by a null from the header + guard bytes.length > offset else { return nil } + guard bytes[offset] == 0x00 else { return nil } + + offset += 1 + guard bytes.length > offset else { return nil } + + return offset + } + + public static func stripHeaderIfAny(_ derKey: Data) -> Data? { + guard let offset = getPKCS1DEROffset(derKey) else { + return nil + } + return derKey.subdata(in: offset.. Bool { + return getPKCS1DEROffset(derKey) != nil + } + + fileprivate static func encodedOctets(_ int: Int) -> [UInt8] { + // Short form + if int < 128 { + return [UInt8(int)] + } + + // Long form + let i = (int / 256) + 1 + var len = int + var result: [UInt8] = [UInt8(i + 0x80)] + + for _ in 0..> 8 + } + + return result + } + } +} + +open class PEM { + + public enum SwError: Error { + case parse(String) + case badPassphrase + case keyNotEncrypted + + public static var debugLevel = 1 + + init(_ type: SwError, function: String = #function, file: String = #file, line: Int = #line) { + self = type + if SwError.debugLevel > 0 { + print("\(file):\(line): [\(function)] \(self._domain): \(self)") + } + } + } + + open class PrivateKey { + + public static func toDER(_ pemKey: String) throws -> Data { + guard let strippedKey = stripHeader(pemKey) else { + throw SwError(.parse("header")) + } + guard let data = PEM.base64Decode(strippedKey) else { + throw SwError(.parse("base64decode")) + } + return data + } + + public static func toPEM(_ derKey: Data) -> String { + let base64 = PEM.base64Encode(derKey) + return addRSAHeader(base64) + } + + fileprivate static let prefix = "-----BEGIN PRIVATE KEY-----\n" + fileprivate static let suffix = "\n-----END PRIVATE KEY-----" + fileprivate static let rsaPrefix = "-----BEGIN RSA PRIVATE KEY-----\n" + fileprivate static let rsaSuffix = "\n-----END RSA PRIVATE KEY-----" + + fileprivate static func addHeader(_ base64: String) -> String { + return prefix + base64 + suffix + } + + fileprivate static func addRSAHeader(_ base64: String) -> String { + return rsaPrefix + base64 + rsaSuffix + } + + fileprivate static func stripHeader(_ pemKey: String) -> String? { + return PEM.stripHeaderFooter(pemKey, header: prefix, footer: suffix) ?? + PEM.stripHeaderFooter(pemKey, header: rsaPrefix, footer: rsaSuffix) + } + } + + open class PublicKey { + + public static func toDER(_ pemKey: String) throws -> Data { + guard let strippedKey = stripHeader(pemKey) else { + throw SwError(.parse("header")) + } + guard let data = PEM.base64Decode(strippedKey) else { + throw SwError(.parse("base64decode")) + } + return data + } + + public static func toPEM(_ derKey: Data) -> String { + let base64 = PEM.base64Encode(derKey) + return addHeader(base64) + } + + fileprivate static let pemPrefix = "-----BEGIN PUBLIC KEY-----\n" + fileprivate static let pemSuffix = "\n-----END PUBLIC KEY-----" + + fileprivate static func addHeader(_ base64: String) -> String { + return pemPrefix + base64 + pemSuffix + } + + fileprivate static func stripHeader(_ pemKey: String) -> String? { + return PEM.stripHeaderFooter(pemKey, header: pemPrefix, footer: pemSuffix) + } + } + + // OpenSSL PKCS#1 compatible encrypted private key + open class EncryptedPrivateKey { + + public enum EncMode { + case aes128CBC, aes256CBC + } + + public static func toDER(_ pemKey: String, passphrase: String) throws -> Data { + guard let strippedKey = PrivateKey.stripHeader(pemKey) else { + throw SwError(.parse("header")) + } + guard let mode = getEncMode(strippedKey) else { + throw SwError(.keyNotEncrypted) + } + guard let iv = getIV(strippedKey) else { + throw SwError(.parse("iv")) + } + let aesKey = getAESKey(mode, passphrase: passphrase, iv: iv) + let base64Data = String(strippedKey[strippedKey.index(strippedKey.startIndex, offsetBy: aesHeaderLength)...]) + guard let data = PEM.base64Decode(base64Data) else { + throw SwError(.parse("base64decode")) + } + guard let decrypted = try? decryptKey(data, key: aesKey, iv: iv) else { + throw SwError(.badPassphrase) + } + guard PKCS8.PrivateKey.hasCorrectHeader(decrypted) else { + throw SwError(.badPassphrase) + } + return decrypted + } + + public static func toPEM(_ derKey: Data, passphrase: String, mode: EncMode) -> String { + let iv = CC.generateRandom(16) + let aesKey = getAESKey(mode, passphrase: passphrase, iv: iv) + let encrypted = encryptKey(derKey, key: aesKey, iv: iv) + let encryptedDERKey = addEncryptHeader(encrypted, iv: iv, mode: mode) + return PrivateKey.addRSAHeader(encryptedDERKey) + } + + fileprivate static let aes128CBCInfo = "Proc-Type: 4,ENCRYPTED\nDEK-Info: AES-128-CBC," + fileprivate static let aes256CBCInfo = "Proc-Type: 4,ENCRYPTED\nDEK-Info: AES-256-CBC," + fileprivate static let aesInfoLength = aes128CBCInfo.count + fileprivate static let aesIVInHexLength = 32 + fileprivate static let aesHeaderLength = aesInfoLength + aesIVInHexLength + + fileprivate static func addEncryptHeader(_ key: Data, iv: Data, mode: EncMode) -> String { + return getHeader(mode) + iv.hexadecimalString() + "\n\n" + PEM.base64Encode(key) + } + + fileprivate static func getHeader(_ mode: EncMode) -> String { + switch mode { + case .aes128CBC: return aes128CBCInfo + case .aes256CBC: return aes256CBCInfo + } + } + + fileprivate static func getEncMode(_ strippedKey: String) -> EncMode? { + if strippedKey.hasPrefix(aes128CBCInfo) { + return .aes128CBC + } + if strippedKey.hasPrefix(aes256CBCInfo) { + return .aes256CBC + } + return nil + } + + fileprivate static func getIV(_ strippedKey: String) -> Data? { + let ivInHex = String(strippedKey[strippedKey.index(strippedKey.startIndex, offsetBy: aesInfoLength) ..< strippedKey.index(strippedKey.startIndex, offsetBy: aesHeaderLength)]) + return ivInHex.dataFromHexadecimalString() + } + + fileprivate static func getAESKey(_ mode: EncMode, passphrase: String, iv: Data) -> Data { + switch mode { + case .aes128CBC: return getAES128Key(passphrase, iv: iv) + case .aes256CBC: return getAES256Key(passphrase, iv: iv) + } + } + + fileprivate static func getAES128Key(_ passphrase: String, iv: Data) -> Data { + // 128bit_Key = MD5(Passphrase + Salt) + let pass = passphrase.data(using: String.Encoding.utf8)! + let salt = iv.subdata(in: 0..<8) + + var key = pass + key.append(salt) + return CC.digest(key, alg: .md5) + } + + fileprivate static func getAES256Key(_ passphrase: String, iv: Data) -> Data { + // 128bit_Key = MD5(Passphrase + Salt) + // 256bit_Key = 128bit_Key + MD5(128bit_Key + Passphrase + Salt) + let pass = passphrase.data(using: String.Encoding.utf8)! + let salt = iv.subdata(in: 0 ..< 8) + + var first = pass + first.append(salt) + let aes128Key = CC.digest(first, alg: .md5) + + var sec = aes128Key + sec.append(pass) + sec.append(salt) + + var aes256Key = aes128Key + aes256Key.append(CC.digest(sec, alg: .md5)) + return aes256Key + } + + fileprivate static func encryptKey(_ data: Data, key: Data, iv: Data) -> Data { + return try! CC.crypt( + .encrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, + data: data, key: key, iv: iv) + } + + fileprivate static func decryptKey(_ data: Data, key: Data, iv: Data) throws -> Data { + return try CC.crypt( + .decrypt, blockMode: .cbc, algorithm: .aes, padding: .pkcs7Padding, + data: data, key: key, iv: iv) + } + + } + + fileprivate static func stripHeaderFooter(_ data: String, header: String, footer: String) -> String? { + guard data.hasPrefix(header) else { + return nil + } + guard let r = data.range(of: footer) else { + return nil + } + return String(data[header.endIndex ..< r.lowerBound]) + } + + fileprivate static func base64Decode(_ base64Data: String) -> Data? { + return Data(base64Encoded: base64Data, options: [.ignoreUnknownCharacters]) + } + + fileprivate static func base64Encode(_ key: Data) -> String { + return key.base64EncodedString( + options: [.lineLength64Characters, .endLineWithLineFeed]) + } + +} + +open class CC { + + public typealias CCCryptorStatus = Int32 + public enum CCError: CCCryptorStatus, Error { + case paramError = -4300 + case bufferTooSmall = -4301 + case memoryFailure = -4302 + case alignmentError = -4303 + case decodeError = -4304 + case unimplemented = -4305 + case overflow = -4306 + case rngFailure = -4307 + case unspecifiedError = -4308 + case callSequenceError = -4309 + case keySizeError = -4310 + case invalidKey = -4311 + + public static var debugLevel = 1 + + init(_ status: CCCryptorStatus, function: String = #function, + file: String = #file, line: Int = #line) { + self = CCError(rawValue: status)! + if CCError.debugLevel > 0 { + print("\(file):\(line): [\(function)] \(self._domain): \(self) (\(self.rawValue))") + } + } + init(_ type: CCError, function: String = #function, file: String = #file, line: Int = #line) { + self = type + if CCError.debugLevel > 0 { + print("\(file):\(line): [\(function)] \(self._domain): \(self) (\(self.rawValue))") + } + } + } + + public static func generateRandom(_ size: Int) -> Data { + var data = Data(count: size) + data.withUnsafeMutableBytes { dataBytes -> Void in + _ = CCRandomGenerateBytes!(dataBytes.baseAddress!, size) + return + } + return data + } + + public typealias CCDigestAlgorithm = UInt32 + public enum DigestAlgorithm: CCDigestAlgorithm { + case none = 0 + case md5 = 3 + case rmd128 = 4, rmd160 = 5, rmd256 = 6, rmd320 = 7 + case sha1 = 8 + case sha224 = 9, sha256 = 10, sha384 = 11, sha512 = 12 + + var length: Int { + return CCDigestGetOutputSize!(self.rawValue) + } + } + + public static func digest(_ data: Data, alg: DigestAlgorithm) -> Data { + var output = Data(count: alg.length) + withUnsafePointers(data, &output, { dataBytes, outputBytes in + _ = CCDigest!(alg.rawValue, + dataBytes, + data.count, + outputBytes) + }) + return output + } + + public typealias CCHmacAlgorithm = UInt32 + public enum HMACAlg: CCHmacAlgorithm { + case sha1, md5, sha256, sha384, sha512, sha224 + + var digestLength: Int { + switch self { + case .sha1: return 20 + case .md5: return 16 + case .sha256: return 32 + case .sha384: return 48 + case .sha512: return 64 + case .sha224: return 28 + } + } + } + + public static func HMAC(_ data: Data, alg: HMACAlg, key: Data) -> Data { + var buffer = Data(count: alg.digestLength) + withUnsafePointers(key, data, &buffer, { keyBytes, dataBytes, bufferBytes in + CCHmac!(alg.rawValue, + keyBytes, key.count, + dataBytes, data.count, + bufferBytes) + }) + return buffer + } + + public typealias CCOperation = UInt32 + public enum OpMode: CCOperation { + case encrypt = 0, decrypt + } + + public typealias CCMode = UInt32 + public enum BlockMode: CCMode { + case ecb = 1, cbc, cfb, ctr, f8, lrw, ofb, xts, rc4, cfb8 + var needIV: Bool { + switch self { + case .cbc, .cfb, .ctr, .ofb, .cfb8: return true + default: return false + } + } + } + + public enum AuthBlockMode: CCMode { + case gcm = 11, ccm + } + + public typealias CCAlgorithm = UInt32 + public enum Algorithm: CCAlgorithm { + case aes = 0, des, threeDES, cast, rc4, rc2, blowfish + + var blockSize: Int? { + switch self { + case .aes: return 16 + case .des: return 8 + case .threeDES: return 8 + case .cast: return 8 + case .rc2: return 8 + case .blowfish: return 8 + default: return nil + } + } + } + + public typealias CCPadding = UInt32 + public enum Padding: CCPadding { + case noPadding = 0, pkcs7Padding + } + + public static func crypt(_ opMode: OpMode, blockMode: BlockMode, + algorithm: Algorithm, padding: Padding, + data: Data, key: Data, iv: Data) throws -> Data { + if blockMode.needIV { + guard iv.count == algorithm.blockSize else { throw CCError(.paramError) } + } + + var cryptor: CCCryptorRef? = nil + var status = withUnsafePointers(iv, key, { ivBytes, keyBytes in + return CCCryptorCreateWithMode!( + opMode.rawValue, blockMode.rawValue, + algorithm.rawValue, padding.rawValue, + ivBytes, keyBytes, key.count, + nil, 0, 0, + CCModeOptions(), &cryptor) + }) + + guard status == noErr else { throw CCError(status) } + + defer { _ = CCCryptorRelease!(cryptor!) } + + let needed = CCCryptorGetOutputLength!(cryptor!, data.count, true) + var result = Data(count: needed) + let rescount = result.count + var updateLen: size_t = 0 + status = withUnsafePointers(data, &result, { dataBytes, resultBytes in + return CCCryptorUpdate!( + cryptor!, + dataBytes, data.count, + resultBytes, rescount, + &updateLen) + }) + guard status == noErr else { throw CCError(status) } + + + var finalLen: size_t = 0 + status = result.withUnsafeMutableBytes { resultBytes -> OSStatus in + return CCCryptorFinal!( + cryptor!, + resultBytes.baseAddress! + updateLen, + rescount - updateLen, + &finalLen) + } + guard status == noErr else { throw CCError(status) } + + + result.count = updateLen + finalLen + return result + } + + // The same behaviour as in the CCM pdf + // http://csrc.nist.gov/publications/nistpubs/800-38C/SP800-38C_updated-July20_2007.pdf + public static func cryptAuth(_ opMode: OpMode, blockMode: AuthBlockMode, algorithm: Algorithm, + data: Data, aData: Data, + key: Data, iv: Data, tagLength: Int) throws -> Data { + let cryptFun = blockMode == .gcm ? GCM.crypt : CCM.crypt + if opMode == .encrypt { + let (cipher, tag) = try cryptFun(opMode, algorithm, data, + key, iv, aData, tagLength) + var result = cipher + result.append(tag) + return result + } else { + let cipher = data.subdata(in: 0..<(data.count - tagLength)) + let tag = data.subdata( + in: (data.count - tagLength).. Bool { + return CCDigest != nil && + CCDigestGetOutputSize != nil + } + + public static func randomAvailable() -> Bool { + return CCRandomGenerateBytes != nil + } + + public static func hmacAvailable() -> Bool { + return CCHmac != nil + } + + public static func cryptorAvailable() -> Bool { + return CCCryptorCreateWithMode != nil && + CCCryptorGetOutputLength != nil && + CCCryptorUpdate != nil && + CCCryptorFinal != nil && + CCCryptorRelease != nil + } + + public static func available() -> Bool { + return digestAvailable() && + randomAvailable() && + hmacAvailable() && + cryptorAvailable() && + KeyDerivation.available() && + KeyWrap.available() && + RSA.available() && + DH.available() && + EC.available() && + CRC.available() && + CMAC.available() && + GCM.available() && + CCM.available() + } + + fileprivate typealias CCCryptorRef = UnsafeRawPointer + fileprivate typealias CCRNGStatus = CCCryptorStatus + fileprivate typealias CC_LONG = UInt32 + fileprivate typealias CCModeOptions = UInt32 + + fileprivate typealias CCRandomGenerateBytesT = @convention(c) ( + _ bytes: UnsafeMutableRawPointer, + _ count: size_t) -> CCRNGStatus + fileprivate typealias CCDigestGetOutputSizeT = @convention(c) ( + _ algorithm: CCDigestAlgorithm) -> size_t + fileprivate typealias CCDigestT = @convention(c) ( + _ algorithm: CCDigestAlgorithm, + _ data: UnsafeRawPointer, + _ dataLen: size_t, + _ output: UnsafeMutableRawPointer) -> CInt + + fileprivate typealias CCHmacT = @convention(c) ( + _ algorithm: CCHmacAlgorithm, + _ key: UnsafeRawPointer, + _ keyLength: Int, + _ data: UnsafeRawPointer, + _ dataLength: Int, + _ macOut: UnsafeMutableRawPointer) -> Void + fileprivate typealias CCCryptorCreateWithModeT = @convention(c)( + _ op: CCOperation, + _ mode: CCMode, + _ alg: CCAlgorithm, + _ padding: CCPadding, + _ iv: UnsafeRawPointer?, + _ key: UnsafeRawPointer, _ keyLength: Int, + _ tweak: UnsafeRawPointer?, _ tweakLength: Int, + _ numRounds: Int32, _ options: CCModeOptions, + _ cryptorRef: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate typealias CCCryptorGetOutputLengthT = @convention(c)( + _ cryptorRef: CCCryptorRef, + _ inputLength: size_t, + _ final: Bool) -> size_t + fileprivate typealias CCCryptorUpdateT = @convention(c)( + _ cryptorRef: CCCryptorRef, + _ dataIn: UnsafeRawPointer, + _ dataInLength: Int, + _ dataOut: UnsafeMutableRawPointer, + _ dataOutAvailable: Int, + _ dataOutMoved: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate typealias CCCryptorFinalT = @convention(c)( + _ cryptorRef: CCCryptorRef, + _ dataOut: UnsafeMutableRawPointer, + _ dataOutAvailable: Int, + _ dataOutMoved: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate typealias CCCryptorReleaseT = @convention(c) + (_ cryptorRef: CCCryptorRef) -> CCCryptorStatus + + + fileprivate static let dl = dlopen("/usr/lib/system/libcommonCrypto.dylib", RTLD_NOW) + fileprivate static let CCRandomGenerateBytes: CCRandomGenerateBytesT? = + getFunc(dl!, f: "CCRandomGenerateBytes") + fileprivate static let CCDigestGetOutputSize: CCDigestGetOutputSizeT? = + getFunc(dl!, f: "CCDigestGetOutputSize") + fileprivate static let CCDigest: CCDigestT? = getFunc(dl!, f: "CCDigest") + fileprivate static let CCHmac: CCHmacT? = getFunc(dl!, f: "CCHmac") + fileprivate static let CCCryptorCreateWithMode: CCCryptorCreateWithModeT? = + getFunc(dl!, f: "CCCryptorCreateWithMode") + fileprivate static let CCCryptorGetOutputLength: CCCryptorGetOutputLengthT? = + getFunc(dl!, f: "CCCryptorGetOutputLength") + fileprivate static let CCCryptorUpdate: CCCryptorUpdateT? = + getFunc(dl!, f: "CCCryptorUpdate") + fileprivate static let CCCryptorFinal: CCCryptorFinalT? = + getFunc(dl!, f: "CCCryptorFinal") + fileprivate static let CCCryptorRelease: CCCryptorReleaseT? = + getFunc(dl!, f: "CCCryptorRelease") + + open class GCM { + + public static func crypt(_ opMode: OpMode, algorithm: Algorithm, data: Data, + key: Data, iv: Data, + aData: Data, tagLength: Int) throws -> (Data, Data) { + var result = Data(count: data.count) + var tagLength_ = tagLength + var tag = Data(count: tagLength) + let status = withUnsafePointers(key, iv, aData, data, &result, &tag, { + keyBytes, ivBytes, aDataBytes, dataBytes, resultBytes, tagBytes in + return CCCryptorGCM!(opMode.rawValue, algorithm.rawValue, + keyBytes, key.count, ivBytes, iv.count, + aDataBytes, aData.count, + dataBytes, data.count, + resultBytes, tagBytes, &tagLength_) + }) + guard status == noErr else { throw CCError(status) } + + tag.count = tagLength_ + return (result, tag) + } + + public static func available() -> Bool { + if CCCryptorGCM != nil { + return true + } + return false + } + + fileprivate typealias CCCryptorGCMT = @convention(c) (_ op: CCOperation, _ alg: CCAlgorithm, + _ key: UnsafeRawPointer, _ keyLength: Int, + _ iv: UnsafeRawPointer, _ ivLen: Int, + _ aData: UnsafeRawPointer, _ aDataLen: Int, + _ dataIn: UnsafeRawPointer, _ dataInLength: Int, + _ dataOut: UnsafeMutableRawPointer, + _ tag: UnsafeRawPointer, _ tagLength: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCCryptorGCM: CCCryptorGCMT? = getFunc(dl!, f: "CCCryptorGCM") + + } + + open class CCM { + + public static func crypt(_ opMode: OpMode, algorithm: Algorithm, data: Data, + key: Data, iv: Data, + aData: Data, tagLength: Int) throws -> (Data, Data) { + var cryptor: CCCryptorRef? = nil + var status = key.withUnsafeBytes { keyBytes -> OSStatus in + CCCryptorCreateWithMode!( + opMode.rawValue, AuthBlockMode.ccm.rawValue, + algorithm.rawValue, Padding.noPadding.rawValue, + nil, keyBytes.baseAddress!, key.count, nil, 0, + 0, CCModeOptions(), &cryptor) + } + guard status == noErr else { throw CCError(status) } + defer { _ = CCCryptorRelease!(cryptor!) } + + status = CCCryptorAddParameter!(cryptor!, + Parameter.dataSize.rawValue, nil, data.count) + guard status == noErr else { throw CCError(status) } + + status = CCCryptorAddParameter!(cryptor!, Parameter.macSize.rawValue, nil, tagLength) + guard status == noErr else { throw CCError(status) } + + status = iv.withUnsafeBytes { ivBytes -> OSStatus in + CCCryptorAddParameter!(cryptor!, Parameter.iv.rawValue, ivBytes.baseAddress!, iv.count) + } + guard status == noErr else { throw CCError(status) } + + status = aData.withUnsafeBytes { aDataBytes -> OSStatus in + CCCryptorAddParameter!(cryptor!, Parameter.authData.rawValue, aDataBytes.baseAddress!, aData.count) + } + guard status == noErr else { throw CCError(status) } + + var result = Data(count: data.count) + let rescount = result.count + var updateLen: size_t = 0 + status = withUnsafePointers(data, &result, { dataBytes, resultBytes in + return CCCryptorUpdate!( + cryptor!, dataBytes, data.count, + resultBytes, rescount, + &updateLen) + }) + guard status == noErr else { throw CCError(status) } + + var finalLen: size_t = 0 + status = result.withUnsafeMutableBytes { resultBytes -> OSStatus in + CCCryptorFinal!(cryptor!, resultBytes.baseAddress! + updateLen, + rescount - updateLen, + &finalLen) + } + guard status == noErr else { throw CCError(status) } + + result.count = updateLen + finalLen + + var tagLength_ = tagLength + var tag = Data(count: tagLength) + status = tag.withUnsafeMutableBytes { tagBytes -> OSStatus in + CCCryptorGetParameter!(cryptor!, Parameter.authTag.rawValue, + tagBytes.baseAddress!, &tagLength_) + } + guard status == noErr else { throw CCError(status) } + + tag.count = tagLength_ + + return (result, tag) + } + + public static func available() -> Bool { + if CCCryptorAddParameter != nil && + CCCryptorGetParameter != nil { + return true + } + return false + } + + fileprivate typealias CCParameter = UInt32 + fileprivate enum Parameter: CCParameter { + case iv, authData, macSize, dataSize, authTag + } + fileprivate typealias CCCryptorAddParameterT = @convention(c) (_ cryptorRef: CCCryptorRef, + _ parameter: CCParameter, + _ data: UnsafeRawPointer?, _ dataLength: size_t) -> CCCryptorStatus + fileprivate static let CCCryptorAddParameter: CCCryptorAddParameterT? = + getFunc(dl!, f: "CCCryptorAddParameter") + + fileprivate typealias CCCryptorGetParameterT = @convention(c) (_ cryptorRef: CCCryptorRef, + _ parameter: CCParameter, + _ data: UnsafeRawPointer, _ dataLength: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCCryptorGetParameter: CCCryptorGetParameterT? = + getFunc(dl!, f: "CCCryptorGetParameter") + } + + open class RSA { + + public typealias CCAsymmetricPadding = UInt32 + + public enum AsymmetricPadding: CCAsymmetricPadding { + case pkcs1 = 1001 + case oaep = 1002 + } + + public enum AsymmetricSAPadding: UInt32 { + case pkcs15 = 1001 + case pss = 1002 + } + + public static func generateKeyPair(_ keySize: Int = 4096) throws -> (Data, Data) { + var privateKey: CCRSACryptorRef? = nil + var publicKey: CCRSACryptorRef? = nil + let status = CCRSACryptorGeneratePair!( + keySize, + 65537, + &publicKey, + &privateKey) + guard status == noErr else { throw CCError(status) } + + defer { + CCRSACryptorRelease!(privateKey!) + CCRSACryptorRelease!(publicKey!) + } + + let privDERKey = try exportToDERKey(privateKey!) + let pubDERKey = try exportToDERKey(publicKey!) + + return (privDERKey, pubDERKey) + } + + public static func getPublicKeyFromPrivateKey(_ derKey: Data) throws -> Data { + let key = try importFromDERKey(derKey) + defer { CCRSACryptorRelease!(key) } + + guard getKeyType(key) == .privateKey else { throw CCError(.paramError) } + + let publicKey = CCRSACryptorGetPublicKeyFromPrivateKey!(key) + defer { CCRSACryptorRelease!(publicKey) } + + let pubDERKey = try exportToDERKey(publicKey) + + return pubDERKey + } + + public static func encrypt(_ data: Data, derKey: Data, tag: Data, + padding: AsymmetricPadding, + digest: DigestAlgorithm) throws -> Data { + let key = try importFromDERKey(derKey) + defer { CCRSACryptorRelease!(key) } + + var bufferSize = getKeySize(key) + var buffer = Data(count: bufferSize) + + let status = withUnsafePointers(data, tag, &buffer, { + dataBytes, tagBytes, bufferBytes in + return CCRSACryptorEncrypt!( + key, + padding.rawValue, + dataBytes, data.count, + bufferBytes, &bufferSize, + tagBytes, tag.count, + digest.rawValue) + }) + guard status == noErr else { throw CCError(status) } + + buffer.count = bufferSize + + return buffer + } + + public static func decrypt(_ data: Data, derKey: Data, tag: Data, + padding: AsymmetricPadding, + digest: DigestAlgorithm) throws -> (Data, Int) { + let key = try importFromDERKey(derKey) + defer { CCRSACryptorRelease!(key) } + + let blockSize = getKeySize(key) + + var bufferSize = blockSize + var buffer = Data(count: bufferSize) + + let status = withUnsafePointers(data, tag, &buffer, { + dataBytes, tagBytes, bufferBytes in + return CCRSACryptorDecrypt!( + key, + padding.rawValue, + dataBytes, data.count, + bufferBytes, &bufferSize, + tagBytes, tag.count, + digest.rawValue) + }) + guard status == noErr else { throw CCError(status) } + buffer.count = bufferSize + + return (buffer, blockSize) + } + + fileprivate static func importFromDERKey(_ derKey: Data) throws -> CCRSACryptorRef { + var key: CCRSACryptorRef? = nil + let status = derKey.withUnsafeBytes { derKeyBytes -> OSStatus in + CCRSACryptorImport!( + derKeyBytes.baseAddress!, + derKey.count, + &key) + } + guard status == noErr else { throw CCError(status) } + + return key! + } + + fileprivate static func exportToDERKey(_ key: CCRSACryptorRef) throws -> Data { + var derKeyLength = 8192 + var derKey = Data(count: derKeyLength) + let status = derKey.withUnsafeMutableBytes { derKeyBytes -> OSStatus in + CCRSACryptorExport!(key, derKeyBytes.baseAddress!, &derKeyLength) + } + guard status == noErr else { throw CCError(status) } + + derKey.count = derKeyLength + return derKey + } + + fileprivate static func getKeyType(_ key: CCRSACryptorRef) -> KeyType { + return KeyType(rawValue: CCRSAGetKeyType!(key))! + } + + fileprivate static func getKeySize(_ key: CCRSACryptorRef) -> Int { + return Int(CCRSAGetKeySize!(key)/8) + } + + public static func sign(_ message: Data, derKey: Data, padding: AsymmetricSAPadding, + digest: DigestAlgorithm, saltLen: Int) throws -> Data { + let key = try importFromDERKey(derKey) + defer { CCRSACryptorRelease!(key) } + guard getKeyType(key) == .privateKey else { throw CCError(.paramError) } + + let keySize = getKeySize(key) + + switch padding { + case .pkcs15: + let hash = CC.digest(message, alg: digest) + var signedDataLength = keySize + var signedData = Data(count:signedDataLength) + let status = withUnsafePointers(hash, &signedData, { + hashBytes, signedDataBytes in + return CCRSACryptorSign!( + key, + AsymmetricPadding.pkcs1.rawValue, + hashBytes, hash.count, + digest.rawValue, 0 /*unused*/, + signedDataBytes, &signedDataLength) + }) + guard status == noErr else { throw CCError(status) } + + signedData.count = signedDataLength + return signedData + case .pss: + let encMessage = try add_pss_padding( + digest, + saltLength: saltLen, + keyLength: keySize, + message: message) + return try crypt(encMessage, key: key) + } + } + + public static func verify(_ message: Data, derKey: Data, padding: AsymmetricSAPadding, + digest: DigestAlgorithm, saltLen: Int, + signedData: Data) throws -> Bool { + let key = try importFromDERKey(derKey) + defer { CCRSACryptorRelease!(key) } + guard getKeyType(key) == .publicKey else { throw CCError(.paramError) } + + let keySize = getKeySize(key) + + switch padding { + case .pkcs15: + let hash = CC.digest(message, alg: digest) + let status = withUnsafePointers(hash, signedData, { + hashBytes, signedDataBytes in + return CCRSACryptorVerify!( + key, + padding.rawValue, + hashBytes, hash.count, + digest.rawValue, 0 /*unused*/, + signedDataBytes, signedData.count) + }) + let kCCNotVerified: CCCryptorStatus = -4306 + if status == kCCNotVerified { + return false + } + guard status == noErr else { throw CCError(status) } + return true + case .pss: + let encoded = try crypt(signedData, key:key) + return try verify_pss_padding( + digest, + saltLength: saltLen, + keyLength: keySize, + message: message, + encMessage: encoded) + } + } + + fileprivate static func crypt(_ data: Data, key: CCRSACryptorRef) throws -> Data { + var outLength = data.count + var out = Data(count: outLength) + + let status = withUnsafePointers(data, &out, { dataBytes, outBytes in + return CCRSACryptorCrypt!( + key, + dataBytes, data.count, + outBytes, &outLength) + }) + + guard status == noErr else { throw CCError(status) } + out.count = outLength + + return out + } + + fileprivate static func mgf1(_ digest: DigestAlgorithm, + seed: Data, maskLength: Int) -> Data { + var tseed = seed + tseed.append(contentsOf: [0,0,0,0] as [UInt8]) + + var interval = maskLength / digest.length + if maskLength % digest.length != 0 { + interval += 1 + } + + func pack(_ n: Int) -> [UInt8] { + return [ + UInt8(n>>24 & 0xff), + UInt8(n>>16 & 0xff), + UInt8(n>>8 & 0xff), + UInt8(n>>0 & 0xff) + ] + } + + var mask = Data() + for counter in 0 ..< interval { + tseed.replaceSubrange((tseed.count - 4) ..< tseed.count, with: pack(counter)) + mask.append(CC.digest(tseed, alg: digest)) + } + mask.count = maskLength + return mask + } + + fileprivate static func xorData(_ data1: Data, _ data2: Data) -> Data { + precondition(data1.count == data2.count) + + var ret = Data(count: data1.count) + let retcount = ret.count + withUnsafePointers(data1, data2, &ret, {( + b1: UnsafePointer, + b2: UnsafePointer, + r: UnsafeMutablePointer) in + for i in 0 ..< retcount { + r[i] = b1[i] ^ b2[i] + } + }) + return ret + } + + fileprivate static func add_pss_padding(_ digest: DigestAlgorithm, + saltLength: Int, + keyLength: Int, + message: Data) throws -> Data { + + if keyLength < 16 || saltLength < 0 { + throw CCError(.paramError) + } + + // The maximal bit size of a non-negative integer is one less than the bit + // size of the key since the first bit is used to store sign + let emBits = keyLength * 8 - 1 + var emLength = emBits / 8 + if emBits % 8 != 0 { + emLength += 1 + } + + let hash = CC.digest(message, alg: digest) + + if emLength < hash.count + saltLength + 2 { + throw CCError(.paramError) + } + + let salt = CC.generateRandom(saltLength) + + var mPrime = Data(count: 8) + mPrime.append(hash) + mPrime.append(salt) + let mPrimeHash = CC.digest(mPrime, alg: digest) + + let padding = Data(count: emLength - saltLength - hash.count - 2) + var db = padding + db.append([0x01] as [UInt8], count: 1) + db.append(salt) + let dbMask = mgf1(digest, seed: mPrimeHash, maskLength: emLength - hash.count - 1) + var maskedDB = xorData(db, dbMask) + + let zeroBits = 8 * emLength - emBits + maskedDB.withUnsafeMutableBytes { maskedDBBytes -> Void in + maskedDBBytes.bindMemory(to: UInt8.self).baseAddress![0] &= 0xff >> UInt8(zeroBits) + } + + var ret = maskedDB + ret.append(mPrimeHash) + ret.append([0xBC] as [UInt8], count: 1) + return ret + } + + fileprivate static func verify_pss_padding(_ digest: DigestAlgorithm, + saltLength: Int, keyLength: Int, + message: Data, + encMessage: Data) throws -> Bool { + if keyLength < 16 || saltLength < 0 { + throw CCError(.paramError) + } + + guard encMessage.count > 0 else { + return false + } + + let emBits = keyLength * 8 - 1 + var emLength = emBits / 8 + if emBits % 8 != 0 { + emLength += 1 + } + + let hash = CC.digest(message, alg: digest) + + if emLength < hash.count + saltLength + 2 { + return false + } + if encMessage.bytesView[encMessage.count-1] != 0xBC { + return false + } + let zeroBits = 8 * emLength - emBits + let zeroBitsM = 8 - zeroBits + let maskedDBLength = emLength - hash.count - 1 + let maskedDB = encMessage.subdata(in: 0..> zeroBitsM != 0 { + return false + } + let mPrimeHash = encMessage.subdata(in: maskedDBLength ..< maskedDBLength + hash.count) + let dbMask = mgf1(digest, seed: mPrimeHash, maskLength: emLength - hash.count - 1) + var db = xorData(maskedDB, dbMask) + db.withUnsafeMutableBytes { dbBytes -> Void in + dbBytes.bindMemory(to: UInt8.self).baseAddress![0] &= 0xff >> UInt8(zeroBits) + } + + let zeroLength = emLength - hash.count - saltLength - 2 + let zeroString = Data(count:zeroLength) + if db.subdata(in: 0 ..< zeroLength) != zeroString { + return false + } + if db.bytesView[zeroLength] != 0x01 { + return false + } + let salt = db.subdata(in: (db.count - saltLength) ..< db.count) + var mPrime = Data(count:8) + mPrime.append(hash) + mPrime.append(salt) + let mPrimeHash2 = CC.digest(mPrime, alg: digest) + if mPrimeHash != mPrimeHash2 { + return false + } + return true + } + + + public static func available() -> Bool { + return CCRSACryptorGeneratePair != nil && + CCRSACryptorGetPublicKeyFromPrivateKey != nil && + CCRSACryptorRelease != nil && + CCRSAGetKeyType != nil && + CCRSAGetKeySize != nil && + CCRSACryptorEncrypt != nil && + CCRSACryptorDecrypt != nil && + CCRSACryptorExport != nil && + CCRSACryptorImport != nil && + CCRSACryptorSign != nil && + CCRSACryptorVerify != nil && + CCRSACryptorCrypt != nil + } + + fileprivate typealias CCRSACryptorRef = UnsafeRawPointer + fileprivate typealias CCRSAKeyType = UInt32 + fileprivate enum KeyType: CCRSAKeyType { + case publicKey = 0, privateKey + case blankPublicKey = 97, blankPrivateKey + case badKey = 99 + } + + fileprivate typealias CCRSACryptorGeneratePairT = @convention(c) ( + _ keySize: Int, + _ e: UInt32, + _ publicKey: UnsafeMutablePointer, + _ privateKey: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCRSACryptorGeneratePair: CCRSACryptorGeneratePairT? = + getFunc(CC.dl!, f: "CCRSACryptorGeneratePair") + + fileprivate typealias CCRSACryptorGetPublicKeyFromPrivateKeyT = @convention(c) (CCRSACryptorRef) -> CCRSACryptorRef + fileprivate static let CCRSACryptorGetPublicKeyFromPrivateKey: CCRSACryptorGetPublicKeyFromPrivateKeyT? = + getFunc(CC.dl!, f: "CCRSACryptorGetPublicKeyFromPrivateKey") + + fileprivate typealias CCRSACryptorReleaseT = @convention(c) (CCRSACryptorRef) -> Void + fileprivate static let CCRSACryptorRelease: CCRSACryptorReleaseT? = + getFunc(dl!, f: "CCRSACryptorRelease") + + fileprivate typealias CCRSAGetKeyTypeT = @convention(c) (CCRSACryptorRef) -> CCRSAKeyType + fileprivate static let CCRSAGetKeyType: CCRSAGetKeyTypeT? = getFunc(dl!, f: "CCRSAGetKeyType") + + fileprivate typealias CCRSAGetKeySizeT = @convention(c) (CCRSACryptorRef) -> Int32 + fileprivate static let CCRSAGetKeySize: CCRSAGetKeySizeT? = getFunc(dl!, f: "CCRSAGetKeySize") + + fileprivate typealias CCRSACryptorEncryptT = @convention(c) ( + _ publicKey: CCRSACryptorRef, + _ padding: CCAsymmetricPadding, + _ plainText: UnsafeRawPointer, + _ plainTextLen: Int, + _ cipherText: UnsafeMutableRawPointer, + _ cipherTextLen: UnsafeMutablePointer, + _ tagData: UnsafeRawPointer, + _ tagDataLen: Int, + _ digestType: CCDigestAlgorithm) -> CCCryptorStatus + fileprivate static let CCRSACryptorEncrypt: CCRSACryptorEncryptT? = + getFunc(dl!, f: "CCRSACryptorEncrypt") + + fileprivate typealias CCRSACryptorDecryptT = @convention (c) ( + _ privateKey: CCRSACryptorRef, + _ padding: CCAsymmetricPadding, + _ cipherText: UnsafeRawPointer, + _ cipherTextLen: Int, + _ plainText: UnsafeMutableRawPointer, + _ plainTextLen: UnsafeMutablePointer, + _ tagData: UnsafeRawPointer, + _ tagDataLen: Int, + _ digestType: CCDigestAlgorithm) -> CCCryptorStatus + fileprivate static let CCRSACryptorDecrypt: CCRSACryptorDecryptT? = + getFunc(dl!, f: "CCRSACryptorDecrypt") + + fileprivate typealias CCRSACryptorExportT = @convention(c) ( + _ key: CCRSACryptorRef, + _ out: UnsafeMutableRawPointer, + _ outLen: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCRSACryptorExport: CCRSACryptorExportT? = + getFunc(dl!, f: "CCRSACryptorExport") + + fileprivate typealias CCRSACryptorImportT = @convention(c) ( + _ keyPackage: UnsafeRawPointer, + _ keyPackageLen: Int, + _ key: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCRSACryptorImport: CCRSACryptorImportT? = + getFunc(dl!, f: "CCRSACryptorImport") + + fileprivate typealias CCRSACryptorSignT = @convention(c) ( + _ privateKey: CCRSACryptorRef, + _ padding: CCAsymmetricPadding, + _ hashToSign: UnsafeRawPointer, + _ hashSignLen: size_t, + _ digestType: CCDigestAlgorithm, + _ saltLen: size_t, + _ signedData: UnsafeMutableRawPointer, + _ signedDataLen: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCRSACryptorSign: CCRSACryptorSignT? = + getFunc(dl!, f: "CCRSACryptorSign") + + fileprivate typealias CCRSACryptorVerifyT = @convention(c) ( + _ publicKey: CCRSACryptorRef, + _ padding: CCAsymmetricPadding, + _ hash: UnsafeRawPointer, + _ hashLen: size_t, + _ digestType: CCDigestAlgorithm, + _ saltLen: size_t, + _ signedData: UnsafeRawPointer, + _ signedDataLen: size_t) -> CCCryptorStatus + fileprivate static let CCRSACryptorVerify: CCRSACryptorVerifyT? = + getFunc(dl!, f: "CCRSACryptorVerify") + + fileprivate typealias CCRSACryptorCryptT = @convention(c) ( + _ rsaKey: CCRSACryptorRef, + _ data: UnsafeRawPointer, _ dataLength: size_t, + _ out: UnsafeMutableRawPointer, + _ outLength: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCRSACryptorCrypt: CCRSACryptorCryptT? = + getFunc(dl!, f: "CCRSACryptorCrypt") + } + + open class DH { + + public enum DHParam { + case rfc3526Group5 + case rfc2409Group2 + } + + // this is stateful in CommonCrypto too, sry + open class DH { + fileprivate var ref: CCDHRef? = nil + + public init(dhParam: DHParam) throws { + if dhParam == .rfc3526Group5 { + ref = CCDHCreate!(kCCDHRFC3526Group5!) + } else { + ref = CCDHCreate!(kCCDHRFC2409Group2!) + } + guard ref != nil else { + throw CCError(.paramError) + } + } + + open func generateKey() throws -> Data { + var outputLength = 8192 + var output = Data(count: outputLength) + let status = output.withUnsafeMutableBytes { outputBytes -> OSStatus in + CCDHGenerateKey!(ref!, outputBytes.baseAddress!, &outputLength) + } + output.count = outputLength + guard status != -1 else { + throw CCError(.paramError) + } + return output + } + + open func computeKey(_ peerKey: Data) throws -> Data { + var sharedKeyLength = 8192 + var sharedKey = Data(count: sharedKeyLength) + let status = withUnsafePointers(peerKey, &sharedKey, { + peerKeyBytes, sharedKeyBytes in + return CCDHComputeKey!( + sharedKeyBytes, &sharedKeyLength, + peerKeyBytes, peerKey.count, + ref!) + }) + sharedKey.count = sharedKeyLength + guard status == 0 else { + throw CCError(.paramError) + } + return sharedKey + } + + deinit { + if ref != nil { + CCDHRelease!(ref!) + } + } + } + + + public static func available() -> Bool { + return CCDHCreate != nil && + CCDHRelease != nil && + CCDHGenerateKey != nil && + CCDHComputeKey != nil && + CCDHParametersCreateFromData != nil && + CCDHParametersRelease != nil + } + + fileprivate typealias CCDHParameters = UnsafeRawPointer + fileprivate typealias CCDHRef = UnsafeRawPointer + + fileprivate typealias kCCDHRFC3526Group5TM = UnsafePointer + fileprivate static let kCCDHRFC3526Group5M: kCCDHRFC3526Group5TM? = + getFunc(dl!, f: "kCCDHRFC3526Group5") + fileprivate static let kCCDHRFC3526Group5 = kCCDHRFC3526Group5M?.pointee + + fileprivate typealias kCCDHRFC2409Group2TM = UnsafePointer + fileprivate static let kCCDHRFC2409Group2M: kCCDHRFC2409Group2TM? = + getFunc(dl!, f: "kCCDHRFC2409Group2") + fileprivate static let kCCDHRFC2409Group2 = kCCDHRFC2409Group2M?.pointee + + fileprivate typealias CCDHCreateT = @convention(c) ( + _ dhParameter: CCDHParameters) -> CCDHRef + fileprivate static let CCDHCreate: CCDHCreateT? = getFunc(dl!, f: "CCDHCreate") + + fileprivate typealias CCDHReleaseT = @convention(c) ( + _ ref: CCDHRef) -> Void + fileprivate static let CCDHRelease: CCDHReleaseT? = getFunc(dl!, f: "CCDHRelease") + + fileprivate typealias CCDHGenerateKeyT = @convention(c) ( + _ ref: CCDHRef, + _ output: UnsafeMutableRawPointer, _ outputLength: UnsafeMutablePointer) -> CInt + fileprivate static let CCDHGenerateKey: CCDHGenerateKeyT? = getFunc(dl!, f: "CCDHGenerateKey") + + fileprivate typealias CCDHComputeKeyT = @convention(c) ( + _ sharedKey: UnsafeMutableRawPointer, _ sharedKeyLen: UnsafeMutablePointer, + _ peerPubKey: UnsafeRawPointer, _ peerPubKeyLen: size_t, + _ ref: CCDHRef) -> CInt + fileprivate static let CCDHComputeKey: CCDHComputeKeyT? = getFunc(dl!, f: "CCDHComputeKey") + + fileprivate typealias CCDHParametersCreateFromDataT = @convention(c) ( + _ p: UnsafeRawPointer, _ pLen: Int, + _ g: UnsafeRawPointer, _ gLen: Int, + _ l: Int) -> CCDHParameters + fileprivate static let CCDHParametersCreateFromData: CCDHParametersCreateFromDataT? = getFunc(dl!, f: "CCDHParametersCreateFromData") + + fileprivate typealias CCDHParametersReleaseT = @convention(c) ( + _ parameters: CCDHParameters) -> Void + fileprivate static let CCDHParametersRelease: CCDHParametersReleaseT? = getFunc(dl!, f: "CCDHParametersRelease") + } + + open class EC { + + public static func generateKeyPair(_ keySize: Int) throws -> (Data, Data) { + var privKey: CCECCryptorRef? = nil + var pubKey: CCECCryptorRef? = nil + let status = CCECCryptorGeneratePair!( + keySize, + &pubKey, + &privKey) + guard status == noErr else { throw CCError(status) } + + defer { + CCECCryptorRelease!(privKey!) + CCECCryptorRelease!(pubKey!) + } + + let privKeyDER = try exportKey(privKey!, format: .binary, type: .keyPrivate) + let pubKeyDER = try exportKey(pubKey!, format: .binary, type: .keyPublic) + return (privKeyDER, pubKeyDER) + } + + public static func getPublicKeyFromPrivateKey(_ privateKey: Data) throws -> Data { + let privKey = try importKey(privateKey, format: .binary, keyType: .keyPrivate) + defer { CCECCryptorRelease!(privKey) } + + let pubKey = CCECCryptorGetPublicKeyFromPrivateKey!(privKey) + defer { CCECCryptorRelease!(pubKey) } + + let pubKeyDER = try exportKey(pubKey, format: .binary, type: .keyPublic) + return pubKeyDER + } + + public static func signHash(_ privateKey: Data, hash: Data) throws -> Data { + let privKey = try importKey(privateKey, format: .binary, keyType: .keyPrivate) + defer { CCECCryptorRelease!(privKey) } + + var signedDataLength = 4096 + var signedData = Data(count:signedDataLength) + let status = withUnsafePointers(hash, &signedData, { + hashBytes, signedDataBytes in + return CCECCryptorSignHash!( + privKey, + hashBytes, hash.count, + signedDataBytes, &signedDataLength) + }) + guard status == noErr else { throw CCError(status) } + + signedData.count = signedDataLength + return signedData + } + + public static func verifyHash(_ publicKey: Data, + hash: Data, + signedData: Data) throws -> Bool { + let pubKey = try importKey(publicKey, format: .binary, keyType: .keyPublic) + defer { CCECCryptorRelease!(pubKey) } + + var valid: UInt32 = 0 + let status = withUnsafePointers(hash, signedData, { hashBytes, signedDataBytes in + return CCECCryptorVerifyHash!( + pubKey, + hashBytes, hash.count, + signedDataBytes, signedData.count, + &valid) + }) + guard status == noErr else { throw CCError(status) } + + return valid != 0 + } + + public static func computeSharedSecret(_ privateKey: Data, + publicKey: Data) throws -> Data { + let privKey = try importKey(privateKey, format: .binary, keyType: .keyPrivate) + let pubKey = try importKey(publicKey, format: .binary, keyType: .keyPublic) + defer { + CCECCryptorRelease!(privKey) + CCECCryptorRelease!(pubKey) + } + + var outSize = 8192 + var result = Data(count:outSize) + let status = result.withUnsafeMutableBytes { resultBytes -> OSStatus in + CCECCryptorComputeSharedSecret!(privKey, pubKey, resultBytes.baseAddress!, &outSize) + } + guard status == noErr else { throw CCError(status) } + + result.count = outSize + return result + } + + public struct KeyComponents { + public init(_ keySize: Int, _ x: Data, _ y: Data, _ d: Data) { + self.keySize = keySize + self.x = x + self.y = y + self.d = d + } + public var keySize: Int + public var x: Data + public var y: Data + public var d: Data + } + + public static func getPublicKeyComponents(_ keyData: Data) throws -> KeyComponents { + let key = try importKey(keyData, format: .binary, keyType: .keyPublic) + defer { CCECCryptorRelease!(key) } + return try getKeyComponents(key) + } + + public static func getPrivateKeyComponents(_ keyData: Data) throws -> KeyComponents { + let key = try importKey(keyData, format: .binary, keyType: .keyPrivate) + defer { CCECCryptorRelease!(key) } + return try getKeyComponents(key) + } + + fileprivate static func getKeyComponents(_ key: CCECCryptorRef) throws -> KeyComponents { + var keySize = 0, xSize = 8192, ySize = 8192, dSize = 8192 + var x = Data(count: xSize), y = Data(count: ySize), d = Data(count: dSize) + let status = withUnsafePointers(&x, &y, &d, { xBytes, yBytes, dBytes in + return CCECCryptorGetKeyComponents!(key, &keySize, + xBytes, &xSize, + yBytes, &ySize, + dBytes, &dSize) + }) + guard status == noErr else { throw CCError(status) } + + x.count = xSize + y.count = ySize + d.count = dSize + if getKeyType(key) == .keyPublic { + d.count = 0 + } + + return KeyComponents(keySize, x, y, d) + } + + public static func createFromData(_ keySize: size_t, _ x: Data, _ y: Data) throws -> Data { + var pubKey: CCECCryptorRef? = nil + + let status = withUnsafePointers(x, y, { xBytes, yBytes in + return CCECCryptorCreateFromData!(keySize, xBytes, x.count, + yBytes, y.count, &pubKey) + }) + guard status == noErr else { throw CCError(status) } + defer { CCECCryptorRelease!(pubKey!) } + + let pubKeyBin = try exportKey(pubKey!, format: .binary, type: .keyPublic) + return pubKeyBin + } + + fileprivate static func importKey(_ key: Data, format: KeyExternalFormat, + keyType: KeyType) throws -> CCECCryptorRef { + var impKey: CCECCryptorRef? = nil + let status = key.withUnsafeBytes { keyBytes -> OSStatus in + CCECCryptorImportKey!(format.rawValue, + keyBytes.baseAddress!, key.count, + keyType.rawValue, &impKey) + } + guard status == noErr else { throw CCError(status) } + + return impKey! + } + + fileprivate static func exportKey(_ key: CCECCryptorRef, format: KeyExternalFormat, + type: KeyType) throws -> Data { + var expKeyLength = 8192 + var expKey = Data(count:expKeyLength) + let status = expKey.withUnsafeMutableBytes { expKeyBytes -> OSStatus in + CCECCryptorExportKey!( + format.rawValue, + expKeyBytes.baseAddress!, + &expKeyLength, + type.rawValue, + key) + } + guard status == noErr else { throw CCError(status) } + + expKey.count = expKeyLength + return expKey + } + + fileprivate static func getKeyType(_ key: CCECCryptorRef) -> KeyType { + return KeyType(rawValue: CCECGetKeyType!(key))! + } + + public static func available() -> Bool { + return CCECCryptorGeneratePair != nil && + CCECCryptorImportKey != nil && + CCECCryptorExportKey != nil && + CCECCryptorRelease != nil && + CCECCryptorSignHash != nil && + CCECCryptorVerifyHash != nil && + CCECCryptorComputeSharedSecret != nil && + CCECCryptorGetKeyComponents != nil && + CCECCryptorCreateFromData != nil && + CCECGetKeyType != nil && + CCECCryptorGetPublicKeyFromPrivateKey != nil + } + + fileprivate enum KeyType: CCECKeyType { + case keyPublic = 0, keyPrivate + case blankPublicKey = 97, blankPrivateKey + case badKey = 99 + } + fileprivate typealias CCECKeyType = UInt32 + + fileprivate typealias CCECKeyExternalFormat = UInt32 + fileprivate enum KeyExternalFormat: CCECKeyExternalFormat { + case binary = 0, der + } + + fileprivate typealias CCECCryptorRef = UnsafeRawPointer + fileprivate typealias CCECCryptorGeneratePairT = @convention(c) ( + _ keySize: size_t , + _ publicKey: UnsafeMutablePointer, + _ privateKey: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCECCryptorGeneratePair: CCECCryptorGeneratePairT? = + getFunc(dl!, f: "CCECCryptorGeneratePair") + + fileprivate typealias CCECCryptorImportKeyT = @convention(c) ( + _ format: CCECKeyExternalFormat, + _ keyPackage: UnsafeRawPointer, _ keyPackageLen: size_t, + _ keyType: CCECKeyType, _ key: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCECCryptorImportKey: CCECCryptorImportKeyT? = + getFunc(dl!, f: "CCECCryptorImportKey") + + fileprivate typealias CCECCryptorExportKeyT = @convention(c) ( + _ format: CCECKeyExternalFormat, + _ keyPackage: UnsafeRawPointer, + _ keyPackageLen: UnsafePointer, + _ keyType: CCECKeyType, _ key: CCECCryptorRef) -> CCCryptorStatus + fileprivate static let CCECCryptorExportKey: CCECCryptorExportKeyT? = + getFunc(dl!, f: "CCECCryptorExportKey") + + fileprivate typealias CCECCryptorReleaseT = @convention(c) ( + _ key: CCECCryptorRef) -> Void + fileprivate static let CCECCryptorRelease: CCECCryptorReleaseT? = + getFunc(dl!, f: "CCECCryptorRelease") + + fileprivate typealias CCECCryptorSignHashT = @convention(c)( + _ privateKey: CCECCryptorRef, + _ hashToSign: UnsafeRawPointer, + _ hashSignLen: size_t, + _ signedData: UnsafeMutableRawPointer, + _ signedDataLen: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCECCryptorSignHash: CCECCryptorSignHashT? = + getFunc(dl!, f: "CCECCryptorSignHash") + + fileprivate typealias CCECCryptorVerifyHashT = @convention(c)( + _ publicKey: CCECCryptorRef, + _ hash: UnsafeRawPointer, _ hashLen: size_t, + _ signedData: UnsafeRawPointer, _ signedDataLen: size_t, + _ valid: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCECCryptorVerifyHash: CCECCryptorVerifyHashT? = + getFunc(dl!, f: "CCECCryptorVerifyHash") + + fileprivate typealias CCECCryptorComputeSharedSecretT = @convention(c)( + _ privateKey: CCECCryptorRef, + _ publicKey: CCECCryptorRef, + _ out: UnsafeMutableRawPointer, + _ outLen: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCECCryptorComputeSharedSecret: CCECCryptorComputeSharedSecretT? = + getFunc(dl!, f: "CCECCryptorComputeSharedSecret") + + fileprivate typealias CCECCryptorGetKeyComponentsT = @convention(c)( + _ ecKey: CCECCryptorRef, + _ keySize: UnsafeMutablePointer, + _ qX: UnsafeMutableRawPointer, + _ qXLength: UnsafeMutablePointer, + _ qY: UnsafeMutableRawPointer, + _ qYLength: UnsafeMutablePointer, + _ d: UnsafeMutableRawPointer?, + _ dLength: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCECCryptorGetKeyComponents: CCECCryptorGetKeyComponentsT? = + getFunc(dl!, f: "CCECCryptorGetKeyComponents") + + fileprivate typealias CCECCryptorCreateFromDataT = @convention(c)( + _ keySize: size_t, + _ qX: UnsafeRawPointer, + _ qXLength: size_t, + _ qY: UnsafeRawPointer, + _ qYLength: size_t, + _ publicKey: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CCECCryptorCreateFromData: CCECCryptorCreateFromDataT? = + getFunc(dl!, f: "CCECCryptorCreateFromData") + + fileprivate typealias CCECGetKeyTypeT = @convention(c) ( + _ key: CCECCryptorRef) -> CCECKeyType + fileprivate static let CCECGetKeyType: CCECGetKeyTypeT? = + getFunc(dl!, f: "CCECGetKeyType") + + fileprivate typealias CCECCryptorGetPublicKeyFromPrivateKeyT = @convention(c) ( + _ key: CCECCryptorRef) -> CCECCryptorRef + fileprivate static let CCECCryptorGetPublicKeyFromPrivateKey: CCECCryptorGetPublicKeyFromPrivateKeyT? = + getFunc(dl!, f: "CCECCryptorGetPublicKeyFromPrivateKey") + } + + open class CRC { + + public typealias CNcrc = UInt32 + public enum Mode: CNcrc { + case crc8 = 10, + crc8ICODE = 11, + crc8ITU = 12, + crc8ROHC = 13, + crc8WCDMA = 14, + crc16 = 20, + crc16CCITTTrue = 21, + crc16CCITTFalse = 22, + crc16USB = 23, + crc16XMODEM = 24, + crc16DECTR = 25, + crc16DECTX = 26, + crc16ICODE = 27, + crc16VERIFONE = 28, + crc16A = 29, + crc16B = 30, + crc16Fletcher = 31, + crc32Adler = 40, + crc32 = 41, + crc32CASTAGNOLI = 42, + crc32BZIP2 = 43, + crc32MPEG2 = 44, + crc32POSIX = 45, + crc32XFER = 46, + crc64ECMA182 = 60 + } + + public static func crc(_ input: Data, mode: Mode) throws -> UInt64 { + var result: UInt64 = 0 + let status = input.withUnsafeBytes { inputBytes -> OSStatus in + CNCRC!( + mode.rawValue, + inputBytes.baseAddress!, input.count, + &result) + } + guard status == noErr else { + throw CCError(status) + } + return result + } + + public static func available() -> Bool { + return CNCRC != nil + } + + fileprivate typealias CNCRCT = @convention(c) ( + _ algorithm: CNcrc, + _ input: UnsafeRawPointer, _ inputLen: size_t, + _ result: UnsafeMutablePointer) -> CCCryptorStatus + fileprivate static let CNCRC: CNCRCT? = getFunc(dl!, f: "CNCRC") + } + + open class CMAC { + + public static func AESCMAC(_ data: Data, key: Data) -> Data { + var result = Data(count: 16) + withUnsafePointers(key, data, &result, { keyBytes, dataBytes, resultBytes in + CCAESCmac!(keyBytes, + dataBytes, data.count, + resultBytes) + }) + return result + } + + public static func available() -> Bool { + return CCAESCmac != nil + } + + fileprivate typealias CCAESCmacT = @convention(c) ( + _ key: UnsafeRawPointer, + _ data: UnsafeRawPointer, _ dataLen: size_t, + _ macOut: UnsafeMutableRawPointer) -> Void + fileprivate static let CCAESCmac: CCAESCmacT? = getFunc(dl!, f: "CCAESCmac") + } + + open class KeyDerivation { + + public typealias CCPseudoRandomAlgorithm = UInt32 + public enum PRFAlg: CCPseudoRandomAlgorithm { + case sha1 = 1, sha224, sha256, sha384, sha512 + var cc: CC.HMACAlg { + switch self { + case .sha1: return .sha1 + case .sha224: return .sha224 + case .sha256: return .sha256 + case .sha384: return .sha384 + case .sha512: return .sha512 + } + } + } + + public static func PBKDF2(_ password: String, salt: Data, + prf: PRFAlg, rounds: UInt32) throws -> Data { + + var result = Data(count:prf.cc.digestLength) + let rescount = result.count + let passwData = password.data(using: String.Encoding.utf8)! + let status = withUnsafePointers(passwData, salt, &result, { + passwDataBytes, saltBytes, resultBytes in + return CCKeyDerivationPBKDF!(PBKDFAlgorithm.pbkdf2.rawValue, + passwDataBytes, passwData.count, + saltBytes, salt.count, + prf.rawValue, rounds, + resultBytes, rescount) + }) + guard status == noErr else { throw CCError(status) } + + return result + } + + public static func available() -> Bool { + return CCKeyDerivationPBKDF != nil + } + + fileprivate typealias CCPBKDFAlgorithm = UInt32 + fileprivate enum PBKDFAlgorithm: CCPBKDFAlgorithm { + case pbkdf2 = 2 + } + + fileprivate typealias CCKeyDerivationPBKDFT = @convention(c) ( + _ algorithm: CCPBKDFAlgorithm, + _ password: UnsafeRawPointer, _ passwordLen: size_t, + _ salt: UnsafeRawPointer, _ saltLen: size_t, + _ prf: CCPseudoRandomAlgorithm, _ rounds: uint, + _ derivedKey: UnsafeMutableRawPointer, _ derivedKeyLen: size_t) -> CCCryptorStatus + fileprivate static let CCKeyDerivationPBKDF: CCKeyDerivationPBKDFT? = + getFunc(dl!, f: "CCKeyDerivationPBKDF") + } + + open class KeyWrap { + + fileprivate static let rfc3394IVData: [UInt8] = [0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6] + public static let rfc3394IV = Data(bytes: UnsafePointer(rfc3394IVData), count:rfc3394IVData.count) + + public static func SymmetricKeyWrap(_ iv: Data, + kek: Data, + rawKey: Data) throws -> Data { + let alg = WrapAlg.aes.rawValue + var wrappedKeyLength = CCSymmetricWrappedSize!(alg, rawKey.count) + var wrappedKey = Data(count:wrappedKeyLength) + let status = withUnsafePointers(iv, kek, rawKey, &wrappedKey, { + ivBytes, kekBytes, rawKeyBytes, wrappedKeyBytes in + return CCSymmetricKeyWrap!( + alg, + ivBytes, iv.count, + kekBytes, kek.count, + rawKeyBytes, rawKey.count, + wrappedKeyBytes, &wrappedKeyLength) + }) + guard status == noErr else { throw CCError(status) } + + wrappedKey.count = wrappedKeyLength + return wrappedKey + } + + public static func SymmetricKeyUnwrap(_ iv: Data, + kek: Data, + wrappedKey: Data) throws -> Data { + let alg = WrapAlg.aes.rawValue + var rawKeyLength = CCSymmetricUnwrappedSize!(alg, wrappedKey.count) + var rawKey = Data(count:rawKeyLength) + let status = withUnsafePointers(iv, kek, wrappedKey, &rawKey, { + ivBytes, kekBytes, wrappedKeyBytes, rawKeyBytes in + return CCSymmetricKeyUnwrap!( + alg, + ivBytes, iv.count, + kekBytes, kek.count, + wrappedKeyBytes, wrappedKey.count, + rawKeyBytes, &rawKeyLength) + }) + guard status == noErr else { throw CCError(status) } + + rawKey.count = rawKeyLength + return rawKey + } + + public static func available() -> Bool { + return CCSymmetricKeyWrap != nil && + CCSymmetricKeyUnwrap != nil && + CCSymmetricWrappedSize != nil && + CCSymmetricUnwrappedSize != nil + } + + fileprivate enum WrapAlg: CCWrappingAlgorithm { + case aes = 1 + } + fileprivate typealias CCWrappingAlgorithm = UInt32 + + fileprivate typealias CCSymmetricKeyWrapT = @convention(c) ( + _ algorithm: CCWrappingAlgorithm, + _ iv: UnsafeRawPointer, _ ivLen: size_t, + _ kek: UnsafeRawPointer, _ kekLen: size_t, + _ rawKey: UnsafeRawPointer, _ rawKeyLen: size_t, + _ wrappedKey: UnsafeMutableRawPointer, + _ wrappedKeyLen: UnsafePointer) -> CCCryptorStatus + fileprivate static let CCSymmetricKeyWrap: CCSymmetricKeyWrapT? = getFunc(dl!, f: "CCSymmetricKeyWrap") + + fileprivate typealias CCSymmetricKeyUnwrapT = @convention(c) ( + _ algorithm: CCWrappingAlgorithm, + _ iv: UnsafeRawPointer, _ ivLen: size_t, + _ kek: UnsafeRawPointer, _ kekLen: size_t, + _ wrappedKey: UnsafeRawPointer, _ wrappedKeyLen: size_t, + _ rawKey: UnsafeMutableRawPointer, + _ rawKeyLen: UnsafePointer) -> CCCryptorStatus + fileprivate static let CCSymmetricKeyUnwrap: CCSymmetricKeyUnwrapT? = + getFunc(dl!, f: "CCSymmetricKeyUnwrap") + + fileprivate typealias CCSymmetricWrappedSizeT = @convention(c) ( + _ algorithm: CCWrappingAlgorithm, + _ rawKeyLen: size_t) -> size_t + fileprivate static let CCSymmetricWrappedSize: CCSymmetricWrappedSizeT? = + getFunc(dl!, f: "CCSymmetricWrappedSize") + + fileprivate typealias CCSymmetricUnwrappedSizeT = @convention(c) ( + _ algorithm: CCWrappingAlgorithm, + _ wrappedKeyLen: size_t) -> size_t + fileprivate static let CCSymmetricUnwrappedSize: CCSymmetricUnwrappedSizeT? = + getFunc(dl!, f: "CCSymmetricUnwrappedSize") + + } + +} + +private func getFunc(_ from: UnsafeMutableRawPointer, f: String) -> T? { + let sym = dlsym(from, f) + guard sym != nil else { + return nil + } + return unsafeBitCast(sym, to: T.self) +} + +extension Data { + /// Create hexadecimal string representation of Data object. + /// + /// - returns: String representation of this Data object. + + public func hexadecimalString() -> String { + return self.withUnsafeBytes { data -> String in + var hexstr = String() + for i in data.bindMemory(to: UInt8.self) { + hexstr += String(format: "%02X", i) + } + return hexstr + } + } + + public func arrayOfBytes() -> [UInt8] { + let count = self.count / MemoryLayout.size + var bytesArray = [UInt8](repeating: 0, count: count) + self.copyBytes(to: &bytesArray, count: count * MemoryLayout.size) + return bytesArray + } + + fileprivate var bytesView: BytesView { return BytesView(self) } + + fileprivate func bytesViewRange(_ range: NSRange) -> BytesView { + return BytesView(self, range: range) + } + + fileprivate struct BytesView: Collection { + // The view retains the Data. That's on purpose. + // Data doesn't retain the view, so there's no loop. + let data: Data + init(_ data: Data) { + self.data = data + self.startIndex = 0 + self.endIndex = data.count + } + + init(_ data: Data, range: NSRange ) { + self.data = data + self.startIndex = range.location + self.endIndex = range.location + range.length + } + + subscript (position: Int) -> UInt8 { + return data.withUnsafeBytes({ dataBytes -> UInt8 in + dataBytes.bindMemory(to: UInt8.self)[position] + }) + } + subscript (bounds: Range) -> Data { + return data.subdata(in: bounds) + } + fileprivate func formIndex(after i: inout Int) { + i += 1 + } + fileprivate func index(after i: Int) -> Int { + return i + 1 + } + var startIndex: Int + var endIndex: Int + var length: Int { return endIndex - startIndex } + } +} + +extension String { + + /// Create Data from hexadecimal string representation + /// + /// This takes a hexadecimal representation and creates a Data object. Note, if the string has + /// any spaces, those are removed. Also if the string started with a '<' or ended with a '>', + /// those are removed, too. This does no validation of the string to ensure it's a valid + /// hexadecimal string + /// + /// The use of `strtoul` inspired by Martin R at http://stackoverflow.com/a/26284562/1271826 + /// + /// - returns: Data represented by this hexadecimal string. + /// Returns nil if string contains characters outside the 0-9 and a-f range. + + public func dataFromHexadecimalString() -> Data? { + let trimmedString = self.trimmingCharacters( + in: CharacterSet(charactersIn: "<> ")).replacingOccurrences( + of: " ", with: "") + + // make sure the cleaned up string consists solely of hex digits, + // and that we have even number of them + + let regex = try! NSRegularExpression(pattern: "^[0-9a-f]*$", options: .caseInsensitive) + + let found = regex.firstMatch(in: trimmedString, options: [], + range: NSRange(location: 0, + length: trimmedString.count)) + guard found != nil && + found?.range.location != NSNotFound && + trimmedString.count % 2 == 0 else { + return nil + } + + // everything ok, so now let's build Data + + var data = Data(capacity: trimmedString.count / 2) + var index: String.Index? = trimmedString.startIndex + + while let i = index { + let byteString = String(trimmedString[i ..< trimmedString.index(i, offsetBy: 2)]) + let num = UInt8(byteString.withCString { strtoul($0, nil, 16) }) + data.append([num] as [UInt8], count: 1) + + index = trimmedString.index(i, offsetBy: 2, limitedBy: trimmedString.endIndex) + if index == trimmedString.endIndex { break } + } + + return data + } +} + +fileprivate func withUnsafePointers( + _ arg0: Data, + _ arg1: Data, + _ body: ( + UnsafePointer, UnsafePointer) throws -> Result + ) rethrows -> Result { + return try arg0.withUnsafeBytes { p0 -> Result in + return try arg1.withUnsafeBytes { p1 -> Result in + return try body(p0.bindMemory(to: A0.self).baseAddress!, + p1.bindMemory(to: A1.self).baseAddress!) + } + } +} + +fileprivate func withUnsafePointers( + _ arg0: Data, + _ arg1: inout Data, + _ body: ( + UnsafePointer, + UnsafeMutablePointer) throws -> Result + ) rethrows -> Result { + return try arg0.withUnsafeBytes { p0 -> Result in + return try arg1.withUnsafeMutableBytes { p1 -> Result in + return try body(p0.bindMemory(to: A0.self).baseAddress!, + p1.bindMemory(to: A1.self).baseAddress!) + } + } +} + +fileprivate func withUnsafePointers( + _ arg0: Data, + _ arg1: Data, + _ arg2: inout Data, + _ body: ( + UnsafePointer, + UnsafePointer, + UnsafeMutablePointer) throws -> Result + ) rethrows -> Result { + return try arg0.withUnsafeBytes { p0 -> Result in + return try arg1.withUnsafeBytes { p1 -> Result in + return try arg2.withUnsafeMutableBytes { p2 -> Result in + return try body(p0.bindMemory(to: A0.self).baseAddress!, + p1.bindMemory(to: A1.self).baseAddress!, + p2.bindMemory(to: A2.self).baseAddress!) + } + } + } +} + +fileprivate func withUnsafePointers( + _ arg0: inout Data, + _ arg1: inout Data, + _ arg2: inout Data, + _ body: ( + UnsafeMutablePointer, + UnsafeMutablePointer, + UnsafeMutablePointer) throws -> Result + ) rethrows -> Result { + return try arg0.withUnsafeMutableBytes { p0 -> Result in + return try arg1.withUnsafeMutableBytes { p1 -> Result in + return try arg2.withUnsafeMutableBytes { p2 -> Result in + return try body(p0.bindMemory(to: A0.self).baseAddress!, + p1.bindMemory(to: A1.self).baseAddress!, + p2.bindMemory(to: A2.self).baseAddress!) + } + } + } +} + +fileprivate func withUnsafePointers( + _ arg0: Data, + _ arg1: Data, + _ arg2: Data, + _ arg3: inout Data, + _ body: ( + UnsafePointer, + UnsafePointer, + UnsafePointer, + UnsafeMutablePointer) throws -> Result + ) rethrows -> Result { + return try arg0.withUnsafeBytes { p0 -> Result in + return try arg1.withUnsafeBytes { p1 -> Result in + return try arg2.withUnsafeBytes { p2 -> Result in + return try arg3.withUnsafeMutableBytes { p3 -> Result in + return try body(p0.bindMemory(to: A0.self).baseAddress!, + p1.bindMemory(to: A1.self).baseAddress!, + p2.bindMemory(to: A2.self).baseAddress!, + p3.bindMemory(to: A3.self).baseAddress!) + } + } + } + } +} + +fileprivate func withUnsafePointers( + _ arg0: Data, + _ arg1: Data, + _ arg2: Data, + _ arg3: Data, + _ arg4: inout Data, + _ arg5: inout Data, + _ body: ( + UnsafePointer, + UnsafePointer, + UnsafePointer, + UnsafePointer, + UnsafeMutablePointer, + UnsafeMutablePointer) throws -> Result + ) rethrows -> Result { + return try arg0.withUnsafeBytes { p0 -> Result in + return try arg1.withUnsafeBytes { p1 -> Result in + return try arg2.withUnsafeBytes { p2 -> Result in + return try arg3.withUnsafeBytes { p3 -> Result in + return try arg4.withUnsafeMutableBytes { p4 -> Result in + return try arg5.withUnsafeMutableBytes { p5 -> Result in + return try body(p0.bindMemory(to: A0.self).baseAddress!, + p1.bindMemory(to: A1.self).baseAddress!, + p2.bindMemory(to: A2.self).baseAddress!, + p3.bindMemory(to: A3.self).baseAddress!, + p4.bindMemory(to: A4.self).baseAddress!, + p5.bindMemory(to: A5.self).baseAddress!) + } + } + } + } + } + } +} diff --git a/Sources/sdlan/UIntExtension.swift b/Sources/sdlan/UIntExtension.swift new file mode 100644 index 0000000..15056c9 --- /dev/null +++ b/Sources/sdlan/UIntExtension.swift @@ -0,0 +1,37 @@ +// +// UIntExtension.swift +// Tun +// +// Created by 安礼成 on 2024/5/30. +// + +import Foundation + +extension UInt16 { + init(bytes: (UInt8, UInt8)) { + self = UInt16(bytes.0) << 8 + UInt16(bytes.1) + } + + init(data: Data) { + self = UInt16(data[0]) << 8 + UInt16(data[1]) + } + + func data() -> Data { + var data = Data() + + data.append(contentsOf: [UInt8(self >> 8), UInt8(self & 0x00FF)]) + + return data + } +} + +extension UInt32 { + init(bytes: (UInt8, UInt8, UInt8, UInt8)) { + self = UInt32(bytes.0) << 24 + UInt32(bytes.1) << 16 + UInt32(bytes.2) << 8 + UInt32(bytes.3) + } + + init(data: Data) { + self = UInt32(data[0]) << 24 | UInt32(data[1]) << 16 | UInt32(data[2]) << 8 | UInt32(data[3]) + } + +} diff --git a/Tests/sdlanTests/sdlanTests.swift b/Tests/sdlanTests/sdlanTests.swift new file mode 100644 index 0000000..34eeffe --- /dev/null +++ b/Tests/sdlanTests/sdlanTests.swift @@ -0,0 +1,6 @@ +import Testing +@testable import sdlan + +@Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. +}