3489 lines
92 KiB
Swift
3489 lines
92 KiB
Swift
//
|
|
// Socket.swift
|
|
// BlueSocket
|
|
//
|
|
// Created by Bill Abt on 11/9/15.
|
|
// Copyright © 2016 IBM. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
|
|
import Darwin
|
|
#elseif os(Linux)
|
|
import Glibc
|
|
#endif
|
|
|
|
import Foundation
|
|
|
|
// MARK: Socket
|
|
|
|
///
|
|
/// **Socket:** Low level BSD sockets wrapper.
|
|
///
|
|
public class Socket: SocketReader, SocketWriter {
|
|
|
|
// MARK: Constants
|
|
|
|
// MARK: -- Generic
|
|
|
|
public static let SOCKET_MINIMUM_READ_BUFFER_SIZE = 1024
|
|
public static let SOCKET_DEFAULT_READ_BUFFER_SIZE = 4096
|
|
public static let SOCKET_DEFAULT_MAX_BACKLOG = 50
|
|
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
|
|
public static let SOCKET_MAX_DARWIN_BACKLOG = 128
|
|
#endif
|
|
|
|
public static let SOCKET_INVALID_PORT = Int32(0)
|
|
public static let SOCKET_INVALID_DESCRIPTOR = Int32(-1)
|
|
|
|
public static let INADDR_ANY = in_addr_t(0)
|
|
|
|
public static let NO_HOSTNAME = "No hostname"
|
|
|
|
// MARK: -- Errors: Domain and Codes
|
|
|
|
public static let SOCKET_ERR_DOMAIN = "com.ibm.oss.Socket.ErrorDomain"
|
|
|
|
public static let SOCKET_ERR_UNABLE_TO_CREATE_SOCKET = -9999
|
|
public static let SOCKET_ERR_BAD_DESCRIPTOR = -9998
|
|
public static let SOCKET_ERR_ALREADY_CONNECTED = -9997
|
|
public static let SOCKET_ERR_NOT_CONNECTED = -9996
|
|
public static let SOCKET_ERR_NOT_LISTENING = -9995
|
|
public static let SOCKET_ERR_ACCEPT_FAILED = -9994
|
|
public static let SOCKET_ERR_SETSOCKOPT_FAILED = -9993
|
|
public static let SOCKET_ERR_BIND_FAILED = -9992
|
|
public static let SOCKET_ERR_INVALID_HOSTNAME = -9991
|
|
public static let SOCKET_ERR_INVALID_PORT = -9990
|
|
public static let SOCKET_ERR_GETADDRINFO_FAILED = -9989
|
|
public static let SOCKET_ERR_CONNECT_FAILED = -9988
|
|
public static let SOCKET_ERR_MISSING_CONNECTION_DATA = -9987
|
|
public static let SOCKET_ERR_SELECT_FAILED = -9986
|
|
public static let SOCKET_ERR_LISTEN_FAILED = -9985
|
|
public static let SOCKET_ERR_INVALID_BUFFER = -9984
|
|
public static let SOCKET_ERR_INVALID_BUFFER_SIZE = -9983
|
|
public static let SOCKET_ERR_RECV_FAILED = -9982
|
|
public static let SOCKET_ERR_RECV_BUFFER_TOO_SMALL = -9981
|
|
public static let SOCKET_ERR_WRITE_FAILED = -9980
|
|
public static let SOCKET_ERR_GET_FCNTL_FAILED = -9979
|
|
public static let SOCKET_ERR_SET_FCNTL_FAILED = -9978
|
|
public static let SOCKET_ERR_NOT_IMPLEMENTED = -9977
|
|
public static let SOCKET_ERR_NOT_SUPPORTED_YET = -9976
|
|
public static let SOCKET_ERR_BAD_SIGNATURE_PARAMETERS = -9975
|
|
public static let SOCKET_ERR_INTERNAL = -9974
|
|
public static let SOCKET_ERR_WRONG_PROTOCOL = -9973
|
|
public static let SOCKET_ERR_NOT_ACTIVE = -9972
|
|
public static let SOCKET_ERR_CONNECTION_RESET = -9971
|
|
|
|
|
|
///
|
|
/// Flag to indicate the endian-ness of the host. (Readonly)
|
|
///
|
|
public static let isLittleEndian: Bool = Int(littleEndian: 42) == 42
|
|
|
|
// MARK: Enums
|
|
|
|
// MARK: -- ProtocolFamily
|
|
|
|
///
|
|
/// Socket Protocol Family Values
|
|
///
|
|
/// **Note:** Only the following are supported at this time:
|
|
/// inet = AF_INET (IPV4)
|
|
/// inet6 = AF_INET6 (IPV6)
|
|
/// unix = AF_UNIX
|
|
///
|
|
public enum ProtocolFamily {
|
|
|
|
/// AF_INET (IPV4)
|
|
case inet
|
|
|
|
/// AF_INET6 (IPV6)
|
|
case inet6
|
|
|
|
/// AF_UNIX
|
|
case unix
|
|
|
|
///
|
|
/// Return the value for a particular case. (Readonly)
|
|
///
|
|
var value: Int32 {
|
|
|
|
switch self {
|
|
|
|
case .inet:
|
|
return Int32(AF_INET)
|
|
|
|
case .inet6:
|
|
return Int32(AF_INET6)
|
|
|
|
case .unix:
|
|
return Int32(AF_UNIX)
|
|
}
|
|
}
|
|
///
|
|
/// Return enum equivalent of a raw value
|
|
///
|
|
/// - Parameter forValue: Value for which enum value is desired
|
|
///
|
|
/// - Returns: Optional contain enum value or nil
|
|
///
|
|
static func getFamily(forValue: Int32) -> ProtocolFamily? {
|
|
|
|
switch forValue {
|
|
|
|
case Int32(AF_INET):
|
|
return .inet
|
|
case Int32(AF_INET6):
|
|
return .inet6
|
|
case Int32(AF_UNIX):
|
|
return .unix
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: -- SocketType
|
|
|
|
///
|
|
/// Socket Type Values
|
|
///
|
|
/// **Note:** Only the following are supported at this time:
|
|
/// stream = SOCK_STREAM (Provides sequenced, reliable, two-way, connection-based byte streams.)
|
|
/// datagram = SOCK_DGRAM (Supports datagrams (connectionless, unreliable messages of a fixed maximum length).)
|
|
///
|
|
public enum SocketType {
|
|
|
|
/// SOCK_STREAM (Provides sequenced, reliable, two-way, connection-based byte streams.)
|
|
case stream
|
|
|
|
/// SOCK_DGRAM (Supports datagrams (connectionless, unreliable messages of a fixed maximum length).)
|
|
case datagram
|
|
|
|
///
|
|
/// Return the value for a particular case. (Readonly)
|
|
///
|
|
var value: Int32 {
|
|
|
|
switch self {
|
|
|
|
case .stream:
|
|
#if os(Linux)
|
|
return Int32(SOCK_STREAM.rawValue)
|
|
#else
|
|
return SOCK_STREAM
|
|
#endif
|
|
case .datagram:
|
|
#if os(Linux)
|
|
return Int32(SOCK_DGRAM.rawValue)
|
|
#else
|
|
return SOCK_DGRAM
|
|
#endif
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Return enum equivalent of a raw value
|
|
///
|
|
/// - Parameter forValue: Value for which enum value is desired
|
|
///
|
|
/// - Returns: Optional contain enum value or nil
|
|
///
|
|
static func getType(forValue: Int32) -> SocketType? {
|
|
|
|
#if os(Linux)
|
|
switch forValue {
|
|
|
|
case Int32(SOCK_STREAM.rawValue):
|
|
return .stream
|
|
case Int32(SOCK_DGRAM.rawValue):
|
|
return .datagram
|
|
default:
|
|
return nil
|
|
}
|
|
#else
|
|
switch forValue {
|
|
|
|
case SOCK_STREAM:
|
|
return .stream
|
|
case SOCK_DGRAM:
|
|
return .datagram
|
|
default:
|
|
return nil
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// MARK: -- SocketProtocol
|
|
|
|
///
|
|
/// Socket Protocol Values
|
|
///
|
|
/// **Note:** Only the following are supported at this time:
|
|
/// tcp = IPPROTO_TCP
|
|
/// udp = IPPROTO_UDP
|
|
/// unix = Unix Domain Socket (raw value = 0)
|
|
///
|
|
public enum SocketProtocol: Int32 {
|
|
|
|
/// IPPROTO_TCP
|
|
case tcp
|
|
|
|
/// IPPROTO_UDP
|
|
case udp
|
|
|
|
/// Unix Domain
|
|
case unix
|
|
|
|
///
|
|
/// Return the value for a particular case. (Readonly)
|
|
///
|
|
var value: Int32 {
|
|
|
|
switch self {
|
|
|
|
case .tcp:
|
|
return Int32(IPPROTO_TCP)
|
|
case .udp:
|
|
return Int32(IPPROTO_UDP)
|
|
case .unix:
|
|
return Int32(0)
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Return enum equivalent of a raw value
|
|
///
|
|
/// - Parameter forValue: Value for which enum value is desired
|
|
///
|
|
/// - Returns: Optional contain enum value or nil
|
|
///
|
|
static func getProtocol(forValue: Int32) -> SocketProtocol? {
|
|
|
|
switch forValue {
|
|
|
|
case Int32(IPPROTO_TCP):
|
|
return .tcp
|
|
case Int32(IPPROTO_UDP):
|
|
return .udp
|
|
case Int32(0):
|
|
return .unix
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: -- Socket Address
|
|
|
|
///
|
|
/// Socket Address
|
|
///
|
|
public enum Address {
|
|
|
|
/// sockaddr_in
|
|
case ipv4(sockaddr_in)
|
|
|
|
/// sockaddr_in6
|
|
case ipv6(sockaddr_in6)
|
|
|
|
/// sockaddr_un
|
|
case unix(sockaddr_un)
|
|
|
|
///
|
|
/// Size of address. (Readonly)
|
|
///
|
|
public var size: Int {
|
|
|
|
switch self {
|
|
|
|
case .ipv4( _):
|
|
return MemoryLayout<(sockaddr_in)>.size
|
|
case .ipv6( _):
|
|
return MemoryLayout<(sockaddr_in6)>.size
|
|
case .unix( _):
|
|
return MemoryLayout<(sockaddr_un)>.size
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Cast as sockaddr. (Readonly)
|
|
///
|
|
public var addr: sockaddr {
|
|
|
|
switch self {
|
|
|
|
case .ipv4(let addr):
|
|
return addr.asAddr()
|
|
|
|
case .ipv6(let addr):
|
|
return addr.asAddr()
|
|
|
|
case .unix(let addr):
|
|
return addr.asAddr()
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: Structs
|
|
|
|
// MARK: -- Signature
|
|
|
|
///
|
|
/// Socket signature: contains the characteristics of the socket.
|
|
///
|
|
public struct Signature: CustomStringConvertible {
|
|
|
|
// MARK: -- Public Properties
|
|
|
|
///
|
|
/// Protocol Family
|
|
///
|
|
public internal(set) var protocolFamily: ProtocolFamily
|
|
|
|
///
|
|
/// Socket Type. (Readonly)
|
|
///
|
|
public internal(set) var socketType: SocketType
|
|
|
|
///
|
|
/// Socket Protocol. (Readonly)
|
|
///
|
|
public internal(set) var proto: SocketProtocol
|
|
|
|
///
|
|
/// Host name for connection. (Readonly)
|
|
///
|
|
public internal(set) var hostname: String? = Socket.NO_HOSTNAME
|
|
|
|
///
|
|
/// Port for connection. (Readonly)
|
|
///
|
|
public internal(set) var port: Int32 = Socket.SOCKET_INVALID_PORT
|
|
|
|
///
|
|
/// Path for .unix type sockets. (Readonly)
|
|
public internal(set) var path: String? = nil
|
|
|
|
///
|
|
/// Address info for socket. (Readonly)
|
|
///
|
|
public internal(set) var address: Address? = nil
|
|
|
|
///
|
|
/// Flag to indicate whether `Socket` is secure or not. (Readonly)
|
|
///
|
|
public internal(set) var isSecure: Bool = false
|
|
|
|
///
|
|
/// True is socket bound, false otherwise.
|
|
///
|
|
public internal(set) var isBound: Bool = false
|
|
|
|
///
|
|
/// Returns a string description of the error.
|
|
///
|
|
public var description: String {
|
|
|
|
return "Signature: family: \(protocolFamily), type: \(socketType), protocol: \(proto), address: \(address as Socket.Address?), hostname: \(hostname as String?), port: \(port), path: \(path), bound: \(isBound), secure: \(isSecure)"
|
|
}
|
|
|
|
// MARK: -- Public Functions
|
|
|
|
///
|
|
/// Create a socket signature
|
|
///
|
|
/// - Parameters:
|
|
/// - protocolFamily: The family of the socket to create.
|
|
/// - socketType: The type of socket to create.
|
|
/// - proto: The protocool to use for the socket.
|
|
/// - address: Address info for the socket.
|
|
///
|
|
/// - Returns: New Signature instance
|
|
///
|
|
public init?(protocolFamily: Int32, socketType: Int32, proto: Int32, address: Address?) throws {
|
|
|
|
guard let family = ProtocolFamily.getFamily(forValue: protocolFamily),
|
|
let type = SocketType.getType(forValue: socketType),
|
|
let pro = SocketProtocol.getProtocol(forValue: proto) else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Bad family, type or protocol passed.")
|
|
}
|
|
|
|
// Validate the parameters...
|
|
if type == .stream {
|
|
guard pro == .tcp || pro == .unix else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Stream socket must use either .tcp or .unix for the protocol.")
|
|
}
|
|
}
|
|
if type == .datagram {
|
|
guard pro == .udp || pro == .unix else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Datagram socket must use .udp or .unix for the protocol.")
|
|
}
|
|
}
|
|
|
|
self.protocolFamily = family
|
|
self.socketType = type
|
|
self.proto = pro
|
|
|
|
self.address = address
|
|
|
|
}
|
|
|
|
///
|
|
/// Create a socket signature
|
|
///
|
|
/// - Parameters:
|
|
/// - protocolFamily: The protocol family to use (only `.inet` and `.inet6` supported by this `init` function).
|
|
/// - socketType: The type of socket to create.
|
|
/// - proto: The protocool to use for the socket.
|
|
/// - hostname: Hostname for this signature.
|
|
/// - port: Port for this signature.
|
|
///
|
|
/// - Returns: New Signature instance
|
|
///
|
|
public init?(protocolFamily: ProtocolFamily, socketType: SocketType, proto: SocketProtocol, hostname: String?, port: Int32?) throws {
|
|
|
|
// Make sure we have what we need...
|
|
guard let _ = hostname,
|
|
let port = port, protocolFamily == .inet || protocolFamily == .inet6 else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Missing hostname, port or both or invalid protocol family.")
|
|
}
|
|
|
|
self.protocolFamily = protocolFamily
|
|
|
|
// Validate the parameters...
|
|
if socketType == .stream {
|
|
guard proto == .tcp || proto == .unix else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Stream socket must use either .tcp or .unix for the protocol.")
|
|
}
|
|
}
|
|
if socketType == .datagram {
|
|
guard proto == .udp || proto == .unix else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Datagram socket must use .udp or .unix for the protocol.")
|
|
}
|
|
}
|
|
|
|
self.socketType = socketType
|
|
self.proto = proto
|
|
|
|
self.hostname = hostname
|
|
self.port = port
|
|
}
|
|
|
|
///
|
|
/// Create a socket signature
|
|
///
|
|
/// - Parameters:
|
|
/// - socketType: The type of socket to create.
|
|
/// - proto: The protocool to use for the socket.
|
|
/// - path: Pathname for this signature.
|
|
///
|
|
/// - Returns: New Signature instance
|
|
///
|
|
public init?(socketType: SocketType, proto: SocketProtocol, path: String?) throws {
|
|
|
|
// Make sure we have what we need...
|
|
guard let _ = path else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Missing pathname.")
|
|
}
|
|
|
|
// Default to Unix socket protocol family...
|
|
self.protocolFamily = .unix
|
|
|
|
self.socketType = socketType
|
|
self.proto = proto
|
|
|
|
// Validate the parameters...
|
|
if socketType == .stream {
|
|
guard proto == .tcp || proto == .unix else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Stream socket must use either .tcp or .unix for the protocol.")
|
|
}
|
|
}
|
|
if socketType == .datagram {
|
|
guard proto == .udp || proto == .unix else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Datagram socket must use .udp or .unix for the protocol.")
|
|
}
|
|
}
|
|
|
|
self.path = path
|
|
|
|
if path!.utf8.count == 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Specified path contains zero (0) bytes.")
|
|
}
|
|
|
|
// Create the address...
|
|
var remoteAddr = sockaddr_un()
|
|
remoteAddr.sun_family = sa_family_t(AF_UNIX)
|
|
|
|
let lengthOfPath = path!.utf8.count
|
|
|
|
// Validate the length...
|
|
guard lengthOfPath < MemoryLayout.size(ofValue: remoteAddr.sun_path) else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Pathname supplied is too long.")
|
|
}
|
|
|
|
_ = withUnsafeMutablePointer(to: &remoteAddr.sun_path.0) { ptr in
|
|
|
|
path!.withCString {
|
|
strncpy(ptr, $0, lengthOfPath)
|
|
}
|
|
}
|
|
|
|
#if !os(Linux)
|
|
remoteAddr.sun_len = UInt8(MemoryLayout<UInt8>.size + MemoryLayout<sa_family_t>.size + path!.utf8.count + 1)
|
|
#endif
|
|
|
|
self.address = .unix(remoteAddr)
|
|
}
|
|
|
|
///
|
|
/// Create a socket signature
|
|
///
|
|
/// - Parameters:
|
|
/// - protocolFamily: The family of the socket to create.
|
|
/// - socketType: The type of socket to create.
|
|
/// - proto: The protocool to use for the socket.
|
|
/// - address: Address info for the socket.
|
|
/// - hostname: Hostname for this signature.
|
|
/// - port: Port for this signature.
|
|
///
|
|
/// - Returns: New Signature instance
|
|
///
|
|
internal init?(protocolFamily: Int32, socketType: Int32, proto: Int32, address: Address?, hostname: String?, port: Int32?) throws {
|
|
|
|
// This constructor requires all items be present...
|
|
guard let family = ProtocolFamily.getFamily(forValue: protocolFamily),
|
|
let type = SocketType.getType(forValue: socketType),
|
|
let pro = SocketProtocol.getProtocol(forValue: proto),
|
|
let _ = hostname,
|
|
let port = port else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Incomplete parameters.")
|
|
}
|
|
|
|
self.protocolFamily = family
|
|
self.socketType = type
|
|
self.proto = pro
|
|
|
|
// Validate the parameters...
|
|
if type == .stream {
|
|
guard (pro == .tcp || pro == .unix) else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Stream socket must use either .tcp or .unix for the protocol.")
|
|
}
|
|
}
|
|
if type == .datagram {
|
|
guard pro == .udp else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Datagram socket must use .udp for the protocol.")
|
|
}
|
|
}
|
|
|
|
self.address = address
|
|
|
|
self.hostname = hostname
|
|
self.port = port
|
|
}
|
|
|
|
///
|
|
/// Retrieve the UNIX address as an UnsafeMutablePointer
|
|
///
|
|
/// - Returns: Tuple containing the pointer plus the size. **Needs to be deallocated after use.**
|
|
///
|
|
internal func unixAddress() throws -> (UnsafeMutablePointer<UInt8>, Int) {
|
|
|
|
// Throw an exception if the path is not set...
|
|
if path == nil {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Specified path contains zero (0) bytes.")
|
|
}
|
|
|
|
let utf8 = path!.utf8
|
|
|
|
// macOS has a size identifier in front, Linux does not...
|
|
#if os(Linux)
|
|
let addrLen = MemoryLayout<sockaddr_un>.size
|
|
#else
|
|
let addrLen = MemoryLayout<UInt8>.size + MemoryLayout<sa_family_t>.size + utf8.count + 1
|
|
#endif
|
|
let addrPtr = UnsafeMutablePointer<UInt8>.allocate(capacity: addrLen)
|
|
|
|
var memLoc = 0
|
|
|
|
// macOS uses one byte for sa_family_t, Linux uses two...
|
|
#if os(Linux)
|
|
let afUnixShort = UInt16(AF_UNIX)
|
|
addrPtr[memLoc] = UInt8(afUnixShort & 0xFF)
|
|
memLoc += 1
|
|
addrPtr[memLoc] = UInt8((afUnixShort >> 8) & 0xFF)
|
|
memLoc += 1
|
|
#else
|
|
addrPtr[memLoc] = UInt8(addrLen)
|
|
memLoc += 1
|
|
addrPtr[memLoc] = UInt8(AF_UNIX)
|
|
memLoc += 1
|
|
#endif
|
|
|
|
// Copy the pathname...
|
|
for char in utf8 {
|
|
addrPtr[memLoc] = char
|
|
memLoc += 1
|
|
}
|
|
|
|
addrPtr[memLoc] = 0
|
|
|
|
return (addrPtr, addrLen)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: -- Error
|
|
|
|
///
|
|
/// `Socket` specific error structure.
|
|
///
|
|
public struct Error: Swift.Error, CustomStringConvertible {
|
|
|
|
// MARK: -- Public Properties
|
|
|
|
///
|
|
/// The error domain.
|
|
///
|
|
public let domain: String = SOCKET_ERR_DOMAIN
|
|
|
|
///
|
|
/// The error code: **see constants above for possible errors** (Readonly)
|
|
///
|
|
public internal(set) var errorCode: Int32
|
|
|
|
///
|
|
/// The reason for the error **(if available)** (Readonly)
|
|
///
|
|
public internal(set) var errorReason: String?
|
|
|
|
///
|
|
/// Returns a string description of the error. (Readonly)
|
|
///
|
|
public var description: String {
|
|
|
|
let reason: String = self.errorReason ?? "Reason: Unavailable"
|
|
return "Error code: \(self.errorCode)(0x\(String(self.errorCode, radix: 16, uppercase: true))), \(reason)"
|
|
}
|
|
|
|
///
|
|
/// The buffer size needed to complete the read. (Readonly)
|
|
///
|
|
public internal(set) var bufferSizeNeeded: Int32
|
|
|
|
// MARK: -- Public Functions
|
|
|
|
///
|
|
/// Initializes an Error Instance
|
|
///
|
|
/// - Parameters:
|
|
/// - code: Error code
|
|
/// - reason: Optional Error Reason
|
|
///
|
|
/// - Returns: Error instance
|
|
///
|
|
init(code: Int, reason: String?) {
|
|
|
|
self.errorCode = Int32(code)
|
|
self.errorReason = reason
|
|
self.bufferSizeNeeded = 0
|
|
}
|
|
|
|
///
|
|
/// Initializes an Error Instance for a too small receive buffer error.
|
|
///
|
|
/// - Parameter bufferSize: Required buffer size
|
|
///
|
|
/// - Returns: Error Instance
|
|
///
|
|
init(bufferSize: Int) {
|
|
|
|
self.init(code: Socket.SOCKET_ERR_RECV_BUFFER_TOO_SMALL, reason: nil)
|
|
self.bufferSizeNeeded = Int32(bufferSize)
|
|
}
|
|
|
|
///
|
|
/// Initializes an Error instance using SSLError
|
|
///
|
|
/// - Parameter error: SSLError instance to be transformed
|
|
///
|
|
/// - Returns: Error Instance
|
|
init(with error: SSLError) {
|
|
|
|
self.init(code: error.code, reason: error.description)
|
|
}
|
|
}
|
|
|
|
// MARK: Properties
|
|
|
|
// MARK: -- Private
|
|
|
|
///
|
|
/// Internal read buffer.
|
|
/// **Note:** The readBuffer is actually allocating unmanaged memory that'll
|
|
/// be deallocated when we're done with it.
|
|
///
|
|
var readBuffer: UnsafeMutablePointer<CChar> = UnsafeMutablePointer<CChar>.allocate(capacity: Socket.SOCKET_DEFAULT_READ_BUFFER_SIZE)
|
|
|
|
///
|
|
/// Internal Storage Buffer initially created with `Socket.SOCKET_DEFAULT_READ_BUFFER_SIZE`.
|
|
///
|
|
var readStorage: NSMutableData = NSMutableData(capacity: Socket.SOCKET_DEFAULT_READ_BUFFER_SIZE)!
|
|
|
|
|
|
// MARK: -- Public
|
|
|
|
///
|
|
/// The file descriptor representing this socket. (Readonly)
|
|
///
|
|
public internal(set) var socketfd: Int32 = SOCKET_INVALID_DESCRIPTOR
|
|
|
|
///
|
|
/// The signature for the socket. (Readonly)
|
|
/// **Note:** See Signature above.
|
|
///
|
|
public internal(set) var signature: Signature? = nil
|
|
|
|
///
|
|
/// The delegate that provides the SSL implementation.
|
|
///
|
|
public var delegate: SSLServiceDelegate?
|
|
|
|
///
|
|
/// Internal Read buffer size for all open sockets.
|
|
/// **Note:** Changing this value will cause the internal read buffer to
|
|
/// be discarded and reallocated with the new size. The value must be
|
|
/// set to at least `Socket.SOCKET_MINIMUM_READ_BUFFER_SIZE`. If set
|
|
/// to something smaller, it will be automatically set to the minimum
|
|
/// size as defined by `Socket.SOCKET_MINIMUM_READ_BUFFER_SIZE`.
|
|
///
|
|
public var readBufferSize: Int = Socket.SOCKET_DEFAULT_READ_BUFFER_SIZE {
|
|
|
|
// If the buffer size changes we need to reallocate the buffer...
|
|
didSet {
|
|
|
|
// Ensure minimum buffer size...
|
|
if readBufferSize < Socket.SOCKET_MINIMUM_READ_BUFFER_SIZE {
|
|
|
|
readBufferSize = Socket.SOCKET_MINIMUM_READ_BUFFER_SIZE
|
|
}
|
|
|
|
print("Creating read buffer of size: \(readBufferSize)")
|
|
if readBufferSize != oldValue {
|
|
|
|
readBuffer.deinitialize()
|
|
readBuffer.deallocate(capacity: oldValue)
|
|
readBuffer = UnsafeMutablePointer<CChar>.allocate(capacity: readBufferSize)
|
|
readBuffer.initialize(to:0)
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Maximum size of the queue containing pending connections.
|
|
/// **Note:** Default value is `Socket.SOCKET_DEFAULT_MAX_BACKLOG`
|
|
///
|
|
public var maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG
|
|
|
|
///
|
|
/// True if this socket is connected. False otherwise. (Readonly)
|
|
///
|
|
public internal(set) var isConnected: Bool = false
|
|
|
|
///
|
|
/// True if this socket is blocking. False otherwise. (Readonly)
|
|
///
|
|
public internal(set) var isBlocking: Bool = true
|
|
|
|
///
|
|
/// True if this socket is listening. False otherwise. (Readonly)
|
|
///
|
|
public internal(set) var isListening: Bool = false
|
|
|
|
///
|
|
/// True if this socket's remote connection has closed. (Readonly)
|
|
/// **Note:** This is only valid after a Socket is connected.
|
|
///
|
|
public internal(set) var remoteConnectionClosed: Bool = false
|
|
|
|
///
|
|
/// True if the socket is listening or connected. (Readonly)
|
|
///
|
|
public var isActive: Bool {
|
|
|
|
return isListening || isConnected
|
|
}
|
|
|
|
///
|
|
/// True if this a server, false otherwise. (Readonly)
|
|
///
|
|
public var isServer: Bool {
|
|
|
|
return isListening
|
|
}
|
|
|
|
///
|
|
/// True if this socket is secure, false otherwise. (Readonly)
|
|
///
|
|
public var isSecure: Bool {
|
|
|
|
guard let sig = signature else {
|
|
return false
|
|
}
|
|
return sig.isSecure
|
|
}
|
|
|
|
///
|
|
/// Listening port (-1 if not listening). (Readonly)
|
|
///
|
|
public var listeningPort: Int32 {
|
|
|
|
guard let sig = signature, isListening else {
|
|
return Int32(-1)
|
|
}
|
|
return sig.port
|
|
}
|
|
|
|
///
|
|
/// The remote host name this socket is connected to. (Readonly)
|
|
///
|
|
public var remoteHostname: String {
|
|
|
|
guard let sig = signature,
|
|
let host = sig.hostname else {
|
|
return Socket.NO_HOSTNAME
|
|
}
|
|
|
|
return host
|
|
}
|
|
|
|
///
|
|
/// The remote port this socket is connected to. (Readonly)
|
|
///
|
|
public var remotePort: Int32 {
|
|
|
|
guard let sig = signature, sig.port != Socket.SOCKET_INVALID_PORT else {
|
|
return Socket.SOCKET_INVALID_PORT
|
|
}
|
|
|
|
return sig.port
|
|
}
|
|
|
|
///
|
|
/// The path this socket is connected to or listening on. (Readonly)
|
|
///
|
|
public var remotePath: String? {
|
|
|
|
guard let sig = signature,
|
|
let path = sig.path else {
|
|
return nil
|
|
}
|
|
|
|
return path
|
|
}
|
|
|
|
|
|
// MARK: Class Functions
|
|
|
|
///
|
|
/// Create a configured Socket instance.
|
|
/// **Note:** Calling with no passed parameters will create a default socket: IPV4, stream, TCP.
|
|
///
|
|
/// - Parameters:
|
|
/// - family: The family of the socket to create.
|
|
/// - type: The type of socket to create.
|
|
/// - proto: The protocool to use for the socket.
|
|
///
|
|
/// - Returns: New Socket instance
|
|
///
|
|
public class func create(family: ProtocolFamily = .inet, type: SocketType = .stream, proto: SocketProtocol = .tcp) throws -> Socket {
|
|
|
|
// Validate the parameters...
|
|
if type == .stream {
|
|
guard proto == .tcp || proto == .unix else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Stream socket must use either .tcp or .unix for the protocol.")
|
|
}
|
|
}
|
|
if type == .datagram {
|
|
guard proto == .udp || proto == .unix else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: "Datagram socket must use .udp or .unix for the protocol.")
|
|
}
|
|
}
|
|
|
|
return try Socket(family: family, type: type, proto: proto)
|
|
}
|
|
|
|
///
|
|
/// Create a configured and connected Socket instance.
|
|
///
|
|
/// - Parameter signature: The socket signature containing the connection information.
|
|
///
|
|
/// - Returns: New Socket instance. **Note:** Connection status should be checked via the *isConnected* property on the returned socket.
|
|
///
|
|
public class func create(connectedUsing signature: Signature) throws -> Socket {
|
|
|
|
let socket = try Socket(family: signature.protocolFamily, type: signature.socketType, proto: signature.proto)
|
|
|
|
try socket.connect(using: signature)
|
|
|
|
return socket
|
|
}
|
|
|
|
///
|
|
/// Create an instance for existing open socket fd.
|
|
///
|
|
/// - Parameters:
|
|
/// - nativeHandle: Open file descriptor.
|
|
/// - address: The Address associated with the open fd.
|
|
///
|
|
/// - Returns: New Socket instance
|
|
///
|
|
public class func create(fromNativeHandle nativeHandle: Int32, address: Address?) throws -> Socket {
|
|
|
|
guard let addr = address else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_MISSING_CONNECTION_DATA, reason: "Unable to access socket connection data.")
|
|
}
|
|
|
|
return try Socket(fd: nativeHandle, remoteAddress: addr)
|
|
}
|
|
|
|
///
|
|
/// Extract the string form of IP address and the port.
|
|
///
|
|
/// - Parameter fromAddress: The Address struct.
|
|
///
|
|
/// - Returns: Optional Tuple containing the hostname and port.
|
|
///
|
|
public class func hostnameAndPort(from address: Address) -> (hostname: String, port: Int32)? {
|
|
|
|
var port: Int32 = 0
|
|
var bufLen: Int = 0
|
|
var buf: [CChar]
|
|
|
|
switch address {
|
|
|
|
case .ipv4(let address_in):
|
|
var addr_in = address_in
|
|
let addr = addr_in.asAddr()
|
|
bufLen = Int(INET_ADDRSTRLEN)
|
|
buf = [CChar](repeating: 0, count: bufLen)
|
|
inet_ntop(Int32(addr.sa_family), &addr_in.sin_addr, &buf, socklen_t(bufLen))
|
|
if isLittleEndian {
|
|
port = Int32(UInt16(addr_in.sin_port).byteSwapped)
|
|
} else {
|
|
port = Int32(UInt16(addr_in.sin_port))
|
|
}
|
|
|
|
case .ipv6(let address_in):
|
|
var addr_in = address_in
|
|
let addr = addr_in.asAddr()
|
|
bufLen = Int(INET6_ADDRSTRLEN)
|
|
buf = [CChar](repeating: 0, count: bufLen)
|
|
inet_ntop(Int32(addr.sa_family), &addr_in.sin6_addr, &buf, socklen_t(bufLen))
|
|
if isLittleEndian {
|
|
port = Int32(UInt16(addr_in.sin6_port).byteSwapped)
|
|
} else {
|
|
port = Int32(UInt16(addr_in.sin6_port))
|
|
}
|
|
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
if let s = String(validatingUTF8: buf) {
|
|
return (s, port)
|
|
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
///
|
|
/// Check whether one or more sockets are available for reading and/or writing
|
|
///
|
|
/// - Parameter sockets: Array of Sockets to be tested.
|
|
///
|
|
/// - Returns: Tuple containing two arrays of Sockets, one each representing readable and writable sockets.
|
|
///
|
|
public class func checkStatus(for sockets: [Socket]) throws -> (readables: [Socket], writables: [Socket]) {
|
|
|
|
var readables: [Socket] = []
|
|
var writables: [Socket] = []
|
|
|
|
for socket in sockets {
|
|
|
|
let result = try socket.isReadableOrWritable()
|
|
if result.readable {
|
|
readables.append(socket)
|
|
}
|
|
if result.writable {
|
|
writables.append(socket)
|
|
}
|
|
}
|
|
|
|
return (readables, writables)
|
|
}
|
|
|
|
///
|
|
/// Monitor an array of sockets, returning when data is available or timeout occurs.
|
|
///
|
|
/// - Parameters:
|
|
/// - sockets: An array of sockets to be monitored.
|
|
/// - timeout: Timeout (in msec) before returning. A timeout value of 0 will return immediately.
|
|
/// - waitForever: If true, this function will wait indefinitely regardless of timeout value. Defaults to false.
|
|
///
|
|
/// - Returns: An optional array of sockets which have data available or nil if a timeout expires.
|
|
///
|
|
public class func wait(for sockets: [Socket], timeout: UInt, waitForever: Bool = false) throws -> [Socket]? {
|
|
|
|
// Validate we have sockets to look for and they are valid...
|
|
for socket in sockets {
|
|
|
|
if socket.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
if !socket.isActive {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_NOT_ACTIVE, reason: nil)
|
|
}
|
|
}
|
|
|
|
// Setup the timeout...
|
|
var timer = timeval()
|
|
if timeout > 0 && !waitForever {
|
|
|
|
// First get seconds...
|
|
let secs = Int(Double(timeout / 1000))
|
|
timer.tv_sec = secs
|
|
|
|
// Now get the leftover millisecs...
|
|
let msecs = Int32(Double(timeout % 1000))
|
|
|
|
// Note: timeval expects microseconds, convert now...
|
|
let uSecs = msecs * 1000
|
|
|
|
// Now the leftover microseconds...
|
|
#if os(Linux)
|
|
timer.tv_usec = Int(uSecs)
|
|
#else
|
|
timer.tv_usec = Int32(uSecs)
|
|
#endif
|
|
}
|
|
|
|
// Setup the array of readfds...
|
|
var readfds = fd_set()
|
|
FD.ZERO(set: &readfds)
|
|
|
|
var highSocketfd: Int32 = 0
|
|
for socket in sockets {
|
|
|
|
if socket.socketfd > highSocketfd {
|
|
highSocketfd = socket.socketfd
|
|
}
|
|
FD.SET(fd: socket.socketfd, set: &readfds)
|
|
}
|
|
|
|
// Issue the select...
|
|
var count: Int32 = 0
|
|
if waitForever {
|
|
count = select(highSocketfd + 1, &readfds, nil, nil, nil)
|
|
} else {
|
|
count = select(highSocketfd + 1, &readfds, nil, nil, &timer)
|
|
}
|
|
|
|
// A count of less than zero indicates select failed...
|
|
if count < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_SELECT_FAILED, reason: String(validatingUTF8: strerror(errno)) ?? "Error: \(errno)")
|
|
}
|
|
|
|
// A count equal zero, indicates we timed out...
|
|
if count == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Build the array of returned sockets...
|
|
var dataSockets = [Socket]()
|
|
for socket in sockets {
|
|
|
|
if FD.ISSET(fd: socket.socketfd, set: &readfds) {
|
|
dataSockets.append(socket)
|
|
}
|
|
}
|
|
|
|
return dataSockets
|
|
}
|
|
|
|
///
|
|
/// Creates an Address for a given host and port.
|
|
///
|
|
/// - Parameters:
|
|
/// - hostname: Hostname for this signature.
|
|
/// - port: Port for this signature.
|
|
///
|
|
/// - Returns: An Address instance, or nil if the hostname and port are not valid.
|
|
///
|
|
public class func createAddress(for host: String, on port: Int32) -> Address? {
|
|
|
|
var info: UnsafeMutablePointer<addrinfo>? = UnsafeMutablePointer<addrinfo>.allocate(capacity: 1)
|
|
|
|
// Retrieve the info on our target...
|
|
var status: Int32 = getaddrinfo(host, String(port), nil, &info)
|
|
if status != 0 {
|
|
|
|
return nil
|
|
}
|
|
|
|
// Defer cleanup of our target info...
|
|
defer {
|
|
|
|
if info != nil {
|
|
freeaddrinfo(info)
|
|
}
|
|
}
|
|
|
|
var address: Address
|
|
if info!.pointee.ai_family == Int32(AF_INET) {
|
|
|
|
var addr = sockaddr_in()
|
|
memcpy(&addr, info!.pointee.ai_addr, Int(MemoryLayout<sockaddr_in>.size))
|
|
address = .ipv4(addr)
|
|
|
|
} else if info!.pointee.ai_family == Int32(AF_INET6) {
|
|
|
|
var addr = sockaddr_in6()
|
|
memcpy(&addr, info!.pointee.ai_addr, Int(MemoryLayout<sockaddr_in6>.size))
|
|
address = .ipv6(addr)
|
|
|
|
} else {
|
|
|
|
return nil
|
|
}
|
|
|
|
return address
|
|
}
|
|
|
|
// MARK: Lifecycle Functions
|
|
|
|
// MARK: -- Private
|
|
|
|
///
|
|
/// Internal initializer to create a configured Socket instance.
|
|
///
|
|
/// - Parameters:
|
|
/// - family: The family of the socket to create.
|
|
/// - type: The type of socket to create.
|
|
/// - proto: The protocol to use for the socket.
|
|
///
|
|
/// - Returns: New Socket instance
|
|
///
|
|
private init(family: ProtocolFamily, type: SocketType, proto: SocketProtocol) throws {
|
|
|
|
// Initialize the read buffer...
|
|
self.readBuffer.initialize(to: 0)
|
|
|
|
// If the family is .unix, set the protocol to .unix as well...
|
|
var sockProto = proto
|
|
if family == .unix {
|
|
sockProto = .unix
|
|
}
|
|
|
|
// Create the socket...
|
|
#if os(Linux)
|
|
self.socketfd = Glibc.socket(family.value, type.value, sockProto.value)
|
|
#else
|
|
self.socketfd = Darwin.socket(family.value, type.value, sockProto.value)
|
|
#endif
|
|
|
|
// If error, throw an appropriate exception...
|
|
if self.socketfd < 0 {
|
|
|
|
self.socketfd = Socket.SOCKET_INVALID_DESCRIPTOR
|
|
throw Error(code: Socket.SOCKET_ERR_UNABLE_TO_CREATE_SOCKET, reason: self.lastError())
|
|
}
|
|
|
|
#if !os(Linux)
|
|
// Set the socket to ignore SIGPIPE to avoid dying on interrupted connections...
|
|
// Note: Linux does not support the SO_NOSIGPIPE option. Instead, we use the
|
|
// MSG_NOSIGNAL flags passed to send. See the write() functions below.
|
|
var on: Int32 = 1
|
|
if setsockopt(self.socketfd, SOL_SOCKET, SO_NOSIGPIPE, &on, socklen_t(MemoryLayout<Int32>.size)) < 0 {
|
|
throw Error(code: Socket.SOCKET_ERR_SETSOCKOPT_FAILED, reason: self.lastError())
|
|
}
|
|
#endif
|
|
|
|
// Create the signature...
|
|
try self.signature = Signature(
|
|
protocolFamily: family.value,
|
|
socketType: type.value,
|
|
proto: sockProto.value,
|
|
address: nil)
|
|
}
|
|
|
|
///
|
|
/// Private constructor to create an instance for existing open socket fd.
|
|
///
|
|
/// - Parameters:
|
|
/// - fd: Open file descriptor.
|
|
/// - remoteAddress: The Address associated with the open fd.
|
|
///
|
|
/// - Returns: New Socket instance
|
|
///
|
|
private init(fd: Int32, remoteAddress: Address, path: String? = nil) throws {
|
|
|
|
self.isConnected = true
|
|
self.isListening = false
|
|
self.readBuffer.initialize(to: 0)
|
|
|
|
self.socketfd = fd
|
|
|
|
// Create the signature...
|
|
#if os(Linux)
|
|
let type = Int32(SOCK_STREAM.rawValue)
|
|
#else
|
|
let type = SOCK_STREAM
|
|
|
|
// Set the socket to ignore SIGPIPE to avoid dying on interrupted connections...
|
|
// Note: Linux does not support the SO_NOSIGPIPE option. Instead, we use the
|
|
// MSG_NOSIGNAL flags passed to send. See the write() functions below.
|
|
var on: Int32 = 1
|
|
if setsockopt(self.socketfd, SOL_SOCKET, SO_NOSIGPIPE, &on, socklen_t(MemoryLayout<Int32>.size)) < 0 {
|
|
throw Error(code: Socket.SOCKET_ERR_SETSOCKOPT_FAILED, reason: self.lastError())
|
|
}
|
|
#endif
|
|
|
|
if path != nil {
|
|
|
|
try self.signature = Signature(socketType: .stream, proto: .unix, path: path)
|
|
|
|
} else {
|
|
if let (hostname, port) = Socket.hostnameAndPort(from: remoteAddress) {
|
|
try self.signature = Signature(
|
|
protocolFamily: Int32(remoteAddress.addr.sa_family),
|
|
socketType: type,
|
|
proto: Int32(IPPROTO_TCP),
|
|
address: remoteAddress,
|
|
hostname: hostname,
|
|
port: port)
|
|
} else {
|
|
try self.signature = Signature(
|
|
protocolFamily: Int32(remoteAddress.addr.sa_family),
|
|
socketType: type,
|
|
proto: Int32(IPPROTO_TCP),
|
|
address: remoteAddress)
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Cleanup: close the socket, free memory buffers.
|
|
///
|
|
deinit {
|
|
|
|
if self.socketfd > 0 {
|
|
|
|
self.close()
|
|
}
|
|
|
|
// Destroy and free the readBuffer...
|
|
self.readBuffer.deinitialize()
|
|
self.readBuffer.deallocate(capacity: self.readBufferSize)
|
|
}
|
|
|
|
// MARK: Public Functions
|
|
|
|
// MARK: -- Accept
|
|
|
|
///
|
|
/// Accepts an incoming client connection request on the current instance, leaving the current instance still listening.
|
|
///
|
|
/// - Returns: New Socket instance representing the newly accepted socket.
|
|
///
|
|
public func acceptClientConnection() throws -> Socket {
|
|
|
|
// The socket must've been created, not connected and listening...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
if self.isConnected {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_ALREADY_CONNECTED, reason: nil)
|
|
}
|
|
|
|
if !self.isListening {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_NOT_LISTENING, reason: nil)
|
|
}
|
|
|
|
// Accept the remote connection...
|
|
var socketfd2: Int32 = Socket.SOCKET_INVALID_DESCRIPTOR
|
|
var address: Address? = nil
|
|
|
|
var keepRunning: Bool = true
|
|
repeat {
|
|
|
|
switch self.signature!.protocolFamily {
|
|
|
|
case .inet:
|
|
var acceptAddr = sockaddr_in()
|
|
var addrSize = socklen_t(MemoryLayout<sockaddr_in>.size)
|
|
|
|
#if os(Linux)
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Glibc.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#else
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Darwin.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#endif
|
|
if fd < 0 {
|
|
|
|
if errno == EINTR {
|
|
continue
|
|
}
|
|
|
|
// Note: if you're running tests inside Xcode and the tests stop on this line
|
|
// and the tests fail, but they work if you run `swift test` on the
|
|
// command line, Hit `Deactivate Breakpoints` in Xcode and try again
|
|
throw Error(code: Socket.SOCKET_ERR_ACCEPT_FAILED, reason: self.lastError())
|
|
}
|
|
socketfd2 = fd
|
|
address = .ipv4(acceptAddr)
|
|
|
|
case .inet6:
|
|
var acceptAddr = sockaddr_in6()
|
|
var addrSize = socklen_t(MemoryLayout<sockaddr_in6>.size)
|
|
|
|
#if os(Linux)
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Glibc.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#else
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Darwin.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#endif
|
|
if fd < 0 {
|
|
|
|
if errno == EINTR {
|
|
continue
|
|
}
|
|
|
|
// Note: if you're running tests inside Xcode and the tests stop on this line
|
|
// and the tests fail, but they work if you run `swift test` on the
|
|
// command line, Hit `Deactivate Breakpoints` in Xcode and try again
|
|
throw Error(code: Socket.SOCKET_ERR_ACCEPT_FAILED, reason: self.lastError())
|
|
}
|
|
socketfd2 = fd
|
|
address = .ipv6(acceptAddr)
|
|
|
|
case .unix:
|
|
var acceptAddr = sockaddr_un()
|
|
var addrSize = socklen_t(MemoryLayout<sockaddr_un>.size)
|
|
|
|
#if os(Linux)
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Glibc.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#else
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Darwin.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#endif
|
|
if fd < 0 {
|
|
|
|
if errno == EINTR {
|
|
continue
|
|
}
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_ACCEPT_FAILED, reason: self.lastError())
|
|
}
|
|
socketfd2 = fd
|
|
address = .unix(acceptAddr)
|
|
|
|
}
|
|
|
|
keepRunning = false
|
|
|
|
} while keepRunning
|
|
|
|
// Create the new socket...
|
|
// Note: The current socket continues to listen.
|
|
let newSocket = try Socket(fd: socketfd2, remoteAddress: address!, path: self.signature?.path)
|
|
|
|
// Let the delegate do post accept handling and verification...
|
|
do {
|
|
|
|
if self.delegate != nil {
|
|
try self.delegate?.onAccept(socket: newSocket)
|
|
newSocket.signature?.isSecure = true
|
|
}
|
|
|
|
} catch let error {
|
|
|
|
guard let sslError = error as? SSLError else {
|
|
|
|
throw error
|
|
}
|
|
|
|
throw Error(with: sslError)
|
|
}
|
|
|
|
// Return the new socket...
|
|
return newSocket
|
|
}
|
|
|
|
///
|
|
/// Accepts an incoming connection request replacing the existing socket with the newly accepted one.
|
|
///
|
|
public func acceptConnection() throws {
|
|
|
|
// The socket must've been created, not connected and listening...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
if self.isConnected {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_ALREADY_CONNECTED, reason: nil)
|
|
}
|
|
|
|
if !self.isListening {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_NOT_LISTENING, reason: nil)
|
|
}
|
|
|
|
// Accept the remote connection...
|
|
var socketfd2: Int32 = Socket.SOCKET_INVALID_DESCRIPTOR
|
|
var address: Address? = nil
|
|
|
|
var keepRunning: Bool = true
|
|
repeat {
|
|
|
|
switch self.signature!.protocolFamily {
|
|
|
|
case .inet:
|
|
var acceptAddr = sockaddr_in()
|
|
var addrSize = socklen_t(MemoryLayout<sockaddr_in>.size)
|
|
|
|
#if os(Linux)
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Glibc.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#else
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Darwin.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#endif
|
|
if fd < 0 {
|
|
|
|
if errno == EINTR {
|
|
continue
|
|
}
|
|
|
|
// Note: if you're running tests inside Xcode and the tests stop on this line
|
|
// and the tests fail, but they work if you run `swift test` on the
|
|
// command line, Hit `Deactivate Breakpoints` in Xcode and try again
|
|
throw Error(code: Socket.SOCKET_ERR_ACCEPT_FAILED, reason: self.lastError())
|
|
}
|
|
socketfd2 = fd
|
|
address = .ipv4(acceptAddr)
|
|
|
|
case .inet6:
|
|
var acceptAddr = sockaddr_in6()
|
|
var addrSize = socklen_t(MemoryLayout<sockaddr_in6>.size)
|
|
|
|
#if os(Linux)
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Glibc.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#else
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Darwin.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#endif
|
|
if fd < 0 {
|
|
|
|
if errno == EINTR {
|
|
continue
|
|
}
|
|
|
|
// Note: if you're running tests inside Xcode and the tests stop on this line
|
|
// and the tests fail, but they work if you run `swift test` on the
|
|
// command line, Hit `Deactivate Breakpoints` in Xcode and try again
|
|
throw Error(code: Socket.SOCKET_ERR_ACCEPT_FAILED, reason: self.lastError())
|
|
}
|
|
socketfd2 = fd
|
|
address = .ipv6(acceptAddr)
|
|
|
|
case .unix:
|
|
var acceptAddr = sockaddr_un()
|
|
var addrSize = socklen_t(MemoryLayout<sockaddr_un>.size)
|
|
|
|
#if os(Linux)
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Glibc.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#else
|
|
let fd = withUnsafeMutablePointer(to: &acceptAddr) {
|
|
Darwin.accept(self.socketfd, UnsafeMutableRawPointer($0).assumingMemoryBound(to: sockaddr.self), &addrSize)
|
|
}
|
|
#endif
|
|
if fd < 0 {
|
|
|
|
if errno == EINTR {
|
|
continue
|
|
}
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_ACCEPT_FAILED, reason: self.lastError())
|
|
}
|
|
socketfd2 = fd
|
|
address = .unix(acceptAddr)
|
|
|
|
}
|
|
|
|
keepRunning = false
|
|
|
|
} while keepRunning
|
|
|
|
// Close the old socket...
|
|
self.close()
|
|
|
|
// Save the address...
|
|
self.signature!.address = address
|
|
|
|
// Replace the existing socketfd with the new one...
|
|
self.socketfd = socketfd2
|
|
|
|
if let (hostname, port) = Socket.hostnameAndPort(from: address!) {
|
|
self.signature!.hostname = hostname
|
|
self.signature!.port = port
|
|
}
|
|
|
|
// We're connected but no longer listening...
|
|
self.isConnected = true
|
|
self.isListening = false
|
|
|
|
// Let the delegate do post accept handling and verification...
|
|
do {
|
|
|
|
if self.delegate != nil {
|
|
try self.delegate?.onAccept(socket: self)
|
|
self.signature?.isSecure = true
|
|
}
|
|
|
|
} catch let error {
|
|
|
|
guard let sslError = error as? SSLError else {
|
|
|
|
throw error
|
|
}
|
|
|
|
throw Error(with: sslError)
|
|
}
|
|
}
|
|
|
|
// MARK: -- Close
|
|
|
|
///
|
|
/// Closes the current socket.
|
|
///
|
|
public func close() {
|
|
|
|
self.close(withSSLCleanup: true)
|
|
}
|
|
|
|
// MARK: -- Connect
|
|
|
|
///
|
|
/// Connects to the named host on the specified port.
|
|
///
|
|
/// - Parameters:
|
|
/// - host: The host name to connect to.
|
|
/// - port: The port to be used.
|
|
///
|
|
public func connect(to host: String, port: Int32) throws {
|
|
|
|
// The socket must've been created and must not be connected...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
if self.isConnected {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_ALREADY_CONNECTED, reason: nil)
|
|
}
|
|
|
|
if host.utf8.count == 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_INVALID_HOSTNAME, reason: nil)
|
|
}
|
|
|
|
if port == 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_INVALID_PORT, reason: "Connect to port cannot be zero (0).")
|
|
}
|
|
|
|
// Tell the delegate to initialize as a client...
|
|
do {
|
|
|
|
try self.delegate?.initialize(asServer: false)
|
|
|
|
} catch let error {
|
|
|
|
guard let sslError = error as? SSLError else {
|
|
|
|
throw error
|
|
}
|
|
|
|
throw Error(with: sslError)
|
|
}
|
|
|
|
// Create the hints for our search...
|
|
let socketType: SocketType = .stream
|
|
#if os(Linux)
|
|
var hints = addrinfo(
|
|
ai_flags: AI_PASSIVE,
|
|
ai_family: AF_UNSPEC,
|
|
ai_socktype: socketType.value,
|
|
ai_protocol: 0,
|
|
ai_addrlen: 0,
|
|
ai_addr: nil,
|
|
ai_canonname: nil,
|
|
ai_next: nil)
|
|
#else
|
|
var hints = addrinfo(
|
|
ai_flags: AI_PASSIVE,
|
|
ai_family: AF_UNSPEC,
|
|
ai_socktype: socketType.value,
|
|
ai_protocol: 0,
|
|
ai_addrlen: 0,
|
|
ai_canonname: nil,
|
|
ai_addr: nil,
|
|
ai_next: nil)
|
|
#endif
|
|
|
|
var targetInfo: UnsafeMutablePointer<addrinfo>? = UnsafeMutablePointer<addrinfo>.allocate(capacity: 1)
|
|
|
|
// Retrieve the info on our target...
|
|
var status: Int32 = getaddrinfo(host, String(port), &hints, &targetInfo)
|
|
if status != 0 {
|
|
|
|
var errorString: String
|
|
if status == EAI_SYSTEM {
|
|
errorString = String(validatingUTF8: strerror(errno)) ?? "Unknown error code."
|
|
} else {
|
|
errorString = String(validatingUTF8: gai_strerror(errno)) ?? "Unknown error code."
|
|
}
|
|
throw Error(code: Socket.SOCKET_ERR_GETADDRINFO_FAILED, reason: errorString)
|
|
}
|
|
|
|
// Defer cleanup of our target info...
|
|
defer {
|
|
|
|
if targetInfo != nil {
|
|
freeaddrinfo(targetInfo)
|
|
}
|
|
}
|
|
|
|
var socketDescriptor: Int32?
|
|
|
|
var info = targetInfo
|
|
while info != nil {
|
|
|
|
#if os(Linux)
|
|
socketDescriptor = Glibc.socket(info!.pointee.ai_family, info!.pointee.ai_socktype, info!.pointee.ai_protocol)
|
|
#else
|
|
socketDescriptor = Darwin.socket(info!.pointee.ai_family, info!.pointee.ai_socktype, info!.pointee.ai_protocol)
|
|
#endif
|
|
if socketDescriptor == -1 {
|
|
continue
|
|
}
|
|
|
|
// Connect to the server...
|
|
#if os(Linux)
|
|
status = Glibc.connect(socketDescriptor!, info!.pointee.ai_addr, info!.pointee.ai_addrlen)
|
|
#else
|
|
status = Darwin.connect(socketDescriptor!, info!.pointee.ai_addr, info!.pointee.ai_addrlen)
|
|
#endif
|
|
|
|
// Break if successful...
|
|
if status == 0 {
|
|
break
|
|
}
|
|
|
|
// Close the socket that was opened... Protocol family may have changed...
|
|
#if os(Linux)
|
|
_ = Glibc.close(socketDescriptor!)
|
|
#else
|
|
_ = Darwin.close(socketDescriptor!)
|
|
#endif
|
|
socketDescriptor = nil
|
|
info = info?.pointee.ai_next
|
|
}
|
|
|
|
// Throw if there is a status error...
|
|
if status != 0 || socketDescriptor == nil {
|
|
|
|
if socketDescriptor != nil {
|
|
#if os(Linux)
|
|
_ = Glibc.close(socketDescriptor!)
|
|
#else
|
|
_ = Darwin.close(socketDescriptor!)
|
|
#endif
|
|
}
|
|
throw Error(code: Socket.SOCKET_ERR_GETADDRINFO_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
// Close the existing socket (if open) before replacing it...
|
|
if self.socketfd != Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
self.close(withSSLCleanup: false)
|
|
}
|
|
|
|
self.socketfd = socketDescriptor!
|
|
self.isConnected = true
|
|
var address: Address
|
|
if info!.pointee.ai_family == Int32(AF_INET6) {
|
|
|
|
var addr = sockaddr_in6()
|
|
memcpy(&addr, info!.pointee.ai_addr, Int(MemoryLayout<sockaddr_in6>.size))
|
|
address = .ipv6(addr)
|
|
|
|
} else if info!.pointee.ai_family == Int32(AF_INET) {
|
|
|
|
var addr = sockaddr_in()
|
|
memcpy(&addr, info!.pointee.ai_addr, Int(MemoryLayout<sockaddr_in>.size))
|
|
address = .ipv4(addr)
|
|
|
|
} else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: "Unable to determine connected socket protocol family.")
|
|
}
|
|
|
|
try self.signature = Signature(
|
|
protocolFamily: Int32(info!.pointee.ai_family),
|
|
socketType: info!.pointee.ai_socktype,
|
|
proto: info!.pointee.ai_protocol,
|
|
address: address,
|
|
hostname: host,
|
|
port: port)
|
|
|
|
// Let the delegate do post connect handling and verification...
|
|
do {
|
|
|
|
if self.delegate != nil {
|
|
try self.delegate?.onConnect(socket: self)
|
|
self.signature?.isSecure = true
|
|
}
|
|
|
|
} catch let error {
|
|
|
|
guard let sslError = error as? SSLError else {
|
|
|
|
throw error
|
|
}
|
|
|
|
throw Error(with: sslError)
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Connects to the named host on the specified port.
|
|
///
|
|
/// - Parameters path: Path to connect to.
|
|
///
|
|
public func connect(to path: String) throws {
|
|
|
|
// Make sure this is a UNIX socket...
|
|
guard let sig = self.signature, sig.protocolFamily == .unix else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created and must not be connected...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
if self.isConnected {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_ALREADY_CONNECTED, reason: nil)
|
|
}
|
|
|
|
// Create the signature...
|
|
self.signature = try Signature(socketType: .stream, proto: .unix, path: path)
|
|
guard let signature = self.signature else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_MISSING_CONNECTION_DATA, reason: "Unable to access connection data.")
|
|
}
|
|
|
|
// Now, do the connection using the supplied address...
|
|
let (addrPtr, addrLen) = try signature.unixAddress()
|
|
defer {
|
|
addrPtr.deallocate(capacity: addrLen)
|
|
}
|
|
|
|
let rc = addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
|
(p:UnsafeMutablePointer<sockaddr>) -> Int32 in
|
|
|
|
#if os(Linux)
|
|
return Glibc.connect(self.socketfd, p, socklen_t(addrLen))
|
|
#else
|
|
return Darwin.connect(self.socketfd, p, socklen_t(addrLen))
|
|
#endif
|
|
}
|
|
if rc < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_CONNECT_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
self.isConnected = true
|
|
}
|
|
|
|
///
|
|
/// Connect to the address or hostname/port or path pointed to by the signature passed.
|
|
///
|
|
/// - Parameter signature: Signature containing the address hostname/port to connect to.
|
|
///
|
|
public func connect(using signature: Signature) throws {
|
|
|
|
// Make sure we've got a valid socket...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
// Ensure we've got a proper address...
|
|
// Handle the Unix style socket first...
|
|
if let path = signature.path {
|
|
|
|
try self.connect(to: path)
|
|
return
|
|
}
|
|
|
|
if signature.hostname == nil || signature.port == Socket.SOCKET_INVALID_PORT {
|
|
|
|
guard let _ = signature.address else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_MISSING_CONNECTION_DATA, reason: "Unable to access connection data.")
|
|
}
|
|
|
|
} else {
|
|
|
|
// Otherwise, make sure we've got a hostname and port...
|
|
guard let hostname = signature.hostname,
|
|
signature.port != Socket.SOCKET_INVALID_PORT else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_MISSING_CONNECTION_DATA, reason: "Unable to access hostname and port.")
|
|
}
|
|
|
|
// Connect using hostname and port....
|
|
try self.connect(to: hostname, port: signature.port)
|
|
return
|
|
}
|
|
|
|
// Tell the delegate to initialize as a client...
|
|
do {
|
|
|
|
try self.delegate?.initialize(asServer: false)
|
|
|
|
} catch let error {
|
|
|
|
guard let sslError = error as? SSLError else {
|
|
|
|
throw error
|
|
}
|
|
|
|
throw Error(with: sslError)
|
|
}
|
|
|
|
// Now, do the connection using the supplied address...
|
|
var remoteAddr = signature.address!.addr
|
|
|
|
#if os(Linux)
|
|
let rc = withUnsafeMutablePointer(to: &remoteAddr) {
|
|
Glibc.connect(self.socketfd, UnsafeMutablePointer($0), socklen_t(signature.address!.size))
|
|
}
|
|
#else
|
|
let rc = withUnsafeMutablePointer(to: &remoteAddr) {
|
|
Darwin.connect(self.socketfd, UnsafeMutablePointer($0), socklen_t(signature.address!.size))
|
|
}
|
|
#endif
|
|
if rc < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_CONNECT_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
if let (hostname, port) = Socket.hostnameAndPort(from: signature.address!) {
|
|
|
|
var sig = signature
|
|
sig.hostname = hostname
|
|
sig.port = Int32(port)
|
|
self.signature = sig
|
|
self.isConnected = true
|
|
}
|
|
|
|
// Let the delegate do post connect handling and verification...
|
|
do {
|
|
|
|
if self.delegate != nil {
|
|
try self.delegate?.onConnect(socket: self)
|
|
self.signature?.isSecure = true
|
|
}
|
|
|
|
} catch let error {
|
|
|
|
guard let sslError = error as? SSLError else {
|
|
|
|
throw error
|
|
}
|
|
|
|
throw Error(with: sslError)
|
|
}
|
|
}
|
|
|
|
// MARK: -- Listen
|
|
|
|
// MARK: --- TCP
|
|
|
|
///
|
|
/// Listen on a port, limiting the maximum number of pending connections.
|
|
///
|
|
/// - Parameters:
|
|
/// - port: The port to listen on.
|
|
/// - maxBacklogSize: The maximum size of the queue containing pending connections. Default is *Socket.SOCKET_DEFAULT_MAX_BACKLOG*.
|
|
///
|
|
public func listen(on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) throws {
|
|
|
|
// Make sure we've got a valid socket...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
// Set a flag so that this address can be re-used immediately after the connection
|
|
// closes. (TCP normally imposes a delay before an address can be re-used.)
|
|
var on: Int32 = 1
|
|
if setsockopt(self.socketfd, SOL_SOCKET, SO_REUSEADDR, &on, socklen_t(MemoryLayout<Int32>.size)) < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_SETSOCKOPT_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
// Get the signature for the socket...
|
|
guard let sig = self.signature else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_INTERNAL, reason: "Socket signature not found.")
|
|
}
|
|
|
|
// No SSL over UDP...
|
|
if sig.socketType != .datagram && sig.proto != .udp {
|
|
|
|
// Tell the delegate to initialize as a server...
|
|
do {
|
|
|
|
try self.delegate?.initialize(asServer: true)
|
|
|
|
} catch let error {
|
|
|
|
guard let sslError = error as? SSLError else {
|
|
|
|
throw error
|
|
}
|
|
|
|
throw Error(with: sslError)
|
|
}
|
|
}
|
|
|
|
// Create the hints for our search...
|
|
#if os(Linux)
|
|
var hints = addrinfo(
|
|
ai_flags: AI_PASSIVE,
|
|
ai_family: sig.protocolFamily.value,
|
|
ai_socktype: sig.socketType.value,
|
|
ai_protocol: 0,
|
|
ai_addrlen: 0,
|
|
ai_addr: nil,
|
|
ai_canonname: nil,
|
|
ai_next: nil)
|
|
#else
|
|
var hints = addrinfo(
|
|
ai_flags: AI_PASSIVE,
|
|
ai_family: sig.protocolFamily.value,
|
|
ai_socktype: sig.socketType.value,
|
|
ai_protocol: 0,
|
|
ai_addrlen: 0,
|
|
ai_canonname: nil,
|
|
ai_addr: nil,
|
|
ai_next: nil)
|
|
#endif
|
|
|
|
var targetInfo: UnsafeMutablePointer<addrinfo>? = UnsafeMutablePointer<addrinfo>.allocate(capacity: 1)
|
|
|
|
// Retrieve the info on our target...
|
|
let status: Int32 = getaddrinfo(nil, String(port), &hints, &targetInfo)
|
|
if status != 0 {
|
|
|
|
var errorString: String
|
|
if status == EAI_SYSTEM {
|
|
errorString = String(validatingUTF8: strerror(errno)) ?? "Unknown error code."
|
|
} else {
|
|
errorString = String(validatingUTF8: gai_strerror(errno)) ?? "Unknown error code."
|
|
}
|
|
throw Error(code: Socket.SOCKET_ERR_GETADDRINFO_FAILED, reason: errorString)
|
|
}
|
|
|
|
// Defer cleanup of our target info...
|
|
defer {
|
|
|
|
if targetInfo != nil {
|
|
freeaddrinfo(targetInfo)
|
|
}
|
|
}
|
|
|
|
var info = targetInfo
|
|
var bound = false
|
|
while info != nil {
|
|
|
|
// Try to bind the socket to the address...
|
|
#if os(Linux)
|
|
if Glibc.bind(self.socketfd, info!.pointee.ai_addr, info!.pointee.ai_addrlen) == 0 {
|
|
|
|
// Success... We've found our address...
|
|
bound = true
|
|
break
|
|
}
|
|
#else
|
|
if Darwin.bind(self.socketfd, info!.pointee.ai_addr, info!.pointee.ai_addrlen) == 0 {
|
|
|
|
// Success... We've found our address...
|
|
bound = true
|
|
break
|
|
}
|
|
#endif
|
|
|
|
// Try the next one...
|
|
info = info?.pointee.ai_next
|
|
}
|
|
|
|
// Throw an error if we weren't able to bind to an address...
|
|
if !bound {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BIND_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
// Save the address info...
|
|
var address: Address
|
|
|
|
// If the port was set to zero, we need to retrieve the port that assigned by the OS...
|
|
if port == 0 {
|
|
|
|
let addr = sockaddr_storage()
|
|
var length = socklen_t(MemoryLayout<sockaddr_storage>.size)
|
|
var addrPtr = addr.asAddr()
|
|
if getsockname(self.socketfd, &addrPtr, &length) == 0 {
|
|
|
|
if addrPtr.sa_family == sa_family_t(AF_INET6) {
|
|
|
|
var addr = sockaddr_in6()
|
|
memcpy(&addr, &addrPtr, Int(MemoryLayout<sockaddr_in6>.size))
|
|
address = .ipv6(addr)
|
|
|
|
} else if addrPtr.sa_family == sa_family_t(AF_INET) {
|
|
|
|
var addr = sockaddr_in()
|
|
memcpy(&addr, &addrPtr, Int(MemoryLayout<sockaddr_in>.size))
|
|
address = .ipv4(addr)
|
|
|
|
} else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: "Unable to determine listening socket protocol family.")
|
|
}
|
|
|
|
} else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BIND_FAILED, reason: "Unable to determine listening socket address after bind.")
|
|
}
|
|
|
|
} else {
|
|
|
|
if info!.pointee.ai_family == Int32(AF_INET6) {
|
|
|
|
var addr = sockaddr_in6()
|
|
memcpy(&addr, info!.pointee.ai_addr, Int(MemoryLayout<sockaddr_in6>.size))
|
|
address = .ipv6(addr)
|
|
|
|
} else if info!.pointee.ai_family == Int32(AF_INET) {
|
|
|
|
var addr = sockaddr_in()
|
|
memcpy(&addr, info!.pointee.ai_addr, Int(MemoryLayout<sockaddr_in>.size))
|
|
address = .ipv4(addr)
|
|
|
|
} else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: "Unable to determine listening socket protocol family.")
|
|
}
|
|
|
|
}
|
|
|
|
// Update our hostname and port...
|
|
if let (hostname, port) = Socket.hostnameAndPort(from: address) {
|
|
self.signature?.hostname = hostname
|
|
self.signature?.port = Int32(port)
|
|
}
|
|
|
|
self.signature?.isBound = true
|
|
self.signature?.address = address
|
|
|
|
// We don't actually listen for connections with a UDP socket, so we skip the next steps...
|
|
if sig.socketType == .datagram && sig.proto == .udp {
|
|
return
|
|
}
|
|
|
|
// Now listen for connections...
|
|
#if os(Linux)
|
|
if Glibc.listen(self.socketfd, Int32(maxBacklogSize)) < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_LISTEN_FAILED, reason: self.lastError())
|
|
}
|
|
#else
|
|
if Darwin.listen(self.socketfd, Int32(maxBacklogSize)) < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_LISTEN_FAILED, reason: self.lastError())
|
|
}
|
|
#endif
|
|
|
|
self.isListening = true
|
|
self.signature?.isSecure = self.delegate != nil ? true : false
|
|
}
|
|
|
|
// MARK: --- UNIX
|
|
|
|
///
|
|
/// Listen on a path, limiting the maximum number of pending connections.
|
|
///
|
|
/// - Parameters:
|
|
/// - path: The path to listen on.
|
|
/// - maxBacklogSize: The maximum size of the queue containing pending connections. Default is *Socket.SOCKET_DEFAULT_MAX_BACKLOG*.
|
|
///
|
|
public func listen(on path: String, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) throws {
|
|
|
|
// Make sure we've got a valid socket...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
// Make sure this is a UNIX socket...
|
|
guard let sockSig = self.signature, sockSig.protocolFamily == .unix else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: nil)
|
|
}
|
|
|
|
// Set a flag so that this address can be re-used immediately after the connection
|
|
// closes. (TCP normally imposes a delay before an address can be re-used.)
|
|
var on: Int32 = 1
|
|
if setsockopt(self.socketfd, SOL_SOCKET, SO_REUSEADDR, &on, socklen_t(MemoryLayout<Int32>.size)) < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_SETSOCKOPT_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
// Create the signature...
|
|
let sig = try Signature(socketType: .stream, proto: .unix, path: path)
|
|
guard let signature = sig else {
|
|
|
|
throw Error(code:Socket.SOCKET_ERR_BAD_SIGNATURE_PARAMETERS, reason: nil)
|
|
}
|
|
|
|
// Ensure the path doesn't exist...
|
|
#if os(Linux)
|
|
_ = Glibc.unlink(path)
|
|
#else
|
|
_ = Darwin.unlink(path)
|
|
#endif
|
|
|
|
// Try to bind the socket to the address...
|
|
// Now, do the connection using the supplied address from the signature...
|
|
let (addrPtr, addrLen) = try signature.unixAddress()
|
|
defer {
|
|
addrPtr.deallocate(capacity: addrLen)
|
|
}
|
|
|
|
let rc = addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
|
(p:UnsafeMutablePointer<sockaddr>) -> Int32 in
|
|
|
|
#if os(Linux)
|
|
return Glibc.bind(self.socketfd, p, socklen_t(addrLen))
|
|
#else
|
|
return Darwin.bind(self.socketfd, p, socklen_t(addrLen))
|
|
#endif
|
|
}
|
|
|
|
if rc < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_LISTEN_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
// Now listen for connections...
|
|
#if os(Linux)
|
|
if Glibc.listen(self.socketfd, Int32(maxBacklogSize)) < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_LISTEN_FAILED, reason: self.lastError())
|
|
}
|
|
#else
|
|
if Darwin.listen(self.socketfd, Int32(maxBacklogSize)) < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_LISTEN_FAILED, reason: self.lastError())
|
|
}
|
|
#endif
|
|
|
|
self.isListening = true
|
|
self.signature?.path = path
|
|
self.signature?.isBound = true
|
|
self.signature?.isSecure = false
|
|
self.signature?.address = signature.address
|
|
}
|
|
|
|
// MARK: --- UDP
|
|
|
|
///
|
|
/// Listen for a message on a UDP socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - buffer: The buffer to return the data in.
|
|
/// - bufSize: The size of the buffer.
|
|
/// - port: Port to listen on.
|
|
/// - maxBacklogSize: The maximum size of the queue containing pending connections. Default is *Socket.SOCKET_DEFAULT_MAX_BACKLOG*.
|
|
///
|
|
/// - Returns: Tuple containing the number of bytes read and the `Address` of the client who sent the data.
|
|
///
|
|
public func listen(forMessage buffer: UnsafeMutablePointer<CChar>, bufSize: Int, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) throws -> (bytesRead: Int, address: Address?) {
|
|
|
|
// Make sure the buffer is valid...
|
|
if bufSize == 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_INVALID_BUFFER, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created for UDP...
|
|
guard let sig = self.signature,
|
|
sig.socketType == .datagram else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: "This is not a UDP socket.")
|
|
}
|
|
|
|
// Set up the socket for listening for a message unless we're already set up...
|
|
if !sig.isBound {
|
|
try self.listen(on: port, maxBacklogSize: maxBacklogSize)
|
|
}
|
|
|
|
// If we're not bound, something went wrong...
|
|
guard self.signature?.isBound == true else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_LISTEN_FAILED, reason: "")
|
|
}
|
|
|
|
self.isListening = true
|
|
|
|
return try self.readDatagram(into: buffer, bufSize: bufSize)
|
|
}
|
|
|
|
///
|
|
/// Listen for a message on a UDP socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - data: Data buffer to receive the data read.
|
|
/// - port: Port to listen on.
|
|
/// - maxBacklogSize: The maximum size of the queue containing pending connections. Default is *Socket.SOCKET_DEFAULT_MAX_BACKLOG*.
|
|
///
|
|
/// - Returns: Tuple containing the number of bytes read and the `Address` of the client who sent the data.
|
|
///
|
|
public func listen(forMessage data: NSMutableData, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) throws -> (bytesRead: Int, address: Address?) {
|
|
|
|
// The socket must've been created...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created for UDP...
|
|
guard let sig = self.signature,
|
|
sig.socketType == .datagram && sig.proto == .udp else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: "This is not a UDP socket.")
|
|
}
|
|
|
|
// Set up the socket for listening for a message unless we're already set up...
|
|
if !sig.isBound {
|
|
try self.listen(on: port, maxBacklogSize: maxBacklogSize)
|
|
}
|
|
|
|
// If we're not bound, something went wrong...
|
|
guard self.signature?.isBound == true else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_LISTEN_FAILED, reason: "")
|
|
}
|
|
|
|
self.isListening = true
|
|
|
|
return try self.readDatagram(into: data)
|
|
}
|
|
|
|
///
|
|
/// Listen for a message on a UDP socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - data: Data buffer to receive the data read.
|
|
/// - port: Port to listen on.
|
|
/// - maxBacklogSize: The maximum size of the queue containing pending connections. Default is *Socket.SOCKET_DEFAULT_MAX_BACKLOG*.
|
|
///
|
|
/// - Returns: Tuple containing the number of bytes read and the `Address` of the client who sent the data.
|
|
///
|
|
public func listen(forMessage data: inout Data, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) throws -> (bytesRead: Int, address: Address?) {
|
|
|
|
// The socket must've been created...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created for UDP...
|
|
guard let sig = self.signature,
|
|
sig.socketType == .datagram else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: "This is not a UDP socket.")
|
|
}
|
|
|
|
// Set up the socket for listening for a message unless we're already set up...
|
|
if !sig.isBound {
|
|
try self.listen(on: port, maxBacklogSize: maxBacklogSize)
|
|
}
|
|
|
|
// If we're not bound, something went wrong...
|
|
guard self.signature?.isBound == true else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_LISTEN_FAILED, reason: "")
|
|
}
|
|
|
|
self.isListening = true
|
|
|
|
return try self.readDatagram(into: &data)
|
|
}
|
|
|
|
// MARK: -- Read
|
|
|
|
// MARK: --- TCP/UNIX
|
|
|
|
///
|
|
/// Read data from the socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - buffer: The buffer to return the data in.
|
|
/// - bufSize: The size of the buffer.
|
|
/// - truncate: Whether the data should be truncated if there is more available data than could fit in `buffer`.
|
|
/// **Note:** If called with `truncate = true` unretrieved data will be returned on next `read` call.
|
|
///
|
|
/// - Throws: `Socket.SOCKET_ERR_RECV_BUFFER_TOO_SMALL` if the buffer provided is too small and `truncate = false`.
|
|
/// Call again with proper buffer size (see `Error.bufferSizeNeeded`) or
|
|
/// use `readData(data: NSMutableData)`.
|
|
///
|
|
/// - Returns: The number of bytes returned in the buffer.
|
|
///
|
|
public func read(into buffer: UnsafeMutablePointer<CChar>, bufSize: Int, truncate: Bool = false) throws -> Int {
|
|
|
|
// Make sure the buffer is valid...
|
|
if bufSize == 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_INVALID_BUFFER, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created and must be connected...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
if !self.isConnected {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_NOT_CONNECTED, reason: nil)
|
|
}
|
|
|
|
// See if we have cached data to send back...
|
|
if self.readStorage.length > 0 {
|
|
|
|
if bufSize < self.readStorage.length {
|
|
|
|
if truncate {
|
|
|
|
memcpy(buffer, self.readStorage.bytes, self.readStorage.length)
|
|
|
|
#if os(Linux)
|
|
// Workaround for apparent bug in NSMutableData
|
|
self.readStorage = NSMutableData(bytes: self.readStorage.bytes.advanced(by: bufSize), length:self.readStorage.length - bufSize)
|
|
#else
|
|
self.readStorage.replaceBytes(in: NSRange(location:0, length:bufSize), withBytes: nil, length: 0)
|
|
#endif
|
|
return bufSize
|
|
|
|
} else {
|
|
|
|
throw Error(bufferSize: self.readStorage.length)
|
|
}
|
|
}
|
|
|
|
let returnCount = self.readStorage.length
|
|
|
|
// - We've got data we've already read, copy to the caller's buffer...
|
|
memcpy(buffer, self.readStorage.bytes, self.readStorage.length)
|
|
|
|
// - Reset the storage buffer...
|
|
self.readStorage.length = 0
|
|
|
|
return returnCount
|
|
}
|
|
|
|
// Read all available bytes...
|
|
let count = try self.readDataIntoStorage()
|
|
|
|
// Check for disconnect...
|
|
if count == 0 {
|
|
|
|
return count
|
|
}
|
|
|
|
// Did we get data?
|
|
var returnCount: Int = 0
|
|
if self.readStorage.length > 0 {
|
|
|
|
// Is the caller's buffer big enough?
|
|
if bufSize < self.readStorage.length {
|
|
|
|
// It isn't should we just use the available space?
|
|
if truncate {
|
|
|
|
// Yep, copy what storage we can and remove the bytes from the internal buffer.
|
|
memcpy(buffer, self.readStorage.bytes, bufSize)
|
|
|
|
#if os(Linux)
|
|
// Workaround for apparent bug in NSMutableData
|
|
self.readStorage = NSMutableData(bytes: self.readStorage.bytes.advanced(by: bufSize), length:self.readStorage.length - bufSize)
|
|
#else
|
|
self.readStorage.replaceBytes(in: NSRange(location:0, length:bufSize), withBytes: nil, length: 0)
|
|
#endif
|
|
|
|
return bufSize
|
|
|
|
} else {
|
|
|
|
// Nope, throw an exception telling the caller how big the buffer must be...
|
|
throw Error(bufferSize: self.readStorage.length)
|
|
}
|
|
}
|
|
|
|
// - We've read data, copy to the callers buffer...
|
|
memcpy(buffer, self.readStorage.bytes, self.readStorage.length)
|
|
|
|
returnCount = self.readStorage.length
|
|
|
|
// - Reset the storage buffer...
|
|
self.readStorage.length = 0
|
|
}
|
|
|
|
return returnCount
|
|
}
|
|
|
|
///
|
|
/// Read a string from the socket
|
|
///
|
|
/// - Returns: String containing the data read from the socket.
|
|
///
|
|
public func readString() throws -> String? {
|
|
|
|
guard let data = NSMutableData(capacity: 2000) else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_INTERNAL, reason: "Unable to create temporary NSMutableData...")
|
|
}
|
|
|
|
let rc = try self.read(into: data)
|
|
|
|
guard let str = NSString(bytes: data.bytes, length: data.length, encoding: String.Encoding.utf8.rawValue),
|
|
rc > 0 else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_INTERNAL, reason: "Unable to convert data to NSString.")
|
|
}
|
|
|
|
return String(describing: str)
|
|
}
|
|
|
|
|
|
///
|
|
/// Read data from the socket.
|
|
///
|
|
/// - Parameter data: The buffer to return the data in.
|
|
///
|
|
/// - Returns: The number of bytes returned in the buffer.
|
|
///
|
|
public func read(into data: NSMutableData) throws -> Int {
|
|
|
|
// The socket must've been created and must be connected...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
if !self.isConnected {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_NOT_CONNECTED, reason: nil)
|
|
}
|
|
|
|
// Read all available bytes...
|
|
let count = try self.readDataIntoStorage()
|
|
|
|
// Did we get data?
|
|
var returnCount: Int = 0
|
|
if count > 0 {
|
|
|
|
data.append(self.readStorage.bytes, length: self.readStorage.length)
|
|
|
|
returnCount = self.readStorage.length
|
|
|
|
// - Reset the storage buffer...
|
|
self.readStorage.length = 0
|
|
}
|
|
|
|
return returnCount
|
|
}
|
|
|
|
///
|
|
/// Read data from the socket.
|
|
///
|
|
/// - Parameter data: The buffer to return the data in.
|
|
///
|
|
/// - Returns: The number of bytes returned in the buffer.
|
|
///
|
|
public func read(into data: inout Data) throws -> Int {
|
|
|
|
// The socket must've been created and must be connected...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
if !self.isConnected {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_NOT_CONNECTED, reason: nil)
|
|
}
|
|
|
|
// Read all available bytes...
|
|
let count = try self.readDataIntoStorage()
|
|
|
|
// Did we get data?
|
|
var returnCount: Int = 0
|
|
if count > 0 {
|
|
|
|
// - Yes, move to caller's buffer...
|
|
data.append(self.readStorage.bytes.assumingMemoryBound(to: UInt8.self), count: self.readStorage.length)
|
|
|
|
returnCount = self.readStorage.length
|
|
|
|
// - Reset the storage buffer...
|
|
self.readStorage.length = 0
|
|
}
|
|
|
|
return returnCount
|
|
}
|
|
|
|
// MARK: --- UDP
|
|
|
|
///
|
|
/// Read data from a UDP socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - buffer: The buffer to return the data in.
|
|
/// - bufSize: The size of the buffer.
|
|
/// - address: Address to write data to.
|
|
///
|
|
/// - Returns: Tuple with the number of bytes returned in the buffer and the address they were received from.
|
|
///
|
|
public func readDatagram(into buffer: UnsafeMutablePointer<CChar>, bufSize: Int) throws -> (bytesRead: Int, address: Address?) {
|
|
|
|
// Make sure the buffer is valid...
|
|
if bufSize == 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_INVALID_BUFFER, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created for UDP...
|
|
guard let sig = self.signature,
|
|
sig.socketType == .datagram else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: "This is not a UDP socket.")
|
|
}
|
|
|
|
// Read all available bytes...
|
|
let (count, address) = try self.readDatagramIntoStorage()
|
|
|
|
// Check for disconnect...
|
|
if count == 0 {
|
|
|
|
return (count, nil)
|
|
}
|
|
|
|
// Did we get data?
|
|
var returnCount: Int = 0
|
|
if self.readStorage.length > 0 {
|
|
|
|
// Is the caller's buffer big enough?
|
|
if bufSize < self.readStorage.length {
|
|
|
|
// No, discard the excess data...
|
|
self.readStorage.length = bufSize
|
|
}
|
|
|
|
// - We've read data, copy to the callers buffer...
|
|
memcpy(buffer, self.readStorage.bytes, self.readStorage.length)
|
|
|
|
returnCount = self.readStorage.length
|
|
|
|
// - Reset the storage buffer...
|
|
self.readStorage.length = 0
|
|
}
|
|
|
|
return (returnCount, address)
|
|
}
|
|
|
|
///
|
|
/// Read data from a UDP socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - data: The buffer to return the data in.
|
|
/// - address: Address to write data to.
|
|
///
|
|
/// - Returns: Tuple with the number of bytes returned in the buffer and the address they were received from.
|
|
///
|
|
public func readDatagram(into data: NSMutableData) throws -> (bytesRead: Int, address: Address?) {
|
|
|
|
// The socket must've been created...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created for UDP...
|
|
guard let sig = self.signature,
|
|
sig.socketType == .datagram else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: "This is not a UDP socket.")
|
|
}
|
|
|
|
// Read all available bytes...
|
|
let (count, address) = try self.readDatagramIntoStorage()
|
|
|
|
// Did we get data?
|
|
var returnCount: Int = 0
|
|
if count > 0 {
|
|
|
|
data.append(self.readStorage.bytes, length: self.readStorage.length)
|
|
|
|
returnCount = self.readStorage.length
|
|
|
|
// - Reset the storage buffer...
|
|
self.readStorage.length = 0
|
|
}
|
|
|
|
return (returnCount, address)
|
|
}
|
|
|
|
///
|
|
/// Read data from a UDP socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - data: The buffer to return the data in.
|
|
/// - address: Address to write data to.
|
|
///
|
|
/// - Returns: Tuple with the number of bytes returned in the buffer and the address they were received from.
|
|
///
|
|
public func readDatagram(into data: inout Data) throws -> (bytesRead: Int, address: Address?) {
|
|
|
|
// The socket must've been created...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created for UDP...
|
|
guard let sig = self.signature,
|
|
sig.socketType == .datagram else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: "This is not a UDP socket.")
|
|
}
|
|
|
|
// Read all available bytes...
|
|
let (count, address) = try self.readDatagramIntoStorage()
|
|
|
|
// Did we get data?
|
|
var returnCount: Int = 0
|
|
if count > 0 {
|
|
|
|
// - Yes, move to caller's buffer...
|
|
data.append(self.readStorage.bytes.assumingMemoryBound(to: UInt8.self), count: self.readStorage.length)
|
|
|
|
returnCount = self.readStorage.length
|
|
|
|
// - Reset the storage buffer...
|
|
self.readStorage.length = 0
|
|
}
|
|
|
|
return (returnCount, address)
|
|
}
|
|
|
|
// MARK: -- Write
|
|
|
|
// MARK: --- TCP/UNIX
|
|
|
|
///
|
|
/// Write data to the socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - buffer: The buffer containing the data to write.
|
|
/// - bufSize: The size of the buffer.
|
|
///
|
|
/// - Returns: Integer representing the number of bytes written.
|
|
///
|
|
@discardableResult public func write(from buffer: UnsafeRawPointer, bufSize: Int) throws -> Int {
|
|
|
|
// Make sure the buffer is valid...
|
|
if bufSize == 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_INVALID_BUFFER, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created and must be connected...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
if !self.isConnected {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_NOT_CONNECTED, reason: nil)
|
|
}
|
|
|
|
var sent = 0
|
|
var sendFlags: Int32 = 0
|
|
#if os(Linux)
|
|
// Ignore SIGPIPE to avoid process termination if the reader has closed the connection.
|
|
// On Linux, we set the MSG_NOSIGNAL send flag. On OSX, we set SO_NOSIGPIPE during init().
|
|
sendFlags = Int32(MSG_NOSIGNAL)
|
|
#endif
|
|
while sent < bufSize {
|
|
|
|
var s = 0
|
|
if self.delegate != nil {
|
|
|
|
repeat {
|
|
|
|
do {
|
|
|
|
s = try self.delegate!.send(buffer: buffer.advanced(by: sent), bufSize: Int(bufSize - sent))
|
|
|
|
break
|
|
|
|
} catch let error {
|
|
|
|
guard let err = error as? SSLError else {
|
|
|
|
throw error
|
|
}
|
|
|
|
switch err {
|
|
|
|
case .success:
|
|
break
|
|
|
|
case .retryNeeded:
|
|
do {
|
|
|
|
try wait(forRead: false)
|
|
|
|
} catch let waitError {
|
|
|
|
throw waitError
|
|
}
|
|
continue
|
|
|
|
default:
|
|
throw Error(with: err)
|
|
}
|
|
|
|
}
|
|
|
|
} while true
|
|
|
|
} else {
|
|
#if os(Linux)
|
|
s = Glibc.send(self.socketfd, buffer.advanced(by: sent), Int(bufSize - sent), sendFlags)
|
|
#else
|
|
s = Darwin.send(self.socketfd, buffer.advanced(by: sent), Int(bufSize - sent), sendFlags)
|
|
#endif
|
|
}
|
|
if s <= 0 {
|
|
|
|
if errno == EAGAIN && !isBlocking {
|
|
|
|
// We have written out as much as we can...
|
|
return sent
|
|
}
|
|
|
|
// - Handle a connection reset by peer (ECONNRESET) and throw a different exception...
|
|
if errno == ECONNRESET {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_CONNECTION_RESET, reason: self.lastError())
|
|
}
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRITE_FAILED, reason: self.lastError())
|
|
}
|
|
sent += s
|
|
}
|
|
|
|
return sent
|
|
}
|
|
|
|
///
|
|
/// Write data to the socket.
|
|
///
|
|
/// - Parameter data: The NSData object containing the data to write.
|
|
///
|
|
/// - Returns: Integer representing the number of bytes written.
|
|
///
|
|
@discardableResult public func write(from data: NSData) throws -> Int {
|
|
|
|
// If there's no data in the NSData object, why bother? Fail silently...
|
|
if data.length == 0 {
|
|
return 0
|
|
}
|
|
|
|
return try write(from: data.bytes.assumingMemoryBound(to: UInt8.self), bufSize: data.length)
|
|
}
|
|
|
|
///
|
|
/// Write data to the socket.
|
|
///
|
|
/// - Parameter data: The Data object containing the data to write.
|
|
///
|
|
/// - Returns: Integer representing the number of bytes written.
|
|
///
|
|
@discardableResult public func write(from data: Data) throws -> Int {
|
|
|
|
// If there's no data in the Data object, why bother? Fail silently...
|
|
if data.count == 0 {
|
|
return 0
|
|
}
|
|
|
|
return try data.withUnsafeBytes() { [unowned self] (buffer: UnsafePointer<UInt8>) throws -> Int in
|
|
|
|
return try self.write(from: buffer, bufSize: data.count)
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Write a string to the socket.
|
|
///
|
|
/// - Parameter string: The string to write.
|
|
///
|
|
/// - Returns: Integer representing the number of bytes written.
|
|
///
|
|
@discardableResult public func write(from string: String) throws -> Int {
|
|
|
|
return try string.utf8CString.withUnsafeBufferPointer() {
|
|
|
|
// The count returned by nullTerminatedUTF8 includes the null terminator...
|
|
return try self.write(from: $0.baseAddress!, bufSize: $0.count-1)
|
|
}
|
|
}
|
|
|
|
// MARK: --- UDP
|
|
|
|
///
|
|
/// Write data to a UDP socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - buffer: The buffer containing the data to write.
|
|
/// - bufSize: The size of the buffer.
|
|
/// - address: Address to write data to.
|
|
///
|
|
/// - Returns: Integer representing the number of bytes written.
|
|
///
|
|
@discardableResult public func write(from buffer: UnsafeRawPointer, bufSize: Int, to address: Address) throws -> Int {
|
|
|
|
// If the remote connection has closed, disallow the operation...
|
|
if self.remoteConnectionClosed {
|
|
return 0
|
|
}
|
|
|
|
// Make sure the buffer is valid...
|
|
if bufSize == 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_INVALID_BUFFER, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created and must be connected...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
// The socket must've been created for UDP...
|
|
guard let sig = self.signature,
|
|
sig.socketType == .datagram else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: "This is not a UDP socket.")
|
|
}
|
|
|
|
var sent = 0
|
|
var sendFlags: Int32 = 0
|
|
#if os(Linux)
|
|
// Ignore SIGPIPE to avoid process termination if the reader has closed the connection.
|
|
// On Linux, we set the MSG_NOSIGNAL send flag. On OSX, we set SO_NOSIGPIPE during init().
|
|
sendFlags = Int32(MSG_NOSIGNAL)
|
|
#endif
|
|
|
|
var addr = address.addr
|
|
let size = address.size
|
|
|
|
while sent < bufSize {
|
|
|
|
var s = 0
|
|
#if os(Linux)
|
|
s = Glibc.sendto(self.socketfd, buffer.advanced(by: sent), Int(bufSize - sent), sendFlags, &addr, socklen_t(size))
|
|
#else
|
|
s = Darwin.sendto(self.socketfd, buffer.advanced(by: sent), Int(bufSize - sent), sendFlags, &addr, socklen_t(size))
|
|
#endif
|
|
|
|
if s <= 0 {
|
|
|
|
if errno == EAGAIN && !isBlocking {
|
|
|
|
// We have written out as much as we can...
|
|
return sent
|
|
}
|
|
|
|
// - Handle a connection reset by peer (ECONNRESET) and throw a different exception...
|
|
if errno == ECONNRESET {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_CONNECTION_RESET, reason: self.lastError())
|
|
}
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRITE_FAILED, reason: self.lastError())
|
|
}
|
|
sent += s
|
|
}
|
|
|
|
return sent
|
|
}
|
|
|
|
///
|
|
/// Write data to a UDP socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - data: The NSData object containing the data to write.
|
|
/// - address: Address to write data to.
|
|
///
|
|
/// - Returns: Integer representing the number of bytes written.
|
|
///
|
|
@discardableResult public func write(from data: NSData, to address: Address) throws -> Int {
|
|
|
|
// Send the bytes...
|
|
return try write(from: data.bytes.assumingMemoryBound(to: UInt8.self), bufSize: data.length)
|
|
}
|
|
|
|
///
|
|
/// Write data to a UDP socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - data: The Data object containing the data to write.
|
|
/// - address: Address to write data to.
|
|
///
|
|
/// - Returns: Integer representing the number of bytes written.
|
|
///
|
|
@discardableResult public func write(from data: Data, to address: Address) throws -> Int {
|
|
|
|
// Send the bytes...
|
|
return try data.withUnsafeBytes() { [unowned self] (buffer: UnsafePointer<UInt8>) throws -> Int in
|
|
|
|
return try self.write(from: buffer, bufSize: data.count, to: address)
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Write a string to the UDP socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - string: The string to write.
|
|
/// - address: Address to write data to.
|
|
///
|
|
/// - Returns: Integer representing the number of bytes written.
|
|
///
|
|
@discardableResult public func write(from string: String, to address: Address) throws -> Int {
|
|
|
|
return try string.utf8CString.withUnsafeBufferPointer() {
|
|
|
|
// The count returned by nullTerminatedUTF8 includes the null terminator...
|
|
return try self.write(from: $0.baseAddress!, bufSize: $0.count-1, to: address)
|
|
}
|
|
}
|
|
|
|
// MARK: -- Utility
|
|
|
|
///
|
|
/// Determines if this socket can be read from or written to.
|
|
///
|
|
/// - Parameters:
|
|
/// - waitForever: True to wait forever, false to check and return. Default: false.
|
|
/// - timeout: Timeout (in msec) before returning. A timeout value of 0 will return immediately.
|
|
///
|
|
/// - Returns: Tuple containing two boolean values, one for readable and one for writable.
|
|
///
|
|
public func isReadableOrWritable(waitForever: Bool = false, timeout: UInt = 0) throws -> (readable: Bool, writable: Bool) {
|
|
|
|
// The socket must've been created and must be connected...
|
|
if self.socketfd == Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_BAD_DESCRIPTOR, reason: nil)
|
|
}
|
|
|
|
if !self.isConnected {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_NOT_CONNECTED, reason: nil)
|
|
}
|
|
|
|
// Create a read and write file descriptor set for this socket...
|
|
var readfds = fd_set()
|
|
FD.ZERO(set: &readfds)
|
|
FD.SET(fd: self.socketfd, set: &readfds)
|
|
|
|
var writefds = fd_set()
|
|
FD.ZERO(set: &writefds)
|
|
FD.SET(fd: self.socketfd, set: &writefds)
|
|
|
|
// Do the wait...
|
|
var count: Int32 = 0
|
|
if waitForever {
|
|
|
|
// Wait forever for data...
|
|
count = select(self.socketfd + 1, &readfds, &writefds, nil, nil)
|
|
|
|
} else {
|
|
|
|
// Default timeout of zero (i.e. don't wait)...
|
|
var timer = timeval()
|
|
|
|
// But honor callers desires...
|
|
if timeout > 0 {
|
|
|
|
// First get seconds...
|
|
let secs = Int(Double(timeout / 1000))
|
|
timer.tv_sec = secs
|
|
|
|
// Now get the leftover millisecs...
|
|
let msecs = Int32(Double(timeout % 1000))
|
|
|
|
// Note: timeval expects microseconds, convert now...
|
|
let uSecs = msecs * 1000
|
|
|
|
// Now the leftover microseconds...
|
|
#if os(Linux)
|
|
timer.tv_usec = Int(uSecs)
|
|
#else
|
|
timer.tv_usec = Int32(uSecs)
|
|
#endif
|
|
}
|
|
|
|
// See if there's data on the socket...
|
|
count = select(self.socketfd + 1, &readfds, &writefds, nil, &timer)
|
|
}
|
|
|
|
// A count of less than zero indicates select failed...
|
|
if count < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_SELECT_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
// Return a tuple containing whether or not this socket is readable and/or writable...
|
|
return (FD.ISSET(fd: self.socketfd, set: &readfds), FD.ISSET(fd: self.socketfd, set: &writefds))
|
|
}
|
|
|
|
///
|
|
/// Set blocking mode for socket.
|
|
///
|
|
/// - Parameter shouldBlock: True to block, false to not.
|
|
///
|
|
public func setBlocking(mode shouldBlock: Bool) throws {
|
|
|
|
let flags = fcntl(self.socketfd, F_GETFL)
|
|
if flags < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_GET_FCNTL_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
var result: Int32 = 0
|
|
if shouldBlock {
|
|
|
|
result = fcntl(self.socketfd, F_SETFL, flags & ~O_NONBLOCK)
|
|
|
|
} else {
|
|
|
|
result = fcntl(self.socketfd, F_SETFL, flags | O_NONBLOCK)
|
|
}
|
|
|
|
if result < 0 {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_SET_FCNTL_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
self.isBlocking = shouldBlock
|
|
}
|
|
|
|
// MARK: Private Functions
|
|
|
|
///
|
|
/// Closes the current socket.
|
|
///
|
|
/// - Parameters:
|
|
/// - withSSLCleanup: True to deinitialize the SSLService if present.
|
|
///
|
|
private func close(withSSLCleanup: Bool) {
|
|
|
|
if self.socketfd != Socket.SOCKET_INVALID_DESCRIPTOR {
|
|
|
|
// If we have a delegate, tell it to cleanup too...
|
|
if withSSLCleanup {
|
|
self.delegate?.deinitialize()
|
|
}
|
|
|
|
// Note: if the socket is listening, we need to shut it down prior to closing
|
|
// or the socket will be left hanging until it times out.
|
|
#if os(Linux)
|
|
if self.isListening {
|
|
_ = Glibc.shutdown(self.socketfd, Int32(SHUT_RDWR))
|
|
}
|
|
_ = Glibc.close(self.socketfd)
|
|
#else
|
|
if self.isListening {
|
|
_ = Darwin.shutdown(self.socketfd, Int32(SHUT_RDWR))
|
|
}
|
|
_ = Darwin.close(self.socketfd)
|
|
#endif
|
|
|
|
self.socketfd = Socket.SOCKET_INVALID_DESCRIPTOR
|
|
}
|
|
|
|
if let _ = self.signature {
|
|
self.signature!.hostname = Socket.NO_HOSTNAME
|
|
self.signature!.port = Socket.SOCKET_INVALID_PORT
|
|
|
|
// If we've got a path to a UNIX socket and we're listening...
|
|
// Delete the file represented by the path as this listener
|
|
// is no longer available.
|
|
if self.signature!.path != nil && self.isListening {
|
|
#if os(Linux)
|
|
_ = Glibc.unlink(self.signature!.path!)
|
|
#else
|
|
_ = Darwin.unlink(self.signature!.path!)
|
|
#endif
|
|
}
|
|
self.signature!.path = nil
|
|
self.signature!.isSecure = false
|
|
}
|
|
self.isConnected = false
|
|
self.isListening = false
|
|
}
|
|
|
|
///
|
|
/// Private function that reads all available data on an open socket into storage.
|
|
///
|
|
/// - Returns: number of bytes read.
|
|
///
|
|
private func readDataIntoStorage() throws -> Int {
|
|
|
|
// Clear the buffer...
|
|
self.readBuffer.deinitialize()
|
|
self.readBuffer.initialize(to: 0x0)
|
|
memset(self.readBuffer, 0x0, self.readBufferSize)
|
|
|
|
var recvFlags: Int32 = 0
|
|
if (self.readStorage.length > 0) {
|
|
recvFlags |= Int32(MSG_DONTWAIT)
|
|
}
|
|
|
|
// Read all the available data...
|
|
var count: Int = 0
|
|
repeat {
|
|
|
|
if self.delegate != nil {
|
|
|
|
repeat {
|
|
|
|
do {
|
|
|
|
count = try self.delegate!.recv(buffer: self.readBuffer, bufSize: self.readBufferSize)
|
|
|
|
break
|
|
|
|
} catch let error {
|
|
|
|
guard let err = error as? SSLError else {
|
|
|
|
throw error
|
|
}
|
|
|
|
switch err {
|
|
|
|
case .success:
|
|
break
|
|
|
|
case .retryNeeded:
|
|
do {
|
|
|
|
try wait(forRead: true)
|
|
|
|
} catch let waitError {
|
|
|
|
throw waitError
|
|
}
|
|
continue
|
|
|
|
default:
|
|
throw Error(with: err)
|
|
}
|
|
|
|
}
|
|
|
|
} while true
|
|
|
|
} else {
|
|
#if os(Linux)
|
|
count = Glibc.recv(self.socketfd, self.readBuffer, self.readBufferSize, recvFlags)
|
|
#else
|
|
count = Darwin.recv(self.socketfd, self.readBuffer, self.readBufferSize, recvFlags)
|
|
#endif
|
|
}
|
|
|
|
// Check for error...
|
|
if count < 0 {
|
|
|
|
// - Could be an error, but if errno is EAGAIN or EWOULDBLOCK (if a non-blocking socket),
|
|
// it means there was NO data to read...
|
|
if errno == EAGAIN || errno == EWOULDBLOCK {
|
|
|
|
// So return the size of whatever data we already have (possibly none)
|
|
return self.readStorage.length
|
|
}
|
|
|
|
// - Handle a connection reset by peer (ECONNRESET) and throw a different exception...
|
|
if errno == ECONNRESET {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_CONNECTION_RESET, reason: self.lastError())
|
|
}
|
|
|
|
// - Something went wrong...
|
|
throw Error(code: Socket.SOCKET_ERR_RECV_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
if count == 0 {
|
|
|
|
self.remoteConnectionClosed = true
|
|
return 0
|
|
}
|
|
|
|
if count > 0 {
|
|
self.readStorage.append(self.readBuffer, length: count)
|
|
}
|
|
|
|
// Didn't fill the buffer so we've got everything available...
|
|
if count < self.readBufferSize {
|
|
|
|
break
|
|
}
|
|
|
|
} while count > 0
|
|
|
|
return self.readStorage.length
|
|
}
|
|
|
|
///
|
|
/// Private function that reads all available data on an open socket into storage.
|
|
///
|
|
/// - Returns: number of bytes read.
|
|
///
|
|
private func readDatagramIntoStorage() throws -> (bytesRead: Int, fromAddress: Address?) {
|
|
|
|
// Clear the buffer...
|
|
self.readBuffer.deinitialize()
|
|
self.readBuffer.initialize(to: 0x0)
|
|
memset(self.readBuffer, 0x0, self.readBufferSize)
|
|
var address: Address? = nil
|
|
|
|
var recvFlags: Int32 = 0
|
|
if (self.readStorage.length > 0) {
|
|
recvFlags |= Int32(MSG_DONTWAIT)
|
|
}
|
|
|
|
let addr = sockaddr_storage()
|
|
var length = socklen_t(MemoryLayout<sockaddr_storage>.size)
|
|
var addrPtr = addr.asAddr()
|
|
|
|
// Read all the available data...
|
|
#if os(Linux)
|
|
let count = Glibc.recvfrom(self.socketfd, self.readBuffer, self.readBufferSize, recvFlags, &addrPtr, &length)
|
|
#else
|
|
let count = Darwin.recvfrom(self.socketfd, self.readBuffer, self.readBufferSize, recvFlags, &addrPtr, &length)
|
|
#endif
|
|
|
|
// Check for error...
|
|
if count < 0 {
|
|
|
|
// - Could be an error, but if errno is EAGAIN or EWOULDBLOCK (if a non-blocking socket),
|
|
// it means there was NO data to read...
|
|
if errno == EAGAIN || errno == EWOULDBLOCK {
|
|
|
|
// FIXME: If we reach this point because data is available in the internal buffer, we will be *unable* to associate it with an address...
|
|
return (self.readStorage.length, nil)
|
|
}
|
|
|
|
// - Handle a connection reset by peer (ECONNRESET) and throw a different exception...
|
|
if errno == ECONNRESET {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_CONNECTION_RESET, reason: self.lastError())
|
|
}
|
|
|
|
// - Something went wrong...
|
|
throw Error(code: Socket.SOCKET_ERR_RECV_FAILED, reason: self.lastError())
|
|
}
|
|
|
|
if count == 0 {
|
|
|
|
self.remoteConnectionClosed = true
|
|
return (0, nil)
|
|
}
|
|
|
|
if count > 0 {
|
|
self.readStorage.append(self.readBuffer, length: count)
|
|
}
|
|
|
|
// Retrieve the address...
|
|
if addrPtr.sa_family == sa_family_t(AF_INET6) {
|
|
|
|
var addr = sockaddr_in6()
|
|
memcpy(&addr, &addrPtr, Int(MemoryLayout<sockaddr_in6>.size))
|
|
address = .ipv6(addr)
|
|
|
|
} else if addrPtr.sa_family == sa_family_t(AF_INET) {
|
|
|
|
var addr = sockaddr_in()
|
|
memcpy(&addr, &addrPtr, Int(MemoryLayout<sockaddr_in>.size))
|
|
address = .ipv4(addr)
|
|
|
|
} else {
|
|
|
|
throw Error(code: Socket.SOCKET_ERR_WRONG_PROTOCOL, reason: "Unable to determine receiving socket protocol family.")
|
|
}
|
|
|
|
|
|
return (self.readStorage.length, address)
|
|
}
|
|
|
|
///
|
|
/// Private function to wait for this instance to be either readable or writable.
|
|
///
|
|
/// - Parameter forRead: True to wait for socket to be readable, false waits for it to be writable.
|
|
///
|
|
private func wait(forRead: Bool) throws {
|
|
|
|
repeat {
|
|
|
|
let result = try self.isReadableOrWritable(waitForever: true)
|
|
|
|
if forRead {
|
|
|
|
if result.readable {
|
|
return
|
|
} else {
|
|
continue
|
|
}
|
|
|
|
} else {
|
|
|
|
if result.writable {
|
|
return
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
|
|
} while true
|
|
}
|
|
|
|
///
|
|
/// Private function to return the last error based on the value of errno.
|
|
///
|
|
/// - Returns: String containing relevant text about the error.
|
|
///
|
|
private func lastError() -> String {
|
|
|
|
return String(validatingUTF8: strerror(errno)) ?? "Error: \(errno)"
|
|
}
|
|
|
|
}
|