├── .gitignore ├── .swift-version ├── .travis.yml ├── Hummingbird.h ├── Info.plist ├── Package.swift ├── README.md ├── Sources ├── ClientSocket.swift ├── Hummingbird.swift ├── ServerSocket.swift ├── Socket.swift ├── SocketError.swift └── String+Transcoding.swift └── Tests ├── Hummingbird ├── ServerSocketTests.swift └── SocketTests.swift ├── Info.plist └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | .DS_Store 3 | Packages 4 | *.xcodeproj 5 | 6 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | DEVELOPMENT-SNAPSHOT-2016-06-20-a 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - xcode8 4 | language: generic 5 | sudo: required 6 | dist: trusty 7 | osx_image: xcode7.3 8 | install: 9 | - eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/02090c7ede5a637b76e6df1710e83cd0bbe7dcdf/swiftenv-install.sh)" 10 | script: 11 | - swift build 12 | - swift test 13 | -------------------------------------------------------------------------------- /Hummingbird.h: -------------------------------------------------------------------------------- 1 | // 2 | // Hummingbird.h 3 | // Hummingbird 4 | // 5 | // Created by James Richard on 2/26/16. 6 | // Copyright © 2016 MagicalPenguin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Hummingbird. 12 | FOUNDATION_EXPORT double HummingbirdVersionNumber; 13 | 14 | //! Project version string for Hummingbird. 15 | FOUNDATION_EXPORT const unsigned char HummingbirdVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2016 MagicalPenguin. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "Hummingbird", 5 | dependencies: [ 6 | .Package(url: "https://github.com/ketzusaka/Strand.git", majorVersion: 1, minor: 5), 7 | .Package(url: "https://github.com/open-swift/C7.git", majorVersion: 0, minor: 9) 8 | ] 9 | ) 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hummingbird 2 | 3 | Hummingbird is a simple Socket library for Swift. Its focus is for my personal use, so it's rather incomplete in terms of functionality and documentation for a socket library could be. Feel free to open PRs on it if you're interested, or to simply copy the useful bits for yourself! 4 | 5 | # License 6 | 7 | MIT 8 | 9 | -------------------------------------------------------------------------------- /Sources/ClientSocket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClientSocket.swift 3 | // Hummingbird 4 | // 5 | // Created by James Richard on 4/20/16. 6 | // 7 | // 8 | 9 | import C7 10 | 11 | #if os(Linux) 12 | import Glibc 13 | #else 14 | import Darwin.C 15 | #endif 16 | 17 | /// A `ClientSocket` is used for opening connections to another socket. 18 | public final class ClientSocket: Socket { 19 | 20 | /// The address to connect to. This can be an IP or hostname. 21 | public let address: String 22 | 23 | /// The port to connect to. 24 | public let port: String 25 | 26 | /** 27 | Creates a new IPv4 TCP socket. 28 | 29 | - throws: `SocketError.socketCreationFailed` if creating the socket failed. 30 | */ 31 | public init(address: String, port: String) throws { 32 | self.address = address 33 | self.port = port 34 | super.init(socketDescriptor: try Socket.createSocketDescriptor()) 35 | } 36 | 37 | } 38 | 39 | extension ClientSocket: Connection { 40 | 41 | /** 42 | Connect to a given host/address and port. 43 | 44 | The socket must be open, and not already connected or binded. 45 | 46 | - throws: `ClosableError.alreadyClosed` if the socket is closed. 47 | `SocketError.invalidPort` when converting the port to `in_port_t` fails. 48 | `SocketError.failedToGetIPFromHostname` when obtaining an IP from a hostname fails. 49 | `SocketError.hostInformationIncomplete` if the IP information obtained is incomplete or incompatible. 50 | `SocketError.connectFailed` if the system connect fall fails. 51 | */ 52 | public func open(timingOut: Double = .never) throws { 53 | guard !closed else { throw ClosableError.alreadyClosed } 54 | 55 | var addr = sockaddr_in() 56 | addr.sin_family = sa_family_t(AF_INET) 57 | 58 | guard let convertedPort = in_port_t(port) else { 59 | throw SocketError.invalidPort 60 | } 61 | 62 | if inet_pton(AF_INET, address, &addr.sin_addr) != 1 { 63 | addr.sin_addr = try getAddrFromHostname(address) 64 | } 65 | 66 | addr.sin_port = in_port_t(htons(convertedPort)) 67 | addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0) 68 | 69 | let len = socklen_t(UInt8(sizeof(sockaddr_in.self))) 70 | 71 | guard systemConnect(socketDescriptor, sockaddr_cast(&addr), len) >= 0 else { 72 | #if swift(>=3.0) 73 | let message = String(validatingUTF8: strerror(errno)) 74 | #else 75 | let message = String.fromCString(strerror(errno)) 76 | #endif 77 | throw SocketError.connectFailed(code: Int(errno), message: message) 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /Sources/Hummingbird.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Hummingbird.swift 3 | // Hummingbird 4 | // 5 | // Created by James Richard on 4/20/16. 6 | // 7 | // 8 | 9 | #if os(Linux) 10 | import Glibc 11 | let sockStream = Int32(SOCK_STREAM.rawValue) 12 | let systemAccept = Glibc.accept 13 | let systemClose = Glibc.close 14 | let systemListen = Glibc.listen 15 | let systemRecv = Glibc.recv 16 | let systemSend = Glibc.send 17 | let systemBind = Glibc.bind 18 | let systemConnect = Glibc.connect 19 | let systemGetHostByName = Glibc.gethostbyname 20 | #else 21 | import Darwin.C 22 | let sockStream = SOCK_STREAM 23 | let systemAccept = Darwin.accept 24 | let systemClose = Darwin.close 25 | let systemListen = Darwin.listen 26 | let systemRecv = Darwin.recv 27 | let systemSend = Darwin.send 28 | let systemBind = Darwin.bind 29 | let systemConnect = Darwin.connect 30 | let systemGetHostByName = Darwin.gethostbyname 31 | #endif 32 | 33 | #if !swift(>=3.0) 34 | public typealias ErrorProtocol = ErrorType 35 | #endif 36 | -------------------------------------------------------------------------------- /Sources/ServerSocket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServerSocket.swift 3 | // Hummingbird 4 | // 5 | // Created by James Richard on 4/20/16. 6 | // 7 | // 8 | 9 | import C7 10 | import Strand 11 | 12 | #if os(Linux) 13 | import Glibc 14 | #else 15 | import Darwin.C 16 | #endif 17 | 18 | /// A `ServerSocket` is used for accepting connections from another socket. 19 | public final class ServerSocket: Socket { 20 | 21 | /// The address to bind to. If `nil`, bind to any address. 22 | public let address: String? 23 | 24 | /// The port to bind to. If `nil`, bind to any port. 25 | public let port: String? 26 | 27 | /** 28 | Creates a new IPv4 TCP socket. 29 | 30 | - throws: `SocketError.socketCreationFailed` if creating the socket failed. 31 | */ 32 | public init(address: String? = nil, port: String? = nil) throws { 33 | self.address = address 34 | self.port = port 35 | super.init(socketDescriptor: try Socket.createSocketDescriptor()) 36 | } 37 | 38 | /** 39 | Binds the socket to a given address and port. 40 | 41 | The socket must be open, and must not already be binded. 42 | 43 | - throws: `ClosableError.alreadyClosed` if the socket is closed. 44 | `SocketError.socketConfigurationFailed` when setting SO_REUSEADDR on the socket fails. 45 | `SocketError.invalidPort` when converting the port to `in_port_t` fails. 46 | `SocketError.bindingFailed` if the system bind command fails. 47 | */ 48 | public func bind() throws { 49 | guard !closed else { throw ClosableError.alreadyClosed } 50 | var optval: Int = 1; 51 | 52 | guard setsockopt(socketDescriptor, SOL_SOCKET, SO_REUSEADDR, &optval, socklen_t(sizeof(Int.self))) != -1 else { 53 | let _ = systemClose(socketDescriptor) 54 | closed = true 55 | #if swift(>=3.0) 56 | let message = String(validatingUTF8: strerror(errno)) 57 | #else 58 | let message = String.fromCString(strerror(errno)) 59 | #endif 60 | throw SocketError.socketConfigurationFailed(code: Int(errno), message: message) 61 | } 62 | 63 | var addr = sockaddr_in() 64 | addr.sin_family = sa_family_t(AF_INET) 65 | 66 | if let port = port { 67 | guard let convertedPort = in_port_t(port) else { 68 | throw SocketError.invalidPort 69 | } 70 | 71 | addr.sin_port = in_port_t(htons(convertedPort)) 72 | } 73 | 74 | if let address = address { 75 | try address.withCString { 76 | var s_addr = in_addr() 77 | 78 | guard inet_pton(AF_INET, $0, &s_addr) == 1 else { 79 | #if swift(>=3.0) 80 | let message = String(validatingUTF8: strerror(errno)) 81 | #else 82 | let message = String.fromCString(strerror(errno)) 83 | #endif 84 | throw SocketError.bindingFailed(code: Int(errno), message: message) 85 | } 86 | 87 | addr.sin_addr = s_addr 88 | } 89 | } 90 | 91 | addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0) 92 | 93 | let len = socklen_t(UInt8(sizeof(sockaddr_in.self))) 94 | guard systemBind(socketDescriptor, sockaddr_cast(&addr), len) != -1 else { 95 | #if swift(>=3.0) 96 | let message = String(validatingUTF8: strerror(errno)) 97 | #else 98 | let message = String.fromCString(strerror(errno)) 99 | #endif 100 | throw SocketError.bindingFailed(code: Int(errno), message: message) 101 | } 102 | } 103 | 104 | /** 105 | Listen for connections. 106 | 107 | - parameter backlog: The maximum length for the queue of pending connections. 108 | - throws: `ClosableError.alreadyClosed` if the socket is closed. 109 | `SocketError.listenFailed` if the system listen fails. 110 | */ 111 | public func listen(pendingConnectionBacklog backlog: Int = 100) throws { 112 | guard !closed else { throw ClosableError.alreadyClosed } 113 | 114 | if systemListen(socketDescriptor, Int32(backlog)) != 0 { 115 | #if swift(>=3.0) 116 | let message = String(validatingUTF8: strerror(errno)) 117 | #else 118 | let message = String.fromCString(strerror(errno)) 119 | #endif 120 | throw SocketError.listenFailed(code: Int(errno), message: message) 121 | } 122 | } 123 | 124 | /** 125 | Begin accepting connections. When a connection is accepted, the `connectionHandler` is passed a new `Socket` 126 | that can communicate with the peer. The `connectionHandler` is executed on a new thread. 127 | 128 | - parameter maximumConsecutiveFailures: The maximum number of failures the system accept can have consecutively. 129 | Passing a negative number means an unlimited number of consecutive errors. 130 | Defaults to SOMAXCONN. 131 | - parameter connectionHandler: The closure executed when a connection is established. 132 | - throws: `ClosableError.alreadyClosed` if the socket is closed. 133 | `SocketError.acceptConsecutivelyFailing` if a the system accept fails a consecutive number of times that 134 | exceeds a positive `maximumConsecutiveFailures`. 135 | */ 136 | public func accept(maximumConsecutiveFailures: Int = Int(SOMAXCONN), connectionHandler: (Socket) -> Void) throws { 137 | guard !closed else { throw ClosableError.alreadyClosed } 138 | 139 | var consecutiveFailedAccepts = 0 140 | ACCEPT_LOOP: while true { 141 | do { 142 | let socket = try accept(timingOut: .never) as! Socket 143 | consecutiveFailedAccepts = 0 144 | 145 | _ = try Strand { 146 | connectionHandler(socket) 147 | } 148 | } catch let e as SocketError where e.isAcceptFailed { 149 | consecutiveFailedAccepts += 1 150 | guard maximumConsecutiveFailures >= 0 && consecutiveFailedAccepts < maximumConsecutiveFailures else { 151 | throw e 152 | } 153 | } 154 | } 155 | } 156 | 157 | } 158 | 159 | extension ServerSocket: Host { 160 | 161 | public func accept(timingOut deadline: Double) throws -> Stream { 162 | guard !closed else { throw ClosableError.alreadyClosed } 163 | 164 | let requestDescriptor = systemAccept(socketDescriptor, nil, nil) 165 | 166 | guard requestDescriptor != -1 else { 167 | #if swift(>=3.0) 168 | let message = String(validatingUTF8: strerror(errno)) 169 | #else 170 | let message = String.fromCString(strerror(errno)) 171 | #endif 172 | throw SocketError.acceptFailed(code: Int(errno), message: message) 173 | } 174 | 175 | return Socket(socketDescriptor: requestDescriptor) 176 | } 177 | 178 | } 179 | 180 | extension SocketError { 181 | 182 | private var isAcceptFailed: Bool { 183 | switch self { 184 | case .acceptFailed(code: _, message: _): return true 185 | default: return false 186 | } 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /Sources/Socket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Socket.swift 3 | // Hummingbird 4 | // 5 | // Created by James Richard on 2/8/16. 6 | // 7 | 8 | import C7 9 | 10 | #if os(Linux) 11 | import Glibc 12 | #else 13 | import Darwin.C 14 | #endif 15 | 16 | /// A `Socket` represents a socket descriptor. 17 | public class Socket { 18 | 19 | let socketDescriptor: Int32 20 | 21 | /// `true` if the socket is closed. Otherwise `false`. 22 | public internal(set) var closed = false 23 | 24 | /** 25 | Initialize a `Socket` with a given socket descriptor. The socket descriptor must be open, and further operations on 26 | the socket descriptor should be through the `Socket` class to properly manage open state. 27 | 28 | - parameter socketDescriptor: An open socket file descriptor. 29 | */ 30 | public init(socketDescriptor: Int32) { 31 | self.socketDescriptor = socketDescriptor 32 | } 33 | 34 | deinit { 35 | if !closed { 36 | let _ = systemClose(socketDescriptor) 37 | } 38 | } 39 | 40 | /** 41 | Sends a sequence of data to the socket. The system send call may be called numberous times to send all of the data 42 | contained in the sequence. 43 | 44 | - parameter data: The sequence of data to send. 45 | - throws: `ClosableError.alreadyClosed` if the socket is closed. 46 | `SocketError.sendFailed` if any invocation of the system send fails. 47 | */ 48 | #if swift(>=3.0) 49 | public func send(_ data: DataSequence) throws { 50 | guard !closed else { throw ClosableError.alreadyClosed } 51 | 52 | #if os(Linux) 53 | let flags = Int32(MSG_NOSIGNAL) 54 | #else 55 | let flags = Int32(SO_NOSIGPIPE) 56 | #endif 57 | 58 | let dataArray = [Byte](data) 59 | 60 | try dataArray.withUnsafeBufferPointer { buffer in 61 | var sent = 0 62 | guard let base = buffer.baseAddress else { throw SocketError.bufferReadFailed } 63 | while sent < dataArray.count { 64 | let s = systemSend(socketDescriptor, base + sent, dataArray.count - sent, flags) 65 | 66 | if s == -1 { 67 | throw SocketError.sendFailed(code: Int(errno), message: String(validatingUTF8: strerror(errno)), sent: sent) 68 | } 69 | 70 | sent += s 71 | } 72 | } 73 | } 74 | #else 75 | public func send(data: DataSequence) throws { 76 | guard !closed else { throw ClosableError.alreadyClosed } 77 | 78 | #if os(Linux) 79 | let flags = Int32(MSG_NOSIGNAL) 80 | #else 81 | let flags = Int32(SO_NOSIGPIPE) 82 | #endif 83 | 84 | let dataArray = [Byte](data) 85 | 86 | try dataArray.withUnsafeBufferPointer { buffer in 87 | var sent = 0 88 | while sent < dataArray.count { 89 | let s = systemSend(socketDescriptor, buffer.baseAddress + sent, dataArray.count - sent, flags) 90 | 91 | if s == -1 { 92 | throw SocketError.sendFailed(code: Int(errno), message: String.fromCString(strerror(errno)), sent: sent) 93 | } 94 | 95 | sent += s 96 | } 97 | } 98 | } 99 | #endif 100 | 101 | /** 102 | Sends a `String` to the socket. The string is sent in its UTF8 representation. The system send call may 103 | be called numberous times to send all of the data contained in the sequence. 104 | 105 | - parameter string: The string to send. 106 | - throws: `ClosableError.alreadyClosed` if the socket is closed. 107 | `SocketError.sendFailed` if any invocation of the system send fails. 108 | */ 109 | public func send(_ string: String) throws { 110 | try send(string.utf8) 111 | } 112 | 113 | /** 114 | Receives a `String` from the socket. The data being sent must be UTF8-encoded data that can be 115 | transcoded into a `String`. 116 | 117 | - parameter byteCount: The amount of space allocated to read data into. This does not ensure that your `String` 118 | will be this size, and does not wait for it to fill. It dictates the maximum amount of data 119 | we can receive within this call. 120 | - returns: A `String` representing the data received. 121 | - throws: `ClosableError.alreadyClosed` if the socket is closed. 122 | `SocketError.connectionClosedByPeer` if the remote peer signaled the connection is being closed. 123 | `SocketError.receiveFailed` when the system recv call fails. 124 | `SocketError.stringTranscodingFailed` if the received data could not be transcoded. 125 | */ 126 | public func receive(upTo byteCount: Int = 1024, timingOut deadline: Double = .never) throws -> String { 127 | let bytes: [Byte] = try receive(upTo: byteCount, timingOut: deadline) 128 | guard let transcodedString = String(utf8: bytes) else { throw SocketError.stringTranscodingFailed } 129 | return transcodedString 130 | } 131 | 132 | /** 133 | Receives an array of `Byte` values from the socket. 134 | 135 | - parameter byteCount: The amount of space allocated to read data into. This does not ensure that your data 136 | will be this size, and does not wait for it to fill. It dictates the maximum amount of data 137 | we can receive within this call. 138 | - returns: The received array of UInt8 values. 139 | - throws: `ClosableError.alreadyClosed` if the socket is closed. 140 | `SocketError.connectionClosedByPeer` if the remote peer signaled the connection is being closed. 141 | `SocketError.receiveFailed` when the system recv call fails. 142 | */ 143 | public func receive(upTo byteCount: Int = 1024, timingOut deadline: Double = .never) throws -> [Byte] { 144 | guard !closed else { throw ClosableError.alreadyClosed } 145 | #if swift(>=3.0) 146 | let buffer = UnsafeMutablePointer(allocatingCapacity: byteCount) 147 | #else 148 | let buffer = UnsafeMutablePointer.alloc(byteCount) 149 | #endif 150 | 151 | defer { 152 | #if swift(>=3.0) 153 | buffer.deallocateCapacity(byteCount) 154 | #else 155 | buffer.dealloc(byteCount) 156 | #endif 157 | } 158 | 159 | let bytesRead = systemRecv(socketDescriptor, buffer, byteCount, 0) 160 | 161 | if bytesRead == -1 { 162 | #if swift(>=3.0) 163 | let message = String(validatingUTF8: strerror(errno)) 164 | #else 165 | let message = String.fromCString(strerror(errno)) 166 | #endif 167 | throw SocketError.receiveFailed(code: Int(errno), message: message) 168 | } 169 | 170 | /* 171 | A message of zero bytes means that the remote end is about to close the connection. This is 172 | done so the socket doesn't sit around waiting until the timeout is reached. 173 | */ 174 | guard bytesRead != 0 else { 175 | _ = try? close() 176 | throw SocketError.connectionClosedByPeer 177 | } 178 | 179 | var readData = [Byte]() 180 | for i in 0 ..< bytesRead { 181 | readData.append(buffer[i]) 182 | } 183 | 184 | return readData 185 | } 186 | 187 | /** 188 | Closes the socket. 189 | 190 | - throws: `ClosableError.alreadyClosed` if the socket is already closed. 191 | `SocketError.closeFailed` when the system close command fials 192 | */ 193 | public func close() throws { 194 | guard !closed else { throw ClosableError.alreadyClosed } 195 | guard systemClose(socketDescriptor) != -1 else { 196 | #if swift(>=3.0) 197 | let message = String(validatingUTF8: strerror(errno)) 198 | #else 199 | let message = String.fromCString(strerror(errno)) 200 | #endif 201 | throw SocketError.closeFailed(code: Int(errno), message: message) 202 | } 203 | closed = true 204 | } 205 | 206 | // MARK: - Host resolution 207 | // Parts of this adapted from https://github.com/czechboy0/Redbird/blob/466056bba8f160b5a9e270be580bb09cf12e1306/Sources/Redbird/ClientSocket.swift#L126-L142 208 | func getAddrFromHostname(_ hostname: String) throws -> in_addr { 209 | let hostInfoPointer = systemGetHostByName(hostname) 210 | 211 | guard hostInfoPointer != nil else { 212 | #if swift(>=3.0) 213 | let message = String(validatingUTF8: strerror(errno)) 214 | #else 215 | let message = String.fromCString(strerror(errno)) 216 | #endif 217 | throw SocketError.failedToGetIPFromHostname(code: Int(errno), message: message) 218 | } 219 | 220 | #if swift(>=3.0) 221 | let hostInfo = hostInfoPointer!.pointee 222 | #else 223 | let hostInfo = hostInfoPointer.memory 224 | #endif 225 | 226 | guard hostInfo.h_addrtype == AF_INET else { 227 | throw SocketError.hostInformationIncomplete(message: "No IPv4 address") 228 | } 229 | 230 | #if swift(>=3.0) 231 | guard let addrList = hostInfo.h_addr_list else { 232 | throw SocketError.hostInformationIncomplete(message: "List is empty") 233 | } 234 | #else 235 | guard hostInfo.h_addr_list != nil else { 236 | throw SocketError.hostInformationIncomplete(message: "List is empty") 237 | } 238 | 239 | let addrList = hostInfo.h_addr_list 240 | #endif 241 | 242 | 243 | #if swift(>=3.0) 244 | let addrStruct = sockadd_list_cast(addrList)[0].pointee 245 | #else 246 | let addrStruct = sockadd_list_cast(addrList)[0].memory 247 | #endif 248 | 249 | return addrStruct 250 | } 251 | 252 | 253 | // MARK: - Utility casts 254 | func htons(_ value: CUnsignedShort) -> CUnsignedShort { 255 | return (value << 8) + (value >> 8) 256 | } 257 | 258 | func sockaddr_cast(_ p: UnsafeMutablePointer) -> UnsafeMutablePointer { 259 | return UnsafeMutablePointer(p) 260 | } 261 | 262 | func sockaddr_in_cast(_ p: UnsafeMutablePointer) -> UnsafeMutablePointer { 263 | return UnsafeMutablePointer(p) 264 | } 265 | 266 | #if swift(>=3.0) 267 | func sockadd_list_cast(_ p: UnsafeMutablePointer?>) -> UnsafeMutablePointer> { 268 | return UnsafeMutablePointer>(p) 269 | } 270 | #else 271 | func sockadd_list_cast(_ p: UnsafeMutablePointer>) -> UnsafeMutablePointer> { 272 | return UnsafeMutablePointer>(p) 273 | } 274 | #endif 275 | 276 | class func createSocketDescriptor() throws -> Int32 { 277 | #if os(Linux) 278 | let sd = socket(AF_INET, sockStream, 0) 279 | #else 280 | let sd = socket(AF_INET, sockStream, IPPROTO_TCP) 281 | #endif 282 | 283 | guard sd >= 0 else { 284 | #if swift(>=3.0) 285 | let message = String(validatingUTF8: strerror(errno)) 286 | #else 287 | let message = String.fromCString(strerror(errno)) 288 | #endif 289 | throw SocketError.socketCreationFailed(code: Int(errno), message: message) 290 | } 291 | 292 | return sd 293 | } 294 | 295 | } 296 | 297 | extension Socket: Hashable { 298 | public var hashValue: Int { return Int(socketDescriptor) } 299 | } 300 | 301 | extension Socket: C7.Stream { 302 | 303 | public func receive(upTo byteCount: Int, timingOut deadline: Double) throws -> Data { 304 | let bytes: [Byte] = try receive(upTo: byteCount, timingOut: deadline) 305 | return Data(bytes) 306 | } 307 | 308 | public func send(_ data: Data, timingOut: Double) throws { 309 | try send(data.bytes) 310 | } 311 | 312 | public func flush(timingOut: Double) throws { 313 | // noop; we always send immediately 314 | } 315 | } 316 | 317 | public func ==(lhs: Socket, rhs: Socket) -> Bool { 318 | return lhs.socketDescriptor == rhs.socketDescriptor 319 | } 320 | -------------------------------------------------------------------------------- /Sources/SocketError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketError.swift 3 | // Hummingbird 4 | // 5 | // Created by James Richard on 4/21/16. 6 | // 7 | // 8 | 9 | #if !swift(>=3.0) 10 | public typealias ErrorProtocol = ErrorType 11 | #endif 12 | 13 | public enum SocketError: ErrorProtocol { 14 | case acceptFailed(code: Int, message: String?) 15 | case bindingFailed(code: Int, message: String?) 16 | case bufferReadFailed 17 | case closeFailed(code: Int, message: String?) 18 | case connectionClosedByPeer 19 | case listenFailed(code: Int, message: String?) 20 | case receiveFailed(code: Int, message: String?) 21 | case connectFailed(code: Int, message: String?) 22 | case hostInformationIncomplete(message: String) 23 | case invalidData 24 | case invalidPort 25 | case sendFailed(code: Int, message: String?, sent: Int) 26 | case obtainingAddressInformationFailed(code: Int, message: String?) 27 | case socketCreationFailed(code: Int, message: String?) 28 | case socketConfigurationFailed(code: Int, message: String?) 29 | case stringTranscodingFailed 30 | case failedToGetIPFromHostname(code: Int, message: String?) 31 | } 32 | 33 | extension SocketError: Equatable { } 34 | 35 | public func == (lhs: SocketError, rhs: SocketError) -> Bool { 36 | switch (lhs, rhs) { 37 | case (let .acceptFailed(lCode, lMessage), let .acceptFailed(rCode, rMessage)): 38 | return lCode == rCode && lMessage == rMessage 39 | case (let .bindingFailed(lCode, lMessage), let .bindingFailed(rCode, rMessage)): 40 | return lCode == rCode && lMessage == rMessage 41 | case (let .listenFailed(lCode, lMessage), let .listenFailed(rCode, rMessage)): 42 | return lCode == rCode && lMessage == rMessage 43 | case (let .receiveFailed(lCode, lMessage), let .receiveFailed(rCode, rMessage)): 44 | return lCode == rCode && lMessage == rMessage 45 | case (let .connectFailed(lCode, lMessage), let .connectFailed(rCode, rMessage)): 46 | return lCode == rCode && lMessage == rMessage 47 | case (let .obtainingAddressInformationFailed(lCode, lMessage), let .obtainingAddressInformationFailed(rCode, rMessage)): 48 | return lCode == rCode && lMessage == rMessage 49 | case (let .socketCreationFailed(lCode, lMessage), let .socketCreationFailed(rCode, rMessage)): 50 | return lCode == rCode && lMessage == rMessage 51 | case (let .socketConfigurationFailed(lCode, lMessage), let .socketConfigurationFailed(rCode, rMessage)): 52 | return lCode == rCode && lMessage == rMessage 53 | case (let .failedToGetIPFromHostname(lCode, lMessage), let .failedToGetIPFromHostname(rCode, rMessage)): 54 | return lCode == rCode && lMessage == rMessage 55 | case (let .sendFailed(lCode, lMessage, lSent), let .sendFailed(rCode, rMessage, rSent)): 56 | return lCode == rCode && lMessage == rMessage && lSent == rSent 57 | case (let .hostInformationIncomplete(lMessage), let .hostInformationIncomplete(rMessage)): 58 | return lMessage == rMessage 59 | case (.bufferReadFailed, .bufferReadFailed): 60 | return true 61 | case (.connectionClosedByPeer, .connectionClosedByPeer): 62 | return true 63 | case (.invalidData, .invalidData): 64 | return true 65 | case (.stringTranscodingFailed, .stringTranscodingFailed): 66 | return true 67 | default: 68 | return false 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/String+Transcoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Transcoding.swift 3 | // Hummingbird 4 | // 5 | // Created by James Richard on 2/26/16. 6 | // 7 | 8 | // Credit to Mike Ash: https://www.mikeash.com/pyblog/friday-qa-2015-11-06-why-is-swifts-string-api-so-hard.html 9 | extension String { 10 | #if swift(>=3.0) 11 | init?(utf16: Seq) { 12 | self.init() 13 | guard !transcode(utf16.makeIterator(), from: UTF16.self, to: UTF32.self, stoppingOnError: true, sendingOutputTo: { self.append(UnicodeScalar($0)) }) else { return nil } 14 | } 15 | 16 | init?(utf8: Seq) { 17 | self.init() 18 | guard !transcode(utf8.makeIterator(), from: UTF8.self, to: UTF32.self, stoppingOnError: true, sendingOutputTo: { self.append(UnicodeScalar($0)) }) else { return nil } 19 | } 20 | #else 21 | init?(utf16: Seq) { 22 | self.init() 23 | 24 | guard transcode(UTF16.self, 25 | UTF32.self, 26 | utf16.generate(), 27 | { self.append(UnicodeScalar($0)) }, 28 | stopOnError: true) 29 | == false else { return nil } 30 | } 31 | 32 | init?(utf8: Seq) { 33 | self.init() 34 | 35 | guard transcode(UTF8.self, 36 | UTF32.self, 37 | utf8.generate(), 38 | { self.append(UnicodeScalar($0)) }, 39 | stopOnError: true) 40 | == false else { return nil } 41 | } 42 | #endif 43 | } 44 | -------------------------------------------------------------------------------- /Tests/Hummingbird/ServerSocketTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServerSocketTests.swift 3 | // Hummingbird 4 | // 5 | // Created by James Richard on 4/20/16. 6 | // 7 | // 8 | 9 | @testable import Hummingbird 10 | import Strand 11 | import XCTest 12 | 13 | #if os(Linux) 14 | import Glibc 15 | #else 16 | import Darwin.C 17 | #endif 18 | 19 | #if os(Linux) 20 | extension ServerSocketTests { 21 | static var allTests : [(String, ServerSocketTests -> () throws -> Void)] { 22 | return [ 23 | ("testBind_withInvalidAddress_throwsCorrectException", testBind_withInvalidAddress_throwsCorrectException), 24 | ("testBind_withInvalidPort_throwsCorrectException", testBind_withInvalidPort_throwsCorrectException), 25 | ("testBind_bindsCorrectly", testBind_bindsCorrectly) 26 | ] 27 | } 28 | } 29 | #endif 30 | 31 | class ServerSocketTests: XCTestCase { 32 | func testBind_bindsCorrectly() { 33 | do { 34 | let s = try ServerSocket(address: "0.0.0.0", port: "29876") 35 | try s.bind() 36 | } catch let error { 37 | XCTFail("Unexpected error: \(error)") 38 | } 39 | } 40 | 41 | func testBind_withInvalidAddress_throwsCorrectException() { 42 | do { 43 | let s = try ServerSocket(address: "derpity&^#@derp!@", port: "29876") 44 | try s.bind() 45 | XCTFail("Expected binding to fail") 46 | } catch let error as SocketError { 47 | switch error { 48 | case .bindingFailed(_, _): break 49 | default: XCTFail("Unexpected error: \(error)") 50 | } 51 | } catch let error { 52 | XCTFail("Unexpected error: \(error)") 53 | } 54 | } 55 | 56 | func testBind_withInvalidPort_throwsCorrectException() { 57 | do { 58 | let s = try ServerSocket(address: "0.0.0.0", port: "derpadee") 59 | try s.bind() 60 | XCTFail("Expected binding to fail") 61 | } catch let error as SocketError { 62 | switch error { 63 | case .invalidPort: break 64 | default: XCTFail("Unexpected error: \(error)") 65 | } 66 | } catch let error { 67 | XCTFail("Unexpected error: \(error)") 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Tests/Hummingbird/SocketTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Hummingbird 2 | import Strand 3 | import XCTest 4 | 5 | #if os(Linux) 6 | import Glibc 7 | #else 8 | import Darwin.C 9 | #endif 10 | 11 | #if os(Linux) 12 | extension SocketTests { 13 | static var allTests : [(String, SocketTests -> () throws -> Void)] { 14 | return [ 15 | ("testSendingRawDataToSocket_sendsDataCorrectly", testSendingRawDataToSocket_sendsDataCorrectly), 16 | ("testSendingStringDataToSocket_sendsDataCorrectly", testSendingStringDataToSocket_sendsDataCorrectly), 17 | ("testReceivingRawDataToSocket_readsCorrectly", testReceivingRawDataToSocket_readsCorrectly), 18 | ("testReceivingStringDataToSocket_readsCorrectly", testReceivingStringDataToSocket_readsCorrectly), 19 | ] 20 | } 21 | } 22 | #endif 23 | 24 | class SocketTests: XCTestCase { 25 | func testSendingRawDataToSocket_sendsDataCorrectly() { 26 | var sds: [Int32] = [0,0] 27 | 28 | if socketpair(AF_UNIX, Hummingbird.sockStream, 0, &sds) == -1 { 29 | XCTFail("Unable to create socket pair") 30 | return 31 | } 32 | 33 | let sendableData = [2, 5, 10] as [UInt8] 34 | 35 | let s = Socket(socketDescriptor: sds[0]) 36 | _ = try! Strand { 37 | do { 38 | try s.send(sendableData) 39 | } catch let error { 40 | XCTFail("Unable to send data to socket due to error: \(error)") 41 | } 42 | } 43 | 44 | let data = try! readDataFromSocket(sds[1]) 45 | 46 | XCTAssertEqual(data, sendableData) 47 | } 48 | 49 | func testSendingStringDataToSocket_sendsDataCorrectly() { 50 | var sds: [Int32] = [0,0] 51 | 52 | if socketpair(AF_UNIX, Hummingbird.sockStream, 0, &sds) == -1 { 53 | XCTFail("Unable to create socket pair") 54 | return 55 | } 56 | 57 | let sendableData = "Boo! 👻" 58 | let s = Socket(socketDescriptor: sds[0]) 59 | _ = try! Strand { 60 | do { 61 | try s.send(sendableData) 62 | } catch let error { 63 | XCTFail("Unable to send data to socket due to error: \(error)") 64 | } 65 | } 66 | 67 | let data = try! readDataFromSocket(sds[1]) 68 | 69 | let stringData = String(utf8: data) 70 | XCTAssertEqual(sendableData, stringData) 71 | } 72 | 73 | func testReceivingRawDataToSocket_readsCorrectly() { 74 | var sds: [Int32] = [0,0] 75 | 76 | if socketpair(AF_UNIX, Hummingbird.sockStream, 0, &sds) == -1 { 77 | XCTFail("Unable to create socket pair") 78 | return 79 | } 80 | 81 | let sendableData = [2, 5, 10] as [UInt8] 82 | 83 | _ = try! Strand { 84 | do { 85 | try self.sendData(sendableData, toSocket: sds[0]) 86 | } catch let error { 87 | XCTFail("Unable to send data to socket due to error: \(error)") 88 | } 89 | } 90 | 91 | let s = Socket(socketDescriptor: sds[1]) 92 | let data: [UInt8] = try! s.receive() 93 | 94 | XCTAssertEqual(data, sendableData) 95 | } 96 | 97 | func testReceivingStringDataToSocket_readsCorrectly() { 98 | var sds: [Int32] = [0,0] 99 | 100 | if socketpair(AF_UNIX, Hummingbird.sockStream, 0, &sds) == -1 { 101 | XCTFail("Unable to create socket pair") 102 | return 103 | } 104 | 105 | let sendableData = "Boo! 👻" 106 | 107 | _ = try! Strand { 108 | do { 109 | try self.sendData(sendableData.utf8.map({ $0 as UInt8 }), toSocket: sds[0]) 110 | } catch let error { 111 | XCTFail("Unable to send data to socket due to error: \(error)") 112 | } 113 | } 114 | 115 | let s = Socket(socketDescriptor: sds[1]) 116 | let data: String = try! s.receive() 117 | XCTAssertEqual(data, sendableData) 118 | } 119 | 120 | private func readDataFromSocket(_ socket: Int32) throws -> [UInt8] { 121 | let buffer = UnsafeMutablePointer(allocatingCapacity: 1024) 122 | 123 | defer { buffer.deallocateCapacity(1024) } 124 | 125 | let bytesRead = systemRecv(socket, buffer, 1024, 0) 126 | if bytesRead == -1 { 127 | throw SocketError.receiveFailed(code: Int(errno), message: String(cString: strerror(errno))) 128 | } 129 | 130 | guard bytesRead != 0 else { return [] } 131 | 132 | var readData = [UInt8]() 133 | for i in 0 ..< bytesRead { 134 | readData.append(buffer[i]) 135 | } 136 | 137 | return readData 138 | } 139 | 140 | private func sendData(_ data: [UInt8], toSocket socket: Int32) throws { 141 | #if os(Linux) 142 | let flags = Int32(MSG_NOSIGNAL) 143 | #else 144 | let flags = Int32(0) 145 | #endif 146 | 147 | systemSend(socket, data, data.count, flags) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import XCTest 3 | @testable import HummingbirdTestSuite 4 | 5 | XCTMain([ 6 | testCase(SocketTests.allTests) 7 | ]) 8 | #endif 9 | --------------------------------------------------------------------------------