From 279e57172051e916b50fc991a6c5b21a0fda18ae Mon Sep 17 00:00:00 2001 From: rien Date: Fri, 16 Aug 2019 19:48:00 +0200 Subject: [PATCH] Reorganized for 1.0.0 release --- .jazzy.yaml | 2 +- Package.swift | 4 +- README.md | 157 +--- ....Client.swift => ConnectToTipServer.swift} | 42 +- ...kets.Connection.swift => Connection.swift} | 208 +----- ...nectionPool.swift => ConnectionPool.swift} | 32 +- .../SwifterSockets/FileDescriptorMacros.swift | 220 ++++++ Sources/SwifterSockets/InterfaceAccess.swift | 94 +++ Sources/SwifterSockets/ReceiverProtocol.swift | 82 ++ Sources/SwifterSockets/ServerProtocol.swift | 64 ++ Sources/SwifterSockets/SetupTipServer.swift | 196 +++++ Sources/SwifterSockets/SocketAddress.swift | 116 +++ Sources/SwifterSockets/SwifterSockets.swift | 699 ------------------ .../SwifterSockets/SwifterSocketsUtils.swift | 262 +++++++ ...erSockets.Accept.swift => TipAccept.swift} | 45 +- Sources/SwifterSockets/TipInterface.swift | 151 ++++ ...ts.Receive.swift => TipReceiverLoop.swift} | 88 +-- ...erSockets.Server.swift => TipServer.swift} | 221 +----- ...ckets.Transmit.swift => TipTransfer.swift} | 90 +-- .../SwifterSockets/TransmitterProtocol.swift | 90 +++ Sources/SwifterSockets/WaitForSelect.swift | 152 ++++ 21 files changed, 1490 insertions(+), 1525 deletions(-) rename Sources/SwifterSockets/{SwifterSockets.Client.swift => ConnectToTipServer.swift} (80%) rename Sources/SwifterSockets/{SwifterSockets.Connection.swift => Connection.swift} (83%) rename Sources/SwifterSockets/{SwifterSockets.ConnectionPool.swift => ConnectionPool.swift} (83%) create mode 100644 Sources/SwifterSockets/FileDescriptorMacros.swift create mode 100644 Sources/SwifterSockets/InterfaceAccess.swift create mode 100644 Sources/SwifterSockets/ReceiverProtocol.swift create mode 100644 Sources/SwifterSockets/ServerProtocol.swift create mode 100644 Sources/SwifterSockets/SetupTipServer.swift create mode 100644 Sources/SwifterSockets/SocketAddress.swift delete mode 100644 Sources/SwifterSockets/SwifterSockets.swift create mode 100644 Sources/SwifterSockets/SwifterSocketsUtils.swift rename Sources/SwifterSockets/{SwifterSockets.Accept.swift => TipAccept.swift} (72%) create mode 100644 Sources/SwifterSockets/TipInterface.swift rename Sources/SwifterSockets/{SwifterSockets.Receive.swift => TipReceiverLoop.swift} (53%) rename Sources/SwifterSockets/{SwifterSockets.Server.swift => TipServer.swift} (53%) rename Sources/SwifterSockets/{SwifterSockets.Transmit.swift => TipTransfer.swift} (71%) create mode 100644 Sources/SwifterSockets/TransmitterProtocol.swift create mode 100644 Sources/SwifterSockets/WaitForSelect.swift diff --git a/.jazzy.yaml b/.jazzy.yaml index ee6090e..141d072 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -6,6 +6,6 @@ module: SwifterSockets hide_documentation_coverage: true xcodebuild_arguments: [-target, SwifterSockets] github_url: https://github.com/Balancingrock/SwifterSockets -output: ~/Documents/Websites/sf/projects/swiftersockets/reference +output: ~/Documents/Websites/swiftfire.nl/projects/swiftersockets/reference clean: true theme: fullwidth diff --git a/Package.swift b/Package.swift index 6943eb8..c12278c 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.0 +// swift-tools-version:5.0 import PackageDescription @@ -9,7 +9,7 @@ let package = Package( name: "SwifterSockets", targets: ["SwifterSockets"]) ], dependencies: [ - .package(url: "https://github.com/Balancingrock/BRUtils", from: "0.13.0") + .package(url: "https://github.com/Balancingrock/BRUtils", from: "1.0.0") ], targets: [ .target( diff --git a/README.md b/README.md index 82683cb..a6fc177 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Extend the dependency of your project package with: .package(url: "https://github.com/Balancingrock/SwifterSockets", from: ) ] -The __ must be replaced with the version number that must be used, for example: "0.11.0". +The __ must be replaced with the version number that must be used, for example: "1.0.0". ## Use as a framework @@ -66,157 +66,6 @@ Note: Planned releases are for information only, they are subject to change with - No new features planned. Features and bugfixes will be made on an ad-hoc basis as needed to support Swiftfire development. - For feature requests and bugfixes please contact rien@balancingrock.nl -#### 1.0.0 (Planned) +#### 1.0.0 (Current) -- The current verion will be upgraded to 1.0.0 status when the full set necessary for Swiftfire 1.0.0 has been completed. - -#### 0.12.0 (Current) - -- Updated the package dependency for Swift 5 - -#### 0.11.0 - -- Migration to SPM 4 - -#### 0.10.11 - -- Migration to Swift 4, minor adjustments. - -#### 0.10.10 - -- Upgraded BRUtils to 0.10.0 - -#### 0.10.9 - -- Upgraded BRUtils to 0.9.0 - -#### 0.10.8 - -- Upgraded BRUtils to 0.5.0 -- Made incrementUsageCount and decrementUsageCount public. - -#### 0.10.7 - -- Bugfix: Closing a connection of a connection object could crash an application (rare, only observed once). -- Bugfix: There was a conditon that would prevent the release of connection objects causing a server to run out of connection objects (common when a server ran for several days without restarts) - -#### 0.10.6 - -- Updated the references captured by closures. -- In transmitterClosed the inerface is immediately set to 'nil' to prevent errors in an SSL connectionon closing. -- Renamed 'abortConnection' to 'connectionWasClosed'. -- Added sorting to the connection pool. -- Added closing of the socket on transmitterClosed. - -#### 0.10.5 - -- Added affectInactivityDetection parameter (default = true) to the transfer calls. - -#### 0.10.4 - -- Bugfix: The sQueue in SwifterSocket.Connection could be deallocated before its processes were complete. - -#### 0.10.3 - -- BRUtils update to 0.2.0 - -#### 0.10.2 - -- Moved Result type to BRUtils - -#### 0.10.1 - -- Compile time improvement - -#### v0.10.0 - -- Added a result function to process Result results - -#### 0.9.15 - -- Added inactivity detection for Connections. - -#### 0.9.14 - -- More access level updates -- Added buffer pointer to transfer protocol -- Added buffered transmissions to Connections -- Moved some protocols from the general area to specific files -- Added connection pool manager - -#### 0.9.13 - -- Re-evaluated open/public access -- Added logId fo the InterfaceAccess protocol -- Updated comments - -#### 0.9.12 - -- Improved documentation for reference manual generation - -#### 0.9.11 - -- Comment changes - -#### 0.9.10 - -- Added xcode project for framework generation - -#### 0.9.9 - -- Fixed some access control levels - -#### 0.9.8 - -- Major redesign to support SecureSockets the SSL complement to SwifterSockets. The old interfaces have changed and the thread related operations have been replaced by the new approach to use Connection objects rather than directly interfacing with the threads. - -#### 0.9.7 - -- Upgraded to Xcode 8 beta 6 (Swift 3) - -#### 0.9.6 - -- Upgraded to Xcode 8 beta 3 (swift 3) - -#### 0.9.5 - -- Fixed a bug where accepting an IPv6 connection would fill an IPv4 sockaddr structure. -- Added SocketAddress enum adopted from Marco Masser: http://blog.obdev.at/representing-socket-addresses-in-swift-using-enums - -#### 0.9.4 - -- Header update to include new website: [swiftfire.nl](http://swiftfire.nl) - -#### 0.9.3 - -- Changed target to framework -- Made the necessary interfaces public -- Removed the dependency on SwifterLog to prevent cross-usage -- Added tags to mark releases -- Removed other not used files/directories - -#### 0.9.2 - -- Upgraded to Swift 2.2. -- Added closeSocket call. -- Added 'logUnixSocketCalls' (needs [SwifterLog](https://github.com/Swiftrien/SwifterLog)). -- Added note on buffer capture to transmitAsync:buffer. -- Added SERVER_CLOSED and CLIENT_CLOSED to TransmitResult. -- Changed DataEndDetector from a class to a protocol. -- Added SERVER_CLOSED to ReceiveResult. -- Replaced error numbers in the receiver functions with #file.#function.#line -- Added CLOSED AcceptResult. -- Fixed a bug that missed the error return from the select call in the accept functions. - - -#### 0.9.1 - -- Changed type of object in 'synchronized' from AnyObject to NSObject -- Added EXC_BAD_INSTRUCTION info to fd_set -- TransmitTelemetry and ReceiveTelemetry now inherit from NSObject -- Replaced (UnsafePointer, length) with UnsafeBufferPointer -- Added note on DataEndDetector that it can be used to receive the data also. - -#### 0.9.0 - -- Initial release +- Restructured for Swiftfire 1.0.0 diff --git a/Sources/SwifterSockets/SwifterSockets.Client.swift b/Sources/SwifterSockets/ConnectToTipServer.swift similarity index 80% rename from Sources/SwifterSockets/SwifterSockets.Client.swift rename to Sources/SwifterSockets/ConnectToTipServer.swift index 97f8dd5..d788374 100644 --- a/Sources/SwifterSockets/SwifterSockets.Client.swift +++ b/Sources/SwifterSockets/ConnectToTipServer.swift @@ -1,17 +1,16 @@ // ===================================================================================================================== // -// File: SwifterSockets.Client.swift +// File: ConnectToTipServer.swift // Project: SwifterSockets // -// Version: 0.10.2 +// Version: 1.0.0 // // Author: Marinus van der Lugt // Company: http://balancingrock.nl -// Website: http://swiftfire.nl/pages/projects/swiftersockets/ -// Blog: http://swiftrien.blogspot.com -// Git: https://github.com/Swiftrien/SwifterSockets +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire // -// Copyright: (c) 2014-2017 Marinus van der Lugt, All rights reserved. +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. // // License: Use or redistribute this code any way you like with the following two provision: // @@ -22,24 +21,13 @@ // // I also ask you to please leave this header with the source code. // -// I strongly believe that voluntarism is the way for societies to function optimally. Thus I have choosen to leave it -// up to you to determine the price for this code. You pay me whatever you think this code is worth to you. +// Like you, I need to make a living: // -// - You can send payment via paypal to: sales@balancingrock.nl +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl // - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH // -// I prefer the above two, but if these options don't suit you, you might also send me a gift from my amazon.co.uk -// wishlist: http://www.amazon.co.uk/gp/registry/wishlist/34GNMPZKAQ0OO/ref=cm_sw_em_r_wsl_cE3Tub013CKN6_wb -// // If you like to pay in another way, please contact me at rien@balancingrock.nl // -// (It is always a good idea to visit the website/blog/google to ensure that you actually pay me and not some imposter) -// -// For private and non-profit use the suggested price is the price of 1 good cup of coffee, say $4. -// For commercial use the suggested price is the price of 1 good meal, say $20. -// -// You are however encouraged to pay more ;-) -// // Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl // // ===================================================================================================================== @@ -48,21 +36,7 @@ // // History // -// 0.10.2 - Added BRUtils for the Result type -// 0.9.13 - Comment section update -// 0.9.12 - Documentation updated to accomodate the documentation tool 'jazzy' -// 0.9.11 - Comment change -// 0.9.9 - Updated access control -// 0.9.8 - Redesign of SwifterSockets to support HTTPS connections. -// 0.9.7 - Upgraded to Xcode 8 beta 6 -// 0.9.6 - Upgraded to Xcode 8 beta 3 (Swift 3) -// 0.9.4 - Header update -// 0.9.3 - Adding Carthage support: Changed target to Framework, added public declarations, removed SwifterLog. -// 0.9.2 - Added support for logUnixSocketCalls -// - Moved closing of sockets to SwifterSockets.closeSocket -// - Upgraded to Swift 2.2 -// 0.9.1 - Replaced (UnsafePointer, length) with UnsafeBufferPointer -// 0.9.0 - Initial release +// 1.0.0 - Removed older history // ===================================================================================================================== diff --git a/Sources/SwifterSockets/SwifterSockets.Connection.swift b/Sources/SwifterSockets/Connection.swift similarity index 83% rename from Sources/SwifterSockets/SwifterSockets.Connection.swift rename to Sources/SwifterSockets/Connection.swift index 74dda72..2091b0f 100644 --- a/Sources/SwifterSockets/SwifterSockets.Connection.swift +++ b/Sources/SwifterSockets/Connection.swift @@ -1,15 +1,14 @@ // ===================================================================================================================== // -// File: SwifterSockets.Connection.swift +// File: Connection.swift // Project: SwifterSockets // -// Version: 0.12.0 +// Version: 1.0.0 // // Author: Marinus van der Lugt // Company: http://balancingrock.nl -// Website: http://swiftfire.nl/pages/projects/swiftersockets/ -// Blog: http://swiftrien.blogspot.com -// Git: https://github.com/Swiftrien/SwifterSockets +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire // // Copyright: (c) 2016-2019 Marinus van der Lugt, All rights reserved. // @@ -22,19 +21,13 @@ // // I also ask you to please leave this header with the source code. // -// I strongly believe that voluntarism is the way for societies to function optimally. Thus I have choosen to leave it -// up to you to determine the price for this code. You pay me whatever you think this code is worth to you. +// Like you, I need to make a living: // -// - You can send payment via paypal to: sales@balancingrock.nl +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl // - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH // -// I prefer the above two, but if these options don't suit you, you can also send me a gift from my amazon.co.uk -// wishlist: http://www.amazon.co.uk/gp/registry/wishlist/34GNMPZKAQ0OO/ref=cm_sw_em_r_wsl_cE3Tub013CKN6_wb -// // If you like to pay in another way, please contact me at rien@balancingrock.nl // -// (It is always a good idea to visit the website/blog/google to ensure that you actually pay me and not some imposter) -// // Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl // // ===================================================================================================================== @@ -43,197 +36,12 @@ // // History // -// 0.12.0 - Replaced depreciated call in Swift 5 -// 0.10.11 - Migration to Swift 4, minor adjustments. -// 0.10.8 - Made incrementUsageCount and decrementUsageCount public. -// 0.10.7 - Bugfix: partial reimplementation to prevent crashes due to clashes of receiver events and close events. -// 0.10.6 - Renamed 'abortConnection' to 'connectionWasClosed'. -// - In transmitterClosed the inerface is immediately set to 'nil' as it is no longer available. This prevents -// errors in the SSL connection that could occur when trying to close an SSL interface that was already -// closed. -// - Updated the references captured by closures. -// 0.10.5 - Added affectInactivityDetection to the transfer calls. -// 0.10.4 - Fixed sQueue deallocation problem by making it static. -// 0.9.15 - Added inactivity detection. -// 0.9.14 - Updated the transfer protocol methods to include the buffer pointer. -// - Added buffered transfer functions. -// 0.9.13 - Allowed overriding of prepare methods. -// - Allow public access of transmitterQueue. -// - Added logId to InterfaceAccess -// - Made interface and remoteAddress members public & private(set) -// - General overhaul of public/private access. -// - Comment section update -// 0.9.12 - Documentation updated to accomodate the documentation tool 'jazzy' -// 0.9.11 - Comment change -// 0.9.9 - Updated access control -// 0.9.8 - Initial release -// +// 1.0.0 - Removed older history // ===================================================================================================================== import Foundation -/// The i/o functions that glue a Connection object to an interface. - -public protocol InterfaceAccess { - - - /// An id that can be used for logging purposes and will differentiate between interfaces on a temporary basis. - /// - /// It should be guaranteed that no two interfaces with the same logId are active at the same time. - - var logId: Int32 { get } - - - /// Closes the connection. - /// - /// - Note: Data transfers will be aborted if running and may result in error messages on the receiver/transmitter protocols. - - mutating func close() - - - /// Transfers the data in the buffer to the peer. - /// - /// - Parameters: - /// - buffer: The buffer with data to be transferred. - /// - timeout: The timeout for the transfer. - /// - callback: The receiver for the TransmitterProtocol method calls (if present). - /// - progress: The closure that is invoked after partial transfers (if any). - /// - /// - Returns: See the TransferResult definition. - - func transfer( - buffer: UnsafeBufferPointer, - timeout: TimeInterval?, - callback: TransmitterProtocol?, - progress: TransmitterProgressMonitor?) -> TransferResult? - - - /// Starts a receiver loop that will call the operations as defined in the ReceiverProtocol on the receiver. - /// - /// - Note: There will be no return from this function until a ReceiverProtocol method singals so, or until an error occurs. - /// - /// - Parameters: - /// - bufferSize: The size of the buffer to create in bytes. - /// - duration: The duration for the loop. - /// - receiver: The receiver for the ReceiverProtocol method calls (if present). - - func receiverLoop( - bufferSize: Int, - duration: TimeInterval, - receiver: ReceiverProtocol) -} - - -/// This class implements the InterfaceAccess protocol for the POSIX TCP/IP socket interface. - -public struct TipInterface: InterfaceAccess { - - - /// An id that can be used for logging purposes and will differentiate between interfaces on a temporary basis. - /// - /// It should be guaranteed that no two interfaces with the same logId are active at the same time. - - public var logId: Int32 { return socket ?? -1 } - - - /// The socket for this connection. - - public private(set) var socket: Int32? - - - /// Returns true if the connection is still usable. - /// - /// - Note: Even if 'true' is returned it is still possible that the next attempt to use the interface will immediately result in a termination of the connection. For example if the peer has already closed its side of the connection. - - public var isValid: Bool { - - if socket == nil { return false } - if socket! < 0 { return false } - return true - } - - - /// Creates a new interface. - /// - /// - Parameter socket: The socket to use for this interface. - - public init(_ socket: Int32) { - - self.socket = socket - } - - - /// Closes this end of a connection. - - public mutating func close() { - - if isValid { - closeSocket(socket) - socket = nil - } - } - - - /// Transfers the data via the socket to the peer. This operation returns when the data has been accepted by the POSIX layer, i.e. the physical transfer may still be ongoing. - /// - /// - Parameters: - /// - buffer: The buffer containing the data to be transferred. - /// - timeout: The timeout that applies to the transfer. - /// - callback: The receiver for the TransmitterProtocol method calls (if present). - /// - progress: The closure that is invoked after partial transfers (if any). - /// - /// - Returns: See the TransferResult definition. - - public func transfer( - buffer: UnsafeBufferPointer, - timeout: TimeInterval?, - callback: TransmitterProtocol? = nil, - progress: TransmitterProgressMonitor? = nil) -> TransferResult? { - - if isValid { - - return tipTransfer( - socket: socket!, - buffer: buffer, - timeout: timeout ?? 10, - callback: callback, - progress: progress) - - } else { - - return nil - } - } - - - /// Starts a receiver loop that will call the operations as defined in the ReceiverProtocol on the receiver. - /// - /// - Note: There will be no return from this function until a ReceiverProtocol method singals so, or until an error occurs. - /// - /// - Parameters: - /// - bufferSize: The size of the buffer to create in bytes. - /// - duration: The duration for the loop. - /// - receiver: The receiver for the ReceiverProtocol method calls (if present). - - public func receiverLoop( - bufferSize: Int = 20 * 1024, - duration: TimeInterval = 10, - receiver: ReceiverProtocol - ) { - - if isValid { - - tipReceiverLoop( - socket: socket!, - bufferSize: bufferSize, - duration: duration, - receiver: receiver) - } - } -} - - /// Signature of a closure that is invoked to retrieve/create a connection object. /// /// - Note: The factory is responsible to retain the connection object. I.e. the factory should ensure that the connection object remains allocated until it is no longer needed (i.e. until the connection is closed). @@ -252,7 +60,7 @@ public typealias ConnectionObjectFactory = (_ intf: InterfaceAccess, _ address: public typealias InactivityHandler = (_ connection: Connection) -> Void -/// Objects of this class represents a connection with another computer. +/// Objects of this class represent a connection with another computer. /// /// - Note: Every connection object is made ready for use with the "prepare" method. The "init" is ineffective for that. /// diff --git a/Sources/SwifterSockets/SwifterSockets.ConnectionPool.swift b/Sources/SwifterSockets/ConnectionPool.swift similarity index 83% rename from Sources/SwifterSockets/SwifterSockets.ConnectionPool.swift rename to Sources/SwifterSockets/ConnectionPool.swift index 84544b1..e041f72 100644 --- a/Sources/SwifterSockets/SwifterSockets.ConnectionPool.swift +++ b/Sources/SwifterSockets/ConnectionPool.swift @@ -1,17 +1,16 @@ // ===================================================================================================================== // -// File: SwifterSockets.ConnectionPool.swift +// File: ConnectionPool.swift // Project: SwifterSockets // -// Version: 0.10.8 +// Version: 1.0.0 // // Author: Marinus van der Lugt // Company: http://balancingrock.nl -// Website: http://swiftfire.nl/pages/projects/swiftersockets/ -// Blog: http://swiftrien.blogspot.com -// Git: https://github.com/Swiftrien/SwifterSockets +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire // -// Copyright: (c) 2017 Marinus van der Lugt, All rights reserved. +// Copyright: (c) 2017-2019 Marinus van der Lugt, All rights reserved. // // License: Use or redistribute this code any way you like with the following two provision: // @@ -22,24 +21,13 @@ // // I also ask you to please leave this header with the source code. // -// I strongly believe that voluntarism is the way for societies to function optimally. Thus I have choosen to leave it -// up to you to determine the price for this code. You pay me whatever you think this code is worth to you. +// Like you, I need to make a living: // -// - You can send payment via paypal to: sales@balancingrock.nl +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl // - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH // -// I prefer the above two, but if these options don't suit you, you might also send me a gift from my amazon.co.uk -// wishlist: http://www.amazon.co.uk/gp/registry/wishlist/34GNMPZKAQ0OO/ref=cm_sw_em_r_wsl_cE3Tub013CKN6_wb -// // If you like to pay in another way, please contact me at rien@balancingrock.nl // -// (It is always a good idea to visit the website/blog/google to ensure that you actually pay me and not some imposter) -// -// For private and non-profit use the suggested price is the price of 1 good cup of coffee, say $4. -// For commercial use the suggested price is the price of 1 good meal, say $20. -// -// You are however encouraged to pay more ;-) -// // Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl // // ===================================================================================================================== @@ -48,11 +36,7 @@ // // History // -// 0.10.8 - Added 'Darwin' to sleep calls. -// 0.10.6 - Added sorting for next available connection. -// 0.10.2 - Added BRUtils for the Result type -// 0.9.15 - Added loopcount to the return of allocateOrTimeout function. -// 0.9.14 - Initial release +// 1.0.0 - Removed older history // ===================================================================================================================== import Foundation diff --git a/Sources/SwifterSockets/FileDescriptorMacros.swift b/Sources/SwifterSockets/FileDescriptorMacros.swift new file mode 100644 index 0000000..16e4679 --- /dev/null +++ b/Sources/SwifterSockets/FileDescriptorMacros.swift @@ -0,0 +1,220 @@ +// ===================================================================================================================== +// +// File: FileDescriptorMacros.swift +// Project: SwifterSockets +// +// Version: 1.0.0 +// +// Author: Marinus van der Lugt +// Company: http://balancingrock.nl +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire +// +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. +// +// License: Use or redistribute this code any way you like with the following two provision: +// +// 1) You ACCEPT this source code AS IS without any guarantees that it will work as intended. Any liability from its +// use is YOURS. +// +// 2) You WILL NOT seek damages from the author or balancingrock.nl. +// +// I also ask you to please leave this header with the source code. +// +// Like you, I need to make a living: +// +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl +// - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH +// +// If you like to pay in another way, please contact me at rien@balancingrock.nl +// +// Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl +// +// ===================================================================================================================== +// PLEASE let me know about bugs, improvements and feature requests. (rien@balancingrock.nl) +// ===================================================================================================================== +// +// History +// +// 1.0.0 - Removed older history +// ===================================================================================================================== + +import Foundation + + +/// Replacement for FD_ZERO macro. +/// +/// - Parameter set: A pointer to a fd_set structure. +/// +/// - Returns: The set that is opinted at is filled with all zero's. + +public func fdZero(_ set: inout fd_set) { + 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) +} + + +/// Replacement for FD_SET macro +/// +/// - Parameter fd: A file descriptor that offsets the bit to be set to 1 in the fd_set pointed at by 'set'. +/// - Parameter set: A pointer to a fd_set structure. +/// +/// - Returns: The given set is updated in place, with the bit at offset 'fd' set to 1. +/// +/// - Note: If you receive an EXC_BAD_INSTRUCTION at the mask statement, then most likely the socket was already closed. + +public func fdSet(_ fd: Int32?, set: inout fd_set) { + + if let fd = fd { + + let intOffset: Int32 = fd / 32 + let bitOffset: Int32 = fd % 32 + let mask: Int32 = 1 << bitOffset + + switch intOffset { + case 0: set.fds_bits.0 = set.fds_bits.0 | mask + case 1: set.fds_bits.1 = set.fds_bits.1 | mask + case 2: set.fds_bits.2 = set.fds_bits.2 | mask + case 3: set.fds_bits.3 = set.fds_bits.3 | mask + case 4: set.fds_bits.4 = set.fds_bits.4 | mask + case 5: set.fds_bits.5 = set.fds_bits.5 | mask + case 6: set.fds_bits.6 = set.fds_bits.6 | mask + case 7: set.fds_bits.7 = set.fds_bits.7 | mask + case 8: set.fds_bits.8 = set.fds_bits.8 | mask + case 9: set.fds_bits.9 = set.fds_bits.9 | mask + case 10: set.fds_bits.10 = set.fds_bits.10 | mask + case 11: set.fds_bits.11 = set.fds_bits.11 | mask + case 12: set.fds_bits.12 = set.fds_bits.12 | mask + case 13: set.fds_bits.13 = set.fds_bits.13 | mask + case 14: set.fds_bits.14 = set.fds_bits.14 | mask + case 15: set.fds_bits.15 = set.fds_bits.15 | mask + case 16: set.fds_bits.16 = set.fds_bits.16 | mask + case 17: set.fds_bits.17 = set.fds_bits.17 | mask + case 18: set.fds_bits.18 = set.fds_bits.18 | mask + case 19: set.fds_bits.19 = set.fds_bits.19 | mask + case 20: set.fds_bits.20 = set.fds_bits.20 | mask + case 21: set.fds_bits.21 = set.fds_bits.21 | mask + case 22: set.fds_bits.22 = set.fds_bits.22 | mask + case 23: set.fds_bits.23 = set.fds_bits.23 | mask + case 24: set.fds_bits.24 = set.fds_bits.24 | mask + case 25: set.fds_bits.25 = set.fds_bits.25 | mask + case 26: set.fds_bits.26 = set.fds_bits.26 | mask + case 27: set.fds_bits.27 = set.fds_bits.27 | mask + case 28: set.fds_bits.28 = set.fds_bits.28 | mask + case 29: set.fds_bits.29 = set.fds_bits.29 | mask + case 30: set.fds_bits.30 = set.fds_bits.30 | mask + case 31: set.fds_bits.31 = set.fds_bits.31 | mask + default: break + } + } +} + + +/// Replacement for FD_CLR macro +/// +/// - Parameter fd: A file descriptor that offsets the bit to be cleared in the fd_set pointed at by 'set'. +/// - Parameter set: A pointer to a fd_set structure. +/// +/// - Returns: The given set is updated in place, with the bit at offset 'fd' cleared to 0. + +public func fdClr(_ fd: Int32?, set: inout fd_set) { + + if let fd = fd { + + let intOffset: Int32 = fd / 32 + let bitOffset: Int32 = fd % 32 + let mask: Int32 = ~(1 << bitOffset) + + switch intOffset { + case 0: set.fds_bits.0 = set.fds_bits.0 & mask + case 1: set.fds_bits.1 = set.fds_bits.1 & mask + case 2: set.fds_bits.2 = set.fds_bits.2 & mask + case 3: set.fds_bits.3 = set.fds_bits.3 & mask + case 4: set.fds_bits.4 = set.fds_bits.4 & mask + case 5: set.fds_bits.5 = set.fds_bits.5 & mask + case 6: set.fds_bits.6 = set.fds_bits.6 & mask + case 7: set.fds_bits.7 = set.fds_bits.7 & mask + case 8: set.fds_bits.8 = set.fds_bits.8 & mask + case 9: set.fds_bits.9 = set.fds_bits.9 & mask + case 10: set.fds_bits.10 = set.fds_bits.10 & mask + case 11: set.fds_bits.11 = set.fds_bits.11 & mask + case 12: set.fds_bits.12 = set.fds_bits.12 & mask + case 13: set.fds_bits.13 = set.fds_bits.13 & mask + case 14: set.fds_bits.14 = set.fds_bits.14 & mask + case 15: set.fds_bits.15 = set.fds_bits.15 & mask + case 16: set.fds_bits.16 = set.fds_bits.16 & mask + case 17: set.fds_bits.17 = set.fds_bits.17 & mask + case 18: set.fds_bits.18 = set.fds_bits.18 & mask + case 19: set.fds_bits.19 = set.fds_bits.19 & mask + case 20: set.fds_bits.20 = set.fds_bits.20 & mask + case 21: set.fds_bits.21 = set.fds_bits.21 & mask + case 22: set.fds_bits.22 = set.fds_bits.22 & mask + case 23: set.fds_bits.23 = set.fds_bits.23 & mask + case 24: set.fds_bits.24 = set.fds_bits.24 & mask + case 25: set.fds_bits.25 = set.fds_bits.25 & mask + case 26: set.fds_bits.26 = set.fds_bits.26 & mask + case 27: set.fds_bits.27 = set.fds_bits.27 & mask + case 28: set.fds_bits.28 = set.fds_bits.28 & mask + case 29: set.fds_bits.29 = set.fds_bits.29 & mask + case 30: set.fds_bits.30 = set.fds_bits.30 & mask + case 31: set.fds_bits.31 = set.fds_bits.31 & mask + default: break + } + } +} + + +/// Replacement for FD_ISSET macro +/// +/// - Parameter fd: A file descriptor that offsets the bit to be tested in the fd_set pointed at by 'set'. +/// - Parameter set: A pointer to a fd_set structure. +/// +/// - Returns: 'true' if the bit at offset 'fd' is 1, 'false' otherwise. + +public func fdIsSet(_ fd: Int32?, set: inout fd_set) -> Bool { + + if let fd = fd { + + let intOffset: Int32 = fd / 32 + let bitOffset: Int32 = fd % 32 + let mask: Int32 = 1 << bitOffset + + switch intOffset { + case 0: return set.fds_bits.0 & mask != 0 + case 1: return set.fds_bits.1 & mask != 0 + case 2: return set.fds_bits.2 & mask != 0 + case 3: return set.fds_bits.3 & mask != 0 + case 4: return set.fds_bits.4 & mask != 0 + case 5: return set.fds_bits.5 & mask != 0 + case 6: return set.fds_bits.6 & mask != 0 + case 7: return set.fds_bits.7 & mask != 0 + case 8: return set.fds_bits.8 & mask != 0 + case 9: return set.fds_bits.9 & mask != 0 + case 10: return set.fds_bits.10 & mask != 0 + case 11: return set.fds_bits.11 & mask != 0 + case 12: return set.fds_bits.12 & mask != 0 + case 13: return set.fds_bits.13 & mask != 0 + case 14: return set.fds_bits.14 & mask != 0 + case 15: return set.fds_bits.15 & mask != 0 + case 16: return set.fds_bits.16 & mask != 0 + case 17: return set.fds_bits.17 & mask != 0 + case 18: return set.fds_bits.18 & mask != 0 + case 19: return set.fds_bits.19 & mask != 0 + case 20: return set.fds_bits.20 & mask != 0 + case 21: return set.fds_bits.21 & mask != 0 + case 22: return set.fds_bits.22 & mask != 0 + case 23: return set.fds_bits.23 & mask != 0 + case 24: return set.fds_bits.24 & mask != 0 + case 25: return set.fds_bits.25 & mask != 0 + case 26: return set.fds_bits.26 & mask != 0 + case 27: return set.fds_bits.27 & mask != 0 + case 28: return set.fds_bits.28 & mask != 0 + case 29: return set.fds_bits.29 & mask != 0 + case 30: return set.fds_bits.30 & mask != 0 + case 31: return set.fds_bits.31 & mask != 0 + default: return false + } + + } else { + return false + } +} diff --git a/Sources/SwifterSockets/InterfaceAccess.swift b/Sources/SwifterSockets/InterfaceAccess.swift new file mode 100644 index 0000000..cc97f87 --- /dev/null +++ b/Sources/SwifterSockets/InterfaceAccess.swift @@ -0,0 +1,94 @@ +// ===================================================================================================================== +// +// File: InterfaceAccess.swift +// Project: SwifterSockets +// +// Version: 1.0.0 +// +// Author: Marinus van der Lugt +// Company: http://balancingrock.nl +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire +// +// Copyright: (c) 2016-2019 Marinus van der Lugt, All rights reserved. +// +// License: Use or redistribute this code any way you like with the following two provision: +// +// 1) You ACCEPT this source code AS IS without any guarantees that it will work as intended. Any liability from its +// use is YOURS. +// +// 2) You WILL NOT seek damages from the author or balancingrock.nl. +// +// I also ask you to please leave this header with the source code. +// +// Like you, I need to make a living: +// +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl +// - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH +// +// If you like to pay in another way, please contact me at rien@balancingrock.nl +// +// Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl +// +// ===================================================================================================================== +// PLEASE let me know about bugs, improvements and feature requests. (rien@balancingrock.nl) +// ===================================================================================================================== +// +// History +// +// 1.0.0 - Removed older history +// ===================================================================================================================== + +import Foundation + + +/// The i/o functions that glue a Connection object to an interface. + +public protocol InterfaceAccess { + + + /// An id that can be used for logging purposes and will differentiate between interfaces on a temporary basis. + /// + /// It should be guaranteed that no two interfaces with the same logId are active at the same time. + + var logId: Int32 { get } + + + /// Closes the connection. + /// + /// - Note: Data transfers will be aborted if running and may result in error messages on the receiver/transmitter protocols. + + mutating func close() + + + /// Transfers the data in the buffer to the peer. + /// + /// - Parameters: + /// - buffer: The buffer with data to be transferred. + /// - timeout: The timeout for the transfer. + /// - callback: The receiver for the TransmitterProtocol method calls (if present). + /// - progress: The closure that is invoked after partial transfers (if any). + /// + /// - Returns: See the TransferResult definition. + + func transfer( + buffer: UnsafeBufferPointer, + timeout: TimeInterval?, + callback: TransmitterProtocol?, + progress: TransmitterProgressMonitor?) -> TransferResult? + + + /// Starts a receiver loop that will call the operations as defined in the ReceiverProtocol on the receiver. + /// + /// - Note: There will be no return from this function until a ReceiverProtocol method singals so, or until an error occurs. + /// + /// - Parameters: + /// - bufferSize: The size of the buffer to create in bytes. + /// - duration: The duration for the loop. + /// - receiver: The receiver for the ReceiverProtocol method calls (if present). + + func receiverLoop( + bufferSize: Int, + duration: TimeInterval, + receiver: ReceiverProtocol) +} diff --git a/Sources/SwifterSockets/ReceiverProtocol.swift b/Sources/SwifterSockets/ReceiverProtocol.swift new file mode 100644 index 0000000..d6eaa3a --- /dev/null +++ b/Sources/SwifterSockets/ReceiverProtocol.swift @@ -0,0 +1,82 @@ +// ===================================================================================================================== +// +// File: ReceiverProtocol.swift +// Project: SwifterSockets +// +// Version: 1.0.0 +// +// Author: Marinus van der Lugt +// Company: http://balancingrock.nl +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire +// +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. +// +// License: Use or redistribute this code any way you like with the following two provision: +// +// 1) You ACCEPT this source code AS IS without any guarantees that it will work as intended. Any liability from its +// use is YOURS. +// +// 2) You WILL NOT seek damages from the author or balancingrock.nl. +// +// I also ask you to please leave this header with the source code. +// +// Like you, I need to make a living: +// +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl +// - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH +// +// If you like to pay in another way, please contact me at rien@balancingrock.nl +// +// Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl +// +// ===================================================================================================================== +// PLEASE let me know about bugs, improvements and feature requests. (rien@balancingrock.nl) +// ===================================================================================================================== +// +// History +// +// 1.0.0 - Removed older history +// ===================================================================================================================== + +import Foundation + + +/// A collection of methods used by a receiver loop to inform a data receiver of the events occuring on the interface. + +public protocol ReceiverProtocol { + + + /// Called when an error occured while receiving. + /// + /// The receiver has stopped, but the connection has not been closed or released. + /// + /// - Parameter message: A textual description of the error that occured. + + func receiverError(_ message: String) + + + /// Some data was received and is ready for processing. + /// + /// Data can arrive in multiple blocks. End detection is the responsibility of the receiver. + /// + /// - Parameter buffer: A buffer where the data that was received is located. + /// - Returns: Return true to continue receiving, false to stop receiving. The connection will not be closed or released. + + func receiverData(_ buffer: UnsafeBufferPointer) -> Bool + + + /// The connection was unexpectedly closed. It is not sure that the connection has been properly closed or deallocated. + /// + /// Probably by the other side or because of a parralel operation on a different thread. + + func receiverClosed() + + + /// Since the last data transfer (or start of operation) a timeinterval as specified in "ReceiverLoopDuration" has elapsed without any activity. + /// + /// - Returns: Return true to continue receiving, false to stop receiving. The connection will not be closed or released. + + func receiverLoop() -> Bool +} + diff --git a/Sources/SwifterSockets/ServerProtocol.swift b/Sources/SwifterSockets/ServerProtocol.swift new file mode 100644 index 0000000..27982a7 --- /dev/null +++ b/Sources/SwifterSockets/ServerProtocol.swift @@ -0,0 +1,64 @@ +// ===================================================================================================================== +// +// File: ServerProtocol.swift +// Project: SwifterSockets +// +// Version: 1.0.0 +// +// Author: Marinus van der Lugt +// Company: http://balancingrock.nl +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire +// +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. +// +// License: Use or redistribute this code any way you like with the following two provision: +// +// 1) You ACCEPT this source code AS IS without any guarantees that it will work as intended. Any liability from its +// use is YOURS. +// +// 2) You WILL NOT seek damages from the author or balancingrock.nl. +// +// I also ask you to please leave this header with the source code. +// +// Like you, I need to make a living: +// +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl +// - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH +// +// If you like to pay in another way, please contact me at rien@balancingrock.nl +// +// Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl +// +// ===================================================================================================================== +// PLEASE let me know about bugs, improvements and feature requests. (rien@balancingrock.nl) +// ===================================================================================================================== +// +// History +// +// 1.0.0 - Removed older history +// ===================================================================================================================== + +import Foundation + +import BRUtils + + +/// Control methods for a server. + +public protocol ServerProtocol { + + + /// Starts the server. + /// + /// - Returns: Either .success(true), or .error(message: String) with the message detailing the kind of error that occured. + + func start() -> Result + + + /// Stops the server. + /// + /// - Note: There are delays involved, the accept loop may still accept new requests until it loops around. Requests being processed will be allowed to continue normally. + + func stop() +} diff --git a/Sources/SwifterSockets/SetupTipServer.swift b/Sources/SwifterSockets/SetupTipServer.swift new file mode 100644 index 0000000..66b9ae4 --- /dev/null +++ b/Sources/SwifterSockets/SetupTipServer.swift @@ -0,0 +1,196 @@ +// ===================================================================================================================== +// +// File: SetupTipServer.swift +// Project: SwifterSockets +// +// Version: 1.0.0 +// +// Author: Marinus van der Lugt +// Company: http://balancingrock.nl +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire +// +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. +// +// License: Use or redistribute this code any way you like with the following two provision: +// +// 1) You ACCEPT this source code AS IS without any guarantees that it will work as intended. Any liability from its +// use is YOURS. +// +// 2) You WILL NOT seek damages from the author or balancingrock.nl. +// +// I also ask you to please leave this header with the source code. +// +// Like you, I need to make a living: +// +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl +// - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH +// +// If you like to pay in another way, please contact me at rien@balancingrock.nl +// +// Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl +// +// ===================================================================================================================== +// PLEASE let me know about bugs, improvements and feature requests. (rien@balancingrock.nl) +// ===================================================================================================================== +// +// History +// +// 1.0.0 - Removed older history +// ===================================================================================================================== + +import Foundation + +import BRUtils + + +/// Sets up a socket for listening on the specified service port number. It will listen on all available IP addresses of the server, either in IPv4 or IPv6. +/// +/// - Parameters: +/// - port: A string containing the number of the port to listen on. +/// - maxPendingConnectionRequest: The number of connections that can be kept pending before they are accepted. A connection request can be put into a queue before it is accepted or rejected. This argument specifies the size of the queue. If the queue is full further connection requests will be rejected. +/// +/// - Returns: Either .success(socket: Int32) or .error(message: String). + +public func setupTipServer(onPort port: String, maxPendingConnectionRequest: Int32) -> Result { + + + // General purpose status variable, used to detect error returns from socket functions + + var status: Int32 = 0 + + + // ================================================================== + // Retrieve the information necessary to create the socket descriptor + // ================================================================== + + // Protocol configuration, used to retrieve the data needed to create the socket descriptor + + var hints = Darwin.addrinfo( + ai_flags: AI_PASSIVE, // Assign the address of the local host to the socket structures + ai_family: AF_UNSPEC, // Either IPv4 or IPv6 + ai_socktype: SOCK_STREAM, // TCP + ai_protocol: 0, + ai_addrlen: 0, + ai_canonname: nil, + ai_addr: nil, + ai_next: nil) + + + // For the information needed to create a socket (result from the getaddrinfo) + + var servinfo: UnsafeMutablePointer? = nil + + + // Get the info we need to create our socket descriptor + + status = Darwin.getaddrinfo( + nil, // Any interface + port, // The port on which will be listenend + &hints, // Protocol configuration as per above + &servinfo) // The created information + + + // Cop out if there is an error + + if status != 0 { + var strError: String + if status == EAI_SYSTEM { + strError = String(validatingUTF8: Darwin.strerror(Darwin.errno)) ?? "Unknown error code" + } else { + strError = String(validatingUTF8: Darwin.gai_strerror(status)) ?? "Unknown error code" + } + return .error(message: strError) + } + + + // ============================ + // Create the socket descriptor + // ============================ + + let socketDescriptor = Darwin.socket( + (servinfo?.pointee.ai_family)!, // Use the servinfo created earlier, this makes it IPv4/IPv6 independant + (servinfo?.pointee.ai_socktype)!, // Use the servinfo created earlier, this makes it IPv4/IPv6 independant + (servinfo?.pointee.ai_protocol)!) // Use the servinfo created earlier, this makes it IPv4/IPv6 independant + + + // Cop out if there is an error + + if socketDescriptor == -1 { + let strError = String(validatingUTF8: Darwin.strerror(Darwin.errno)) ?? "Unknown error code" + Darwin.freeaddrinfo(servinfo) + return .error(message: strError) + } + + + // ======================================================== + // Set the socket option: prevent the "socket in use" error + // ======================================================== + + var optval: Int = 1; // Use 1 to enable the option, 0 to disable + + status = Darwin.setsockopt( + socketDescriptor, // The socket descriptor of the socket on which the option will be set + SOL_SOCKET, // Type of socket options + SO_REUSEADDR, // The socket option id + &optval, // The socket option value + socklen_t(MemoryLayout.size)) // The size of the socket option value + + if status == -1 { + let strError = String(validatingUTF8: Darwin.strerror(Darwin.errno)) ?? "Unknown error code" + Darwin.freeaddrinfo(servinfo) + closeSocket(socketDescriptor) + return .error(message: strError) + } + + + // ==================================== + // Bind the socket descriptor to a port + // ==================================== + + status = Darwin.bind( + socketDescriptor, // The socket descriptor of the socket to bind + servinfo?.pointee.ai_addr, // Use the servinfo created earlier, this makes it IPv4/IPv6 independant + (servinfo?.pointee.ai_addrlen)!) // Use the servinfo created earlier, this makes it IPv4/IPv6 independant + + // Cop out if there is an error + + if status != 0 { + let strError = String(validatingUTF8: Darwin.strerror(Darwin.errno)) ?? "Unknown error code" + Darwin.freeaddrinfo(servinfo) + closeSocket(socketDescriptor) + return .error(message: strError) + } + + + // =============================== + // Don't need the servinfo anymore + // =============================== + + Darwin.freeaddrinfo(servinfo) + + + // ======================================== + // Start listening for incoming connections + // ======================================== + + status = Darwin.listen( + socketDescriptor, // The socket on which to listen + maxPendingConnectionRequest) // The number of connections that will be allowed before they are accepted + + + // Cop out if there are any errors + + if status != 0 { + let strError = String(validatingUTF8: Darwin.strerror(Darwin.errno)) ?? "Unknown error code" + closeSocket(socketDescriptor) + return .error(message: strError) + } + + + // ============================ + // Return the socket descriptor + // ============================ + + return .success(socketDescriptor) +} diff --git a/Sources/SwifterSockets/SocketAddress.swift b/Sources/SwifterSockets/SocketAddress.swift new file mode 100644 index 0000000..f805622 --- /dev/null +++ b/Sources/SwifterSockets/SocketAddress.swift @@ -0,0 +1,116 @@ +// ===================================================================================================================== +// +// File: SocketAddress.swift +// Project: SwifterSockets +// +// Version: 1.0.0 +// +// Author: Marinus van der Lugt +// Company: http://balancingrock.nl +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire +// +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. +// +// License: Use or redistribute this code any way you like with the following two provision: +// +// 1) You ACCEPT this source code AS IS without any guarantees that it will work as intended. Any liability from its +// use is YOURS. +// +// 2) You WILL NOT seek damages from the author or balancingrock.nl. +// +// I also ask you to please leave this header with the source code. +// +// Like you, I need to make a living: +// +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl +// - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH +// +// If you like to pay in another way, please contact me at rien@balancingrock.nl +// +// Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl +// +// ===================================================================================================================== +// PLEASE let me know about bugs, improvements and feature requests. (rien@balancingrock.nl) +// ===================================================================================================================== +// +// History +// +// 1.0.0 - Removed older history +// ===================================================================================================================== + +import Foundation + + +/// A Swift wrapper for sockaddr. +/// +/// This wrapper was described on the blog from [Marco Masser](http://blog.obdev.at/representing-socket-addresses-in-swift-using-enums/) + +public enum SocketAddress { + + + /// For IPv4 addresses. + + case version4(address: sockaddr_in) + + + /// For IPv6 addresses. + + case version6(address: sockaddr_in6) + + + /// Initialize a SocketAddress from the given addrinfo. + /// + /// - Parameter addrinfo: The addrinfo from which to build the SocketAddress + + public init(addrInfo: addrinfo) { + switch addrInfo.ai_family { + case AF_INET: self = .version4(address: UnsafeRawPointer(addrInfo.ai_addr!).bindMemory(to: sockaddr_in.self, capacity: 1).pointee) + case AF_INET6: self = .version6(address: UnsafeRawPointer(addrInfo.ai_addr!).bindMemory(to: sockaddr_in6.self, capacity: 1).pointee) + default: fatalError("Unknown address family") + } + } + + + /// Initialize a SocketAddress from the result of the closure. + /// + /// - Parameter addressProvider: A closure that returns either an IPv4 addrinfo structure or an IPv6 addrinfo structure, or nil. + + public init?(addressProvider: @escaping (UnsafeMutablePointer, UnsafeMutablePointer) throws -> Void) rethrows { + + var addressStorage = sockaddr_storage() + var addressStorageLength = socklen_t(MemoryLayout.size) + + let sockaddrPtr: UnsafeMutablePointer = UnsafeMutableRawPointer(&addressStorage)!.bindMemory(to: sockaddr.self, capacity: 1) + + try addressProvider(sockaddrPtr, &addressStorageLength) + + switch Int32(addressStorage.ss_family) { + case AF_INET: self = .version4(address: UnsafeMutableRawPointer(&addressStorage).bindMemory(to: sockaddr_in.self, capacity: 1).pointee) + case AF_INET6: self = .version6(address: UnsafeMutableRawPointer(&addressStorage).bindMemory(to: sockaddr_in6.self, capacity: 1).pointee) + default: return nil + } + } + + + /// Use the SocketAddress in the given closure. + /// + /// - Parameter body: A closure that needs a sockaddr pointer. + /// + /// - Returns: The rsult of the closure. + + public func doWithPtr(body: (UnsafePointer, socklen_t) throws -> Result) rethrows -> Result { + + switch self { + case .version4(var address): + let sockaddrMutablePtr = UnsafeMutableRawPointer(&address).bindMemory(to: sockaddr.self, capacity: 1) + let sockaddrPtr = UnsafePointer(sockaddrMutablePtr) + return try body(sockaddrPtr, socklen_t(MemoryLayout.size)) + + case .version6(var address): + let sockaddrMutablePtr = UnsafeMutableRawPointer(&address).bindMemory(to: sockaddr.self, capacity: 1) + let sockaddrPtr = UnsafePointer(sockaddrMutablePtr) + return try body(sockaddrPtr, socklen_t(MemoryLayout.size)) + } + } +} diff --git a/Sources/SwifterSockets/SwifterSockets.swift b/Sources/SwifterSockets/SwifterSockets.swift deleted file mode 100644 index 16a2e23..0000000 --- a/Sources/SwifterSockets/SwifterSockets.swift +++ /dev/null @@ -1,699 +0,0 @@ -// ===================================================================================================================== -// -// File: SwifterSockets.swift -// Project: SwifterSockets -// -// Version: 0.10.11 -// -// Author: Marinus van der Lugt -// Company: http://balancingrock.nl -// Website: http://swiftfire.nl/pages/projects/swiftersockets/ -// Blog: http://swiftrien.blogspot.com -// Git: https://github.com/Balancingrock/SwifterSockets -// -// Copyright: (c) 2014-2017 Marinus van der Lugt, All rights reserved. -// -// License: Use or redistribute this code any way you like with the following two provision: -// -// 1) You ACCEPT this source code AS IS without any guarantees that it will work as intended. Any liability from its -// use is YOURS. -// -// 2) You WILL NOT seek damages from the author or balancingrock.nl. -// -// I also ask you to please leave this header with the source code. -// -// I strongly believe that voluntarism is the way for societies to function optimally. Thus I have choosen to leave it -// up to you to determine the price for this code. You pay me whatever you think this code is worth to you. -// -// - You can send payment via paypal to: sales@balancingrock.nl -// - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH -// -// I prefer the above two, but if these options don't suit you, you can also send me a gift from my amazon.co.uk -// wishlist: http://www.amazon.co.uk/gp/registry/wishlist/34GNMPZKAQ0OO/ref=cm_sw_em_r_wsl_cE3Tub013CKN6_wb -// -// If you like to pay in another way, please contact me at rien@balancingrock.nl -// -// (It is always a good idea to visit the website/blog/google to ensure that you actually pay me and not some imposter) -// -// For private and non-profit use the suggested price is the price of 1 good cup of coffee, say $4. -// For commercial use the suggested price is the price of 1 good meal, say $20. -// -// You are however encouraged to pay more ;-) -// -// Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl -// -// ===================================================================================================================== -// PLEASE let me know about bugs, improvements and feature requests. (rien@balancingrock.nl) -// ===================================================================================================================== -// -// History -// -// 0.10.10 - Migration to Swift 4, minor adjustments. -// 0.10.2 - Added BRUtils for the Result type -// 0.10.1 - Analysed compilation time and speed up -// 0.10.0 - Added func result -// 0.9.15 - Added Integer extension -// 0.9.14 - Moved receiver protocol to receiver file -// - Moved transmitter protocol to transmitter file -// - Moved progress signature to transmitter file -// - Moved server protocol to server file -// 0.9.13 - Comment section update -// 0.9.12 - Documentation updated to accomodate the documentation tool 'jazzy' -// 0.9.11 - Comment change -// 0.9.9 - Updated access control -// 0.9.8 - Redesign of SwifterSockets to support HTTPS connections. -// 0.9.7 - Upgraded to Xcode 8 beta 6 -// - Added isValidIpAddress -// 0.9.6 - Upgraded to Xcode 8 beta 3 (Swift 3) -// 0.9.5 - Added SocketAddress enum adopted from Marco Masser: http://blog.obdev.at/representing-socket-addresses-in-swift-using-enums -// 0.9.4 - Header update -// 0.9.3 - Changed target to Framework, added public declarations, removed SwifterLog. -// 0.9.2 - Added closeSocket -// - Added 'logUnixSocketCalls' -// - Upgraded to Swift 2.2 -// 0.9.1 - Changed type of object in 'synchronized' from AnyObject to NSObject -// - Added EXC_BAD_INSTRUCTION information to fd_set -// 0.9.0 - Initial release -// -// ===================================================================================================================== - -import Foundation - - -/// A helper extensions to allow increment/decrement operations on counter values - -extension BinaryInteger { - - - /// Increases self by one - - mutating func increment() { - self = self + 1 - } - - - /// Decreases self by 1 if self > 0, then starts the closure if self == 0. - - mutating func decrementAndExecuteOnNull(execute: (() throws -> T)) rethrows -> T? { - if self > 0 { - self = self - 1 - } - if self == 0 { - return try execute() - } else { - return nil - } - } -} - - -/// A general purpose return value. Possible values are: -/// -/// - error(message: String) -/// - success() -/* - // TODO: If BRUtils is not available, uncomment this enum -public enum Result { - - - // An error occured. The message details the kind of error. - - case error(message: String) - - - // The operation was sucessfull. The result is contained. - - case success(T) -}*/ - - -/// A wrapper for functions that return a Result -/// -/// Executes the given function and returns the unwrapped result. If an error was returned in the function then the onError closure is invoked with the error message. This closure should then return a default value to be used instead. -/// -/// - Parameters: -/// - function: The closure that will be executed and evaluated. -/// - onError: The closure that will be executed when the function returned the error case. -/// -/// - Returns: On success the value received from the function or on error the default value from the onError closure. -/* -// TODO: If BRUtils is not available, uncomment this function -public func result(function: (@autoclosure() -> Result), onError: (String) -> T?) -> T? { - switch function() { - case .error(let message): return onError(message) - case .success(let t): return t - } -}*/ - - -/// Signature for a closure that is used to process error messages. -/// -/// - Parameter message: Contains a textual description of the error. - -public typealias ErrorHandler = (_ message: String) -> () - - -/// Return values of the _waitForSelect_ function. - -public enum SelectResult { - - - /// The event has occured. - - case ready - - - /// Nothing happened within the timeout period. - - case timeout - - - /// An error occured. - /// - /// - Parameter message: A textual description of the error that occured. - - case error(message: String) - - - /// Either the remote or a parralel thread closed the connection unexpectedly. - - case closed -} - - -/// Wait until the POSIX select call returns for the requested event(s). If no event occurs within the timeout period, the .timeout value is returned. -/// -/// - Parameter socket: The socket on which something must happen. -/// - Parameter timeout: The time until the select call waits for an event -/// - Parameter forRead: Wait for a read event. -/// - Parameter forWrite: Wait for a write event. -/// -/// - Returns: A SelectResult value. - -public func waitForSelect(socket: Int32, timeout: Date, forRead: Bool, forWrite: Bool) -> SelectResult { - - - // ============================================= - // Check timout interval and calculate remainder - // ============================================= - - let availableTime = timeout.timeIntervalSinceNow - - if availableTime < 0.0 { - return .timeout - } - - let availableSeconds = Int(availableTime) - let availableUSeconds = Int32((availableTime - Double(availableSeconds)) * 1_000_000.0) - var availableTimeval = timeval(tv_sec: availableSeconds, tv_usec: availableUSeconds) - - - // ====================================================================================================== - // Use the select API to wait for anything to happen on our client socket only within the timeout period. - // ====================================================================================================== - - // Note: Since SSL may require a handshake it is necessary to check for both read & write activity. - - let numOfFd:Int32 = socket + 1 - var readSet:fd_set = fd_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)) - var writeSet:fd_set = fd_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)) - - if forRead { fdSet(socket, set: &readSet) } - if forWrite { fdSet(socket, set: &writeSet) } - let status = Darwin.select(numOfFd, &readSet, &writeSet, nil, &availableTimeval) - - // Because we only specified 1 FD, we do not need to check on which FD the event was received - - - // ========================= - // Exit in case of a timeout - // ========================= - - if status == 0 { - return .timeout - } - - - // ======================== - // Exit in case of an error - // ======================== - - if status == -1 { - - switch errno { - - case EBADF: - // Case 1: In a multi-threaded environment it can happen that one thread closes a socket while another thread is waiting for data on the same socket. - // In that case this is not really an error, but simply a signal that the receiving thread should be terminated. - // Case 2: Of course it could also happen that the programmer made a mistake and is using a socket that is not initialized. - // The first case is more important, so as to avoid uneccesary error messages we return the CLOSED result case. - // If the programmer made an error, it is presumed that this error will become appearant in other ways (during testing!). - return .closed - - case EINVAL, EAGAIN, EINTR: fallthrough // These are the other possible error's - - default: // Catch-all to satisfy the compiler - let errstr = String(validatingUTF8: strerror(errno)) ?? "Unknown error code" - return .error(message: errstr) - - } - } - - return .ready -} - - -/// A Swift wrapper for sockaddr. -/// -/// This wrapper was described on the blog from [Marco Masser](http://blog.obdev.at/representing-socket-addresses-in-swift-using-enums/) - -public enum SocketAddress { - - - /// For IPv4 addresses. - - case version4(address: sockaddr_in) - - - /// For IPv6 addresses. - - case version6(address: sockaddr_in6) - - - /// Initialize a SocketAddress from the given addrinfo. - /// - /// - Parameter addrinfo: The addrinfo from which to build the SocketAddress - - public init(addrInfo: addrinfo) { - switch addrInfo.ai_family { - case AF_INET: self = .version4(address: UnsafeRawPointer(addrInfo.ai_addr!).bindMemory(to: sockaddr_in.self, capacity: 1).pointee) - case AF_INET6: self = .version6(address: UnsafeRawPointer(addrInfo.ai_addr!).bindMemory(to: sockaddr_in6.self, capacity: 1).pointee) - default: fatalError("Unknown address family") - } - } - - - /// Initialize a SocketAddress from the result of the closure. - /// - /// - Parameter addressProvider: A closure that returns either an IPv4 addrinfo structure or an IPv6 addrinfo structure, or nil. - - public init?(addressProvider: @escaping (UnsafeMutablePointer, UnsafeMutablePointer) throws -> Void) rethrows { - - var addressStorage = sockaddr_storage() - var addressStorageLength = socklen_t(MemoryLayout.size) - - let sockaddrPtr: UnsafeMutablePointer = UnsafeMutableRawPointer(&addressStorage)!.bindMemory(to: sockaddr.self, capacity: 1) - - try addressProvider(sockaddrPtr, &addressStorageLength) - - switch Int32(addressStorage.ss_family) { - case AF_INET: self = .version4(address: UnsafeMutableRawPointer(&addressStorage).bindMemory(to: sockaddr_in.self, capacity: 1).pointee) - case AF_INET6: self = .version6(address: UnsafeMutableRawPointer(&addressStorage).bindMemory(to: sockaddr_in6.self, capacity: 1).pointee) - default: return nil - } - } - - - /// Use the SocketAddress in the given closure. - /// - /// - Parameter body: A closure that needs a sockaddr pointer. - /// - /// - Returns: The rsult of the closure. - - public func doWithPtr(body: (UnsafePointer, socklen_t) throws -> Result) rethrows -> Result { - - switch self { - case .version4(var address): - let sockaddrMutablePtr = UnsafeMutableRawPointer(&address).bindMemory(to: sockaddr.self, capacity: 1) - let sockaddrPtr = UnsafePointer(sockaddrMutablePtr) - return try body(sockaddrPtr, socklen_t(MemoryLayout.size)) - - case .version6(var address): - let sockaddrMutablePtr = UnsafeMutableRawPointer(&address).bindMemory(to: sockaddr.self, capacity: 1) - let sockaddrPtr = UnsafePointer(sockaddrMutablePtr) - return try body(sockaddrPtr, socklen_t(MemoryLayout.size)) - } - } -} - - -/// Verifies if the given string is a valid IPv4 or IPv6 address by converting it to a network address and back again. If the result equals the input, it must be correct. -/// -/// - Parameter address: A string containing the address specification. -/// -/// - Returns: True if the string is a valid inet address, false otherwise - -public func isValidIpAddress(_ address: String) -> Bool { - - - // Test IPv6 first, because it may also contain dots - - if address.contains(":") { - var ipv6n = sockaddr_in6() - inet_pton(AF_INET6, address, &ipv6n) - var ipv6p = Array(repeating: 0, count: Int(INET6_ADDRSTRLEN)) - inet_ntop(AF_INET6, &ipv6n, &ipv6p, socklen_t(INET6_ADDRSTRLEN)) - let ipv6str = String(cString: ipv6p) - return address.lowercased() == ipv6str - } - - - // Test if it is an IPv4 address - - if address.contains(".") { - var ipv4n = sockaddr_in() - inet_pton(AF_INET, address, &ipv4n) - var ipv4p = Array(repeating: 0, count: Int(INET_ADDRSTRLEN)) - inet_ntop(AF_INET, &ipv4n, &ipv4p, socklen_t(INET_ADDRSTRLEN)) - let ipv4str = String(cString: ipv4p) - return address.lowercased() == ipv4str - } - - return false -} - - -/// Returns the (ipAddress, portNumber) tuple for a given sockaddr if available. -/// -/// - Parameter addr: A pointer to a sockaddr structure. -/// -/// - Returns: (nil, nil) on failure, (ipAddress, portNumber) on success. - -public func sockaddrDescription(_ addr: UnsafePointer) -> (ipAddress: String?, portNumber: String?) { - - var host : String? - var service : String? - - var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - var serviceBuffer = [CChar](repeating: 0, count: Int(NI_MAXSERV)) - - if Darwin.getnameinfo( - addr, - socklen_t(addr.pointee.sa_len), - &hostBuffer, - socklen_t(hostBuffer.count), - &serviceBuffer, - socklen_t(serviceBuffer.count), - NI_NUMERICHOST | NI_NUMERICSERV) - - == 0 { - - host = String(cString: hostBuffer) - service = String(cString: serviceBuffer) - } - return (host, service) -} - - -/// Replacement for FD_ZERO macro. -/// -/// - Parameter set: A pointer to a fd_set structure. -/// -/// - Returns: The set that is opinted at is filled with all zero's. - -public func fdZero(_ set: inout fd_set) { - 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) -} - - -/// Replacement for FD_SET macro -/// -/// - Parameter fd: A file descriptor that offsets the bit to be set to 1 in the fd_set pointed at by 'set'. -/// - Parameter set: A pointer to a fd_set structure. -/// -/// - Returns: The given set is updated in place, with the bit at offset 'fd' set to 1. -/// -/// - Note: If you receive an EXC_BAD_INSTRUCTION at the mask statement, then most likely the socket was already closed. - -public func fdSet(_ fd: Int32?, set: inout fd_set) { - - if let fd = fd { - - let intOffset: Int32 = fd / 32 - let bitOffset: Int32 = fd % 32 - let mask: Int32 = 1 << bitOffset - - switch intOffset { - case 0: set.fds_bits.0 = set.fds_bits.0 | mask - case 1: set.fds_bits.1 = set.fds_bits.1 | mask - case 2: set.fds_bits.2 = set.fds_bits.2 | mask - case 3: set.fds_bits.3 = set.fds_bits.3 | mask - case 4: set.fds_bits.4 = set.fds_bits.4 | mask - case 5: set.fds_bits.5 = set.fds_bits.5 | mask - case 6: set.fds_bits.6 = set.fds_bits.6 | mask - case 7: set.fds_bits.7 = set.fds_bits.7 | mask - case 8: set.fds_bits.8 = set.fds_bits.8 | mask - case 9: set.fds_bits.9 = set.fds_bits.9 | mask - case 10: set.fds_bits.10 = set.fds_bits.10 | mask - case 11: set.fds_bits.11 = set.fds_bits.11 | mask - case 12: set.fds_bits.12 = set.fds_bits.12 | mask - case 13: set.fds_bits.13 = set.fds_bits.13 | mask - case 14: set.fds_bits.14 = set.fds_bits.14 | mask - case 15: set.fds_bits.15 = set.fds_bits.15 | mask - case 16: set.fds_bits.16 = set.fds_bits.16 | mask - case 17: set.fds_bits.17 = set.fds_bits.17 | mask - case 18: set.fds_bits.18 = set.fds_bits.18 | mask - case 19: set.fds_bits.19 = set.fds_bits.19 | mask - case 20: set.fds_bits.20 = set.fds_bits.20 | mask - case 21: set.fds_bits.21 = set.fds_bits.21 | mask - case 22: set.fds_bits.22 = set.fds_bits.22 | mask - case 23: set.fds_bits.23 = set.fds_bits.23 | mask - case 24: set.fds_bits.24 = set.fds_bits.24 | mask - case 25: set.fds_bits.25 = set.fds_bits.25 | mask - case 26: set.fds_bits.26 = set.fds_bits.26 | mask - case 27: set.fds_bits.27 = set.fds_bits.27 | mask - case 28: set.fds_bits.28 = set.fds_bits.28 | mask - case 29: set.fds_bits.29 = set.fds_bits.29 | mask - case 30: set.fds_bits.30 = set.fds_bits.30 | mask - case 31: set.fds_bits.31 = set.fds_bits.31 | mask - default: break - } - } -} - - -/// Replacement for FD_CLR macro -/// -/// - Parameter fd: A file descriptor that offsets the bit to be cleared in the fd_set pointed at by 'set'. -/// - Parameter set: A pointer to a fd_set structure. -/// -/// - Returns: The given set is updated in place, with the bit at offset 'fd' cleared to 0. - -public func fdClr(_ fd: Int32?, set: inout fd_set) { - - if let fd = fd { - - let intOffset: Int32 = fd / 32 - let bitOffset: Int32 = fd % 32 - let mask: Int32 = ~(1 << bitOffset) - - switch intOffset { - case 0: set.fds_bits.0 = set.fds_bits.0 & mask - case 1: set.fds_bits.1 = set.fds_bits.1 & mask - case 2: set.fds_bits.2 = set.fds_bits.2 & mask - case 3: set.fds_bits.3 = set.fds_bits.3 & mask - case 4: set.fds_bits.4 = set.fds_bits.4 & mask - case 5: set.fds_bits.5 = set.fds_bits.5 & mask - case 6: set.fds_bits.6 = set.fds_bits.6 & mask - case 7: set.fds_bits.7 = set.fds_bits.7 & mask - case 8: set.fds_bits.8 = set.fds_bits.8 & mask - case 9: set.fds_bits.9 = set.fds_bits.9 & mask - case 10: set.fds_bits.10 = set.fds_bits.10 & mask - case 11: set.fds_bits.11 = set.fds_bits.11 & mask - case 12: set.fds_bits.12 = set.fds_bits.12 & mask - case 13: set.fds_bits.13 = set.fds_bits.13 & mask - case 14: set.fds_bits.14 = set.fds_bits.14 & mask - case 15: set.fds_bits.15 = set.fds_bits.15 & mask - case 16: set.fds_bits.16 = set.fds_bits.16 & mask - case 17: set.fds_bits.17 = set.fds_bits.17 & mask - case 18: set.fds_bits.18 = set.fds_bits.18 & mask - case 19: set.fds_bits.19 = set.fds_bits.19 & mask - case 20: set.fds_bits.20 = set.fds_bits.20 & mask - case 21: set.fds_bits.21 = set.fds_bits.21 & mask - case 22: set.fds_bits.22 = set.fds_bits.22 & mask - case 23: set.fds_bits.23 = set.fds_bits.23 & mask - case 24: set.fds_bits.24 = set.fds_bits.24 & mask - case 25: set.fds_bits.25 = set.fds_bits.25 & mask - case 26: set.fds_bits.26 = set.fds_bits.26 & mask - case 27: set.fds_bits.27 = set.fds_bits.27 & mask - case 28: set.fds_bits.28 = set.fds_bits.28 & mask - case 29: set.fds_bits.29 = set.fds_bits.29 & mask - case 30: set.fds_bits.30 = set.fds_bits.30 & mask - case 31: set.fds_bits.31 = set.fds_bits.31 & mask - default: break - } - } -} - - -/// Replacement for FD_ISSET macro -/// -/// - Parameter fd: A file descriptor that offsets the bit to be tested in the fd_set pointed at by 'set'. -/// - Parameter set: A pointer to a fd_set structure. -/// -/// - Returns: 'true' if the bit at offset 'fd' is 1, 'false' otherwise. - -public func fdIsSet(_ fd: Int32?, set: inout fd_set) -> Bool { - - if let fd = fd { - - let intOffset: Int32 = fd / 32 - let bitOffset: Int32 = fd % 32 - let mask: Int32 = 1 << bitOffset - - switch intOffset { - case 0: return set.fds_bits.0 & mask != 0 - case 1: return set.fds_bits.1 & mask != 0 - case 2: return set.fds_bits.2 & mask != 0 - case 3: return set.fds_bits.3 & mask != 0 - case 4: return set.fds_bits.4 & mask != 0 - case 5: return set.fds_bits.5 & mask != 0 - case 6: return set.fds_bits.6 & mask != 0 - case 7: return set.fds_bits.7 & mask != 0 - case 8: return set.fds_bits.8 & mask != 0 - case 9: return set.fds_bits.9 & mask != 0 - case 10: return set.fds_bits.10 & mask != 0 - case 11: return set.fds_bits.11 & mask != 0 - case 12: return set.fds_bits.12 & mask != 0 - case 13: return set.fds_bits.13 & mask != 0 - case 14: return set.fds_bits.14 & mask != 0 - case 15: return set.fds_bits.15 & mask != 0 - case 16: return set.fds_bits.16 & mask != 0 - case 17: return set.fds_bits.17 & mask != 0 - case 18: return set.fds_bits.18 & mask != 0 - case 19: return set.fds_bits.19 & mask != 0 - case 20: return set.fds_bits.20 & mask != 0 - case 21: return set.fds_bits.21 & mask != 0 - case 22: return set.fds_bits.22 & mask != 0 - case 23: return set.fds_bits.23 & mask != 0 - case 24: return set.fds_bits.24 & mask != 0 - case 25: return set.fds_bits.25 & mask != 0 - case 26: return set.fds_bits.26 & mask != 0 - case 27: return set.fds_bits.27 & mask != 0 - case 28: return set.fds_bits.28 & mask != 0 - case 29: return set.fds_bits.29 & mask != 0 - case 30: return set.fds_bits.30 & mask != 0 - case 31: return set.fds_bits.31 & mask != 0 - default: return false - } - - } else { - return false - } -} - - -/// Returns all IP addresses in the addrinfo structure as a String. -/// -/// - Parameter infoPtr: A pointer to an addrinfo structure of which the IP addresses should be logged. -/// -/// - Returns: A string with the IP Addresses of all entries in the infoPtr addrinfo structure chain. - -public func logAddrInfoIPAddresses(_ infoPtr: UnsafeMutablePointer) -> String { - - let addrInfoNil: UnsafeMutablePointer? = nil - var count = 0 - var info = infoPtr - var str = "" - - while info != addrInfoNil { - - let (clientIpOrNil, serviceOrNil) = sockaddrDescription(info.pointee.ai_addr) - let clientIp = clientIpOrNil ?? "?" - let service = serviceOrNil ?? "?" - str += "No: \(count), HostIp: \(clientIp) at port: \(service)\n" - count += 1 - info = info.pointee.ai_next - } - return str -} - - -/// Returns a string with all socket options. -/// -/// - Parameter socket: The socket of which to log the options. -/// -/// - Returns: A string with all socket options of the given socket. - -public func logSocketOptions(_ socket: Int32) -> String { - - - // To identify the logging source - - var res = "" - - - // Assist functions do the actual logging - - func forFlagOptionAtLevel(_ level: Int32, withName name: Int32, str: String) { - var optionValueFlag: Int32 = 0 - var ovFlagLength: socklen_t = 4 - _ = getsockopt(socket, level, name, &optionValueFlag, &ovFlagLength) - res += "\(str) = " + (optionValueFlag == 0 ? "No" : "Yes") - } - - func forIntOptionAtLevel(_ level: Int32, withName name: Int32, str: String) { - var optionValueInt: Int32 = 0 - var ovIntLength: socklen_t = 4 - _ = getsockopt(socket, level, name, &optionValueInt, &ovIntLength) - res += "\(str) = \(optionValueInt)" - } - - func forLingerOptionAtLevel(_ level: Int32, withName name: Int32, str: String) { - var optionValueLinger = linger(l_onoff: 0, l_linger: 0) - var ovLingerLength: socklen_t = 8 - _ = getsockopt(socket, level, name, &optionValueLinger, &ovLingerLength) - res += "\(str) onOff = \(optionValueLinger.l_onoff), linger = \(optionValueLinger.l_linger)" - } - - func forTimeOptionAtLevel(_ level: Int32, withName name: Int32, str: String) { - var optionValueTime = time_value(seconds: 0, microseconds: 0) - var ovTimeLength: socklen_t = 8 - _ = getsockopt(socket, level, name, &optionValueTime, &ovTimeLength) - res += "\(str) seconds = \(optionValueTime.seconds), microseconds = \(optionValueTime.microseconds)" - } - - - // Call the assist functions for the available options - - forFlagOptionAtLevel(SOL_SOCKET, withName: SO_BROADCAST, str: "SO_BROADCAST") - forFlagOptionAtLevel(SOL_SOCKET, withName: SO_DEBUG, str: "SO_DEBUG") - forFlagOptionAtLevel(SOL_SOCKET, withName: SO_DONTROUTE, str: "SO_DONTROUTE") - forIntOptionAtLevel(SOL_SOCKET, withName: SO_ERROR, str: "SO_ERROR") - forFlagOptionAtLevel(SOL_SOCKET, withName: SO_KEEPALIVE, str: "SO_KEEPALIVE") - forLingerOptionAtLevel(SOL_SOCKET, withName: SO_LINGER, str: "SO_LINGER") - forFlagOptionAtLevel(SOL_SOCKET, withName: SO_OOBINLINE, str: "SO_OOBINLINE") - forIntOptionAtLevel(SOL_SOCKET, withName: SO_RCVBUF, str: "SO_RCVBUF") - forIntOptionAtLevel(SOL_SOCKET, withName: SO_SNDBUF, str: "SO_SNDBUF") - forIntOptionAtLevel(SOL_SOCKET, withName: SO_RCVLOWAT, str: "SO_RCVLOWAT") - forIntOptionAtLevel(SOL_SOCKET, withName: SO_SNDLOWAT, str: "SO_SNDLOWAT") - forTimeOptionAtLevel(SOL_SOCKET, withName: SO_RCVTIMEO, str: "SO_RCVTIMEO") - forTimeOptionAtLevel(SOL_SOCKET, withName: SO_SNDTIMEO, str: "SO_SNDTIMEO") - forFlagOptionAtLevel(SOL_SOCKET, withName: SO_REUSEADDR, str: "SO_REUSEADDR") - forFlagOptionAtLevel(SOL_SOCKET, withName: SO_REUSEPORT, str: "SO_REUSEPORT") - forIntOptionAtLevel(SOL_SOCKET, withName: SO_TYPE, str: "SO_TYPE") - forFlagOptionAtLevel(SOL_SOCKET, withName: SO_USELOOPBACK, str: "SO_USELOOPBACK") - forIntOptionAtLevel(IPPROTO_IP, withName: IP_TOS, str: "IP_TOS") - forIntOptionAtLevel(IPPROTO_IP, withName: IP_TTL, str: "IP_TTL") - forIntOptionAtLevel(IPPROTO_IPV6, withName: IPV6_UNICAST_HOPS, str: "IPV6_UNICAST_HOPS") - forFlagOptionAtLevel(IPPROTO_IPV6, withName: IPV6_V6ONLY, str: "IPV6_V6ONLY") - forIntOptionAtLevel(IPPROTO_TCP, withName: TCP_MAXSEG, str: "TCP_MAXSEG") - forFlagOptionAtLevel(IPPROTO_TCP, withName: TCP_NODELAY, str: "TCP_NODELAY") - - return res -} - - -/// Closes the given socket if not nil. -/// -/// This method is supplied to have a single place that closes all sockets. During debugging it is often good to create a logging entry for the calls on the unix sockets. This method prevents having to look through all code to find all occurances of the close call. -/// -/// - Returns: True if the port was closed, nil if it was closed already and false if an error occured (errno will contain an error reason). - -@discardableResult -public func closeSocket(_ socket: Int32?) -> Bool? { - - guard let s = socket else { return nil } - - return Darwin.close(s) == 0 -} diff --git a/Sources/SwifterSockets/SwifterSocketsUtils.swift b/Sources/SwifterSockets/SwifterSocketsUtils.swift new file mode 100644 index 0000000..73bed2c --- /dev/null +++ b/Sources/SwifterSockets/SwifterSocketsUtils.swift @@ -0,0 +1,262 @@ +// ===================================================================================================================== +// +// File: SwifterSocketsUtils.swift +// Project: SwifterSockets +// +// Version: 1.0.0 +// +// Author: Marinus van der Lugt +// Company: http://balancingrock.nl +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire +// +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. +// +// License: Use or redistribute this code any way you like with the following two provision: +// +// 1) You ACCEPT this source code AS IS without any guarantees that it will work as intended. Any liability from its +// use is YOURS. +// +// 2) You WILL NOT seek damages from the author or balancingrock.nl. +// +// I also ask you to please leave this header with the source code. +// +// Like you, I need to make a living: +// +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl +// - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH +// +// If you like to pay in another way, please contact me at rien@balancingrock.nl +// +// Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl +// +// ===================================================================================================================== +// PLEASE let me know about bugs, improvements and feature requests. (rien@balancingrock.nl) +// ===================================================================================================================== +// +// History +// +// 1.0.0 - Removed older history +// ===================================================================================================================== + +import Foundation + + +/// A helper extensions to allow increment/decrement operations on counter values + +extension BinaryInteger { + + + /// Increases self by one + + mutating func increment() { + self = self + 1 + } + + + /// Decreases self by 1 if self > 0, then starts the closure if self == 0. + + mutating func decrementAndExecuteOnNull(execute: (() throws -> T)) rethrows -> T? { + if self > 0 { + self = self - 1 + } + if self == 0 { + return try execute() + } else { + return nil + } + } +} + + +/// Signature for a closure that is used to process error messages. +/// +/// - Parameter message: Contains a textual description of the error. + +public typealias ErrorHandler = (_ message: String) -> () + + +/// Verifies if the given string is a valid IPv4 or IPv6 address by converting it to a network address and back again. If the result equals the input, it must be correct. +/// +/// - Parameter address: A string containing the address specification. +/// +/// - Returns: True if the string is a valid inet address, false otherwise + +public func isValidIpAddress(_ address: String) -> Bool { + + + // Test IPv6 first, because it may also contain dots + + if address.contains(":") { + var ipv6n = sockaddr_in6() + inet_pton(AF_INET6, address, &ipv6n) + var ipv6p = Array(repeating: 0, count: Int(INET6_ADDRSTRLEN)) + inet_ntop(AF_INET6, &ipv6n, &ipv6p, socklen_t(INET6_ADDRSTRLEN)) + let ipv6str = String(cString: ipv6p) + return address.lowercased() == ipv6str + } + + + // Test if it is an IPv4 address + + if address.contains(".") { + var ipv4n = sockaddr_in() + inet_pton(AF_INET, address, &ipv4n) + var ipv4p = Array(repeating: 0, count: Int(INET_ADDRSTRLEN)) + inet_ntop(AF_INET, &ipv4n, &ipv4p, socklen_t(INET_ADDRSTRLEN)) + let ipv4str = String(cString: ipv4p) + return address.lowercased() == ipv4str + } + + return false +} + + +/// Returns the (ipAddress, portNumber) tuple for a given sockaddr if available. +/// +/// - Parameter addr: A pointer to a sockaddr structure. +/// +/// - Returns: (nil, nil) on failure, (ipAddress, portNumber) on success. + +public func sockaddrDescription(_ addr: UnsafePointer) -> (ipAddress: String?, portNumber: String?) { + + var host : String? + var service : String? + + var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + var serviceBuffer = [CChar](repeating: 0, count: Int(NI_MAXSERV)) + + if Darwin.getnameinfo( + addr, + socklen_t(addr.pointee.sa_len), + &hostBuffer, + socklen_t(hostBuffer.count), + &serviceBuffer, + socklen_t(serviceBuffer.count), + NI_NUMERICHOST | NI_NUMERICSERV) + + == 0 { + + host = String(cString: hostBuffer) + service = String(cString: serviceBuffer) + } + return (host, service) +} + + + + +/// Returns all IP addresses in the addrinfo structure as a String. +/// +/// - Parameter infoPtr: A pointer to an addrinfo structure of which the IP addresses should be logged. +/// +/// - Returns: A string with the IP Addresses of all entries in the infoPtr addrinfo structure chain. + +public func logAddrInfoIPAddresses(_ infoPtr: UnsafeMutablePointer) -> String { + + let addrInfoNil: UnsafeMutablePointer? = nil + var count = 0 + var info = infoPtr + var str = "" + + while info != addrInfoNil { + + let (clientIpOrNil, serviceOrNil) = sockaddrDescription(info.pointee.ai_addr) + let clientIp = clientIpOrNil ?? "?" + let service = serviceOrNil ?? "?" + str += "No: \(count), HostIp: \(clientIp) at port: \(service)\n" + count += 1 + info = info.pointee.ai_next + } + return str +} + + +/// Returns a string with all socket options. +/// +/// - Parameter socket: The socket of which to log the options. +/// +/// - Returns: A string with all socket options of the given socket. + +public func logSocketOptions(_ socket: Int32) -> String { + + + // To identify the logging source + + var res = "" + + + // Assist functions do the actual logging + + func forFlagOptionAtLevel(_ level: Int32, withName name: Int32, str: String) { + var optionValueFlag: Int32 = 0 + var ovFlagLength: socklen_t = 4 + _ = getsockopt(socket, level, name, &optionValueFlag, &ovFlagLength) + res += "\(str) = " + (optionValueFlag == 0 ? "No" : "Yes") + } + + func forIntOptionAtLevel(_ level: Int32, withName name: Int32, str: String) { + var optionValueInt: Int32 = 0 + var ovIntLength: socklen_t = 4 + _ = getsockopt(socket, level, name, &optionValueInt, &ovIntLength) + res += "\(str) = \(optionValueInt)" + } + + func forLingerOptionAtLevel(_ level: Int32, withName name: Int32, str: String) { + var optionValueLinger = linger(l_onoff: 0, l_linger: 0) + var ovLingerLength: socklen_t = 8 + _ = getsockopt(socket, level, name, &optionValueLinger, &ovLingerLength) + res += "\(str) onOff = \(optionValueLinger.l_onoff), linger = \(optionValueLinger.l_linger)" + } + + func forTimeOptionAtLevel(_ level: Int32, withName name: Int32, str: String) { + var optionValueTime = time_value(seconds: 0, microseconds: 0) + var ovTimeLength: socklen_t = 8 + _ = getsockopt(socket, level, name, &optionValueTime, &ovTimeLength) + res += "\(str) seconds = \(optionValueTime.seconds), microseconds = \(optionValueTime.microseconds)" + } + + + // Call the assist functions for the available options + + forFlagOptionAtLevel(SOL_SOCKET, withName: SO_BROADCAST, str: "SO_BROADCAST") + forFlagOptionAtLevel(SOL_SOCKET, withName: SO_DEBUG, str: "SO_DEBUG") + forFlagOptionAtLevel(SOL_SOCKET, withName: SO_DONTROUTE, str: "SO_DONTROUTE") + forIntOptionAtLevel(SOL_SOCKET, withName: SO_ERROR, str: "SO_ERROR") + forFlagOptionAtLevel(SOL_SOCKET, withName: SO_KEEPALIVE, str: "SO_KEEPALIVE") + forLingerOptionAtLevel(SOL_SOCKET, withName: SO_LINGER, str: "SO_LINGER") + forFlagOptionAtLevel(SOL_SOCKET, withName: SO_OOBINLINE, str: "SO_OOBINLINE") + forIntOptionAtLevel(SOL_SOCKET, withName: SO_RCVBUF, str: "SO_RCVBUF") + forIntOptionAtLevel(SOL_SOCKET, withName: SO_SNDBUF, str: "SO_SNDBUF") + forIntOptionAtLevel(SOL_SOCKET, withName: SO_RCVLOWAT, str: "SO_RCVLOWAT") + forIntOptionAtLevel(SOL_SOCKET, withName: SO_SNDLOWAT, str: "SO_SNDLOWAT") + forTimeOptionAtLevel(SOL_SOCKET, withName: SO_RCVTIMEO, str: "SO_RCVTIMEO") + forTimeOptionAtLevel(SOL_SOCKET, withName: SO_SNDTIMEO, str: "SO_SNDTIMEO") + forFlagOptionAtLevel(SOL_SOCKET, withName: SO_REUSEADDR, str: "SO_REUSEADDR") + forFlagOptionAtLevel(SOL_SOCKET, withName: SO_REUSEPORT, str: "SO_REUSEPORT") + forIntOptionAtLevel(SOL_SOCKET, withName: SO_TYPE, str: "SO_TYPE") + forFlagOptionAtLevel(SOL_SOCKET, withName: SO_USELOOPBACK, str: "SO_USELOOPBACK") + forIntOptionAtLevel(IPPROTO_IP, withName: IP_TOS, str: "IP_TOS") + forIntOptionAtLevel(IPPROTO_IP, withName: IP_TTL, str: "IP_TTL") + forIntOptionAtLevel(IPPROTO_IPV6, withName: IPV6_UNICAST_HOPS, str: "IPV6_UNICAST_HOPS") + forFlagOptionAtLevel(IPPROTO_IPV6, withName: IPV6_V6ONLY, str: "IPV6_V6ONLY") + forIntOptionAtLevel(IPPROTO_TCP, withName: TCP_MAXSEG, str: "TCP_MAXSEG") + forFlagOptionAtLevel(IPPROTO_TCP, withName: TCP_NODELAY, str: "TCP_NODELAY") + + return res +} + + +/// Closes the given socket if not nil. +/// +/// This method is supplied to have a single place that closes all sockets. During debugging it is often good to create a logging entry for the calls on the unix sockets. This method prevents having to look through all code to find all occurances of the close call. +/// +/// - Returns: True if the port was closed, nil if it was closed already and false if an error occured (errno will contain an error reason). + +@discardableResult +public func closeSocket(_ socket: Int32?) -> Bool? { + + guard let s = socket else { return nil } + + return Darwin.close(s) == 0 +} diff --git a/Sources/SwifterSockets/SwifterSockets.Accept.swift b/Sources/SwifterSockets/TipAccept.swift similarity index 72% rename from Sources/SwifterSockets/SwifterSockets.Accept.swift rename to Sources/SwifterSockets/TipAccept.swift index 4e0b719..fde53c9 100644 --- a/Sources/SwifterSockets/SwifterSockets.Accept.swift +++ b/Sources/SwifterSockets/TipAccept.swift @@ -1,17 +1,16 @@ // ===================================================================================================================== // -// File: SwifterSockets.Accept.swift +// File: TipAccept.swift // Project: SwifterSockets // -// Version: 0.9.13 +// Version: 1.0.0 // // Author: Marinus van der Lugt // Company: http://balancingrock.nl -// Website: http://swiftfire.nl/pages/projects/swiftersockets/ -// Blog: http://swiftrien.blogspot.com -// Git: https://github.com/Balancingrock/SwifterSockets +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire // -// Copyright: (c) 2014-2017 Marinus van der Lugt, All rights reserved. +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. // // License: Use or redistribute this code any way you like with the following two provision: // @@ -22,24 +21,13 @@ // // I also ask you to please leave this header with the source code. // -// I strongly believe that voluntarism is the way for societies to function optimally. Thus I have choosen to leave it -// up to you to determine the price for this code. You pay me whatever you think this code is worth to you. +// Like you, I need to make a living: // -// - You can send payment via paypal to: sales@balancingrock.nl +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl // - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH // -// I prefer the above two, but if these options don't suit you, you might also send me a gift from my amazon.co.uk -// wishlist: http://www.amazon.co.uk/gp/registry/wishlist/34GNMPZKAQ0OO/ref=cm_sw_em_r_wsl_cE3Tub013CKN6_wb -// // If you like to pay in another way, please contact me at rien@balancingrock.nl // -// (It is always a good idea to visit the website/blog/google to ensure that you actually pay me and not some imposter) -// -// For private and non-profit use the suggested price is the price of 1 good cup of coffee, say $4. -// For commercial use the suggested price is the price of 1 good meal, say $20. -// -// You are however encouraged to pay more ;-) -// // Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl // // ===================================================================================================================== @@ -48,24 +36,7 @@ // // History // -// 0.9.13 - Comment section update -// 0.9.12 - Documentation updated to accomodate the documentation tool 'jazzy' -// 0.9.11 - Comment change -// 0.9.9 - Updated access control -// 0.9.8 - Redesign of SwifterSockets to support HTTPS connections. -// - Added 'AddressHandler' closure to accept routine. -// 0.9.7 - Upgraded to Xcode 8 beta 6 -// 0.9.6 - Upgraded to Xcode 8 beta 3 (Swift 3) -// 0.9.5 - Fixed a bug where accepting an IPv6 connection would fill an IPv4 sockaddr structure. -// 0.9.4 - Header update -// 0.9.3 - Adding Carthage support: Changed target to Framework, added public declarations, removed SwifterLog. -// 0.9.2 - Added support for logUnixSocketCalls -// - Moved closing of sockets to SwifterSockets.closeSocket -// - Upgraded to Swift 2.2 -// - Added CLOSED as a possible result (this happens when a thread is accepting while another thread closes the associated socket) -// - Fixed a bug that missed the error return from the select call. -// 0.9.1 - AcceptTelemetry now inherits from NSObject -// 0.9.0 - Initial release +// 1.0.0 - Removed older history // ===================================================================================================================== diff --git a/Sources/SwifterSockets/TipInterface.swift b/Sources/SwifterSockets/TipInterface.swift new file mode 100644 index 0000000..3efa662 --- /dev/null +++ b/Sources/SwifterSockets/TipInterface.swift @@ -0,0 +1,151 @@ +// ===================================================================================================================== +// +// File: TipTransfer.swift +// Project: SwifterSockets +// +// Version: 1.0.0 +// +// Author: Marinus van der Lugt +// Company: http://balancingrock.nl +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire +// +// Copyright: (c) 2016-2019 Marinus van der Lugt, All rights reserved. +// +// License: Use or redistribute this code any way you like with the following two provision: +// +// 1) You ACCEPT this source code AS IS without any guarantees that it will work as intended. Any liability from its +// use is YOURS. +// +// 2) You WILL NOT seek damages from the author or balancingrock.nl. +// +// I also ask you to please leave this header with the source code. +// +// Like you, I need to make a living: +// +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl +// - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH +// +// If you like to pay in another way, please contact me at rien@balancingrock.nl +// +// Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl +// +// ===================================================================================================================== +// PLEASE let me know about bugs, improvements and feature requests. (rien@balancingrock.nl) +// ===================================================================================================================== +// +// History +// +// 1.0.0 - Removed older history +// ===================================================================================================================== + +import Foundation + + +/// This class implements the InterfaceAccess protocol for the POSIX TCP/IP socket interface. + +public struct TipInterface: InterfaceAccess { + + + /// An id that can be used for logging purposes and will differentiate between interfaces on a temporary basis. + /// + /// It should be guaranteed that no two interfaces with the same logId are active at the same time. + + public var logId: Int32 { return socket ?? -1 } + + + /// The socket for this connection. + + public private(set) var socket: Int32? + + + /// Returns true if the connection is still usable. + /// + /// - Note: Even if 'true' is returned it is still possible that the next attempt to use the interface will immediately result in a termination of the connection. For example if the peer has already closed its side of the connection. + + public var isValid: Bool { + + if socket == nil { return false } + if socket! < 0 { return false } + return true + } + + + /// Creates a new interface. + /// + /// - Parameter socket: The socket to use for this interface. + + public init(_ socket: Int32) { + + self.socket = socket + } + + + /// Closes this end of a connection. + + public mutating func close() { + + if isValid { + closeSocket(socket) + socket = nil + } + } + + + /// Transfers the data via the socket to the peer. This operation returns when the data has been accepted by the POSIX layer, i.e. the physical transfer may still be ongoing. + /// + /// - Parameters: + /// - buffer: The buffer containing the data to be transferred. + /// - timeout: The timeout that applies to the transfer. + /// - callback: The receiver for the TransmitterProtocol method calls (if present). + /// - progress: The closure that is invoked after partial transfers (if any). + /// + /// - Returns: See the TransferResult definition. + + public func transfer( + buffer: UnsafeBufferPointer, + timeout: TimeInterval?, + callback: TransmitterProtocol? = nil, + progress: TransmitterProgressMonitor? = nil) -> TransferResult? { + + if isValid { + + return tipTransfer( + socket: socket!, + buffer: buffer, + timeout: timeout ?? 10, + callback: callback, + progress: progress) + + } else { + + return nil + } + } + + + /// Starts a receiver loop that will call the operations as defined in the ReceiverProtocol on the receiver. + /// + /// - Note: There will be no return from this function until a ReceiverProtocol method singals so, or until an error occurs. + /// + /// - Parameters: + /// - bufferSize: The size of the buffer to create in bytes. + /// - duration: The duration for the loop. + /// - receiver: The receiver for the ReceiverProtocol method calls (if present). + + public func receiverLoop( + bufferSize: Int = 20 * 1024, + duration: TimeInterval = 10, + receiver: ReceiverProtocol + ) { + + if isValid { + + tipReceiverLoop( + socket: socket!, + bufferSize: bufferSize, + duration: duration, + receiver: receiver) + } + } +} diff --git a/Sources/SwifterSockets/SwifterSockets.Receive.swift b/Sources/SwifterSockets/TipReceiverLoop.swift similarity index 53% rename from Sources/SwifterSockets/SwifterSockets.Receive.swift rename to Sources/SwifterSockets/TipReceiverLoop.swift index 4c9901b..983748c 100644 --- a/Sources/SwifterSockets/SwifterSockets.Receive.swift +++ b/Sources/SwifterSockets/TipReceiverLoop.swift @@ -1,17 +1,16 @@ // ===================================================================================================================== // -// File: SwifterSockets.Receive.swift +// File: TipReceiverLoop.swift // Project: SwifterSockets // -// Version: 0.10.11 +// Version: 1.0.0 // // Author: Marinus van der Lugt // Company: http://balancingrock.nl -// Website: http://swiftfire.nl/pages/projects/swiftersockets/ -// Blog: http://swiftrien.blogspot.com -// Git: https://github.com/Balancingrock/SwifterSockets +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire // -// Copyright: (c) 2014-2017 Marinus van der Lugt, All rights reserved. +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. // // License: Use or redistribute this code any way you like with the following two provision: // @@ -22,24 +21,13 @@ // // I also ask you to please leave this header with the source code. // -// I strongly believe that voluntarism is the way for societies to function optimally. Thus I have choosen to leave it -// up to you to determine the price for this code. You pay me whatever you think this code is worth to you. +// Like you, I need to make a living: // -// - You can send payment via paypal to: sales@balancingrock.nl +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl // - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH // -// I prefer the above two, but if these options don't suit you, you might also send me a gift from my amazon.co.uk -// wishlist: http://www.amazon.co.uk/gp/registry/wishlist/34GNMPZKAQ0OO/ref=cm_sw_em_r_wsl_cE3Tub013CKN6_wb -// // If you like to pay in another way, please contact me at rien@balancingrock.nl // -// (It is always a good idea to visit the website/blog/google to ensure that you actually pay me and not some imposter) -// -// For private and non-profit use the suggested price is the price of 1 good cup of coffee, say $4. -// For commercial use the suggested price is the price of 1 good meal, say $20. -// -// You are however encouraged to pay more ;-) -// // Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl // // ===================================================================================================================== @@ -48,73 +36,13 @@ // // History // -// 0.10.11 - Migration to Swift 4, minor adjustments. -// 0.9.14 - Moved receiver protocol to this file -// 0.9.13 - Comment section update -// 0.9.12 - Documentation updated to accomodate the documentation tool 'jazzy' -// 0.9.11 - Comment change -// 0.9.9 - Updated access control -// 0.9.8 - Redesign of SwifterSockets to support HTTPS connections. -// 0.9.7 - Upgraded to Xcode 8 beta 6 -// - Fixed type in receiveDataOrThrow (was receiveNSDataOrThrow) -// 0.9.6 - Upgraded to Xcode 8 beta 3 (Swift 3) -// 0.9.4 - Header update -// 0.9.3 - Adding Carthage support: Changed target to Framework, added public declarations, removed SwifterLog. -// 0.9.2 - Added support for logUnixSocketCalls -// - Moved closing of sockets to SwifterSockets.closeSocket -// - Upgraded to Swift 2.2 -// - Changed DataEndDetector from a class to a protocol. -// - Added return result SERVER_CLOSED to cover the case where the server closed a connection while a receiver process is still waiting for data. -// - Replaced error numbers with #file.#function.#line -// 0.9.1 - ReceiveTelemetry now inherits from NSObject -// - Replaced (UnsafeMutablePointer, length) with UnsafeMutableBufferPointer -// - Added note on DataEndDetector that it can be used to receive the data also. -// 0.9.0 - Initial release +// 1.0.0 - Removed older history // ===================================================================================================================== import Foundation -/// A collection of methods used by a receiver loop to inform a data receiver of the events occuring on the interface. - -public protocol ReceiverProtocol { - - - /// Called when an error occured while receiving. - /// - /// The receiver has stopped, but the connection has not been closed or released. - /// - /// - Parameter message: A textual description of the error that occured. - - func receiverError(_ message: String) - - - /// Some data was received and is ready for processing. - /// - /// Data can arrive in multiple blocks. End detection is the responsibility of the receiver. - /// - /// - Parameter buffer: A buffer where the data that was received is located. - /// - Returns: Return true to continue receiving, false to stop receiving. The connection will not be closed or released. - - func receiverData(_ buffer: UnsafeBufferPointer) -> Bool - - - /// The connection was unexpectedly closed. It is not sure that the connection has been properly closed or deallocated. - /// - /// Probably by the other side or because of a parralel operation on a different thread. - - func receiverClosed() - - - /// Since the last data transfer (or start of operation) a timeinterval as specified in "ReceiverLoopDuration" has elapsed without any activity. - /// - /// - Returns: Return true to continue receiving, false to stop receiving. The connection will not be closed or released. - - func receiverLoop() -> Bool -} - - /// This function loops and calls out to the ReceiverProtocol data (if present) for received data and interface events. The loop does not terminate until a ReceiverProtocol method returns a status indicating termination, or an error occured. /// /// - Parameters: diff --git a/Sources/SwifterSockets/SwifterSockets.Server.swift b/Sources/SwifterSockets/TipServer.swift similarity index 53% rename from Sources/SwifterSockets/SwifterSockets.Server.swift rename to Sources/SwifterSockets/TipServer.swift index 9054d3b..49d66d0 100644 --- a/Sources/SwifterSockets/SwifterSockets.Server.swift +++ b/Sources/SwifterSockets/TipServer.swift @@ -1,17 +1,16 @@ // ===================================================================================================================== // -// File: SwifterSockets.Server.swift +// File: TipServer.swift // Project: SwifterSockets // -// Version: 0.10.6 +// Version: 1.0.0 // // Author: Marinus van der Lugt // Company: http://balancingrock.nl -// Website: http://swiftfire.nl/pages/projects/swiftersockets/ -// Blog: http://swiftrien.blogspot.com -// Git: https://github.com/Swiftrien/SwifterSockets +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire // -// Copyright: (c) 2014-2017 Marinus van der Lugt, All rights reserved. +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. // // License: Use or redistribute this code any way you like with the following two provision: // @@ -22,24 +21,13 @@ // // I also ask you to please leave this header with the source code. // -// I strongly believe that voluntarism is the way for societies to function optimally. Thus I have choosen to leave it -// up to you to determine the price for this code. You pay me whatever you think this code is worth to you. +// Like you, I need to make a living: // -// - You can send payment via paypal to: sales@balancingrock.nl +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl // - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH // -// I prefer the above two, but if these options don't suit you, you might also send me a gift from my amazon.co.uk -// wishlist: http://www.amazon.co.uk/gp/registry/wishlist/34GNMPZKAQ0OO/ref=cm_sw_em_r_wsl_cE3Tub013CKN6_wb -// // If you like to pay in another way, please contact me at rien@balancingrock.nl // -// (It is always a good idea to visit the website/blog/google to ensure that you actually pay me and not some imposter) -// -// For private and non-profit use the suggested price is the price of 1 good cup of coffee, say $4. -// For commercial use the suggested price is the price of 1 good meal, say $20. -// -// You are however encouraged to pay more ;-) -// // Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl // // ===================================================================================================================== @@ -48,201 +36,12 @@ // // History // -// 0.10.6 - Changes captured reference in accept loop from unowned to weak -// 0.10.2 - Added BRUtils for the Result type -// 0.9.14 - Moved server protocol to this file -// 0.9.13 - General overhaul of public/private access. -// - Comment section update -// 0.9.12 - Documentation updated to accomodate the documentation tool 'jazzy' -// 0.9.11 - Comment change -// 0.9.9 - Updated access control -// 0.9.8 - Redesign of SwifterSockets to support HTTPS connections. -// 0.9.7 - Upgraded to Xcode 8 beta 6 -// 0.9.6 - Upgraded to Xcode 8 beta 3 Swift 3) -// 0.9.4 - Header update -// 0.9.3 - Adding Carthage support: Changed target to Framework, added public declarations, removed SwifterLog. -// 0.9.2 - Added support for logUnixSocketCalls -// - Moved closing of sockets to SwifterSockets.closeSocket -// - Upgraded to Swift 2.2 -// 0.9.1 - No changes -// 0.9.0 - Initial release +// 1.0.0 - Removed older history // ===================================================================================================================== - import Foundation -import BRUtils - - -/// Control methods for a server. - -public protocol ServerProtocol { - - - /// Starts the server. - /// - /// - Returns: Either .success(true), or .error(message: String) with the message detailing the kind of error that occured. - - func start() -> Result - - - /// Stops the server. - /// - /// - Note: There are delays involved, the accept loop may still accept new requests until it loops around. Requests being processed will be allowed to continue normally. - - func stop() -} - -/// Sets up a socket for listening on the specified service port number. It will listen on all available IP addresses of the server, either in IPv4 or IPv6. -/// -/// - Parameters: -/// - port: A string containing the number of the port to listen on. -/// - maxPendingConnectionRequest: The number of connections that can be kept pending before they are accepted. A connection request can be put into a queue before it is accepted or rejected. This argument specifies the size of the queue. If the queue is full further connection requests will be rejected. -/// -/// - Returns: Either .success(socket: Int32) or .error(message: String). - -public func setupTipServer(onPort port: String, maxPendingConnectionRequest: Int32) -> Result { - - - // General purpose status variable, used to detect error returns from socket functions - - var status: Int32 = 0 - - - // ================================================================== - // Retrieve the information necessary to create the socket descriptor - // ================================================================== - - // Protocol configuration, used to retrieve the data needed to create the socket descriptor - - var hints = Darwin.addrinfo( - ai_flags: AI_PASSIVE, // Assign the address of the local host to the socket structures - ai_family: AF_UNSPEC, // Either IPv4 or IPv6 - ai_socktype: SOCK_STREAM, // TCP - ai_protocol: 0, - ai_addrlen: 0, - ai_canonname: nil, - ai_addr: nil, - ai_next: nil) - - - // For the information needed to create a socket (result from the getaddrinfo) - - var servinfo: UnsafeMutablePointer? = nil - - - // Get the info we need to create our socket descriptor - - status = Darwin.getaddrinfo( - nil, // Any interface - port, // The port on which will be listenend - &hints, // Protocol configuration as per above - &servinfo) // The created information - - - // Cop out if there is an error - - if status != 0 { - var strError: String - if status == EAI_SYSTEM { - strError = String(validatingUTF8: Darwin.strerror(Darwin.errno)) ?? "Unknown error code" - } else { - strError = String(validatingUTF8: Darwin.gai_strerror(status)) ?? "Unknown error code" - } - return .error(message: strError) - } - - - // ============================ - // Create the socket descriptor - // ============================ - - let socketDescriptor = Darwin.socket( - (servinfo?.pointee.ai_family)!, // Use the servinfo created earlier, this makes it IPv4/IPv6 independant - (servinfo?.pointee.ai_socktype)!, // Use the servinfo created earlier, this makes it IPv4/IPv6 independant - (servinfo?.pointee.ai_protocol)!) // Use the servinfo created earlier, this makes it IPv4/IPv6 independant - - - // Cop out if there is an error - - if socketDescriptor == -1 { - let strError = String(validatingUTF8: Darwin.strerror(Darwin.errno)) ?? "Unknown error code" - Darwin.freeaddrinfo(servinfo) - return .error(message: strError) - } - - - // ======================================================== - // Set the socket option: prevent the "socket in use" error - // ======================================================== - - var optval: Int = 1; // Use 1 to enable the option, 0 to disable - - status = Darwin.setsockopt( - socketDescriptor, // The socket descriptor of the socket on which the option will be set - SOL_SOCKET, // Type of socket options - SO_REUSEADDR, // The socket option id - &optval, // The socket option value - socklen_t(MemoryLayout.size)) // The size of the socket option value - - if status == -1 { - let strError = String(validatingUTF8: Darwin.strerror(Darwin.errno)) ?? "Unknown error code" - Darwin.freeaddrinfo(servinfo) - closeSocket(socketDescriptor) - return .error(message: strError) - } - - - // ==================================== - // Bind the socket descriptor to a port - // ==================================== - - status = Darwin.bind( - socketDescriptor, // The socket descriptor of the socket to bind - servinfo?.pointee.ai_addr, // Use the servinfo created earlier, this makes it IPv4/IPv6 independant - (servinfo?.pointee.ai_addrlen)!) // Use the servinfo created earlier, this makes it IPv4/IPv6 independant - - // Cop out if there is an error - - if status != 0 { - let strError = String(validatingUTF8: Darwin.strerror(Darwin.errno)) ?? "Unknown error code" - Darwin.freeaddrinfo(servinfo) - closeSocket(socketDescriptor) - return .error(message: strError) - } - - - // =============================== - // Don't need the servinfo anymore - // =============================== - - Darwin.freeaddrinfo(servinfo) - - - // ======================================== - // Start listening for incoming connections - // ======================================== - - status = Darwin.listen( - socketDescriptor, // The socket on which to listen - maxPendingConnectionRequest) // The number of connections that will be allowed before they are accepted - - - // Cop out if there are any errors - - if status != 0 { - let strError = String(validatingUTF8: Darwin.strerror(Darwin.errno)) ?? "Unknown error code" - closeSocket(socketDescriptor) - return .error(message: strError) - } - - - // ============================ - // Return the socket descriptor - // ============================ - - return .success(socketDescriptor) -} +import BRUtils /// This class implements a TCP/IP server. @@ -257,7 +56,7 @@ public class TipServer: ServerProtocol { public typealias AliveHandler = () -> () - /// Options with which the TipServer can be initialized. + /// Initialization options for a TipServer. public enum Option { diff --git a/Sources/SwifterSockets/SwifterSockets.Transmit.swift b/Sources/SwifterSockets/TipTransfer.swift similarity index 71% rename from Sources/SwifterSockets/SwifterSockets.Transmit.swift rename to Sources/SwifterSockets/TipTransfer.swift index a9b58d3..0c5dfb0 100644 --- a/Sources/SwifterSockets/SwifterSockets.Transmit.swift +++ b/Sources/SwifterSockets/TipTransfer.swift @@ -1,15 +1,14 @@ // ===================================================================================================================== // -// File: SwifterSockets.Transmit.swift +// File: TipTransfer.swift // Project: SwifterSockets // -// Version: 0.12.0 +// Version: 1.0.0 // // Author: Marinus van der Lugt // Company: http://balancingrock.nl -// Website: http://swiftfire.nl/pages/projects/swiftersockets/ -// Blog: http://swiftrien.blogspot.com -// Git: https://github.com/Balancingrock/SwifterSockets +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire // // Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. // @@ -22,19 +21,13 @@ // // I also ask you to please leave this header with the source code. // -// I strongly believe that voluntarism is the way for societies to function optimally. Thus I have choosen to leave it -// up to you to determine the price for this code. You pay me whatever you think this code is worth to you. +// Like you, I need to make a living: // -// - You can send payment via paypal to: sales@balancingrock.nl +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl // - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH // -// I prefer the above two, but if these options don't suit you, you might also send me a gift from my amazon.co.uk -// wishlist: http://www.amazon.co.uk/gp/registry/wishlist/34GNMPZKAQ0OO/ref=cm_sw_em_r_wsl_cE3Tub013CKN6_wb -// // If you like to pay in another way, please contact me at rien@balancingrock.nl // -// (It is always a good idea to visit the website/blog/google to ensure that you actually pay me and not some imposter) -// // Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl // // ===================================================================================================================== @@ -43,82 +36,13 @@ // // History // -// 0.12.0 - Replaced depreciated call in Swift 5 -// 0.10.6 - Added closing of the socket on transmitterClosed. -// 0.9.14 - Moved transmitter protocol to this file -// - Moved progress signature to this file -// - Added id to transmitter protocol methods -// - Added queued to transfer result -// - Bugfix: added breakout of transmit loop when the progress callback indicates that the transfer must be stopped. -// 0.9.13 - Comment section update -// 0.9.12 - Documentation updated to accomodate the documentation tool 'jazzy' -// 0.9.11 - Comment change -// 0.9.9 - Updated access control -// 0.9.8 - Redesign of SwifterSockets to support HTTPS connections. -// 0.9.7 - Upgraded to Xcode 8 beta 6 -// 0.9.6 - Upgraded to Xcode 8 beta 3 (Swift 3) -// 0.9.4 - Header update -// 0.9.3 - Changed target to Framework to support Carthage -// 0.9.2 - Added support for logUnixSocketCalls -// - Moved closing of sockets to SwifterSockets.closeSocket -// - Added note on buffer capture to transmitAsync:buffer -// - Upgraded to Swift 2.2 -// - Added SERVER_CLOSED and CLIENT_CLOSED as possible results for harmonization with SwifterSockets.Receive -// 0.9.1 - TransmitTelemetry now inherits from NSObject -// - Replaced (UnsafePointer, length) with UnsafeBufferPointer -// 0.9.0 - Initial release +// 1.0.0 - Removed older history // ===================================================================================================================== import Foundation -/// A collection of methods used by a transmit operation to inform the transmitter of the events occuring on the interface. - -public protocol TransmitterProtocol { - - - /// An error occured during transmission. - /// - /// The transmitter has stopped, but the connection has not been closed or released. - /// - /// - Parameters: - /// - id: An id that is associated with the transfer. Only usefull if the transfer is scheduled in a dispatch queue. - /// - message: A textual description of the error that occured. - - func transmitterError(_ id: Int, _ message: String) - - - /// A timeout occured during (or waiting for) transmission. - /// - /// The connection has not been closed or released. - /// - /// The data transfer is in an unknown state, i.e. it is uncertain how much data was transferred before this happenend. - /// - /// - Parameters: - /// - id: An id that is associated with the transfer. Only usefull if the transfer is scheduled in a dispatch queue. - - - func transmitterTimeout(_ id: Int) - - - /// The connection was unexpectedly closed. It is not sure that the connection has been properly closed or deallocated. - /// - /// Probably by the other side or because of a parralel operation on a different thread. - /// - /// - Parameters: - /// - id: An id that is associated with the transfer. Only usefull if the transfer is scheduled in a dispatch queue. - - func transmitterClosed(_ id: Int) - - - /// The transmission has successfully concluded. - /// - /// - Parameters: - /// - id: An id that is associated with the transfer. Only usefull if the transfer is scheduled in a dispatch queue. - - func transmitterReady(_ id: Int) -} /// Signature of a closure that can be used as a progress indicator for lengthy transfers. It can also be used to abort a transfer. diff --git a/Sources/SwifterSockets/TransmitterProtocol.swift b/Sources/SwifterSockets/TransmitterProtocol.swift new file mode 100644 index 0000000..d6e88a1 --- /dev/null +++ b/Sources/SwifterSockets/TransmitterProtocol.swift @@ -0,0 +1,90 @@ +// ===================================================================================================================== +// +// File: TransmitterProtocol.swift +// Project: SwifterSockets +// +// Version: 1.0.0 +// +// Author: Marinus van der Lugt +// Company: http://balancingrock.nl +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire +// +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. +// +// License: Use or redistribute this code any way you like with the following two provision: +// +// 1) You ACCEPT this source code AS IS without any guarantees that it will work as intended. Any liability from its +// use is YOURS. +// +// 2) You WILL NOT seek damages from the author or balancingrock.nl. +// +// I also ask you to please leave this header with the source code. +// +// Like you, I need to make a living: +// +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl +// - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH +// +// If you like to pay in another way, please contact me at rien@balancingrock.nl +// +// Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl +// +// ===================================================================================================================== +// PLEASE let me know about bugs, improvements and feature requests. (rien@balancingrock.nl) +// ===================================================================================================================== +// +// History +// +// 1.0.0 - Removed older history +// ===================================================================================================================== + +import Foundation + + +/// A collection of methods used by a transmit operation to inform the transmitter of the events occuring on the interface. + +public protocol TransmitterProtocol { + + + /// An error occured during transmission. + /// + /// The transmitter has stopped, but the connection has not been closed or released. + /// + /// - Parameters: + /// - id: An id that is associated with the transfer. Only usefull if the transfer is scheduled in a dispatch queue. + /// - message: A textual description of the error that occured. + + func transmitterError(_ id: Int, _ message: String) + + + /// A timeout occured during (or waiting for) transmission. + /// + /// The connection has not been closed or released. + /// + /// The data transfer is in an unknown state, i.e. it is uncertain how much data was transferred before this happenend. + /// + /// - Parameters: + /// - id: An id that is associated with the transfer. Only usefull if the transfer is scheduled in a dispatch queue. + + + func transmitterTimeout(_ id: Int) + + + /// The connection was unexpectedly closed. It is not sure that the connection has been properly closed or deallocated. + /// + /// Probably by the other side or because of a parralel operation on a different thread. + /// + /// - Parameters: + /// - id: An id that is associated with the transfer. Only usefull if the transfer is scheduled in a dispatch queue. + + func transmitterClosed(_ id: Int) + + + /// The transmission has successfully concluded. + /// + /// - Parameters: + /// - id: An id that is associated with the transfer. Only usefull if the transfer is scheduled in a dispatch queue. + + func transmitterReady(_ id: Int) +} diff --git a/Sources/SwifterSockets/WaitForSelect.swift b/Sources/SwifterSockets/WaitForSelect.swift new file mode 100644 index 0000000..caf3b5b --- /dev/null +++ b/Sources/SwifterSockets/WaitForSelect.swift @@ -0,0 +1,152 @@ +// ===================================================================================================================== +// +// File: WaitForSelect.swift +// Project: SwifterSockets +// +// Version: 1.0.0 +// +// Author: Marinus van der Lugt +// Company: http://balancingrock.nl +// Website: http://swiftfire.nl/ +// Git: https://github.com/Balancingrock/Swiftfire +// +// Copyright: (c) 2014-2019 Marinus van der Lugt, All rights reserved. +// +// License: Use or redistribute this code any way you like with the following two provision: +// +// 1) You ACCEPT this source code AS IS without any guarantees that it will work as intended. Any liability from its +// use is YOURS. +// +// 2) You WILL NOT seek damages from the author or balancingrock.nl. +// +// I also ask you to please leave this header with the source code. +// +// Like you, I need to make a living: +// +// - You can send payment (you choose the amount) via paypal to: sales@balancingrock.nl +// - Or wire bitcoins to: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH +// +// If you like to pay in another way, please contact me at rien@balancingrock.nl +// +// Prices/Quotes for support, modifications or enhancements can be obtained from: rien@balancingrock.nl +// +// ===================================================================================================================== +// PLEASE let me know about bugs, improvements and feature requests. (rien@balancingrock.nl) +// ===================================================================================================================== +// +// History +// +// 1.0.0 - Removed older history +// ===================================================================================================================== + +import Foundation + + +/// Return values of the _waitForSelect_ function. + +public enum SelectResult { + + + /// The event has occured. + + case ready + + + /// Nothing happened within the timeout period. + + case timeout + + + /// An error occured. + /// + /// - Parameter message: A textual description of the error that occured. + + case error(message: String) + + + /// Either the remote or a parralel thread closed the connection unexpectedly. + + case closed +} + + +/// Wait until the POSIX select call returns for the requested event(s). If no event occurs within the timeout period, the .timeout value is returned. +/// +/// - Parameter socket: The socket on which something must happen. +/// - Parameter timeout: The time until the select call waits for an event +/// - Parameter forRead: Wait for a read event. +/// - Parameter forWrite: Wait for a write event. +/// +/// - Returns: A SelectResult value. + +public func waitForSelect(socket: Int32, timeout: Date, forRead: Bool, forWrite: Bool) -> SelectResult { + + + // ============================================= + // Check timout interval and calculate remainder + // ============================================= + + let availableTime = timeout.timeIntervalSinceNow + + if availableTime < 0.0 { + return .timeout + } + + let availableSeconds = Int(availableTime) + let availableUSeconds = Int32((availableTime - Double(availableSeconds)) * 1_000_000.0) + var availableTimeval = timeval(tv_sec: availableSeconds, tv_usec: availableUSeconds) + + + // ====================================================================================================== + // Use the select API to wait for anything to happen on our client socket only within the timeout period. + // ====================================================================================================== + + // Note: Since SSL may require a handshake it is necessary to check for both read & write activity. + + let numOfFd:Int32 = socket + 1 + var readSet:fd_set = fd_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)) + var writeSet:fd_set = fd_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)) + + if forRead { fdSet(socket, set: &readSet) } + if forWrite { fdSet(socket, set: &writeSet) } + let status = Darwin.select(numOfFd, &readSet, &writeSet, nil, &availableTimeval) + + // Because we only specified 1 FD, we do not need to check on which FD the event was received + + + // ========================= + // Exit in case of a timeout + // ========================= + + if status == 0 { + return .timeout + } + + + // ======================== + // Exit in case of an error + // ======================== + + if status == -1 { + + switch errno { + + case EBADF: + // Case 1: In a multi-threaded environment it can happen that one thread closes a socket while another thread is waiting for data on the same socket. + // In that case this is not really an error, but simply a signal that the receiving thread should be terminated. + // Case 2: Of course it could also happen that the programmer made a mistake and is using a socket that is not initialized. + // The first case is more important, so as to avoid uneccesary error messages we return the CLOSED result case. + // If the programmer made an error, it is presumed that this error will become appearant in other ways (during testing!). + return .closed + + case EINVAL, EAGAIN, EINTR: fallthrough // These are the other possible error's + + default: // Catch-all to satisfy the compiler + let errstr = String(validatingUTF8: strerror(errno)) ?? "Unknown error code" + return .error(message: errstr) + + } + } + + return .ready +}