From 0f7363eb0470b4bcd94ef767ad661d6aaa167a1d Mon Sep 17 00:00:00 2001 From: "Dr. Brandon Wiley" Date: Mon, 31 Aug 2020 22:03:58 -0500 Subject: [PATCH] Initial import --- Package.swift | 10 +- Sources/Transmission/Connection.swift | 118 ++++++++++++++++++ Sources/Transmission/Listener.swift | 42 +++++++ Sources/Transmission/Transmission.swift | 3 - .../TransmissionTests/TransmissionTests.swift | 50 ++++++-- 5 files changed, 208 insertions(+), 15 deletions(-) create mode 100644 Sources/Transmission/Connection.swift create mode 100644 Sources/Transmission/Listener.swift delete mode 100644 Sources/Transmission/Transmission.swift diff --git a/Package.swift b/Package.swift index 2a28ae4..93590ed 100644 --- a/Package.swift +++ b/Package.swift @@ -5,6 +5,7 @@ import PackageDescription let package = Package( name: "Transmission", + platforms: [.macOS(.v10_15)], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. .library( @@ -14,15 +15,18 @@ 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.5"), + .package(url: "https://github.com/OperatorFoundation/Datable", from: "3.0.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: "Transmission", - dependencies: []), + dependencies: ["Chord"]), .testTarget( name: "TransmissionTests", - dependencies: ["Transmission"]), - ] + dependencies: ["Transmission", "Datable"]), + ], + swiftLanguageVersions: [.v5] ) diff --git a/Sources/Transmission/Connection.swift b/Sources/Transmission/Connection.swift new file mode 100644 index 0000000..8974c93 --- /dev/null +++ b/Sources/Transmission/Connection.swift @@ -0,0 +1,118 @@ +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) + { + let nwhost = NWEndpoint.Host(host) + + let port16 = UInt16(port) + let nwport = NWEndpoint.Port(integerLiteral: port16) + + let nwconnection = NWConnection(host: nwhost, port: nwport, using: .tcp) + + 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.connectLock.leave() + return + case .failed(_): + self.connectLock.leave() + return + default: + return + } + } + + self.connection.start(queue: .global()) + + connectLock.wait() + + guard success else {return nil} + } + + public func read(size: Int) -> Data? + { + 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 write(string: String) -> Bool + { + let data = string.data + return write(data: data) + } + + public func write(data: Data) -> Bool + { + 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 + } +} diff --git a/Sources/Transmission/Listener.swift b/Sources/Transmission/Listener.swift new file mode 100644 index 0000000..a0c6986 --- /dev/null +++ b/Sources/Transmission/Listener.swift @@ -0,0 +1,42 @@ +// +// 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) + { + let port16 = UInt16(port) + let nwport = NWEndpoint.Port(integerLiteral: port16) + + guard let listener = try? NWListener(using: .tcp, 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/Transmission/Transmission.swift b/Sources/Transmission/Transmission.swift deleted file mode 100644 index 4b28ae3..0000000 --- a/Sources/Transmission/Transmission.swift +++ /dev/null @@ -1,3 +0,0 @@ -struct Transmission { - var text = "Hello, World!" -} diff --git a/Tests/TransmissionTests/TransmissionTests.swift b/Tests/TransmissionTests/TransmissionTests.swift index 640f642..9630896 100644 --- a/Tests/TransmissionTests/TransmissionTests.swift +++ b/Tests/TransmissionTests/TransmissionTests.swift @@ -1,15 +1,47 @@ import XCTest +import Foundation @testable import Transmission -final class TransmissionTests: XCTestCase { - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - XCTAssertEqual(Transmission().text, "Hello, World!") +final class TransmissionTests: XCTestCase +{ + public func testConnection() + { + let lock = DispatchGroup() + let queue = DispatchQueue(label: "testing") + + lock.enter() + + queue.async + { + self.runServer(lock) + } + + lock.wait() + + runClient() } + + func runServer(_ lock: DispatchGroup) + { + guard let listener = Listener(port: 1234) else {return} + lock.leave() - static var allTests = [ - ("testExample", testExample), - ] + let connection = listener.accept() + let _ = connection.read(size: 4) + let _ = connection.write(string: "back") + } + + func runClient() + { + let connection = Connection(host: "127.0.0.1", port: 1234) + XCTAssertNotNil(connection) + + let writeResult = connection!.write(string: "test") + XCTAssertTrue(writeResult) + + let result = connection!.read(size: 4) + XCTAssertNotNil(result) + + XCTAssertEqual(result!, "back") + } }