├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── Connection.swift ├── FDSet.swift ├── FileDescriptor.swift ├── Listener.swift ├── Pipe.swift ├── ReadableFileDescriptor.swift ├── TCPConnection.swift ├── TCPListener.swift ├── UNIXConnection.swift ├── UNIXListener.swift ├── WritableFileDescriptor.swift └── select.swift └── Tests ├── LinuxMain.swift └── fdTests ├── FileDescriptorSpec.swift ├── PipeSpec.swift ├── TCPConnectionSpec.swift ├── TCPListenerSpec.swift ├── UNIXConnectionSpec.swift ├── UNIXListenerSpec.swift └── XCTest.swift /.gitignore: -------------------------------------------------------------------------------- 1 | /.build/ 2 | /Packages/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - osx 3 | - linux 4 | language: generic 5 | sudo: required 6 | dist: trusty 7 | osx_image: xcode10.2 8 | install: 9 | - eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)" 10 | script: 11 | - swift test 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # fd Changelog 2 | 3 | ## 0.3.0 4 | 5 | ### Breaking 6 | 7 | - Support for Swift < 4.2 has been removed. 8 | 9 | ## 0.2.0 10 | 11 | ### Enhancements 12 | 13 | - Adds support for Swift 3.0. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Kyle Fuller 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Spectre", 6 | "repositoryURL": "https://github.com/kylef/Spectre.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "f14ff47f45642aa5703900980b014c2e9394b6e5", 10 | "version": "0.9.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.2 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "fd", 6 | products: [ 7 | .library(name: "fd", targets: ["fd"]) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/kylef/Spectre.git", from: "0.9.0") 11 | ], 12 | targets: [ 13 | .target(name: "fd", dependencies: [], path: "Sources"), 14 | .testTarget(name: "fdTests", dependencies: ["fd", "Spectre"], path: "Tests/fdTests") 15 | ] 16 | ) 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fd: File Descriptor 2 | 3 | Swift file descriptor library. fd is a minimal layer on-top of the underlying 4 | system file descriptor and socket APIs. 5 | 6 | ## Usage 7 | 8 | ### FileDescriptor 9 | 10 | FileDescriptor is a protocol containing a single property (fileNumber). 11 | 12 | ```swift 13 | protocol FileDescriptor { 14 | var fileNumber: FileNumber { get } 15 | } 16 | ``` 17 | 18 | There are various protocol extensions to FileDescriptor to add functionality. 19 | 20 | #### Close 21 | 22 | You may close a file descriptor. 23 | 24 | ```swift 25 | try descriptor.close() 26 | ``` 27 | 28 | #### Select 29 | 30 | You may use the select function to examine which file descriptors are ready for 31 | reading, writing or have error conditions. 32 | 33 | ```swift 34 | let readable = try select(reads: [descriptor]).reads 35 | ``` 36 | 37 | ### ReadableFileDescriptor 38 | 39 | You may read from a readable file descriptor. 40 | 41 | ```swift 42 | let bytes = try descriptor.read(1024) 43 | ``` 44 | 45 | ### WritableFileDescriptor 46 | 47 | You may write to a writable file descriptor. 48 | 49 | ```swift 50 | try descriptor.write([1]) 51 | ``` 52 | 53 | ### Pipe 54 | 55 | You may use the pipe function to create a unidirectional data flow. The reader 56 | allows you to read data which was previously written to the writer. 57 | 58 | ```swift 59 | let (reader, writer) = try pipe() 60 | try writer.write([1]) 61 | let bytes = try reader.read(1) 62 | ``` 63 | 64 | ### Listener 65 | 66 | A listener is a file descriptor which can accept connections. 67 | 68 | ```swift 69 | let connection = try listener.accept() 70 | ``` 71 | 72 | #### TCPListener 73 | 74 | ```swift 75 | let listener = try TCPListener(address: "127.0.0.1", port: 8000) 76 | ``` 77 | 78 | #### UNIXListener 79 | 80 | UNIXListener is a Unix domain socket listener. 81 | 82 | ```swift 83 | let listener = try UNIXListener(path: "/tmp/my-unix-listener") 84 | ``` 85 | 86 | ### Connection 87 | 88 | A connection conforms to FileDescriptor so you can use the previously 89 | documented `read` and `write` capabilities. 90 | -------------------------------------------------------------------------------- /Sources/Connection.swift: -------------------------------------------------------------------------------- 1 | public protocol Connection : ReadableFileDescriptor, WritableFileDescriptor { 2 | } 3 | -------------------------------------------------------------------------------- /Sources/FDSet.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | #else 4 | import Darwin 5 | #endif 6 | 7 | 8 | func fdZero(_ set: inout fd_set) { 9 | #if os(Linux) 10 | set.__fds_bits = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 11 | #else 12 | set.fds_bits = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 13 | #endif 14 | } 15 | 16 | 17 | func fdSet(_ descriptor: FileNumber, _ set: inout fd_set) { 18 | #if os(Linux) 19 | let intOffset = Int(descriptor / 16) 20 | let bitOffset = Int(descriptor % 16) 21 | let mask = 1 << bitOffset 22 | switch intOffset { 23 | case 0: set.__fds_bits.0 = set.__fds_bits.0 | mask 24 | case 1: set.__fds_bits.1 = set.__fds_bits.1 | mask 25 | case 2: set.__fds_bits.2 = set.__fds_bits.2 | mask 26 | case 3: set.__fds_bits.3 = set.__fds_bits.3 | mask 27 | case 4: set.__fds_bits.4 = set.__fds_bits.4 | mask 28 | case 5: set.__fds_bits.5 = set.__fds_bits.5 | mask 29 | case 6: set.__fds_bits.6 = set.__fds_bits.6 | mask 30 | case 7: set.__fds_bits.7 = set.__fds_bits.7 | mask 31 | case 8: set.__fds_bits.8 = set.__fds_bits.8 | mask 32 | case 9: set.__fds_bits.9 = set.__fds_bits.9 | mask 33 | case 10: set.__fds_bits.10 = set.__fds_bits.10 | mask 34 | case 11: set.__fds_bits.11 = set.__fds_bits.11 | mask 35 | case 12: set.__fds_bits.12 = set.__fds_bits.12 | mask 36 | case 13: set.__fds_bits.13 = set.__fds_bits.13 | mask 37 | case 14: set.__fds_bits.14 = set.__fds_bits.14 | mask 38 | case 15: set.__fds_bits.15 = set.__fds_bits.15 | mask 39 | default: break 40 | } 41 | #else 42 | let intOffset = Int32(descriptor / 16) 43 | let bitOffset = Int32(descriptor % 16) 44 | let mask: Int32 = 1 << bitOffset 45 | 46 | switch intOffset { 47 | case 0: set.fds_bits.0 = set.fds_bits.0 | mask 48 | case 1: set.fds_bits.1 = set.fds_bits.1 | mask 49 | case 2: set.fds_bits.2 = set.fds_bits.2 | mask 50 | case 3: set.fds_bits.3 = set.fds_bits.3 | mask 51 | case 4: set.fds_bits.4 = set.fds_bits.4 | mask 52 | case 5: set.fds_bits.5 = set.fds_bits.5 | mask 53 | case 6: set.fds_bits.6 = set.fds_bits.6 | mask 54 | case 7: set.fds_bits.7 = set.fds_bits.7 | mask 55 | case 8: set.fds_bits.8 = set.fds_bits.8 | mask 56 | case 9: set.fds_bits.9 = set.fds_bits.9 | mask 57 | case 10: set.fds_bits.10 = set.fds_bits.10 | mask 58 | case 11: set.fds_bits.11 = set.fds_bits.11 | mask 59 | case 12: set.fds_bits.12 = set.fds_bits.12 | mask 60 | case 13: set.fds_bits.13 = set.fds_bits.13 | mask 61 | case 14: set.fds_bits.14 = set.fds_bits.14 | mask 62 | case 15: set.fds_bits.15 = set.fds_bits.15 | mask 63 | default: break 64 | } 65 | #endif 66 | } 67 | 68 | 69 | func fdIsSet(_ descriptor: FileNumber, _ set: inout fd_set) -> Bool { 70 | #if os(Linux) 71 | let intOffset = Int(descriptor / 32) 72 | let bitOffset = Int(descriptor % 32) 73 | let mask = Int(1 << bitOffset) 74 | 75 | switch intOffset { 76 | case 0: return set.__fds_bits.0 & mask != 0 77 | case 1: return set.__fds_bits.1 & mask != 0 78 | case 2: return set.__fds_bits.2 & mask != 0 79 | case 3: return set.__fds_bits.3 & mask != 0 80 | case 4: return set.__fds_bits.4 & mask != 0 81 | case 5: return set.__fds_bits.5 & mask != 0 82 | case 6: return set.__fds_bits.6 & mask != 0 83 | case 7: return set.__fds_bits.7 & mask != 0 84 | case 8: return set.__fds_bits.8 & mask != 0 85 | case 9: return set.__fds_bits.9 & mask != 0 86 | case 10: return set.__fds_bits.10 & mask != 0 87 | case 11: return set.__fds_bits.11 & mask != 0 88 | case 12: return set.__fds_bits.12 & mask != 0 89 | case 13: return set.__fds_bits.13 & mask != 0 90 | case 14: return set.__fds_bits.14 & mask != 0 91 | case 15: return set.__fds_bits.15 & mask != 0 92 | default: return false 93 | } 94 | #else 95 | let intOffset = Int32(descriptor / 32) 96 | let bitOffset = Int32(descriptor % 32) 97 | let mask = Int32(1 << bitOffset) 98 | 99 | switch intOffset { 100 | case 0: return set.fds_bits.0 & mask != 0 101 | case 1: return set.fds_bits.1 & mask != 0 102 | case 2: return set.fds_bits.2 & mask != 0 103 | case 3: return set.fds_bits.3 & mask != 0 104 | case 4: return set.fds_bits.4 & mask != 0 105 | case 5: return set.fds_bits.5 & mask != 0 106 | case 6: return set.fds_bits.6 & mask != 0 107 | case 7: return set.fds_bits.7 & mask != 0 108 | case 8: return set.fds_bits.8 & mask != 0 109 | case 9: return set.fds_bits.9 & mask != 0 110 | case 10: return set.fds_bits.10 & mask != 0 111 | case 11: return set.fds_bits.11 & mask != 0 112 | case 12: return set.fds_bits.12 & mask != 0 113 | case 13: return set.fds_bits.13 & mask != 0 114 | case 14: return set.fds_bits.14 & mask != 0 115 | case 15: return set.fds_bits.15 & mask != 0 116 | case 16: return set.fds_bits.16 & mask != 0 117 | case 17: return set.fds_bits.17 & mask != 0 118 | case 18: return set.fds_bits.18 & mask != 0 119 | case 19: return set.fds_bits.19 & mask != 0 120 | case 20: return set.fds_bits.20 & mask != 0 121 | case 21: return set.fds_bits.21 & mask != 0 122 | case 22: return set.fds_bits.22 & mask != 0 123 | case 23: return set.fds_bits.23 & mask != 0 124 | case 24: return set.fds_bits.24 & mask != 0 125 | case 25: return set.fds_bits.25 & mask != 0 126 | case 26: return set.fds_bits.26 & mask != 0 127 | case 27: return set.fds_bits.27 & mask != 0 128 | case 28: return set.fds_bits.28 & mask != 0 129 | case 29: return set.fds_bits.29 & mask != 0 130 | case 30: return set.fds_bits.30 & mask != 0 131 | case 31: return set.fds_bits.31 & mask != 0 132 | default: return false 133 | } 134 | #endif 135 | } 136 | -------------------------------------------------------------------------------- /Sources/FileDescriptor.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | private let system_close = Glibc.close 4 | #else 5 | import Darwin 6 | private let system_close = Darwin.close 7 | #endif 8 | 9 | 10 | public typealias Byte = Int8 11 | public typealias FileNumber = Int32 12 | 13 | 14 | public protocol FileDescriptor { 15 | var fileNumber: FileNumber { get } 16 | } 17 | 18 | 19 | struct FileDescriptorError : Error { 20 | } 21 | 22 | 23 | extension FileDescriptor { 24 | /// Close deletes the file descriptor from the per-process object reference table 25 | public func close() throws { 26 | if system_close(fileNumber) == -1 { 27 | throw FileDescriptorError() 28 | } 29 | } 30 | 31 | public var isClosed: Bool { 32 | if fcntl(fileNumber, F_GETFL) == -1 { 33 | return errno == EBADF 34 | } 35 | 36 | return false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Listener.swift: -------------------------------------------------------------------------------- 1 | public protocol Listener : FileDescriptor { 2 | func accept() throws -> Connection 3 | } 4 | -------------------------------------------------------------------------------- /Sources/Pipe.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | private let system_pipe = Glibc.pipe 4 | #else 5 | import Darwin 6 | private let system_pipe = Darwin.pipe 7 | #endif 8 | 9 | 10 | class PipeReadFileDescriptor : ReadableFileDescriptor { 11 | let fileNumber: FileNumber 12 | 13 | init(fileNumber: FileNumber) { 14 | self.fileNumber = fileNumber 15 | } 16 | 17 | deinit { 18 | let _ = try? close() 19 | } 20 | } 21 | 22 | 23 | class PipeWriteFileDescriptor : WritableFileDescriptor { 24 | let fileNumber: FileNumber 25 | 26 | init(fileNumber: FileNumber) { 27 | self.fileNumber = fileNumber 28 | } 29 | 30 | deinit { 31 | let _ = try? close() 32 | } 33 | } 34 | 35 | 36 | public func pipe() throws -> (reader: ReadableFileDescriptor, writer: WritableFileDescriptor) { 37 | var fileNumbers: [FileNumber] = [0, 0] 38 | if system_pipe(&fileNumbers) == -1 { 39 | throw FileDescriptorError() 40 | } 41 | return (PipeReadFileDescriptor(fileNumber: fileNumbers[0]), PipeWriteFileDescriptor(fileNumber: fileNumbers[1])) 42 | } 43 | -------------------------------------------------------------------------------- /Sources/ReadableFileDescriptor.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | private let system_read = Glibc.read 4 | #else 5 | import Darwin 6 | private let system_read = Darwin.read 7 | #endif 8 | 9 | 10 | public protocol ReadableFileDescriptor : FileDescriptor { 11 | } 12 | 13 | 14 | extension ReadableFileDescriptor { 15 | /// Read attempts to read the given size from the file descriptor 16 | public func read(_ bufferSize: Int) throws -> [Byte] { 17 | let buffer = UnsafeMutableRawPointer(malloc(bufferSize)) 18 | defer { free(buffer) } 19 | let size = system_read(fileNumber, buffer!, bufferSize) 20 | 21 | if size > 0 { 22 | let readSize = min(size, bufferSize) 23 | var bytes = [Byte](repeating: 0, count: readSize) 24 | memcpy(&bytes, buffer!, readSize) 25 | return bytes 26 | } 27 | 28 | throw FileDescriptorError() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/TCPConnection.swift: -------------------------------------------------------------------------------- 1 | public class TCPConnection : FileDescriptor, Connection { 2 | public let fileNumber: FileNumber 3 | 4 | public init(fileNumber: FileNumber) { 5 | self.fileNumber = fileNumber 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/TCPListener.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | private let system_accept = Glibc.accept 4 | private let system_listen = Glibc.listen 5 | private let system_bind = Glibc.bind 6 | private let sock_stream = Int32(SOCK_STREAM.rawValue) 7 | #else 8 | import Darwin 9 | private let system_accept = Darwin.accept 10 | private let system_listen = Darwin.listen 11 | private let system_bind = Darwin.bind 12 | private let sock_stream = SOCK_STREAM 13 | #endif 14 | 15 | 16 | public class TCPListener : FileDescriptor { 17 | public let fileNumber: FileNumber 18 | 19 | init(fileNumber: FileNumber) { 20 | self.fileNumber = fileNumber 21 | } 22 | 23 | public init(address: String, port: UInt16) throws { 24 | fileNumber = socket(AF_INET, sock_stream, 0) 25 | if fileNumber == -1 { 26 | throw FileDescriptorError() 27 | } 28 | 29 | do { 30 | try bind(address, port: port) 31 | } catch { 32 | try close() 33 | throw error 34 | } 35 | } 36 | 37 | deinit { 38 | let _ = try? close() 39 | } 40 | 41 | fileprivate func bind(_ address: String, port: UInt16) throws { 42 | var addr = sockaddr_in() 43 | addr.sin_family = sa_family_t(AF_INET) 44 | addr.sin_port = in_port_t(htons(in_port_t(port))) 45 | addr.sin_addr = in_addr(s_addr: address.withCString { inet_addr($0) }) 46 | addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0) 47 | 48 | let len = socklen_t(UInt8(MemoryLayout.size)) 49 | 50 | try withUnsafePointer(to: &addr) { 51 | try $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { 52 | guard system_bind(fileNumber, $0, len) != -1 else { 53 | throw FileDescriptorError() 54 | } 55 | } 56 | } 57 | } 58 | 59 | fileprivate func listen(backlog: Int32) throws { 60 | if system_listen(fileNumber, backlog) == -1 { 61 | throw FileDescriptorError() 62 | } 63 | } 64 | 65 | fileprivate func htons(_ value: CUnsignedShort) -> CUnsignedShort { 66 | return (value << 8) + (value >> 8) 67 | } 68 | 69 | /// Accepts a connection socket 70 | open func accept() throws -> TCPConnection { 71 | let fileNumber = system_accept(self.fileNumber, nil, nil) 72 | if fileNumber == -1 { 73 | throw FileDescriptorError() 74 | } 75 | 76 | return TCPConnection(fileNumber: fileNumber) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/UNIXConnection.swift: -------------------------------------------------------------------------------- 1 | public class UNIXConnection : FileDescriptor, Connection { 2 | public let fileNumber: FileNumber 3 | 4 | init(fileNumber: FileNumber) { 5 | self.fileNumber = fileNumber 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/UNIXListener.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | private let system_accept = Glibc.accept 4 | private let system_listen = Glibc.listen 5 | private let sock_stream = Int32(SOCK_STREAM.rawValue) 6 | private let system_bind = Glibc.bind 7 | #else 8 | import Darwin 9 | private let system_accept = Darwin.accept 10 | private let system_listen = Darwin.listen 11 | private let sock_stream = SOCK_STREAM 12 | private let system_bind = Darwin.bind 13 | #endif 14 | 15 | 16 | public class UNIXListener : FileDescriptor { 17 | public let fileNumber: FileNumber 18 | 19 | public init(path: String) throws { 20 | fileNumber = socket(AF_UNIX, sock_stream, 0) 21 | if fileNumber == -1 { 22 | throw FileDescriptorError() 23 | } 24 | 25 | do { 26 | try bind(path) 27 | } catch { 28 | try close() 29 | throw error 30 | } 31 | } 32 | 33 | deinit { 34 | let _ = try? close() 35 | } 36 | 37 | fileprivate func bind(_ path: String) throws { 38 | var addr = sockaddr_un() 39 | addr.sun_family = sa_family_t(AF_UNIX) 40 | 41 | let lengthOfPath = path.withCString { Int(strlen($0)) } 42 | 43 | guard lengthOfPath < MemoryLayout.size(ofValue: addr.sun_path) else { 44 | throw FileDescriptorError() 45 | } 46 | 47 | _ = withUnsafeMutablePointer(to: &addr.sun_path.0) { ptr in 48 | path.withCString { 49 | strncpy(ptr, $0, lengthOfPath) 50 | } 51 | } 52 | 53 | try withUnsafePointer(to: &addr) { 54 | try $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { 55 | guard system_bind(fileNumber, $0, UInt32(MemoryLayout.stride)) != -1 else { 56 | throw FileDescriptorError() 57 | } 58 | } 59 | } 60 | } 61 | 62 | fileprivate func listen(backlog: Int32) throws { 63 | if system_listen(fileNumber, backlog) == -1 { 64 | throw FileDescriptorError() 65 | } 66 | } 67 | 68 | /// Accepts a connection socket 69 | public func accept() throws -> UNIXConnection { 70 | let fileNumber = system_accept(self.fileNumber, nil, nil) 71 | if fileNumber == -1 { 72 | throw FileDescriptorError() 73 | } 74 | 75 | return UNIXConnection(fileNumber: fileNumber) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/WritableFileDescriptor.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | private let system_write = Glibc.write 4 | #else 5 | import Darwin 6 | private let system_write = Darwin.write 7 | #endif 8 | 9 | 10 | public protocol WritableFileDescriptor : FileDescriptor { 11 | } 12 | 13 | 14 | extension WritableFileDescriptor { 15 | /// Write attemps to write the given bytes to the file descriptor 16 | public func write(_ bytes: [Byte]) throws -> Int { 17 | let size = system_write(fileNumber, bytes, bytes.count) 18 | 19 | if size == -1 { 20 | throw FileDescriptorError() 21 | } 22 | 23 | return size 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/select.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | private let system_select = Glibc.select 4 | #else 5 | import Darwin 6 | private let system_select = Darwin.select 7 | #endif 8 | 9 | 10 | func filter(_ sockets: [T]?, _ set: inout fd_set) -> [T] { 11 | return sockets?.filter { 12 | fdIsSet($0.fileNumber, &set) 13 | } ?? [] 14 | } 15 | 16 | 17 | public func select(reads: [R] = [], writes: [W] = [], errors: [E] = [], timeout: timeval? = nil) throws -> (reads: [R], writes: [W], errors: [E]) { 18 | var readFDs = fd_set() 19 | fdZero(&readFDs) 20 | reads.forEach { fdSet($0.fileNumber, &readFDs) } 21 | 22 | var writeFDs = fd_set() 23 | fdZero(&writeFDs) 24 | writes.forEach { fdSet($0.fileNumber, &writeFDs) } 25 | 26 | var errorFDs = fd_set() 27 | fdZero(&errorFDs) 28 | errors.forEach { fdSet($0.fileNumber, &errorFDs) } 29 | 30 | let readFDNumbers = reads.map { $0.fileNumber } 31 | let writeFDNumbers = writes.map { $0.fileNumber } 32 | let errorFDNumbers = errors.map { $0.fileNumber } 33 | let maxFD = (readFDNumbers + writeFDNumbers + errorFDNumbers).reduce(0, max) 34 | let result: Int32 35 | if let timeout = timeout { 36 | var timeout = timeout 37 | result = system_select(maxFD + 1, &readFDs, &writeFDs, &errorFDs, &timeout) 38 | } else { 39 | result = system_select(maxFD + 1, &readFDs, &writeFDs, &errorFDs, nil) 40 | } 41 | 42 | if result == 0 { 43 | return ([], [], []) 44 | } else if result > 0 { 45 | return ( 46 | filter(reads, &readFDs), 47 | filter(writes, &writeFDs), 48 | filter(errors, &errorFDs) 49 | ) 50 | } 51 | 52 | throw FileDescriptorError() 53 | } 54 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import fdTests 2 | 3 | testFileDescriptor() 4 | testPipe() 5 | testUNIXConnection() 6 | testUNIXListener() 7 | testTCPConnection() 8 | testTCPListener() 9 | -------------------------------------------------------------------------------- /Tests/fdTests/FileDescriptorSpec.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | private let system_pipe = Glibc.pipe 4 | #else 5 | import Darwin 6 | private let system_pipe = Darwin.pipe 7 | #endif 8 | 9 | import Spectre 10 | import fd 11 | 12 | 13 | struct TestFileDescriptor : FileDescriptor, WritableFileDescriptor, ReadableFileDescriptor { 14 | let fileNumber: FileNumber 15 | 16 | init(fileNumber: FileNumber) { 17 | self.fileNumber = fileNumber 18 | } 19 | } 20 | 21 | 22 | public func testFileDescriptor() { 23 | describe("FileDescriptor") { 24 | $0.it("may be closed") { 25 | let (read, write) = try pipe() 26 | 27 | try expect(read.isClosed).to.beFalse() 28 | try expect(write.isClosed).to.beFalse() 29 | 30 | try write.close() 31 | try read.close() 32 | 33 | try expect(read.isClosed).to.beTrue() 34 | try expect(write.isClosed).to.beTrue() 35 | } 36 | 37 | $0.it("errors while closing an invalid file descriptor") { 38 | let descriptor = TestFileDescriptor(fileNumber: -1) 39 | try expect { try descriptor.close() }.toThrow() 40 | } 41 | } 42 | 43 | describe("ReadableFileDescriptor/WritableFileDescriptor") { 44 | $0.it("may be written to, and read from") { 45 | let (read, write) = try pipe() 46 | try expect(try write.write([1, 2, 3])) == 3 47 | 48 | let bytes = try read.read(3) 49 | try expect(bytes.count) == 3 50 | try expect(bytes[0]) == 1 51 | try expect(bytes[1]) == 2 52 | try expect(bytes[2]) == 3 53 | } 54 | 55 | $0.it("errors while writing from an invalid file descriptor") { 56 | let descriptor = TestFileDescriptor(fileNumber: -1) 57 | try expect { try descriptor.write([1]) }.toThrow() 58 | } 59 | 60 | $0.it("errors while reading from an invalid file descriptor") { 61 | let descriptor = TestFileDescriptor(fileNumber: -1) 62 | try expect { try descriptor.read(1) }.toThrow() 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Tests/fdTests/PipeSpec.swift: -------------------------------------------------------------------------------- 1 | import Spectre 2 | import fd 3 | 4 | 5 | public func testPipe() { 6 | describe("Pipe") { _ in 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/fdTests/TCPConnectionSpec.swift: -------------------------------------------------------------------------------- 1 | import Spectre 2 | import fd 3 | 4 | 5 | public func testTCPConnection() { 6 | describe("TCPConnection") { 7 | $0.it("may be initialised with a file number") { 8 | let connection = TCPConnection(fileNumber: -1) 9 | try expect(connection.fileNumber) == -1 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/fdTests/TCPListenerSpec.swift: -------------------------------------------------------------------------------- 1 | import Spectre 2 | import fd 3 | 4 | 5 | public func testTCPListener() { 6 | describe("TCPListener") { 7 | $0.it("may be initialised with an address and port") { 8 | let listener = try TCPListener(address: "127.0.0.1", port: 0) 9 | try expect(listener.fileNumber) != -1 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/fdTests/UNIXConnectionSpec.swift: -------------------------------------------------------------------------------- 1 | import Spectre 2 | import fd 3 | 4 | 5 | public func testUNIXConnection() { 6 | describe("UNIXConnection") { _ in 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tests/fdTests/UNIXListenerSpec.swift: -------------------------------------------------------------------------------- 1 | import Spectre 2 | import fd 3 | 4 | 5 | public func testUNIXListener() { 6 | describe("UNIXListener") { 7 | $0.it("may be initialised with a path") { 8 | let listener = try UNIXListener(path: "/tmp/fd-unixlistener-test") 9 | try expect(listener.fileNumber) != -1 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/fdTests/XCTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | 4 | class FDTests: XCTestCase { 5 | func testRunFDTests() { 6 | testFileDescriptor() 7 | testPipe() 8 | testTCPConnection() 9 | testTCPListener() 10 | testUNIXConnection() 11 | testUNIXListener() 12 | } 13 | } 14 | --------------------------------------------------------------------------------