diff --git a/Sources/PerfectNet/Net.swift b/Sources/PerfectNet/Net.swift index 89ab641d..bc2b3cb3 100644 --- a/Sources/PerfectNet/Net.swift +++ b/Sources/PerfectNet/Net.swift @@ -87,7 +87,10 @@ public enum PerfectNetError: Error { func ThrowNetworkError(file: String = #file, function: String = #function, line: Int = #line) throws -> Never { let err = errno - let msg = String(validatingUTF8: strerror(err))! + let bufferSize = 1024 + var buffer = [CChar](repeating: 0, count: bufferSize) + _ = strerror_r(errno, &buffer, bufferSize) + let msg = String(cString: buffer) throw PerfectNetError.networkError(err, msg + " \(file) \(function) \(line)") } @@ -203,48 +206,55 @@ open class Net { fd.switchToNonBlocking() } } -// -// func makeAddress(_ sin: inout sockaddr_storage, host: String, port: UInt16) -> Int { -// let aiFlags: Int32 = 0 -// let family: Int32 = AF_UNSPEC -// let bPort = port.bigEndian -// var hints = addrinfo(ai_flags: aiFlags, ai_family: family, ai_socktype: SOCK_STREAM, ai_protocol: 0, ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil) -// var resultList = UnsafeMutablePointer(bitPattern: 0) -// var result = getaddrinfo(host, nil, &hints, &resultList) -// while EAI_AGAIN == result { -// Threading.sleep(seconds: 0.1) -// result = getaddrinfo(host, nil, &hints, &resultList) -// } -// if result == EAI_NONAME { -// hints = addrinfo(ai_flags: aiFlags, ai_family: AF_INET6, ai_socktype: SOCK_STREAM, ai_protocol: 0, ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil) -// result = getaddrinfo(host, nil, &hints, &resultList) -// } -// if result == 0, var resultList = resultList { -// defer { -// freeaddrinfo(resultList) -// } -// guard let addr = resultList.pointee.ai_addr else { -// return -1 -// } -// switch Int32(addr.pointee.sa_family) { -// case AF_INET6: -// memcpy(&sin, addr, MemoryLayout.size) -// UnsafeMutablePointer(&sin).withMemoryRebound(to: sockaddr_in6.self, capacity: 1) { -// $0.pointee.sin6_port = in_port_t(bPort) -// } -// case AF_INET: -// memcpy(&sin, addr, MemoryLayout.size) -// UnsafeMutablePointer(&sin).withMemoryRebound(to: sockaddr_in.self, capacity: 1) { -// $0.pointee.sin_port = in_port_t(bPort) -// } -// default: -// return -1 -// } -// } else { -// return -1 -// } -// return 0 -// } + + func makeAddress(_ sin: inout sockaddr_storage, host: String, port: UInt16) -> Int { + let aiFlags: Int32 = 0 + let family: Int32 = AF_UNSPEC + let bPort = port.bigEndian + #if os(Linux) + let SOCK_STREAM = __socket_type(1) + #endif + var hints = addrinfo(ai_flags: aiFlags, ai_family: family, ai_socktype: SOCK_STREAM, ai_protocol: 0, ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil) + var resultList = UnsafeMutablePointer(bitPattern: 0) + var result = getaddrinfo(host, nil, &hints, &resultList) + while EAI_AGAIN == result { + Threading.sleep(seconds: 0.1) + result = getaddrinfo(host, nil, &hints, &resultList) + } + if result == EAI_NONAME { + hints = addrinfo(ai_flags: aiFlags, ai_family: AF_INET6, ai_socktype: SOCK_STREAM, ai_protocol: 0, ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil) + result = getaddrinfo(host, nil, &hints, &resultList) + } + if result == 0, let resultList { + defer { + freeaddrinfo(resultList) + } + guard let addr = resultList.pointee.ai_addr else { + return -1 + } + switch Int32(addr.pointee.sa_family) { + case AF_INET6: + let size = MemoryLayout.size + memcpy(&sin, addr, size) + var addr6 = sockaddr_in6() + memcpy(&addr6, &sin, size) + addr6.sin6_port = in_port_t(bPort) + memcpy(&sin, &addr6, size) + case AF_INET: + let size = MemoryLayout.size + memcpy(&sin, addr, size) + var addr4 = sockaddr_in() + memcpy(&addr4, &sin, size) + addr4.sin_port = in_port_t(bPort) + memcpy(&sin, &addr4, size) + default: + return -1 + } + } else { + return -1 + } + return 0 + } func isEAgain(err: Int) -> Bool { return err == -1 && errno == EAGAIN diff --git a/Sources/PerfectNet/NetExt.swift b/Sources/PerfectNet/NetExt.swift new file mode 100644 index 00000000..cdfcc587 --- /dev/null +++ b/Sources/PerfectNet/NetExt.swift @@ -0,0 +1,106 @@ +// +// NetExt.swift +// +// +// Created by Rocky Wei on 2023-08-21. +// + +import Foundation + +@available(macOS 10.15.0, *) +public extension NetTCP { + /// Read the indicated number of bytes and deliver them concurrently. + /// - parameter count: The number of bytes to read + /// - parameter timeoutSeconds: The number of seconds to wait for the requested number of bytes. A timeout value of negative one indicates that the request should have no timeout. + /// - returns: If the timeout occurs before the requested number of bytes have been read, an empty array will be delivered to the callback. + /// If an error or disconnection occurs then a nil object will be delivered. + func readBytesFullyWithContinuation(count: Int, timeoutSeconds: Double) async -> [UInt8]? { + return await withCheckedContinuation { continuation in + readBytesFully(count: count, timeoutSeconds: timeoutSeconds) { data in + continuation.resume(returning: data) + } + } + } + + /// Read up to the indicated number of bytes and deliver them concurrently + /// - parameter count: The maximum number of bytes to read. + /// - returns: If an error occurs during the read then a nil object will be passed, otherwise, the immediately available number of bytes, which may be zero, will be passed. + func readSomeBytes(count: Int) async -> [UInt8]? { + return await withCheckedContinuation { continuation in + readSomeBytes(count: count) { data in + continuation.resume(returning: data) + } + } + } + + /// Write the string and return the number of bytes which were written. + /// - parameter s: The string to write. The string will be written based on its UTF-8 encoding. + /// - returns: The number of bytes which were successfuly written, which may be zero. + func writeWithContinuation(string: String) async -> Int { + return await withCheckedContinuation { continuation in + write(string: string) { written in + continuation.resume(returning: written) + } + } + } + + /// Write the indicated bytes and return the number of bytes which were written. + /// - parameter bytes: The array of UInt8 to write. + /// - returns: The number of bytes which were successfuly written, which may be zero. + func writeWithContinuation(bytes: [UInt8]) async -> Int { + return await withCheckedContinuation { continuation in + write(bytes: bytes) { written in + continuation.resume(returning: written) + } + } + } + + /// Write the indicated bytes and return the number of bytes which were written. + /// - parameter bytes: The array of UInt8 to write. + /// - parameter offsetBy: The offset within `bytes` at which to begin writing. + /// - parameter count: The number of bytes to write. + /// - returns: The number of bytes which were successfuly written, which may be zero. + func writeWithContinuation(bytes: [UInt8], offsetBy: Int, count: Int) async -> Int { + return await withCheckedContinuation { continuation in + write(bytes: bytes, offsetBy: offsetBy, count: count) { written in + continuation.resume(returning: written) + } + } + } + + /// Connect to the indicated server + /// - parameter address: The server's address, expressed as a string. + /// - parameter port: The port on which to connect. + /// - parameter timeoutSeconds: The number of seconds to wait for the connection to complete. A timeout of negative one indicates that there is no timeout. + /// - returns: If the connection completes successfully then the current NetTCP instance will be passed to the callback, otherwise, a nil object will be passed. + /// - returns: `PerfectError.NetworkError` + func connectWithContinuation(address: String, port: UInt16, timeoutSeconds: Double) async throws -> NetTCP? { + return try await withCheckedThrowingContinuation { continuation in + do { + try connect(address: address, port: port, timeoutSeconds: timeoutSeconds) { + continuation.resume(returning: $0) + } + } catch { + continuation.resume(throwing: error) + } + } + } + + /// Connect to the indicated server + /// - parameter address: The server's address, expressed as a string. + /// - parameter port: The port on which to connect. + /// - parameter timeoutSeconds: The number of seconds to wait for the connection to complete. A timeout of negative one indicates that there is no timeout. + /// - returns:. If the connection completes successfully then the current NetTCP instance will be passed to the callback, otherwise, a nil object will be passed. + /// - throws: `PerfectError.NetworkError` + func acceptWithContinuation(timeoutSeconds: Double) async throws -> NetTCP? { + return try await withCheckedThrowingContinuation { continuation in + do { + try accept(timeoutSeconds: timeoutSeconds) { + continuation.resume(returning: $0) + } + } catch { + continuation.resume(throwing: error) + } + } + } +}