diff --git a/Package.resolved b/Package.resolved index b71ed5f..c90130a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/OperatorFoundation/Chord", "state": { "branch": null, - "revision": "25e5267c7dde0df1f7bcc285979f6302d46f7101", - "version": "0.0.12" + "revision": "94ef383494fb4c1b8b0e0e368df6f35265ff62d1", + "version": "0.0.14" } }, { @@ -24,8 +24,26 @@ "repositoryURL": "https://github.com/OperatorFoundation/Datable", "state": { "branch": null, - "revision": "31b055da961f794f1a2ce01b5521ee650815a9c9", - "version": "3.0.6" + "revision": "f935c18f2527f1b3cb6cad2511758b54effac5b1", + "version": "3.1.2" + } + }, + { + "package": "Net", + "repositoryURL": "https://github.com/OperatorFoundation/Net", + "state": { + "branch": null, + "revision": "57de753edfa076c8b6c2ce5d08bbd48e3cdc1d52", + "version": "0.0.1" + } + }, + { + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", + "state": { + "branch": null, + "revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7", + "version": "1.4.2" } }, { @@ -33,8 +51,8 @@ "repositoryURL": "https://github.com/OperatorFoundation/SwiftQueue", "state": { "branch": null, - "revision": "eaa39c586fddb49ad1e0e8b5b3e9b0aaf0b3be82", - "version": "0.1.0" + "revision": "5e6cd8b325493f520a45d20d788efca707b53e6c", + "version": "0.1.2" } }, { @@ -42,8 +60,8 @@ "repositoryURL": "https://github.com/OperatorFoundation/Transport", "state": { "branch": null, - "revision": "91dca4e691a9d2a6d38acd9b6dd64a1240da85f1", - "version": "2.3.6" + "revision": "a95c7bd870c558a040f9a22a88badab5577bd4df", + "version": "2.3.8" } } ] diff --git a/Package.swift b/Package.swift index a3e3fe8..3e5aad4 100644 --- a/Package.swift +++ b/Package.swift @@ -1,11 +1,11 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.5 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "TransmissionLinux", - platforms: [.macOS(.v11), .iOS(.v13)], + platforms: [.macOS(.v10_15)], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. .library( @@ -15,17 +15,23 @@ let package = Package( dependencies: [ // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), - .package(url: "https://github.com/OperatorFoundation/Chord", from: "0.0.12"), - .package(url: "https://github.com/OperatorFoundation/Datable", from: "3.0.6"), + .package(url: "https://github.com/OperatorFoundation/Chord", from: "0.0.15"), + .package(url: "https://github.com/OperatorFoundation/Datable", from: "3.1.2"), .package(name: "Socket", url: "https://github.com/OperatorFoundation/BlueSocket", from: "1.1.0"), - .package(url: "https://github.com/OperatorFoundation/Transport", from: "2.3.6"), + .package(url: "https://github.com/OperatorFoundation/Net", from: "0.0.1"), + .package(url: "https://github.com/OperatorFoundation/Transport", from: "2.3.9"), + .package(url: "https://github.com/apple/swift-log.git", from: "1.4.2") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages which this package depends on. .target( name: "TransmissionLinux", - dependencies: ["Chord", "Socket", "Datable", "Transport"]), + dependencies: [ + "Chord", "Socket", "Datable", "Transport", "Net", + .product(name: "Logging", package: "swift-log") + ] + ), .testTarget( name: "TransmissionLinuxTests", dependencies: ["TransmissionLinux", "Datable"]), diff --git a/Sources/Transmission/Connection.swift b/Sources/Transmission/Connection.swift deleted file mode 100644 index 4f90e1b..0000000 --- a/Sources/Transmission/Connection.swift +++ /dev/null @@ -1,172 +0,0 @@ -import Foundation -import Network -import Datable - -public class Connection -{ - var connection: NWConnection - var connectLock = DispatchGroup() - var readLock = DispatchGroup() - var writeLock = DispatchGroup() - - public convenience init?(host: String, port: Int, type: ConnectionType = .tcp) - { - let nwhost = NWEndpoint.Host(host) - - let port16 = UInt16(port) - let nwport = NWEndpoint.Port(integerLiteral: port16) - - switch type - { - case .tcp: - let nwconnection = NWConnection(host: nwhost, port: nwport, using: .tcp) - self.init(connection: nwconnection) - case .udp: - let nwconnection = NWConnection(host: nwhost, port: nwport, using: .udp) - self.init(connection: nwconnection) - } - } - - init?(connection: NWConnection) - { - self.connection = connection - - var success = false - - self.connectLock.enter() - self.connection.stateUpdateHandler = - { - (state) in - - switch state - { - case .ready: - success = true - self.connectLock.leave() - return - case .cancelled: - self.failConnect() - return - case .failed(_): - self.failConnect() - return - case .waiting(_): - self.failConnect() - return - default: - return - } - } - - self.connection.start(queue: .global()) - - connectLock.wait() - - guard success else {return nil} - } - - func failConnect() - { - self.connection.stateUpdateHandler = nil - self.connection.cancel() - self.connectLock.leave() - } - - public func read(size: Int) -> Data? - { - print("TransmissionLinux read called: \(#file), \(#line)") - var result: Data? - - self.readLock.enter() - self.connection.receive(minimumIncompleteLength: size, maximumLength: size) - { - (maybeData, maybeContext, isComplete, maybeError) in - - guard maybeError == nil else - { - self.readLock.leave() - return - } - - if let data = maybeData - { - result = data - } - - self.readLock.leave() - } - - readLock.wait() - - return result - } - - public func read(maxSize: Int) -> Data? - { - print("TransmissionLinux read called: \(#file), \(#line)") - var result: Data? - - self.readLock.enter() - self.connection.receive(minimumIncompleteLength: 1, maximumLength: maxSize) - { - (maybeData, maybeContext, isComplete, maybeError) in - - guard maybeError == nil else - { - self.readLock.leave() - return - } - - if let data = maybeData - { - result = data - } - - self.readLock.leave() - } - - readLock.wait() - - return result - } - - public func write(string: String) -> Bool - { - print("TransmissionLinux write called: \(#file), \(#line)") - let data = string.data - return write(data: data) - } - - public func write(data: Data) -> Bool - { - print("TransmissionLinux write called: \(#file), \(#line)") - var success = false - - self.writeLock.enter() - self.connection.send(content: data, completion: NWConnection.SendCompletion.contentProcessed( - { - (maybeError) in - - guard maybeError == nil else - { - success = false - self.writeLock.leave() - return - } - - success = true - self.writeLock.leave() - return - })) - - self.writeLock.wait() - - return success - } -} - -public enum ConnectionType -{ - case udp - case tcp -} diff --git a/Sources/Transmission/Listener.swift b/Sources/Transmission/Listener.swift deleted file mode 100644 index e005542..0000000 --- a/Sources/Transmission/Listener.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Listener.swift -// -// -// Created by Dr. Brandon Wiley on 8/31/20. -// - -import Foundation -import Network -import Chord - -public class Listener -{ - let listener: NWListener - let queue: BlockingQueue = BlockingQueue() - let lock: DispatchGroup = DispatchGroup() - - public init?(port: Int, type: ConnectionType = .tcp) - { - let port16 = UInt16(port) - let nwport = NWEndpoint.Port(integerLiteral: port16) - - var params: NWParameters! - switch type - { - case .tcp: - params = NWParameters.tcp - case .udp: - params = NWParameters.udp - } - - guard let listener = try? NWListener(using: params, on: nwport) else {return nil} - self.listener = listener - - self.listener.newConnectionHandler = - { - nwconnection in - - guard let connection = Connection(connection: nwconnection) else {return} - - self.queue.enqueue(element: connection) - } - - self.listener.start(queue: .global()) - } - - public func accept() -> Connection - { - return self.queue.dequeue() - } -} diff --git a/Sources/TransmissionLinux/Connection.swift b/Sources/TransmissionLinux/Connection.swift index 68adbd7..bcec568 100644 --- a/Sources/TransmissionLinux/Connection.swift +++ b/Sources/TransmissionLinux/Connection.swift @@ -1,12 +1,28 @@ import Foundation -import Socket +import Net import Datable -import Chord +import Transport +import Logging public protocol Connection { + // Reads exactly size bytes func read(size: Int) -> Data? + + // reads up to maxSize bytes + func read(maxSize: Int) -> Data? + + func readWithLengthPrefix(prefixSizeInBits: Int) -> Data? + func write(string: String) -> Bool + func write(data: Data) -> Bool - func identifier() -> Int + + func writeWithLengthPrefix(data: Data, prefixSizeInBits: Int) -> Bool +} + +public enum ConnectionType +{ + case udp + case tcp } diff --git a/Sources/TransmissionLinux/Listener.swift b/Sources/TransmissionLinux/Listener.swift index f58b2ec..e128f52 100644 --- a/Sources/TransmissionLinux/Listener.swift +++ b/Sources/TransmissionLinux/Listener.swift @@ -1,36 +1,14 @@ // // Listener.swift -// +// // // Created by Dr. Brandon Wiley on 8/31/20. // import Foundation -import Socket -import Chord +import Logging -public class Listener +public protocol Listener { - var socket: Socket - - public init?(port: Int) - { - guard let socket = try? Socket.create() else {return nil} - self.socket = socket - - do - { - try socket.listen(on: port) - } - catch - { - return nil - } - } - - public func accept() -> Connection? - { - guard let newConnection = try? self.socket.acceptClientConnection(invokeDelegate: false) else {return nil} - return SocketConnection(socket: newConnection) - } + func accept() -> Connection } diff --git a/Sources/TransmissionLinux/SocketConnection.swift b/Sources/TransmissionLinux/SocketConnection.swift deleted file mode 100644 index ebc447f..0000000 --- a/Sources/TransmissionLinux/SocketConnection.swift +++ /dev/null @@ -1,102 +0,0 @@ -import Foundation -import Socket -import Datable -import Chord - -public class SocketConnection: Connection -{ - var connectLock = DispatchGroup() - var readLock = DispatchGroup() - var writeLock = DispatchGroup() - - let socket: Socket - var buffer: Data = Data() - - public init?(host: String, port: Int) - { - guard let socket = try? Socket.create() else {return nil} - self.socket = socket - - do - { - try self.socket.connect(to: host, port: Int32(port)) - } - catch - { - return nil - } - } - - public init(socket: Socket) - { - self.socket = socket - } - - public func read(size: Int) -> Data? - { - print("TransmissionLinux read called: \(#file), \(#line)") - if size == 0 - { - return nil - } - - if size <= buffer.count - { - let result = Data(buffer[0.. Bool - { - print("TransmissionLinux write called: \(#file), \(#line)") - let data = string.data - return write(data: data) - } - - public func write(data: Data) -> Bool - { - print("TransmissionLinux write called: \(#file), \(#line)") - do - { - try self.socket.write(from: data) - } - catch - { - return false - } - - return true - } - - public func identifier() -> Int { - return Int(self.socket.socketfd) - } -} - -public enum ConnectionType -{ - case udp - case tcp -} diff --git a/Sources/TransmissionLinux/TransmissionConnection.swift b/Sources/TransmissionLinux/TransmissionConnection.swift new file mode 100644 index 0000000..6377b2a --- /dev/null +++ b/Sources/TransmissionLinux/TransmissionConnection.swift @@ -0,0 +1,406 @@ +import Foundation +import Socket +import Datable +import Chord + +public class TransmissionConnection: Connection +{ + var connectLock = DispatchGroup() + var readLock = DispatchGroup() + var writeLock = DispatchGroup() + + let socket: Socket + var buffer: Data = Data() + + public init?(host: String, port: Int) + { + guard let socket = try? Socket.create() else {return nil} + self.socket = socket + + do + { + try self.socket.connect(to: host, port: Int32(port)) + } + catch + { + return nil + } + } + + public init(socket: Socket) + { + self.socket = socket + } + + public func read(size: Int) -> Data? + { + print("TransmissionLinux read called: \(#file), \(#line)") + readLock.enter() + + if size == 0 + { + readLock.leave() + return nil + } + + if size <= buffer.count + { + let result = Data(buffer[0.. Data? + { + print("TransmissionLinux read called: \(#file), \(#line)") + readLock.enter() + + if maxSize == 0 + { + readLock.leave() + return nil + } + + let size = maxSize <= buffer.count ? maxSize : buffer.count + + if size > 0 + { + let result = Data(buffer[0.. Bool + { + print("TransmissionLinux write called: \(#file), \(#line)") + + writeLock.enter() + + let data = string.data + + writeLock.leave() + return write(data: data) + } + + public func write(data: Data) -> Bool + { + print("TransmissionLinux write called: \(#file), \(#line)") + writeLock.enter() + + do + { + try self.socket.write(from: data) + } + catch + { + writeLock.leave() + return false + } + + writeLock.leave() + return true + } + + public func readWithLengthPrefix(prefixSizeInBits: Int) -> Data? + { + readLock.enter() + + var maybeLength: Int? = nil + + switch prefixSizeInBits + { + case 8: + var lengthData = Data(repeating: 0, count: 1) + + do + { + let readSize = try self.socket.read(into: &lengthData) + + guard readSize == 1 else + { + readLock.leave() + return nil + } + + guard let length8 = UInt8(maybeNetworkData: lengthData) else + { + readLock.leave() + return nil + } + + maybeLength = Int(length8) + } + catch + { + readLock.leave() + return nil + } + case 16: + var lengthData = Data(repeating: 0, count: 2) + + do + { + let readSize = try self.socket.read(into: &lengthData) + + guard readSize == 2 else + { + readLock.leave() + return nil + } + + guard let length16 = UInt16(maybeNetworkData: lengthData) else + { + readLock.leave() + return nil + } + + maybeLength = Int(length16) + } + catch + { + readLock.leave() + return nil + } + case 32: + var lengthData = Data(repeating: 0, count: 4) + + do + { + let readSize = try self.socket.read(into: &lengthData) + + guard readSize == 4 else + { + readLock.leave() + return nil + } + + guard let length32 = UInt32(maybeNetworkData: lengthData) else + { + readLock.leave() + return nil + } + + maybeLength = Int(length32) + } + catch + { + readLock.leave() + return nil + } + case 64: + var lengthData = Data(repeating: 0, count: 8) + + do + { + let readSize = try self.socket.read(into: &lengthData) + + guard readSize == 8 else + { + readLock.leave() + return nil + } + + guard let length64 = UInt64(maybeNetworkData: lengthData) else + { + readLock.leave() + return nil + } + + maybeLength = Int(length64) + } + catch + { + readLock.leave() + return nil + } + default: + readLock.leave() + return nil + } + + guard let length = maybeLength else + { + readLock.leave() + return nil + } + + var data = Data(repeating: 0, count: length) + + do + { + let readSize = try self.socket.read(into: &data) + + guard readSize == length else + { + readLock.leave() + return nil + } + + return data + } + catch + { + readLock.leave() + return nil + } + } + + public func writeWithLengthPrefix(data: Data, prefixSizeInBits: Int) -> Bool + { + writeLock.enter() + + let length = data.count + + switch prefixSizeInBits + { + case 8: + let length8 = UInt8(length) + + guard let lengthData = length8.maybeNetworkData else + { + writeLock.leave() + return false + } + + do + { + try self.socket.write(from: lengthData) + try self.socket.write(from: data) + + writeLock.leave() + return true + } + catch + { + writeLock.leave() + return false + } + + case 16: + let length16 = UInt16(length) + + guard let lengthData = length16.maybeNetworkData else + { + writeLock.leave() + return false + } + + do + { + try self.socket.write(from: lengthData) + try self.socket.write(from: data) + + writeLock.leave() + return true + } + catch + { + writeLock.leave() + return false + } + + case 32: + let length32 = UInt32(length) + + guard let lengthData = length32.maybeNetworkData else + { + writeLock.leave() + return false + } + + do + { + try self.socket.write(from: lengthData) + try self.socket.write(from: data) + + writeLock.leave() + return true + } + catch + { + writeLock.leave() + return false + } + + case 64: + let length64 = UInt8(length) + + guard let lengthData = length64.maybeNetworkData else + { + writeLock.leave() + return false + } + + do + { + try self.socket.write(from: lengthData) + try self.socket.write(from: data) + + writeLock.leave() + return true + } + catch + { + writeLock.leave() + return false + } + + default: + writeLock.leave() + return false + } + } + + public func identifier() -> Int { + return Int(self.socket.socketfd) + } +} diff --git a/Sources/TransmissionLinux/TransmissionListener.swift b/Sources/TransmissionLinux/TransmissionListener.swift new file mode 100644 index 0000000..2c952d2 --- /dev/null +++ b/Sources/TransmissionLinux/TransmissionListener.swift @@ -0,0 +1,41 @@ +// +// Listener.swift +// +// +// Created by Dr. Brandon Wiley on 8/31/20. +// + +import Foundation +import Socket +import Chord + +public class TransmissionListener: Listener +{ + var socket: Socket + + public init?(port: Int) + { + guard let socket = try? Socket.create() else {return nil} + self.socket = socket + + do + { + try socket.listen(on: port) + } + catch + { + return nil + } + } + + public func accept() -> Connection + { + while true + { + if let newConnection = try? self.socket.acceptClientConnection(invokeDelegate: false) + { + return TransmissionConnection(socket: newConnection) + } + } + } +} diff --git a/Tests/TransmissionLinuxTests/TransmissionLinuxTests.swift b/Tests/TransmissionLinuxTests/TransmissionLinuxTests.swift index 3d26096..f002384 100644 --- a/Tests/TransmissionLinuxTests/TransmissionLinuxTests.swift +++ b/Tests/TransmissionLinuxTests/TransmissionLinuxTests.swift @@ -23,10 +23,10 @@ final class TransmissionTests: XCTestCase func runServer(_ lock: DispatchGroup) { - guard let listener = Listener(port: 1234) else {return} + guard let listener = TransmissionListener(port: 1234) else {return} lock.leave() - guard let connection = listener.accept() else {return} + let connection = listener.accept() let _ = connection.read(size: 4) let _ = connection.write(string: "back") }