From 271d965c62d84cc0bd7ae19c0fa10cc9b1a95c61 Mon Sep 17 00:00:00 2001 From: consuelita Date: Sun, 18 Feb 2024 20:44:17 -0600 Subject: [PATCH 01/28] Update Package.swift --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 5448945..37d2f5e 100644 --- a/Package.swift +++ b/Package.swift @@ -11,7 +11,7 @@ let package = Package( ], products: [.library(name: "ReplicantSwift", targets: ["ReplicantSwift"])], dependencies: [ - .package(url: "https://github.com/apple/swift-crypto", from: "2.5.0"), + .package(url: "https://github.com/apple/swift-crypto", from: "3.2.0"), .package(url: "https://github.com/apple/swift-log", from: "1.5.3"), .package(url: "https://github.com/OperatorFoundation/Datable", branch: "main"), From 6e53ed0de5545cc7ba0f34be8343581c3682146a Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 5 Mar 2024 13:24:34 -0600 Subject: [PATCH 02/28] Open Starburst Class --- Sources/ReplicantSwift/{Async => Polish}/PolishAsync.swift | 0 .../ReplicantSwift/{Async => Polish}/PolishClientAsync.swift | 0 .../ReplicantSwift/{Async => Polish}/PolishServerAsync.swift | 0 .../{Async => ToneBurst/Starburst}/StarburstAsync.swift | 2 +- .../ReplicantSwift/{Async => ToneBurst}/ToneburstAsync.swift | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename Sources/ReplicantSwift/{Async => Polish}/PolishAsync.swift (100%) rename Sources/ReplicantSwift/{Async => Polish}/PolishClientAsync.swift (100%) rename Sources/ReplicantSwift/{Async => Polish}/PolishServerAsync.swift (100%) rename Sources/ReplicantSwift/{Async => ToneBurst/Starburst}/StarburstAsync.swift (99%) rename Sources/ReplicantSwift/{Async => ToneBurst}/ToneburstAsync.swift (100%) diff --git a/Sources/ReplicantSwift/Async/PolishAsync.swift b/Sources/ReplicantSwift/Polish/PolishAsync.swift similarity index 100% rename from Sources/ReplicantSwift/Async/PolishAsync.swift rename to Sources/ReplicantSwift/Polish/PolishAsync.swift diff --git a/Sources/ReplicantSwift/Async/PolishClientAsync.swift b/Sources/ReplicantSwift/Polish/PolishClientAsync.swift similarity index 100% rename from Sources/ReplicantSwift/Async/PolishClientAsync.swift rename to Sources/ReplicantSwift/Polish/PolishClientAsync.swift diff --git a/Sources/ReplicantSwift/Async/PolishServerAsync.swift b/Sources/ReplicantSwift/Polish/PolishServerAsync.swift similarity index 100% rename from Sources/ReplicantSwift/Async/PolishServerAsync.swift rename to Sources/ReplicantSwift/Polish/PolishServerAsync.swift diff --git a/Sources/ReplicantSwift/Async/StarburstAsync.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift similarity index 99% rename from Sources/ReplicantSwift/Async/StarburstAsync.swift rename to Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift index f86ee06..841f5c8 100644 --- a/Sources/ReplicantSwift/Async/StarburstAsync.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift @@ -12,7 +12,7 @@ import Datable import Ghostwriter import TransmissionAsync -public class StarburstAsync: ToneBurstAsync, Codable +open class StarburstAsync: ToneBurstAsync, Codable { public var type: ToneBurstType = .starburst diff --git a/Sources/ReplicantSwift/Async/ToneburstAsync.swift b/Sources/ReplicantSwift/ToneBurst/ToneburstAsync.swift similarity index 100% rename from Sources/ReplicantSwift/Async/ToneburstAsync.swift rename to Sources/ReplicantSwift/ToneBurst/ToneburstAsync.swift From b6cd5a280581c93021dc827658d846b7e2cd19b1 Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 5 Mar 2024 13:30:10 -0600 Subject: [PATCH 03/28] Update StarburstAsync.swift --- Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift index 841f5c8..6726e6b 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift @@ -23,7 +23,7 @@ open class StarburstAsync: ToneBurstAsync, Codable self.mode = mode } - public func perform(connection: TransmissionAsync.AsyncConnection) async throws + open func perform(connection: TransmissionAsync.AsyncConnection) async throws { let instance = StarburstInstanceAsync(self.mode, connection) try await instance.perform() From 791a92359018341670b05f1b239963333813e7f7 Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 5 Mar 2024 14:13:20 -0600 Subject: [PATCH 04/28] Update ReplicantAsync.swift --- Sources/ReplicantSwift/Async/ReplicantAsync.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ReplicantSwift/Async/ReplicantAsync.swift b/Sources/ReplicantSwift/Async/ReplicantAsync.swift index 64114b7..5398e78 100644 --- a/Sources/ReplicantSwift/Async/ReplicantAsync.swift +++ b/Sources/ReplicantSwift/Async/ReplicantAsync.swift @@ -36,7 +36,7 @@ public class ReplicantAsync { var result: TransmissionAsync.AsyncConnection = connection - if let starBurst = config.toneBurst as? StarburstAsync + if var starBurst = config.toneBurst as? ToneBurstAsync { try await starBurst.perform(connection: connection) } From 78c0b7ba704b4b8e3cc94c09179d95785ddef372 Mon Sep 17 00:00:00 2001 From: CryptoSax Date: Mon, 11 Mar 2024 16:36:48 -0500 Subject: [PATCH 05/28] added structuredText speak and listen --- .../ToneBurst/Starburst/Starburst.swift | 48 ++++++++++++++ .../ToneBurst/Starburst/StarburstAsync.swift | 62 +++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift index 3bc060e..ff1eed1 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift @@ -149,6 +149,23 @@ public struct StarburstInstance throw StarburstError.writeFailed } } + + func speak(structuredText: StructuredText) throws + { + do + { + let string = structuredText.string + guard connection.write(string: string) else + { + throw StarburstError.writeFailed + } + } + catch + { + print(error) + throw StarburstError.writeFailed + } + } func listen(size: Int) throws -> Data { @@ -232,6 +249,37 @@ public struct StarburstInstance throw StarburstError.timeout } } + + func listen(structuredText: StructuredText, maxSize: Int = 255) -> Bool + { + var buffer = Data() + while buffer.count < maxSize + { + guard let byte = connection.read(size: 1) else + { + return false + } + + buffer.append(byte) + + guard let string = String(data: buffer, encoding: .utf8) else + { + // This could fail because we're in the middle of a UTF8 rune. + continue + } + + do + { + return try structuredText.match(string: string) + } + catch + { + continue + } + } + return true + } + func wait(seconds: Double) { diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift index 6726e6b..1ad4287 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift @@ -140,6 +140,20 @@ public struct StarburstInstanceAsync throw StarburstError.writeFailed } } + + func speak(structuredText: StructuredText) async throws + { + do + { + let string = structuredText.string + try await connection.writeString(string: string) + } + catch + { + print(error) + throw StarburstError.writeFailed + } + } func listen(size: Int) async throws -> Data { @@ -199,6 +213,54 @@ public struct StarburstInstanceAsync throw StarburstError.timeout } } + + func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> Bool + { + let listenTask: Task = Task { + var buffer = Data() + while buffer.count < maxSize + { + do { + let byte = try await connection.readSize(1) + + buffer.append(byte) + + guard let string = String(data: buffer, encoding: .utf8) else + { + // This could fail because we're in the middle of a UTF8 rune. + continue + } + + do + { + return try structuredText.match(string: string) + } + catch + { + continue + } + } catch { + return nil + } + } + + return nil + } + + let _ = Task { + try await Task.sleep(for: timeout) + listenTask.cancel() + } + + do { + guard let result = try await listenTask.value else { + throw StarburstError.readFailed + } + return result + } catch { + throw StarburstError.timeout + } + } func wait(seconds: Double) { From ce425b9f8aab5b609a1cfc9fd65e5f3ea4ccc2d2 Mon Sep 17 00:00:00 2001 From: CryptoSax Date: Mon, 11 Mar 2024 17:48:14 -0500 Subject: [PATCH 06/28] fixed structuredText listen return types --- Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift | 6 +++--- .../ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift index ff1eed1..81d3c78 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift @@ -250,14 +250,14 @@ public struct StarburstInstance } } - func listen(structuredText: StructuredText, maxSize: Int = 255) -> Bool + func listen(structuredText: StructuredText, maxSize: Int = 255) -> MatchResult { var buffer = Data() while buffer.count < maxSize { guard let byte = connection.read(size: 1) else { - return false + return MatchResult.SHORT } buffer.append(byte) @@ -277,7 +277,7 @@ public struct StarburstInstance continue } } - return true + return MatchResult.SUCCESS("") } diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift index 1ad4287..6d0f6db 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift @@ -214,9 +214,9 @@ public struct StarburstInstanceAsync } } - func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> Bool + func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> MatchResult { - let listenTask: Task = Task { + let listenTask: Task = Task { var buffer = Data() while buffer.count < maxSize { From 624883f3d69dc6ce23c9c6f1be61d9a2a46b6356 Mon Sep 17 00:00:00 2001 From: "Dr. Brandon Wiley" Date: Tue, 12 Mar 2024 11:05:05 -0500 Subject: [PATCH 07/28] changed listen() API to return the SUCCESS string --- .../ToneBurst/Starburst/StarburstAsync.swift | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift index 6d0f6db..faa585e 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift @@ -214,32 +214,48 @@ public struct StarburstInstanceAsync } } - func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> MatchResult + func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> String { - let listenTask: Task = Task { + let listenTask: Task = Task + { var buffer = Data() while buffer.count < maxSize { - do { + do + { let byte = try await connection.readSize(1) buffer.append(byte) guard let string = String(data: buffer, encoding: .utf8) else { - // This could fail because we're in the middle of a UTF8 rune. + // This could fail because we're in the middle of a UTF8 rune that is encoded as multiple bytes. continue } do { - return try structuredText.match(string: string) + let matchResult = try structuredText.match(string: string) + + switch matchResult + { + case .SUCCESS(let value): + return value + + case .SHORT: + continue + + case .FAILURE: + throw StarburstError.listenFailed + } } catch { continue } - } catch { + } + catch + { return nil } } @@ -247,17 +263,22 @@ public struct StarburstInstanceAsync return nil } - let _ = Task { + let _ = Task + { try await Task.sleep(for: timeout) listenTask.cancel() } - do { - guard let result = try await listenTask.value else { + do + { + guard let result = try await listenTask.value else + { throw StarburstError.readFailed } return result - } catch { + } + catch + { throw StarburstError.timeout } } From e37447e80a6cb216883e22bf1d5b88bb7e308f4d Mon Sep 17 00:00:00 2001 From: "Dr. Brandon Wiley" Date: Tue, 12 Mar 2024 14:24:52 -0500 Subject: [PATCH 08/28] return a MatchResult from inside the Task and a String from inside the function --- .../ToneBurst/Starburst/StarburstAsync.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift index faa585e..b3ba40e 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift @@ -240,7 +240,7 @@ public struct StarburstInstanceAsync switch matchResult { case .SUCCESS(let value): - return value + return matchResult case .SHORT: continue @@ -275,7 +275,15 @@ public struct StarburstInstanceAsync { throw StarburstError.readFailed } - return result + + switch result + { + case .SUCCESS(let value): + return value + + default: + throw StarburstError.listenFailed + } } catch { From f951df3355919b33b4ae9d226073f25739dc7b6c Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 12 Mar 2024 14:41:45 -0500 Subject: [PATCH 09/28] ReplicantConfigAsync because ToneburstAsync --- .../Config/ConfigGenerator.swift | 2 - .../Config/ReplicantClientConfig.swift | 33 -- .../Config/ReplicantConfigAsync.swift | 290 ++++++++++++++++++ .../Config/ReplicantConfigTemplate.swift | 117 ------- .../ToneBurst/SupportedToneBursts.swift | 131 -------- 5 files changed, 290 insertions(+), 283 deletions(-) create mode 100644 Sources/ReplicantSwift/Config/ReplicantConfigAsync.swift delete mode 100644 Sources/ReplicantSwift/Config/ReplicantConfigTemplate.swift diff --git a/Sources/ReplicantSwift/Config/ConfigGenerator.swift b/Sources/ReplicantSwift/Config/ConfigGenerator.swift index 6c5d437..1d0afdb 100644 --- a/Sources/ReplicantSwift/Config/ConfigGenerator.swift +++ b/Sources/ReplicantSwift/Config/ConfigGenerator.swift @@ -61,9 +61,7 @@ public func generateNewConfigPair(serverAddress: String, usePolish: Bool, useTon if useToneburst { toneBurstClient = Starburst(.SMTPClient) -// ToneBurstClientJsonConfig(mode: "SMTPClient") toneBurstServer = Starburst(.SMTPServer) -// toneburstServerConfig = ToneBurstServerJsonConfig(mode: "SMTPServer") } if usePolish { diff --git a/Sources/ReplicantSwift/Config/ReplicantClientConfig.swift b/Sources/ReplicantSwift/Config/ReplicantClientConfig.swift index 64f3be5..fbbada1 100644 --- a/Sources/ReplicantSwift/Config/ReplicantClientConfig.swift +++ b/Sources/ReplicantSwift/Config/ReplicantClientConfig.swift @@ -153,36 +153,3 @@ extension ReplicantClientConfig: Decodable } } } - -//public struct ReplicantClientJsonConfig: Codable -//{ -// public let serverAddress: String -// public var polish: PolishClientConfig? -// public var toneburst: ToneBurstClientJsonConfig? -// public var transport: String -// -// public init(serverAddress: String, polish maybePolish: PolishClientConfig?, toneBurst maybeToneBurst: ToneBurstClientJsonConfig?, transport: String) -// { -// self.serverAddress = serverAddress -// self.polish = maybePolish -// self.toneburst = maybeToneBurst -// self.transport = transport -// } -// -// public func createJSON() -> Data? -// { -// let encoder = JSONEncoder() -// encoder.outputFormatting = .prettyPrinted -// -// do -// { -// let configData = try encoder.encode(self) -// return configData -// } -// catch (let error) -// { -// print("Failed to encode config into JSON format: \(error)") -// return nil -// } -// } -//} diff --git a/Sources/ReplicantSwift/Config/ReplicantConfigAsync.swift b/Sources/ReplicantSwift/Config/ReplicantConfigAsync.swift new file mode 100644 index 0000000..9070fa7 --- /dev/null +++ b/Sources/ReplicantSwift/Config/ReplicantConfigAsync.swift @@ -0,0 +1,290 @@ +// +// ReplicantConfigAsync.swift +// +// +// Created by Mafalda on 3/12/24. +// + +import Foundation + +// TODO: Should eventually replace the old non-async Replicant config classes +public class ReplicantConfigAsync +{ + public struct ServerConfig: Codable + { + public let serverAddress: String + public let serverIP: String + public let serverPort: UInt16 + public let polish: PolishServerConfig? + public let toneburst: ToneBurstAsync? + public let toneburstType: ToneBurstType? + public var transportName = "replicant" + + enum CodingKeys: String, CodingKey + { + case serverAddress + case polish + case toneburst + case toneburstType + case transportName = "transport" + } + + public init(serverAddress: String, polish maybePolish: PolishServerConfig?, toneBurst maybeToneBurst: ToneBurstAsync?) throws + { + let addressStrings = serverAddress.replacingOccurrences(of: " ", with: "").split(separator: ":") + guard let port = UInt16(addressStrings[1]) else + { + print("Error decoding Replicant server config data: invalid server port \(addressStrings[1])") + throw ReplicantError.invalidPort + } + + self.serverAddress = serverAddress + self.serverIP = String(addressStrings[0]) + self.serverPort = port + self.polish = maybePolish + self.toneburst = maybeToneBurst + self.toneburstType = toneburst?.type + } + + public init?(from data: Data) + { + let decoder = JSONDecoder() + do + { + let decoded = try decoder.decode(ServerConfig.self, from: data) + + self = decoded + } + catch + { + print("Error received while attempting to decode a Replicant server config json file: \(error)") + return nil + } + } + + public init?(withConfigAtPath path: String) + { + let url = URL(fileURLWithPath: path) + + do + { + let data = try Data(contentsOf: url) + self.init(from: data) + } + catch + { + print("Error decoding Replicant server config file: \(error)") + + return nil + } + } + + public init(from decoder: Decoder) throws + { + let container = try decoder.container(keyedBy: CodingKeys.self) + let address = try container.decode(String.self, forKey: .serverAddress) + let addressStrings = address.replacingOccurrences(of: " ", with: "").split(separator: ":") + let ipAddress = String(addressStrings[0]) + guard let port = UInt16(addressStrings[1]) else + { + print("Error decoding Replicant server config - invalid server port: \(addressStrings[1])") + throw ReplicantError.decoderFailure + } + + self.serverAddress = address + self.serverIP = ipAddress + self.serverPort = port + self.polish = try container.decodeIfPresent(PolishServerConfig.self, forKey: .polish) + self.toneburstType = try container.decodeIfPresent(ToneBurstType.self, forKey: .toneburstType) + + // TODO: Support additional ToneBurst types + switch self.toneburstType + { + case .starburst: + self.toneburst = try container.decodeIfPresent(StarburstAsync.self, forKey: .toneburst) + case .none: + print("No supported Toneburst type was indicated while decoding a Replicant server config. Skipping Toneburst setup.") + self.toneburst = nil + } + } + + public func encode(to encoder: Encoder) throws + { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(serverAddress, forKey: .serverAddress) + try container.encode(transportName, forKey: .transportName) + try container.encode(polish, forKey: .polish) + try container.encode(toneburstType, forKey: .toneburstType) + + // TODO: Support additional ToneBurst types + switch toneburstType + { + case .starburst: + if let starburst = toneburst as? Starburst + { + try container.encode(starburst, forKey: .toneburst) + } + case .none: + print("Encoded a Replicant server config without a Toneburst.") + } + } + + /// Creates and returns a JSON representation of the ReplicantServerConfig struct. + public func createJSON() -> Data? + { + let encoder = JSONEncoder() + encoder.outputFormatting.insert(.prettyPrinted) + encoder.outputFormatting.insert(.withoutEscapingSlashes) + + do + { + let serverConfigData = try encoder.encode(self) + return serverConfigData + } + catch (let error) + { + print("Failed to encode Server config into JSON format: \(error)") + return nil + } + } + } + + public struct ClientConfig: Codable + { + public let serverAddress: String + public let serverIP: String + public let serverPort: UInt16 + public let polish: PolishServerConfig? + public let toneburst: ToneBurstAsync? + public let toneburstType: ToneBurstType? + public var transportName = "replicant" + + enum CodingKeys: String, CodingKey + { + case serverAddress + case polish + case toneburst + case toneburstType + case transportName = "transport" + } + + public init(serverAddress: String, polish maybePolish: PolishServerConfig?, toneBurst maybeToneBurst: ToneBurstAsync?) throws + { + let addressStrings = serverAddress.replacingOccurrences(of: " ", with: "").split(separator: ":") + guard let port = UInt16(addressStrings[1]) else + { + print("Error decoding Replicant client config data: invalid server port \(addressStrings[1])") + throw ReplicantError.invalidPort + } + + self.serverAddress = serverAddress + self.serverIP = String(addressStrings[0]) + self.serverPort = port + self.polish = maybePolish + self.toneburst = maybeToneBurst + self.toneburstType = toneburst?.type + } + + public init?(from data: Data) + { + let decoder = JSONDecoder() + do + { + let decoded = try decoder.decode(ClientConfig.self, from: data) + + self = decoded + } + catch + { + print("Error received while attempting to decode a Replicant client config json file: \(error)") + return nil + } + } + + public init?(withConfigAtPath path: String) + { + let url = URL(fileURLWithPath: path) + + do + { + let data = try Data(contentsOf: url) + self.init(from: data) + } + catch + { + print("Error decoding Replicant client config file: \(error)") + + return nil + } + } + + public init(from decoder: Decoder) throws + { + let container = try decoder.container(keyedBy: CodingKeys.self) + let address = try container.decode(String.self, forKey: .serverAddress) + let addressStrings = address.replacingOccurrences(of: " ", with: "").split(separator: ":") + let ipAddress = String(addressStrings[0]) + guard let port = UInt16(addressStrings[1]) else + { + print("Error decoding Replicant client config - invalid server port: \(addressStrings[1])") + throw ReplicantError.decoderFailure + } + + self.serverAddress = address + self.serverIP = ipAddress + self.serverPort = port + self.polish = try container.decodeIfPresent(PolishServerConfig.self, forKey: .polish) + self.toneburstType = try container.decodeIfPresent(ToneBurstType.self, forKey: .toneburstType) + + // TODO: Support additional ToneBurst types + switch self.toneburstType + { + case .starburst: + self.toneburst = try container.decodeIfPresent(StarburstAsync.self, forKey: .toneburst) + case .none: + print("No supported Toneburst type was indicated while decoding a Replicant client config. Skipping Toneburst setup.") + self.toneburst = nil + } + } + + public func encode(to encoder: Encoder) throws + { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(serverAddress, forKey: .serverAddress) + try container.encode(transportName, forKey: .transportName) + try container.encode(polish, forKey: .polish) + try container.encode(toneburstType, forKey: .toneburstType) + + // TODO: Support additional ToneBurst types + switch toneburstType + { + case .starburst: + if let starburst = toneburst as? Starburst + { + try container.encode(starburst, forKey: .toneburst) + } + case .none: + print("Encoded a Replicant client config without a Toneburst.") + } + } + + /// Creates and returns a JSON representation of the ReplicantClientConfig struct. + public func createJSON() -> Data? + { + let encoder = JSONEncoder() + encoder.outputFormatting.insert(.prettyPrinted) + encoder.outputFormatting.insert(.withoutEscapingSlashes) + + do + { + let clientConfigData = try encoder.encode(self) + return clientConfigData + } + catch (let error) + { + print("Failed to encode a Replicant client config into JSON format: \(error)") + return nil + } + } + } +} diff --git a/Sources/ReplicantSwift/Config/ReplicantConfigTemplate.swift b/Sources/ReplicantSwift/Config/ReplicantConfigTemplate.swift deleted file mode 100644 index f0f2f3f..0000000 --- a/Sources/ReplicantSwift/Config/ReplicantConfigTemplate.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// ReplicantConfigTemplate.swift -// ReplicantSwift -// -// Created by Adelita Schule on 12/14/18. -// - -import Foundation - -//public struct ReplicantConfigTemplate: Codable -//{ -// var maybePolishClientConfig: PolishClientConfig? -// var maybeToneBurst: ToneBurst? -// -// public init(polishClientConfig: PolishClientConfig?, toneBurst: ToneBurst?) -// { -// self.maybePolishClientConfig = polishClientConfig -// self.maybeToneBurst = toneBurst -// } -// -// public init?(withConfigAtPath path: String) -// { -// guard let config = ReplicantConfigTemplate.parseJSON(atPath: path) -// else -// { -// return nil -// } -// -// self = config -// } -// -// public func createJSON() -> Data? -// { -// let encoder = JSONEncoder() -// encoder.outputFormatting = .prettyPrinted -// -// do -// { -// let configData = try encoder.encode(self) -// return configData -// } -// catch (let error) -// { -// print("Failed to encode config into JSON format: \(error)") -// return nil -// } -// } -// -// public static func parseJSON(atPath path: String) -> ReplicantConfigTemplate? -// { -// let fileManager = FileManager() -// let decoder = JSONDecoder() -// -// guard let jsonData = fileManager.contents(atPath: path) -// else -// { -// print("\nUnable to get JSON data at path: \(path)\n") -// return nil -// } -// -// do -// { -// let config = try decoder.decode(ReplicantConfigTemplate.self, from: jsonData) -// return config -// } -// catch (let error) -// { -// print("\nUnable to decode JSON into ReplicantConfigTemplate: \(error)\n") -// return nil -// } -// } -// -// /// Creates a Replicant client configuration file at the specified path. -// /// - Parameters: -// /// - path: The filepath as a String, where the new config file should be saved, this should included the desired file name. -// /// - serverIP: The IP address of the Replicant Server as a String. -// /// - port: The port the provided server will be listening on for Replicant traffic as a UInt16 -// /// - serverPublicKey: The public key for the Replicant server. This is required in order for the client to be able to communicate with the server. -// /// - Returns: A boolean indicating whether or not the config was created successfully -// public func createClientConfig(atPath path: String, serverAddress: String) -> Bool -// { -// let fileManager = FileManager.default -// -// guard let replicantConfig = ReplicantClientConfig(serverAddress: serverAddress, polish: self.maybePolishClientConfig, toneBurst: self.maybeToneBurst, transport: "Replicant") else -// { -// return false -// } -// -// guard let jsonData = replicantConfig.createJSON() -// else -// { -// return false -// } -// -// let configCreated = fileManager.createFile(atPath: path, contents: jsonData) -// -// if configCreated -// { -// print("Created a Replicant client config at \(path):\n\(jsonData)") -// } -// -// return configCreated -// } -// -// -// public func printTemplateJSON() -// { -// guard let jsonData = createJSON() -// else -// { -// print("There was an error printing this template: we were unable to encode it to JSON format") -// return -// } -// -// print(jsonData) -// } -//} diff --git a/Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift b/Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift index f4a898d..79b06a9 100644 --- a/Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift +++ b/Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift @@ -12,134 +12,3 @@ public enum ToneBurstType: String, Codable { case starburst } - -//public enum ToneBurstClientConfig -//{ -//// case monotone(config: MonotoneConfig) -// case starburst(config: StarburstConfig) -//} -// -////public class ToneBurstClientJsonConfig: Codable { -//// let mode: String -//// -//// init(mode: String) { -//// self.mode = mode -//// } -////} -// -//extension ToneBurstClientConfig: ToneBurstConfig -//{ -// public func getToneBurst() -> ToneBurst { -// switch self -// { -//// case .monotone(config: let monotoneConfig): -//// return monotoneConfig.construct() -// case .starburst(config: let config): -// return config.construct() -// } -// } -//} - -//extension ToneBurstClientConfig: Codable -//{ -// enum CodingKeys: CodingKey -// { -//// case monotone -// case starburst -// } -// -// public init(from decoder: Decoder) throws -// { -// // FIXME: This only inits starburst flavor -// -// let container = try decoder.container(keyedBy: CodingKeys.self) -// do -// { -// let starburstConfig = try container.decode(StarburstConfig.self, forKey: .starburst) -// self = .starburst(config: starburstConfig) -// } -// catch -// { -// throw error -// } -// } -// -// public func encode(to encoder: Encoder) throws -// { -// var container = encoder.container(keyedBy: CodingKeys.self) -// switch self { -//// case .monotone(config: let config): -//// try container.encode(config, forKey: .monotone) -// case .starburst(config: let config): -// try container.encode(config, forKey: .starburst) -// } -// } -//} - - -// -// -//public enum ToneBurstServerConfig -//{ -//// case monotone(config: MonotoneConfig) -// case starburst(config: StarburstConfig) -//} -// -//public class ToneBurstServerJsonConfig: Codable { -// let mode: String -// -// init(mode: String) { -// self.mode = mode -// } -//} -// -//extension ToneBurstServerConfig: ToneBurstConfig -//{ -// public func getToneBurst() -> ToneBurst -// { -// switch self -// { -//// case .monotone(config: let monotoneConfig): -//// return monotoneConfig.construct() -// case .starburst(config: let config): -// return config.construct() -// } -// } -//} -// -//extension ToneBurstServerConfig: Codable -//{ -// enum CodingKeys: CodingKey -// { -//// case monotone -// case starburst -// } -// -// public init(from decoder: Decoder) throws -// { -// let container = try decoder.container(keyedBy: CodingKeys.self) -// // FIXME: This only inits starburst flavor -// do -// { -// let starburstConfig = try container.decode(StarburstConfig.self, forKey: .starburst) -// self = .starburst(config: starburstConfig) -// } -// catch -// { -// throw error -// } -// } -// -// public func encode(to encoder: Encoder) throws -// { -// var container = encoder.container(keyedBy: CodingKeys.self) -// -// switch self -// { -//// case .monotone(config: let monotoneConfig): -//// try container.encode(monotoneConfig, forKey: .monotone) -// case .starburst(config: let config): -// try container.encode(config, forKey: .starburst) -// } -// } -//} From 9d60264e281060b44d558e100cab2288085f3a7b Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 12 Mar 2024 16:29:33 -0500 Subject: [PATCH 10/28] Moar AsyncConfig --- .../Config/ReplicantConfigAsync.swift | 90 +++++++++++++++++-- .../Config/ReplicantServerConfig.swift | 33 ------- .../ToneBurst/Starburst/StarburstAsync.swift | 2 +- 3 files changed, 82 insertions(+), 43 deletions(-) diff --git a/Sources/ReplicantSwift/Config/ReplicantConfigAsync.swift b/Sources/ReplicantSwift/Config/ReplicantConfigAsync.swift index 9070fa7..e31df33 100644 --- a/Sources/ReplicantSwift/Config/ReplicantConfigAsync.swift +++ b/Sources/ReplicantSwift/Config/ReplicantConfigAsync.swift @@ -5,11 +5,77 @@ // Created by Mafalda on 3/12/24. // +import Crypto import Foundation +import Gardener +import KeychainTypes + // TODO: Should eventually replace the old non-async Replicant config classes public class ReplicantConfigAsync { + public static func generateNewConfigPair(serverAddress: String, polish: Bool, toneburstType: ToneBurstType?) throws -> (serverConfig: ServerConfig, clientConfig: ClientConfig) + { + var toneburstClient: ToneBurstAsync? = nil + var toneburstServer: ToneBurstAsync? = nil + var polishClient: PolishClientConfig? = nil + var polishServer: PolishServerConfig? = nil + + // TODO: Suppport other toneburst types + switch toneburstType + { + case .starburst: + toneburstClient = StarburstAsync(.SMTPClient) + toneburstServer = StarburstAsync(.SMTPServer) + case .none: + toneburstClient = nil + toneburstServer = nil + } + + if polish { + let compactRepresentable = P256.KeyAgreement.PrivateKey(compactRepresentable: true) + print("raw representation: \(compactRepresentable.rawRepresentation.hex) (count: \(compactRepresentable.rawRepresentation.count)) | x963 representation: \(compactRepresentable.x963Representation.hex) (count: \(compactRepresentable.x963Representation.count)") + let privateKey = PrivateKey.P256KeyAgreement(compactRepresentable) + + let publicKey = privateKey.publicKey + polishClient = PolishClientConfig(serverAddress: serverAddress, serverPublicKey: publicKey) + polishServer = PolishServerConfig(serverAddress: serverAddress, serverPrivateKey: privateKey) + } + + let clientConfig = try ClientConfig(serverAddress: serverAddress, polish: polishClient, toneBurst: toneburstClient) + let serverConfig = try ServerConfig(serverAddress: serverAddress, polish: polishServer, toneBurst: toneburstServer) + + return (serverConfig, clientConfig) + } + + public static func createNewConfigFiles(inDirectory saveDirectory: URL, serverAddress: String, polish: Bool, toneburstType: ToneBurstType?) throws + { + guard saveDirectory.isDirectory else + { + throw ReplicantConfigError.directoryNotFound(directory: saveDirectory.path) + } + + let newConfigs = try generateNewConfigPair(serverAddress: serverAddress, polish: polish, toneburstType: toneburstType) + let clientJson = try newConfigs.clientConfig.createJSON() + let serverJson = try newConfigs.serverConfig.createJSON() + + let serverConfigFilename = "ReplicantServerConfig.json" + let serverConfigFilePath = saveDirectory.appendingPathComponent(serverConfigFilename).path + + guard File.put(serverConfigFilePath, contents: serverJson) else + { + throw ReplicantConfigError.failedToSaveFile(filePath: serverConfigFilePath) + } + + let clientConfigFilename = "ReplicantClientConfig.json" + let clientConfigFilePath = saveDirectory.appendingPathComponent(clientConfigFilename).path + + guard File.put(clientConfigFilePath, contents: clientJson) else + { + throw ReplicantConfigError.failedToSaveFile(filePath: clientConfigFilePath) + } + } + public struct ServerConfig: Codable { public let serverAddress: String @@ -130,7 +196,7 @@ public class ReplicantConfigAsync } /// Creates and returns a JSON representation of the ReplicantServerConfig struct. - public func createJSON() -> Data? + public func createJSON() throws -> Data { let encoder = JSONEncoder() encoder.outputFormatting.insert(.prettyPrinted) @@ -143,8 +209,7 @@ public class ReplicantConfigAsync } catch (let error) { - print("Failed to encode Server config into JSON format: \(error)") - return nil + throw ReplicantConfigError.serverConfigJsonEncodingError(error: error) } } } @@ -154,7 +219,7 @@ public class ReplicantConfigAsync public let serverAddress: String public let serverIP: String public let serverPort: UInt16 - public let polish: PolishServerConfig? + public let polish: PolishClientConfig? public let toneburst: ToneBurstAsync? public let toneburstType: ToneBurstType? public var transportName = "replicant" @@ -168,7 +233,7 @@ public class ReplicantConfigAsync case transportName = "transport" } - public init(serverAddress: String, polish maybePolish: PolishServerConfig?, toneBurst maybeToneBurst: ToneBurstAsync?) throws + public init(serverAddress: String, polish maybePolish: PolishClientConfig?, toneBurst maybeToneBurst: ToneBurstAsync?) throws { let addressStrings = serverAddress.replacingOccurrences(of: " ", with: "").split(separator: ":") guard let port = UInt16(addressStrings[1]) else @@ -233,7 +298,7 @@ public class ReplicantConfigAsync self.serverAddress = address self.serverIP = ipAddress self.serverPort = port - self.polish = try container.decodeIfPresent(PolishServerConfig.self, forKey: .polish) + self.polish = try container.decodeIfPresent(PolishClientConfig.self, forKey: .polish) self.toneburstType = try container.decodeIfPresent(ToneBurstType.self, forKey: .toneburstType) // TODO: Support additional ToneBurst types @@ -269,7 +334,7 @@ public class ReplicantConfigAsync } /// Creates and returns a JSON representation of the ReplicantClientConfig struct. - public func createJSON() -> Data? + public func createJSON() throws -> Data { let encoder = JSONEncoder() encoder.outputFormatting.insert(.prettyPrinted) @@ -282,9 +347,16 @@ public class ReplicantConfigAsync } catch (let error) { - print("Failed to encode a Replicant client config into JSON format: \(error)") - return nil + throw ReplicantConfigError.clientConfigJsonEncodingError(error: error) } } } } + +public enum ReplicantConfigError: Error +{ + case directoryNotFound(directory: String) + case clientConfigJsonEncodingError(error: Error) + case serverConfigJsonEncodingError(error: Error) + case failedToSaveFile(filePath: String) +} diff --git a/Sources/ReplicantSwift/Config/ReplicantServerConfig.swift b/Sources/ReplicantSwift/Config/ReplicantServerConfig.swift index baabe51..312fc15 100644 --- a/Sources/ReplicantSwift/Config/ReplicantServerConfig.swift +++ b/Sources/ReplicantSwift/Config/ReplicantServerConfig.swift @@ -124,36 +124,3 @@ extension ReplicantServerConfig: Decodable } } } - -//public struct ReplicantServerJsonConfig: Codable -//{ -// public let serverAddress: String -// public var polish: PolishServerConfig? -// public var toneburst: ToneBurstServerJsonConfig? -// public var transport: String -// -// public init(serverAddress: String, polish maybePolish: PolishServerConfig?, toneBurst maybeToneBurst: ToneBurstServerJsonConfig?, transport: String) -// { -// self.serverAddress = serverAddress -// self.polish = maybePolish -// self.toneburst = maybeToneBurst -// self.transport = transport -// } -// -// public func createJSON() -> Data? -// { -// let encoder = JSONEncoder() -// encoder.outputFormatting = .prettyPrinted -// -// do -// { -// let configData = try encoder.encode(self) -// return configData -// } -// catch (let error) -// { -// print("Failed to encode config into JSON format: \(error)") -// return nil -// } -// } -//} diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift index b3ba40e..b48f5c6 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift @@ -239,7 +239,7 @@ public struct StarburstInstanceAsync switch matchResult { - case .SUCCESS(let value): + case .SUCCESS(_): return matchResult case .SHORT: From 28cad774df96e74b773a9bb3639a141331d881e5 Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 12 Mar 2024 16:33:36 -0500 Subject: [PATCH 11/28] Renamed unclear label --- Sources/ReplicantSwift/Async/ReplicantAsync.swift | 4 ++-- Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/ReplicantSwift/Async/ReplicantAsync.swift b/Sources/ReplicantSwift/Async/ReplicantAsync.swift index 5398e78..30822db 100644 --- a/Sources/ReplicantSwift/Async/ReplicantAsync.swift +++ b/Sources/ReplicantSwift/Async/ReplicantAsync.swift @@ -19,9 +19,9 @@ public class ReplicantAsync self.logger = logger } - public func listen(address: String, port: Int, config: ReplicantServerConfig) async throws -> TransmissionAsync.AsyncListener + public func listen(serverIP: String, port: Int, config: ReplicantServerConfig) async throws -> TransmissionAsync.AsyncListener { - return try ReplicantListenerAsync(address: address, port: port, config: config, logger: logger) + return try ReplicantListenerAsync(serverIP: serverIP, port: port, config: config, logger: logger) } public func connect(host: String, port: Int, config: ReplicantClientConfig) async throws -> TransmissionAsync.AsyncConnection diff --git a/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift b/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift index 1ae0a67..87ada9e 100644 --- a/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift +++ b/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift @@ -17,11 +17,11 @@ open class ReplicantListenerAsync: TransmissionAsync.AsyncListener let logger: Logger let listener: AsyncListener - public init(address: String, port: Int, config: ReplicantServerConfig, logger: Logger) throws + public init(serverIP: String, port: Int, config: ReplicantServerConfig, logger: Logger) throws { self.config = config self.logger = logger - self.listener = try AsyncTcpSocketListener(host: address, port: port, logger) + self.listener = try AsyncTcpSocketListener(host: serverIP, port: port, logger) } open func accept() async throws -> TransmissionAsync.AsyncConnection From 1dfac3282b47fd7740907341f5f33a98ad5b950d Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 12 Mar 2024 16:46:06 -0500 Subject: [PATCH 12/28] Removed unused init args --- .../ReplicantSwift/Async/ReplicantAsync.swift | 6 ++--- .../Async/ReplicantListenerAsync.swift | 26 +++++++++++++------ Sources/ReplicantSwift/ReplicantError.swift | 1 + 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Sources/ReplicantSwift/Async/ReplicantAsync.swift b/Sources/ReplicantSwift/Async/ReplicantAsync.swift index 30822db..3e2a658 100644 --- a/Sources/ReplicantSwift/Async/ReplicantAsync.swift +++ b/Sources/ReplicantSwift/Async/ReplicantAsync.swift @@ -19,17 +19,15 @@ public class ReplicantAsync self.logger = logger } - public func listen(serverIP: String, port: Int, config: ReplicantServerConfig) async throws -> TransmissionAsync.AsyncListener + public func listen(serverIP: String, port: Int, config: ReplicantConfigAsync.ServerConfig) async throws -> TransmissionAsync.AsyncListener { - return try ReplicantListenerAsync(serverIP: serverIP, port: port, config: config, logger: logger) + return try ReplicantListenerAsync(config: config, logger: logger) } public func connect(host: String, port: Int, config: ReplicantClientConfig) async throws -> TransmissionAsync.AsyncConnection { let network = try await AsyncTcpSocketConnection(host, port, logger) - return try await self.replicantClientTransformationAsync(connection: network, config, logger) - } public func replicantClientTransformationAsync(connection: TransmissionAsync.AsyncConnection, _ config: ReplicantClientConfig, _ logger: Logger) async throws -> TransmissionAsync.AsyncConnection diff --git a/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift b/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift index 87ada9e..733e5d0 100644 --- a/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift +++ b/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift @@ -13,22 +13,21 @@ import TransmissionAsync // This is just a normal TCP listener, except for the config and special accept behavior. open class ReplicantListenerAsync: TransmissionAsync.AsyncListener { - let config: ReplicantServerConfig + let config: ReplicantConfigAsync.ServerConfig let logger: Logger let listener: AsyncListener - public init(serverIP: String, port: Int, config: ReplicantServerConfig, logger: Logger) throws + public init(config: ReplicantConfigAsync.ServerConfig, logger: Logger) throws { self.config = config self.logger = logger - self.listener = try AsyncTcpSocketListener(host: serverIP, port: port, logger) + self.listener = try AsyncTcpSocketListener(host: config.serverIP, port: Int(config.serverPort), logger) } open func accept() async throws -> TransmissionAsync.AsyncConnection { let network = try await self.listener.accept() - - return try await self.replicantServerTransformation(connection: network, config, logger) + return try await self.replicantServerTransformation(connection: network, config: config, logger: logger) } open func close() async throws @@ -36,15 +35,26 @@ open class ReplicantListenerAsync: TransmissionAsync.AsyncListener try await self.listener.close() } - public func replicantServerTransformation(connection: TransmissionAsync.AsyncConnection, _ config: ReplicantServerConfig, _ logger: Logger) async throws -> TransmissionAsync.AsyncConnection + public func replicantServerTransformation(connection: TransmissionAsync.AsyncConnection, config: ReplicantConfigAsync.ServerConfig, logger: Logger) async throws -> TransmissionAsync.AsyncConnection { var result: TransmissionAsync.AsyncConnection = connection // TODO: Add more ToneBurst types as they become available - if let starBurst = config.toneBurst as? StarburstAsync + switch config.toneburstType { - try await starBurst.perform(connection: connection) + case .starburst: + if let starBurst = config.toneburst as? StarburstAsync + { + try await starBurst.perform(connection: connection) + } + else + { + throw ReplicantError.invalidToneburst + } + case .none: + print("ReplicantServerTransformation: Skipping Toneburst.") } + if let polishConfig = config.polish { diff --git a/Sources/ReplicantSwift/ReplicantError.swift b/Sources/ReplicantSwift/ReplicantError.swift index 95468e6..f081593 100644 --- a/Sources/ReplicantSwift/ReplicantError.swift +++ b/Sources/ReplicantSwift/ReplicantError.swift @@ -41,4 +41,5 @@ public enum ReplicantError: Error case invalidResponse case serverError case serverUnavailable + case invalidToneburst } From c0601a8d4758c645ea776ae7fbfd5a273d5a313b Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 12 Mar 2024 17:28:19 -0500 Subject: [PATCH 13/28] Update ReplicantAsync.swift --- .../ReplicantSwift/Async/ReplicantAsync.swift | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Sources/ReplicantSwift/Async/ReplicantAsync.swift b/Sources/ReplicantSwift/Async/ReplicantAsync.swift index 3e2a658..a609eed 100644 --- a/Sources/ReplicantSwift/Async/ReplicantAsync.swift +++ b/Sources/ReplicantSwift/Async/ReplicantAsync.swift @@ -24,20 +24,29 @@ public class ReplicantAsync return try ReplicantListenerAsync(config: config, logger: logger) } - public func connect(host: String, port: Int, config: ReplicantClientConfig) async throws -> TransmissionAsync.AsyncConnection + public func connect(host: String, port: Int, config: ReplicantConfigAsync.ClientConfig) async throws -> TransmissionAsync.AsyncConnection { let network = try await AsyncTcpSocketConnection(host, port, logger) return try await self.replicantClientTransformationAsync(connection: network, config, logger) } - public func replicantClientTransformationAsync(connection: TransmissionAsync.AsyncConnection, _ config: ReplicantClientConfig, _ logger: Logger) async throws -> TransmissionAsync.AsyncConnection + public func replicantClientTransformationAsync(connection: TransmissionAsync.AsyncConnection, _ config: ReplicantConfigAsync.ClientConfig, _ logger: Logger) async throws -> TransmissionAsync.AsyncConnection { var result: TransmissionAsync.AsyncConnection = connection - - if var starBurst = config.toneBurst as? ToneBurstAsync + + switch config.toneburstType { - try await starBurst.perform(connection: connection) + case .starburst: + guard let starBurst = config.toneburst as? StarburstAsync else + { + throw ReplicantError.invalidToneburst + } + try await starBurst.perform(connection: connection) + + case .none: + print("replicantClientTransformationAsync skipping Toneburst: none provided") } + if let polishConfig = config.polish { From e4da1db8732a273ab74a651843eb727dfa5c4bc8 Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 12 Mar 2024 17:30:05 -0500 Subject: [PATCH 14/28] Update ReplicantAsync.swift --- Sources/ReplicantSwift/Async/ReplicantAsync.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ReplicantSwift/Async/ReplicantAsync.swift b/Sources/ReplicantSwift/Async/ReplicantAsync.swift index a609eed..8871808 100644 --- a/Sources/ReplicantSwift/Async/ReplicantAsync.swift +++ b/Sources/ReplicantSwift/Async/ReplicantAsync.swift @@ -27,10 +27,10 @@ public class ReplicantAsync public func connect(host: String, port: Int, config: ReplicantConfigAsync.ClientConfig) async throws -> TransmissionAsync.AsyncConnection { let network = try await AsyncTcpSocketConnection(host, port, logger) - return try await self.replicantClientTransformationAsync(connection: network, config, logger) + return try await self.replicantClientTransformationAsync(connection: network, config: config, logger: logger) } - public func replicantClientTransformationAsync(connection: TransmissionAsync.AsyncConnection, _ config: ReplicantConfigAsync.ClientConfig, _ logger: Logger) async throws -> TransmissionAsync.AsyncConnection + public func replicantClientTransformationAsync(connection: TransmissionAsync.AsyncConnection, config: ReplicantConfigAsync.ClientConfig, logger: Logger) async throws -> TransmissionAsync.AsyncConnection { var result: TransmissionAsync.AsyncConnection = connection From f3b6f22b4b0114c22dca19318229e48d1425d3b3 Mon Sep 17 00:00:00 2001 From: consuelita Date: Wed, 13 Mar 2024 16:45:17 -0500 Subject: [PATCH 15/28] Debug Print --- Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift b/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift index 733e5d0..37fddfe 100644 --- a/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift +++ b/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift @@ -49,6 +49,7 @@ open class ReplicantListenerAsync: TransmissionAsync.AsyncListener } else { + logger.error("Invalid Replicant Server Config toneburst type: \(String(describing: config.toneburstType))") throw ReplicantError.invalidToneburst } case .none: From c0a97ef3bae573a6099603554cda0d45f493921e Mon Sep 17 00:00:00 2001 From: consuelita Date: Wed, 13 Mar 2024 17:55:08 -0500 Subject: [PATCH 16/28] Added Omnitone --- .../Async/ReplicantListenerAsync.swift | 55 ++++--- .../ToneBurst/Omnitone/Omnitone.swift | 151 ++++++++++++++++++ 2 files changed, 180 insertions(+), 26 deletions(-) create mode 100644 Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift diff --git a/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift b/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift index 37fddfe..33f7e9d 100644 --- a/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift +++ b/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift @@ -36,32 +36,35 @@ open class ReplicantListenerAsync: TransmissionAsync.AsyncListener } public func replicantServerTransformation(connection: TransmissionAsync.AsyncConnection, config: ReplicantConfigAsync.ServerConfig, logger: Logger) async throws -> TransmissionAsync.AsyncConnection + { + var result: TransmissionAsync.AsyncConnection = connection + + // TODO: Add more ToneBurst types as they become available + switch config.toneburstType + { + case .starburst: + if let starburst = config.toneburst as? StarburstAsync + { + try await starburst.perform(connection: connection) + } + else if let starburst = config.toneburst as? Omnitone + { + try await starburst.perform(connection: connection) + } + else + { + logger.error("Invalid Replicant Server Config toneburst type is starburst, but toneburst could not be initialized.") + throw ReplicantError.invalidToneburst + } + case .none: + print("ReplicantServerTransformation: Skipping Toneburst.") + } + + if let polishConfig = config.polish { - var result: TransmissionAsync.AsyncConnection = connection - - // TODO: Add more ToneBurst types as they become available - switch config.toneburstType - { - case .starburst: - if let starBurst = config.toneburst as? StarburstAsync - { - try await starBurst.perform(connection: connection) - } - else - { - logger.error("Invalid Replicant Server Config toneburst type: \(String(describing: config.toneburstType))") - throw ReplicantError.invalidToneburst - } - case .none: - print("ReplicantServerTransformation: Skipping Toneburst.") - } - - - if let polishConfig = config.polish - { - result = try await polishConfig.polish(result, logger) - } - - return result + result = try await polishConfig.polish(result, logger) } + + return result + } } diff --git a/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift b/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift new file mode 100644 index 0000000..ab5cb9b --- /dev/null +++ b/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift @@ -0,0 +1,151 @@ +// +// Omnitone.swift +// +// + +import Foundation + +import Chord +import Datable +import Ghostwriter +import TransmissionAsync + +public enum OmnitoneMode: String, Codable +{ + case POP3Client + case POP3Server + +} + +public class Omnitone: ToneBurstAsync, Codable +{ + public var type: ReplicantSwift.ToneBurstType = .starburst + + let mode: OmnitoneMode + + public init(_ mode: OmnitoneMode) + { + self.mode = mode + } + + public func perform(connection: TransmissionAsync.AsyncConnection) async throws + { + let instance = OmnitoneInstance(self.mode, connection) + try await instance.perform() + } +} + +public struct OmnitoneInstance +{ + let connection: TransmissionAsync.AsyncConnection + let mode: OmnitoneMode + + public init(_ mode: OmnitoneMode, _ connection: TransmissionAsync.AsyncConnection) + { + self.mode = mode + self.connection = connection + } + + public func perform() async throws + { + switch mode + { + + case .POP3Client: + try await handlePOP3Client() + + case .POP3Server: + try await handlePOP3Server() + + } + } + + func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> MatchResult + { + let listenTask: Task = Task { + var buffer = Data() + while buffer.count < maxSize + { + do { + let byte = try await connection.readSize(1) + + buffer.append(byte) + + guard let string = String(data: buffer, encoding: .utf8) else + { + // This could fail because we're in the middle of a UTF8 rune. + continue + } + + do + { + return try structuredText.match(string: string) + } + catch + { + continue + } + } catch { + return nil + } + } + + return nil + } + + let _ = Task { + try await Task.sleep(for: timeout) + listenTask.cancel() + } + + do { + guard let result = try await listenTask.value else { + throw StarburstError.readFailed + } + return result + } catch { + throw StarburstError.timeout + } + } + + func speak(structuredText: StructuredText) async throws + { + do + { + let string = structuredText.string + try await connection.writeString(string: string) + } + catch + { + print(error) + throw StarburstError.writeFailed + } + } + + + private func handlePOP3Server() async throws + { + let _ = try await self.listen(structuredText: StructuredText(TypedText.text("+OK POP3 server ready."), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) + try await self.speak(structuredText: StructuredText(TypedText.text("STLS"), TypedText.newline(Newline.crlf))) + let _ = try await self.listen(structuredText: StructuredText(TypedText.text("+OK Begin TLS Negotiation"), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) + } + + private func handlePOP3Client() async throws + { + try await self.speak(structuredText: StructuredText(TypedText.text("+OK POP3 server ready."), TypedText.newline(Newline.crlf))) + let _ = try await self.listen(structuredText: StructuredText(TypedText.text("STLS"), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) + try await self.speak(structuredText: StructuredText(TypedText.text("+OK Begin TLS Negotiation"), TypedText.newline(Newline.crlf))) + } + +} + +public enum OmnitoneError: Error +{ + case timeout + case connectionClosed + case writeFailed + case readFailed + case listenFailed + case speakFailed + case maxSizeReached +} From af260290478008f2bc3f0aa758919ab833145fe6 Mon Sep 17 00:00:00 2001 From: "Dr. Brandon Wiley" Date: Wed, 13 Mar 2024 22:54:49 -0500 Subject: [PATCH 17/28] Allow for omnitone configs --- Sources/ReplicantSwift/Async/ReplicantAsync.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Sources/ReplicantSwift/Async/ReplicantAsync.swift b/Sources/ReplicantSwift/Async/ReplicantAsync.swift index 8871808..6b73356 100644 --- a/Sources/ReplicantSwift/Async/ReplicantAsync.swift +++ b/Sources/ReplicantSwift/Async/ReplicantAsync.swift @@ -37,12 +37,18 @@ public class ReplicantAsync switch config.toneburstType { case .starburst: - guard let starBurst = config.toneburst as? StarburstAsync else + switch config.toneburst { - throw ReplicantError.invalidToneburst + case let starBurst as StarburstAsync: + try await starBurst.perform(connection: connection) + + case let omnitone as Omnitone: + try await omnitone.perform(connection: connection) + + default: + throw ReplicantError.invalidToneburst } - try await starBurst.perform(connection: connection) - + case .none: print("replicantClientTransformationAsync skipping Toneburst: none provided") } From 9b05ec48066d1b593ab99010021add8b1e354d27 Mon Sep 17 00:00:00 2001 From: "Dr. Brandon Wiley" Date: Wed, 13 Mar 2024 23:05:20 -0500 Subject: [PATCH 18/28] POP3 client and server were backward Note: this was generated code, so we need to change it in the original source. I'm just changing it here so that we can debug the transport and getting it working for the first time. --- .../ToneBurst/Omnitone/Omnitone.swift | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift b/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift index ab5cb9b..efc4024 100644 --- a/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift +++ b/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift @@ -121,22 +121,20 @@ public struct OmnitoneInstance throw StarburstError.writeFailed } } - - - private func handlePOP3Server() async throws - { - let _ = try await self.listen(structuredText: StructuredText(TypedText.text("+OK POP3 server ready."), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) - try await self.speak(structuredText: StructuredText(TypedText.text("STLS"), TypedText.newline(Newline.crlf))) - let _ = try await self.listen(structuredText: StructuredText(TypedText.text("+OK Begin TLS Negotiation"), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) - } - private func handlePOP3Client() async throws - { - try await self.speak(structuredText: StructuredText(TypedText.text("+OK POP3 server ready."), TypedText.newline(Newline.crlf))) - let _ = try await self.listen(structuredText: StructuredText(TypedText.text("STLS"), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) - try await self.speak(structuredText: StructuredText(TypedText.text("+OK Begin TLS Negotiation"), TypedText.newline(Newline.crlf))) - } + private func handlePOP3Server() async throws + { + try await self.speak(structuredText: StructuredText(TypedText.text("+OK POP3 server ready."), TypedText.newline(Newline.crlf))) + let _ = try await self.listen(structuredText: StructuredText(TypedText.text("STLS"), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) + try await self.speak(structuredText: StructuredText(TypedText.text("+OK Begin TLS Negotiation"), TypedText.newline(Newline.crlf))) + } + private func handlePOP3Client() async throws + { + let _ = try await self.listen(structuredText: StructuredText(TypedText.text("+OK POP3 server ready."), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) + try await self.speak(structuredText: StructuredText(TypedText.text("STLS"), TypedText.newline(Newline.crlf))) + let _ = try await self.listen(structuredText: StructuredText(TypedText.text("+OK Begin TLS Negotiation"), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) + } } public enum OmnitoneError: Error From 3658f3b19d75964c437bd63524632a90d8bed583 Mon Sep 17 00:00:00 2001 From: "Dr. Brandon Wiley" Date: Wed, 13 Mar 2024 23:42:08 -0500 Subject: [PATCH 19/28] Simplified and hopefully corrected listen() --- .../ToneBurst/Omnitone/Omnitone.swift | 59 +++++++++---------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift b/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift index efc4024..88fc32e 100644 --- a/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift +++ b/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift @@ -62,50 +62,45 @@ public struct OmnitoneInstance func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> MatchResult { - let listenTask: Task = Task { + let listenTask: Task = Task + { var buffer = Data() while buffer.count < maxSize { - do { - let byte = try await connection.readSize(1) - - buffer.append(byte) - - guard let string = String(data: buffer, encoding: .utf8) else - { - // This could fail because we're in the middle of a UTF8 rune. - continue - } - - do - { - return try structuredText.match(string: string) - } - catch - { + let byte = try await connection.readSize(1) + + buffer.append(byte) + + guard let string = String(data: buffer, encoding: .utf8) else + { + // This could fail because we're in the middle of a UTF8 rune. + continue + } + + let result = structuredText.match(string: string) + switch result + { + case .FAILURE: + return result + + case .SHORT: continue - } - } catch { - return nil + + case .SUCCESS(_): + return result } } - - return nil + + throw StarburstError.maxSizeReached } - let _ = Task { + Task + { try await Task.sleep(for: timeout) listenTask.cancel() } - do { - guard let result = try await listenTask.value else { - throw StarburstError.readFailed - } - return result - } catch { - throw StarburstError.timeout - } + return try await listenTask.value } func speak(structuredText: StructuredText) async throws From 87ed52dc15f909d491771c96d7316adcab7546c7 Mon Sep 17 00:00:00 2001 From: "Dr. Brandon Wiley" Date: Wed, 13 Mar 2024 23:55:38 -0500 Subject: [PATCH 20/28] trivial formatting, but it was annoying me Note: this should be fixed in the code generator too (I tried to, maybe it worked) --- Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift b/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift index 88fc32e..3e6e1d8 100644 --- a/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift +++ b/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift @@ -50,13 +50,11 @@ public struct OmnitoneInstance { switch mode { - case .POP3Client: try await handlePOP3Client() case .POP3Server: try await handlePOP3Server() - } } From 998f3be27c4cf2f091688ca5eee6414be406f5c1 Mon Sep 17 00:00:00 2001 From: consuelita Date: Thu, 14 Mar 2024 13:53:46 -0500 Subject: [PATCH 21/28] Removed Non-Async and Deprecated Code --- Package.swift | 10 - .../Config/ConfigGenerator.swift | 85 ----- .../Config/ReplicantClientConfig.swift | 155 --------- .../Config/ReplicantServerConfig.swift | 126 -------- .../ReplicantSwift/Models/SequenceModel.swift | 39 --- .../{PolishAsync.swift => Polish.swift} | 7 +- .../Polish/PolishClientAsync.swift | 24 -- .../Polish/PolishClientConfig.swift | 34 +- .../ReplicantSwift/Polish/PolishConfig.swift | 23 -- .../Polish/PolishServerAsync.swift | 24 -- .../Polish/PolishServerConfig.swift | 31 +- .../ReplicantAsync.swift => Replicant.swift} | 14 +- ...onfigAsync.swift => ReplicantConfig.swift} | 23 +- ...nerAsync.swift => ReplicantListener.swift} | 10 +- .../ReplicantSwift/Spacetime/Replicant.swift | 40 --- .../Spacetime/ReplicantUniverse.swift | 86 ----- .../Spacetime/ReplicantUniverseListener.swift | 45 --- .../ToneBurst/Monotone/Monotone.swift | 138 -------- .../ToneBurst/Monotone/MonotoneClient.swift | 46 --- .../ToneBurst/Monotone/MonotoneConfig.swift | 32 -- .../ToneBurst/Monotone/MonotoneServer.swift | 23 -- .../ToneBurst/Monotone/MonotoneState.swift | 43 --- .../ToneBurst/Omnitone/Omnitone.swift | 4 +- .../ToneBurst/Starburst/Starburst.swift | 263 +++++++-------- .../ToneBurst/Starburst/StarburstAsync.swift | 299 ------------------ .../ReplicantSwift/ToneBurst/ToneBurst.swift | 19 -- .../{ToneburstAsync.swift => Toneburst.swift} | 2 +- build.sh | 2 - gen.sh | 4 - 29 files changed, 176 insertions(+), 1475 deletions(-) delete mode 100644 Sources/ReplicantSwift/Config/ConfigGenerator.swift delete mode 100644 Sources/ReplicantSwift/Config/ReplicantClientConfig.swift delete mode 100644 Sources/ReplicantSwift/Config/ReplicantServerConfig.swift delete mode 100644 Sources/ReplicantSwift/Models/SequenceModel.swift rename Sources/ReplicantSwift/Polish/{PolishAsync.swift => Polish.swift} (72%) delete mode 100644 Sources/ReplicantSwift/Polish/PolishClientAsync.swift delete mode 100644 Sources/ReplicantSwift/Polish/PolishConfig.swift delete mode 100644 Sources/ReplicantSwift/Polish/PolishServerAsync.swift rename Sources/ReplicantSwift/{Async/ReplicantAsync.swift => Replicant.swift} (66%) rename Sources/ReplicantSwift/{Config/ReplicantConfigAsync.swift => ReplicantConfig.swift} (95%) rename Sources/ReplicantSwift/{Async/ReplicantListenerAsync.swift => ReplicantListener.swift} (85%) delete mode 100644 Sources/ReplicantSwift/Spacetime/Replicant.swift delete mode 100644 Sources/ReplicantSwift/Spacetime/ReplicantUniverse.swift delete mode 100644 Sources/ReplicantSwift/Spacetime/ReplicantUniverseListener.swift delete mode 100644 Sources/ReplicantSwift/ToneBurst/Monotone/Monotone.swift delete mode 100644 Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneClient.swift delete mode 100644 Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneConfig.swift delete mode 100644 Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneServer.swift delete mode 100644 Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneState.swift delete mode 100644 Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift delete mode 100644 Sources/ReplicantSwift/ToneBurst/ToneBurst.swift rename Sources/ReplicantSwift/ToneBurst/{ToneburstAsync.swift => Toneburst.swift} (89%) delete mode 100755 build.sh delete mode 100755 gen.sh diff --git a/Package.swift b/Package.swift index 37d2f5e..fbdce93 100644 --- a/Package.swift +++ b/Package.swift @@ -19,13 +19,9 @@ let package = Package( .package(url: "https://github.com/OperatorFoundation/Ghostwriter", branch: "main"), .package(url: "https://github.com/OperatorFoundation/KeychainTypes", branch: "main"), .package(url: "https://github.com/OperatorFoundation/Monolith", branch: "main"), - .package(url: "https://github.com/OperatorFoundation/Net", branch: "main"), .package(url: "https://github.com/OperatorFoundation/ShadowSwift", branch: "main"), - .package(url: "https://github.com/OperatorFoundation/Song", branch: "main"), - .package(url: "https://github.com/OperatorFoundation/Spacetime", branch: "main"), .package(url: "https://github.com/OperatorFoundation/SwiftHexTools", branch: "main"), .package(url: "https://github.com/OperatorFoundation/SwiftQueue", branch: "main"), - .package(url: "https://github.com/OperatorFoundation/Transmission", branch: "main"), .package(url: "https://github.com/OperatorFoundation/TransmissionAsync", branch: "main"), ], targets: [ @@ -39,15 +35,9 @@ let package = Package( "Ghostwriter", "KeychainTypes", "Monolith", - "Net", "ShadowSwift", - "Song", - .product(name: "Simulation", package: "Spacetime"), - .product(name: "Spacetime", package: "Spacetime"), - .product(name: "Universe", package: "Spacetime"), "SwiftHexTools", "SwiftQueue", - "Transmission", "TransmissionAsync" ] ), diff --git a/Sources/ReplicantSwift/Config/ConfigGenerator.swift b/Sources/ReplicantSwift/Config/ConfigGenerator.swift deleted file mode 100644 index 1d0afdb..0000000 --- a/Sources/ReplicantSwift/Config/ConfigGenerator.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// ConfigGenerator.swift -// -// -// Created by Joshua Clark on 12/19/22. -// - -import Crypto -import Foundation - -import Gardener -import KeychainTypes -import ShadowSwift - -public func createNewConfigFiles(inDirectory saveDirectory: URL, serverAddress: String, polish: Bool, toneburst: Bool) -> Bool -{ - guard saveDirectory.isDirectory else - { - print("The provided destination is not a directory: \(saveDirectory.path)") - return false - } - - guard let newConfigs = generateNewConfigPair(serverAddress: serverAddress, usePolish: polish, useToneburst: toneburst) else - { - return false - } - - guard let clientJson = newConfigs.clientConfig.createJSON() else { - return false - } - - guard let serverJson = newConfigs.serverConfig.createJSON() else { - return false - } - - let serverConfigFilename = "ReplicantServerConfig.json" - let serverConfigFilePath = saveDirectory.appendingPathComponent(serverConfigFilename).path - - guard File.put(serverConfigFilePath, contents: serverJson) else - { - return false - } - - let clientConfigFilename = "ReplicantClientConfig.json" - let clientConfigFilePath = saveDirectory.appendingPathComponent(clientConfigFilename).path - - guard File.put(clientConfigFilePath, contents: clientJson) else - { - return false - } - - return true -} - -public func generateNewConfigPair(serverAddress: String, usePolish: Bool, useToneburst: Bool) -> (serverConfig: ReplicantServerConfig, clientConfig: ReplicantClientConfig)? -{ - var toneBurstClient: ToneBurst? = nil - var toneBurstServer: ToneBurst? = nil - var polishClientConfig: PolishClientConfig? = nil - var polishServerConfig: PolishServerConfig? = nil - - if useToneburst { - toneBurstClient = Starburst(.SMTPClient) - toneBurstServer = Starburst(.SMTPServer) - } - - if usePolish { - let compactRepresentable = P256.KeyAgreement.PrivateKey(compactRepresentable: true) - print("raw representation: \(compactRepresentable.rawRepresentation.hex) (count: \(compactRepresentable.rawRepresentation.count)) | x963 representation: \(compactRepresentable.x963Representation.hex) (count: \(compactRepresentable.x963Representation.count)") - let privateKey = PrivateKey.P256KeyAgreement(compactRepresentable) - - let publicKey = privateKey.publicKey - polishClientConfig = PolishClientConfig(serverAddress: serverAddress, serverPublicKey: publicKey) - polishServerConfig = PolishServerConfig(serverAddress: serverAddress, serverPrivateKey: privateKey) - } - - guard let clientConfig = ReplicantClientConfig(serverAddress: serverAddress, polish: polishClientConfig, toneBurst: toneBurstClient, transport: "Replicant") else - { - return nil - } - - let serverConfig = ReplicantServerConfig(serverAddress: serverAddress, polish: polishServerConfig, toneBurst: toneBurstServer, transport: "Replicant") - - return (serverConfig, clientConfig) -} diff --git a/Sources/ReplicantSwift/Config/ReplicantClientConfig.swift b/Sources/ReplicantSwift/Config/ReplicantClientConfig.swift deleted file mode 100644 index fbbada1..0000000 --- a/Sources/ReplicantSwift/Config/ReplicantClientConfig.swift +++ /dev/null @@ -1,155 +0,0 @@ -// -// ReplicantConfig.swift -// ReplicantSwift -// -// Created by Adelita Schule on 11/14/18. -// - -import Foundation -import Song - -public struct ReplicantClientConfig -{ - public let serverAddress: String - public let serverIP: String - public let serverPort: UInt16 - public var polish: PolishClientConfig? - public var toneBurst: ToneBurst? - public var transport: String - - enum CodingKeys: String, CodingKey - { - case serverAddress - case polish - case toneburst - case transport - } - - public init?(from data: Data) - { - let decoder = JSONDecoder() - do - { - let decoded = try decoder.decode(ReplicantClientConfig.self, from: data) - let toneBurst = decoded.toneBurst - let maybePolishConfig = decoded.polish - - self.init(serverAddress: decoded.serverAddress, polish: maybePolishConfig, toneBurst: toneBurst, transport: decoded.transport) - } - catch let decodeError - { - print("Error decoding ReplicantConfig data: \(decodeError)") - return nil - } - } - - public init?(serverAddress: String, polish maybePolish: PolishClientConfig?, toneBurst maybeToneBurst: ToneBurst?, transport: String) - { - let addressStrings = serverAddress.split(separator: ":") - let ipAddress = String(addressStrings[0]) - guard let port = UInt16(addressStrings[1]) else - { - print("Error decoding ReplicantConfig data: invalid server port") - return nil - } - - self.init(serverAddress: serverAddress, serverIP: ipAddress, serverPort: port, polish: maybePolish, toneBurst: maybeToneBurst, transport: transport) - } - - public init(serverAddress: String, serverIP: String, serverPort: UInt16, polish maybePolish: PolishClientConfig?, toneBurst maybeToneBurst: ToneBurst?, transport: String) - { - self.serverAddress = serverAddress - self.serverIP = serverIP - self.serverPort = serverPort - self.polish = maybePolish - self.toneBurst = maybeToneBurst - self.transport = transport - } - - public init?(withConfigAtPath path: String) - { - let url = URL(fileURLWithPath: path) - guard let data = try? Data(contentsOf: url) else {return nil} - self.init(from: data) - } - - public func createSong(filePath: String) throws - { - let songEncoder = SongEncoder() - let songData = try songEncoder.encode(self) - let dirURL = URL(fileURLWithPath: filePath) - - try songData.write(to: dirURL) - } - - /// Creates and returns a JSON representation of the ReplicantConfig struct. - public func createJSON() -> Data? - { - let encoder = JSONEncoder() - encoder.outputFormatting.insert(.prettyPrinted) - encoder.outputFormatting.insert(.withoutEscapingSlashes) - - do - { - let configData = try encoder.encode(self) - return configData - } - catch (let error) - { - print("Failed to encode config into JSON format: \(error)") - return nil - } - } -} - -extension ReplicantClientConfig: Encodable -{ - public func encode(to encoder: Encoder) throws - { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(serverAddress, forKey: .serverAddress) - try container.encode(transport, forKey: .transport) - try container.encode(polish, forKey: .polish) - - // TODO: Add additional Toneburst types here - if let toneBurst = toneBurst - { - switch toneBurst.type - { - case .starburst: - if let starburst = toneBurst as? Starburst - { - try container.encode(starburst, forKey: .toneburst) - } - } - } - } -} - -extension ReplicantClientConfig: Decodable -{ - public init(from decoder: Decoder) throws - { - let container = try decoder.container(keyedBy: CodingKeys.self) - let address = try container.decode(String.self, forKey: .serverAddress) - let addressStrings = address.split(separator: ":") - let ipAddress = String(addressStrings[0]) - guard let port = UInt16(addressStrings[1]) else - { - print("Error decoding ReplicantConfig data: invalid server port") - throw ReplicantError.decoderFailure - } - - self.serverAddress = address - self.serverIP = ipAddress - self.serverPort = port - self.transport = try container.decode(String.self, forKey: .transport) - self.polish = try container.decodeIfPresent(PolishClientConfig.self, forKey: .polish) - - // TODO: Add additional Toneburst types here - if let starburst = try container.decodeIfPresent(Starburst.self, forKey: .toneburst) - { - self.toneBurst = starburst - } - } -} diff --git a/Sources/ReplicantSwift/Config/ReplicantServerConfig.swift b/Sources/ReplicantSwift/Config/ReplicantServerConfig.swift deleted file mode 100644 index 312fc15..0000000 --- a/Sources/ReplicantSwift/Config/ReplicantServerConfig.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// ReplicantServerConfig.swift -// ReplicantSwift -// -// Created by Adelita Schule on 12/7/18. -// - -import Foundation - -public struct ReplicantServerConfig -{ - public let serverAddress: String - public var polish: PolishServerConfig? - public var toneBurst: ToneBurst? - public var transport: String - - enum CodingKeys: String, CodingKey - { - case serverAddress - case polish - case toneburst - case transport - } - - public init(serverAddress: String, polish maybePolish: PolishServerConfig?, toneBurst maybeToneBurst: ToneBurst?, transport: String) - { - self.serverAddress = serverAddress - self.polish = maybePolish - self.toneBurst = maybeToneBurst - self.transport = transport - } - - public init?(withConfigAtPath path: String) - { - guard let config = ReplicantServerConfig.parseJSON(atPath: path) - else - { - return nil - } - - self = config - } - - /// Creates and returns a JSON representation of the ReplicantServerConfig struct. - public func createJSON() -> Data? - { - let encoder = JSONEncoder() - encoder.outputFormatting.insert(.prettyPrinted) - encoder.outputFormatting.insert(.withoutEscapingSlashes) - - do - { - let serverConfigData = try encoder.encode(self) - return serverConfigData - } - catch (let error) - { - print("Failed to encode Server config into JSON format: \(error)") - return nil - } - } - - /// Checks for a valid JSON at the provided path and attempts to decode it into a Replicant server configuration file. Returns a ReplicantConfig struct if it is successful - /// - Parameters: - /// - path: The complete path where the config file is located. - /// - Returns: The ReplicantServerConfig struct that was decoded from the JSON file located at the provided path, or nil if the file was invalid or missing. - static public func parseJSON(atPath path: String) -> ReplicantServerConfig? - { - let filemanager = FileManager() - let decoder = JSONDecoder() - - guard let jsonData = filemanager.contents(atPath: path) - else - { - print("\nUnable to get JSON data at path: \(path)\n") - return nil - } - - do - { - let jsonConfig = try decoder.decode(ReplicantServerConfig.self, from: jsonData) - let config = ReplicantServerConfig(serverAddress: jsonConfig.serverAddress, polish: jsonConfig.polish, toneBurst: jsonConfig.toneBurst, transport: jsonConfig.transport) - - return config - } - catch (let error) - { - print("\nUnable to decode JSON into ReplicantServerConfig: \(error)\n") - return nil - } - } -} - -extension ReplicantServerConfig: Encodable -{ - public func encode(to encoder: Encoder) throws - { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(serverAddress, forKey: .serverAddress) - try container.encode(transport, forKey: .transport) - try container.encode(polish, forKey: .polish) - - // TODO: Add additional Toneburst types here - if let starburst = toneBurst as? Starburst - { - try container.encode(starburst, forKey: .toneburst) - } - } -} - -extension ReplicantServerConfig: Decodable -{ - public init(from decoder: Decoder) throws - { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.serverAddress = try container.decode(String.self, forKey: .serverAddress) - self.transport = try container.decode(String.self, forKey: .transport) - self.polish = try container.decodeIfPresent(PolishServerConfig.self, forKey: .polish) - - // TODO: Add additional ToneBurst types here - if let starburst = try container.decodeIfPresent(Starburst.self, forKey: .toneburst) - { - self.toneBurst = starburst - } - } -} diff --git a/Sources/ReplicantSwift/Models/SequenceModel.swift b/Sources/ReplicantSwift/Models/SequenceModel.swift deleted file mode 100644 index 43df8bd..0000000 --- a/Sources/ReplicantSwift/Models/SequenceModel.swift +++ /dev/null @@ -1,39 +0,0 @@ -//// -//// SequenceModel.swift -//// ReplicantSwift -//// -//// Created by Adelita Schule on 11/15/18. -//// -// -//import Foundation -// -//public struct SequenceModel: Codable -//{ -// /// Byte Sequence. -// var sequence: Data -// -// /// Target sequence Length. -// var length: UInt -// -// public init?(sequence: Data, length: UInt) -// { -// ///FIXME: Is this still correct? Length must be no larger than 1440 bytes -// if length == 0 || length > 65535 -// { -// print("\nSequenceModel initialization failed: target length was either 0 or larger than 65535\n") -// return nil -// } -// -// self.sequence = sequence -// self.length = length -// } -//} -// -//extension SequenceModel: Equatable -//{ -// public static func == (lhs: SequenceModel, rhs: SequenceModel) -> Bool -// { -// return lhs.sequence == rhs.sequence && -// lhs.length == rhs.length -// } -//} diff --git a/Sources/ReplicantSwift/Polish/PolishAsync.swift b/Sources/ReplicantSwift/Polish/Polish.swift similarity index 72% rename from Sources/ReplicantSwift/Polish/PolishAsync.swift rename to Sources/ReplicantSwift/Polish/Polish.swift index b1ce2cf..05361ef 100644 --- a/Sources/ReplicantSwift/Polish/PolishAsync.swift +++ b/Sources/ReplicantSwift/Polish/Polish.swift @@ -10,7 +10,12 @@ import Foundation import Logging import TransmissionAsync -public protocol PolishAsync +public protocol Polish { func polish(_ connection: TransmissionAsync.AsyncConnection, _ logger: Logger) async throws -> TransmissionAsync.AsyncConnection } + +public enum PolishType: String, Codable +{ + case darkStar = "DarkStar" +} diff --git a/Sources/ReplicantSwift/Polish/PolishClientAsync.swift b/Sources/ReplicantSwift/Polish/PolishClientAsync.swift deleted file mode 100644 index 8c70cb0..0000000 --- a/Sources/ReplicantSwift/Polish/PolishClientAsync.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// PolishClientConfig.swift -// -// -// Created by Dr. Brandon Wiley on 6/7/22. -// - -import Foundation - -import Logging - -import KeychainTypes -import Net -import ShadowSwift -import TransmissionAsync - -extension PolishClientConfig: PolishAsync -{ - public func polish(_ connection: TransmissionAsync.AsyncConnection, _ logger: Logger) async throws -> TransmissionAsync.AsyncConnection - { - let config = try ShadowConfig.ShadowClientConfig(serverAddress: self.serverAddress, serverPublicKey: self.serverPublicKey, mode: .DARKSTAR) - return try await AsyncDarkstarClientConnection(connection, config, logger) - } -} diff --git a/Sources/ReplicantSwift/Polish/PolishClientConfig.swift b/Sources/ReplicantSwift/Polish/PolishClientConfig.swift index 6b847a2..27e3b8d 100644 --- a/Sources/ReplicantSwift/Polish/PolishClientConfig.swift +++ b/Sources/ReplicantSwift/Polish/PolishClientConfig.swift @@ -9,10 +9,8 @@ import Foundation import Logging import KeychainTypes -import Net import ShadowSwift -import TransmissionTypes -import TransmissionTransport +import TransmissionAsync public class PolishClientConfig: Codable { @@ -25,36 +23,12 @@ public class PolishClientConfig: Codable } } -extension PolishClientConfig: PolishConfig +extension PolishClientConfig: Polish { - public func polish(_ connection: TransmissionTypes.Connection, _ logger: Logger) throws -> TransmissionTypes.Connection + public func polish(_ connection: TransmissionAsync.AsyncConnection, _ logger: Logger) async throws -> TransmissionAsync.AsyncConnection { - let addressArray = self.serverAddress.split(separator: ":") - let host = String(addressArray[0]) - guard let port = UInt16(addressArray[1]) else { - throw PolishClientConfigError.invalidPort - } - - guard let ipv4 = IPv4Address(host) else - { - throw PolishClientConfigError.notV4Address(host) - } - - let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host.ipv4(ipv4), port: NWEndpoint.Port(integerLiteral: port)) let config = try ShadowConfig.ShadowClientConfig(serverAddress: self.serverAddress, serverPublicKey: self.serverPublicKey, mode: .DARKSTAR) - - guard let result = DarkStarClientConnection(connection: connection, endpoint: endpoint, parameters: .tcp, config: config, logger: logger) else - { - throw PolishClientConfigError.nullDarkStarConnection - } - - guard let transmission = TransportToTransmissionConnection({return result}) else - { - throw PolishClientConfigError.transportToTransmissionFailed - } - - return transmission - + return try await AsyncDarkstarClientConnection(connection, config, logger) } } diff --git a/Sources/ReplicantSwift/Polish/PolishConfig.swift b/Sources/ReplicantSwift/Polish/PolishConfig.swift deleted file mode 100644 index f4bd796..0000000 --- a/Sources/ReplicantSwift/Polish/PolishConfig.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// File.swift -// -// -// Created by Mafalda on 1/19/22. -// - -import Foundation -import Logging - -import Net -import TransmissionTypes - -public protocol PolishConfig -{ - func polish(_ connection: TransmissionTypes.Connection, _ logger: Logger) throws -> TransmissionTypes.Connection -} - -public enum PolishType: String, Codable -{ - case darkStar = "DarkStar" -} - diff --git a/Sources/ReplicantSwift/Polish/PolishServerAsync.swift b/Sources/ReplicantSwift/Polish/PolishServerAsync.swift deleted file mode 100644 index d909935..0000000 --- a/Sources/ReplicantSwift/Polish/PolishServerAsync.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// PolishServerConfig.swift -// -// -// Created by Dr. Brandon Wiley on 6/7/22. -// - -import Foundation - -import Logging - -import KeychainTypes -import Net -import ShadowSwift -import TransmissionAsync - -extension PolishServerConfig: PolishAsync -{ - public func polish(_ connection: TransmissionAsync.AsyncConnection, _ logger: Logger) async throws -> TransmissionAsync.AsyncConnection - { - let config = try ShadowConfig.ShadowServerConfig(serverAddress: self.serverAddress, serverPrivateKey: self.serverPrivateKey, mode: .DARKSTAR) - return try await AsyncDarkstarServerConnection(connection, config, logger) - } -} diff --git a/Sources/ReplicantSwift/Polish/PolishServerConfig.swift b/Sources/ReplicantSwift/Polish/PolishServerConfig.swift index 365bbff..b06ddbe 100644 --- a/Sources/ReplicantSwift/Polish/PolishServerConfig.swift +++ b/Sources/ReplicantSwift/Polish/PolishServerConfig.swift @@ -9,10 +9,8 @@ import Foundation import Logging import KeychainTypes -import Net import ShadowSwift -import TransmissionTypes -import TransmissionTransport +import TransmissionAsync public class PolishServerConfig: Codable { @@ -25,33 +23,12 @@ public class PolishServerConfig: Codable } } -extension PolishServerConfig: PolishConfig +extension PolishServerConfig: Polish { - public func polish(_ connection: TransmissionTypes.Connection, _ logger: Logger) throws -> TransmissionTypes.Connection + public func polish(_ connection: TransmissionAsync.AsyncConnection, _ logger: Logger) async throws -> TransmissionAsync.AsyncConnection { - let addressArray = self.serverAddress.split(separator: ":") - let host = String(addressArray[0]) - guard let port = UInt16(addressArray[1]) else { - throw PolishServerConfigError.invalidPort - } - guard let ipv4 = IPv4Address(host) else - { - throw PolishServerConfigError.notV4Address(host) - } - - let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host.ipv4(ipv4), port: NWEndpoint.Port(integerLiteral: port)) let config = try ShadowConfig.ShadowServerConfig(serverAddress: self.serverAddress, serverPrivateKey: self.serverPrivateKey, mode: .DARKSTAR) - guard let result = DarkStarServerConnection(connection: connection, endpoint: endpoint, parameters: .tcp, config: config, logger: logger) else - { - throw PolishServerConfigError.nullDarkStarConnection - } - - guard let transmission = TransportToTransmissionConnection({return result}) else - { - throw PolishServerConfigError.transportToTransmissionFailed - } - - return transmission + return try await AsyncDarkstarServerConnection(connection, config, logger) } } diff --git a/Sources/ReplicantSwift/Async/ReplicantAsync.swift b/Sources/ReplicantSwift/Replicant.swift similarity index 66% rename from Sources/ReplicantSwift/Async/ReplicantAsync.swift rename to Sources/ReplicantSwift/Replicant.swift index 6b73356..cb2c400 100644 --- a/Sources/ReplicantSwift/Async/ReplicantAsync.swift +++ b/Sources/ReplicantSwift/Replicant.swift @@ -10,7 +10,7 @@ import Foundation import Logging import TransmissionAsync -public class ReplicantAsync +public class Replicant { var logger: Logger @@ -19,18 +19,18 @@ public class ReplicantAsync self.logger = logger } - public func listen(serverIP: String, port: Int, config: ReplicantConfigAsync.ServerConfig) async throws -> TransmissionAsync.AsyncListener + public func listen(serverIP: String, port: Int, config: ReplicantConfig.ServerConfig) async throws -> TransmissionAsync.AsyncListener { - return try ReplicantListenerAsync(config: config, logger: logger) + return try ReplicantListener(config: config, logger: logger) } - public func connect(host: String, port: Int, config: ReplicantConfigAsync.ClientConfig) async throws -> TransmissionAsync.AsyncConnection + public func connect(host: String, port: Int, config: ReplicantConfig.ClientConfig) async throws -> TransmissionAsync.AsyncConnection { let network = try await AsyncTcpSocketConnection(host, port, logger) - return try await self.replicantClientTransformationAsync(connection: network, config: config, logger: logger) + return try await self.replicantClientTransformation(connection: network, config: config, logger: logger) } - public func replicantClientTransformationAsync(connection: TransmissionAsync.AsyncConnection, config: ReplicantConfigAsync.ClientConfig, logger: Logger) async throws -> TransmissionAsync.AsyncConnection + public func replicantClientTransformation(connection: TransmissionAsync.AsyncConnection, config: ReplicantConfig.ClientConfig, logger: Logger) async throws -> TransmissionAsync.AsyncConnection { var result: TransmissionAsync.AsyncConnection = connection @@ -39,7 +39,7 @@ public class ReplicantAsync case .starburst: switch config.toneburst { - case let starBurst as StarburstAsync: + case let starBurst as Starburst: try await starBurst.perform(connection: connection) case let omnitone as Omnitone: diff --git a/Sources/ReplicantSwift/Config/ReplicantConfigAsync.swift b/Sources/ReplicantSwift/ReplicantConfig.swift similarity index 95% rename from Sources/ReplicantSwift/Config/ReplicantConfigAsync.swift rename to Sources/ReplicantSwift/ReplicantConfig.swift index e31df33..058f909 100644 --- a/Sources/ReplicantSwift/Config/ReplicantConfigAsync.swift +++ b/Sources/ReplicantSwift/ReplicantConfig.swift @@ -11,13 +11,12 @@ import Foundation import Gardener import KeychainTypes -// TODO: Should eventually replace the old non-async Replicant config classes -public class ReplicantConfigAsync +public class ReplicantConfig { public static func generateNewConfigPair(serverAddress: String, polish: Bool, toneburstType: ToneBurstType?) throws -> (serverConfig: ServerConfig, clientConfig: ClientConfig) { - var toneburstClient: ToneBurstAsync? = nil - var toneburstServer: ToneBurstAsync? = nil + var toneburstClient: ToneBurst? = nil + var toneburstServer: ToneBurst? = nil var polishClient: PolishClientConfig? = nil var polishServer: PolishServerConfig? = nil @@ -25,8 +24,8 @@ public class ReplicantConfigAsync switch toneburstType { case .starburst: - toneburstClient = StarburstAsync(.SMTPClient) - toneburstServer = StarburstAsync(.SMTPServer) + toneburstClient = Starburst(.SMTPClient) + toneburstServer = Starburst(.SMTPServer) case .none: toneburstClient = nil toneburstServer = nil @@ -82,7 +81,7 @@ public class ReplicantConfigAsync public let serverIP: String public let serverPort: UInt16 public let polish: PolishServerConfig? - public let toneburst: ToneBurstAsync? + public let toneburst: ToneBurst? public let toneburstType: ToneBurstType? public var transportName = "replicant" @@ -95,7 +94,7 @@ public class ReplicantConfigAsync case transportName = "transport" } - public init(serverAddress: String, polish maybePolish: PolishServerConfig?, toneBurst maybeToneBurst: ToneBurstAsync?) throws + public init(serverAddress: String, polish maybePolish: PolishServerConfig?, toneBurst maybeToneBurst: ToneBurst?) throws { let addressStrings = serverAddress.replacingOccurrences(of: " ", with: "").split(separator: ":") guard let port = UInt16(addressStrings[1]) else @@ -167,7 +166,7 @@ public class ReplicantConfigAsync switch self.toneburstType { case .starburst: - self.toneburst = try container.decodeIfPresent(StarburstAsync.self, forKey: .toneburst) + self.toneburst = try container.decodeIfPresent(Starburst.self, forKey: .toneburst) case .none: print("No supported Toneburst type was indicated while decoding a Replicant server config. Skipping Toneburst setup.") self.toneburst = nil @@ -220,7 +219,7 @@ public class ReplicantConfigAsync public let serverIP: String public let serverPort: UInt16 public let polish: PolishClientConfig? - public let toneburst: ToneBurstAsync? + public let toneburst: ToneBurst? public let toneburstType: ToneBurstType? public var transportName = "replicant" @@ -233,7 +232,7 @@ public class ReplicantConfigAsync case transportName = "transport" } - public init(serverAddress: String, polish maybePolish: PolishClientConfig?, toneBurst maybeToneBurst: ToneBurstAsync?) throws + public init(serverAddress: String, polish maybePolish: PolishClientConfig?, toneBurst maybeToneBurst: ToneBurst?) throws { let addressStrings = serverAddress.replacingOccurrences(of: " ", with: "").split(separator: ":") guard let port = UInt16(addressStrings[1]) else @@ -305,7 +304,7 @@ public class ReplicantConfigAsync switch self.toneburstType { case .starburst: - self.toneburst = try container.decodeIfPresent(StarburstAsync.self, forKey: .toneburst) + self.toneburst = try container.decodeIfPresent(Starburst.self, forKey: .toneburst) case .none: print("No supported Toneburst type was indicated while decoding a Replicant client config. Skipping Toneburst setup.") self.toneburst = nil diff --git a/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift b/Sources/ReplicantSwift/ReplicantListener.swift similarity index 85% rename from Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift rename to Sources/ReplicantSwift/ReplicantListener.swift index 33f7e9d..a196dd1 100644 --- a/Sources/ReplicantSwift/Async/ReplicantListenerAsync.swift +++ b/Sources/ReplicantSwift/ReplicantListener.swift @@ -11,13 +11,13 @@ import Logging import TransmissionAsync // This is just a normal TCP listener, except for the config and special accept behavior. -open class ReplicantListenerAsync: TransmissionAsync.AsyncListener +open class ReplicantListener: TransmissionAsync.AsyncListener { - let config: ReplicantConfigAsync.ServerConfig + let config: ReplicantConfig.ServerConfig let logger: Logger let listener: AsyncListener - public init(config: ReplicantConfigAsync.ServerConfig, logger: Logger) throws + public init(config: ReplicantConfig.ServerConfig, logger: Logger) throws { self.config = config self.logger = logger @@ -35,7 +35,7 @@ open class ReplicantListenerAsync: TransmissionAsync.AsyncListener try await self.listener.close() } - public func replicantServerTransformation(connection: TransmissionAsync.AsyncConnection, config: ReplicantConfigAsync.ServerConfig, logger: Logger) async throws -> TransmissionAsync.AsyncConnection + public func replicantServerTransformation(connection: TransmissionAsync.AsyncConnection, config: ReplicantConfig.ServerConfig, logger: Logger) async throws -> TransmissionAsync.AsyncConnection { var result: TransmissionAsync.AsyncConnection = connection @@ -43,7 +43,7 @@ open class ReplicantListenerAsync: TransmissionAsync.AsyncListener switch config.toneburstType { case .starburst: - if let starburst = config.toneburst as? StarburstAsync + if let starburst = config.toneburst as? Starburst { try await starburst.perform(connection: connection) } diff --git a/Sources/ReplicantSwift/Spacetime/Replicant.swift b/Sources/ReplicantSwift/Spacetime/Replicant.swift deleted file mode 100644 index 4bdc687..0000000 --- a/Sources/ReplicantSwift/Spacetime/Replicant.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Replicant.swift -// -// -// Created by Dr. Brandon Wiley on 6/14/22. -// - -import Foundation -import Logging - -import Simulation -import Spacetime -import TransmissionTypes -import Universe - -public class Replicant -{ - var logger: Logger - let simulation: Simulation - let universe: ReplicantUniverse - - public init(logger: Logger) - { - self.logger = logger - self.simulation = Simulation(capabilities: Capabilities(.display, .random, .networkConnect, .networkListen)) - - // TODO: Update universe to accept Logger instead of os.Logger - self.universe = ReplicantUniverse(effects: self.simulation.effects, events: self.simulation.events, logger: nil) - } - - public func listen(address: String, port: Int, config: ReplicantServerConfig) throws -> TransmissionTypes.Listener - { - return try self.universe.replicantListen(address, port, config: config, logger: self.logger) - } - - public func connect(host: String, port: Int, config: ReplicantClientConfig) throws -> TransmissionTypes.Connection - { - return try self.universe.replicantConnect(host, port, config: config, self.logger) - } -} diff --git a/Sources/ReplicantSwift/Spacetime/ReplicantUniverse.swift b/Sources/ReplicantSwift/Spacetime/ReplicantUniverse.swift deleted file mode 100644 index 56d13ae..0000000 --- a/Sources/ReplicantSwift/Spacetime/ReplicantUniverse.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// ReplicantUniverse.swift -// -// -// Created by Dr. Brandon Wiley on 6/7/22. -// - -import Foundation -import Logging - -import Ghostwriter -import Spacetime -import TransmissionTypes -import Universe - -public class ReplicantUniverse: Universe -{ - public func replicantListen(_ address: String, _ port: Int, config: ReplicantServerConfig, logger: Logger) throws -> UniverseListener - { - return try ReplicantUniverseListener(universe: self, address: address, port: port, config: config, logger: logger) - } - - public func replicantConnect(_ address: String, _ port: Int, config: ReplicantClientConfig, _ logger: Logger) throws -> TransmissionTypes.Connection - { - let network = try super.connect(address, port) - - guard let connection = network as? ConnectConnection else - { - throw ReplicantUniverseError.wrongConnectionType - } - - return try connection.replicantClientTransformation(config, logger) - } -} - -extension ListenConnection -{ - public func replicantServerTransformation(_ config: ReplicantServerConfig, _ logger: Logger) throws -> TransmissionTypes.Connection - { - var result: TransmissionTypes.Connection = self - - // TODO: Add more ToneBurst types as they become available - if let starBurst = config.toneBurst as? Starburst - { - try starBurst.perform(connection: self) - } - - if let polishConfig = config.polish - { - result = try polishConfig.polish(result, logger) - } - - return result - } -} - -extension ConnectConnection -{ - public func replicantClientTransformation(_ config: ReplicantClientConfig, _ logger: Logger) throws -> TransmissionTypes.Connection - { - var result: TransmissionTypes.Connection = self - - if let starBurst = config.toneBurst as? Starburst - { - try starBurst.perform(connection: self) - } - - if let polishConfig = config.polish - { - result = try polishConfig.polish(result, logger) - } - - return result - } -} - -public enum ReplicantUniverseError: Error -{ - case wrongConnectionType - - // Starburst - case speakFailed - case listenFailed - case wrongResultType - case waitFailed -} diff --git a/Sources/ReplicantSwift/Spacetime/ReplicantUniverseListener.swift b/Sources/ReplicantSwift/Spacetime/ReplicantUniverseListener.swift deleted file mode 100644 index dea6a30..0000000 --- a/Sources/ReplicantSwift/Spacetime/ReplicantUniverseListener.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// ReplicantUniverseListener.swift -// -// -// Created by Dr. Brandon Wiley on 6/7/22. -// - -import Foundation -import Logging - -import Spacetime -import TransmissionTypes -import Universe - -// This is just a normal TCP listener, except for the config and special accept behavior. -open class ReplicantUniverseListener: UniverseListener -{ - let config: ReplicantServerConfig - let logger: Logger - - public init(universe: Universe, address: String, port: Int, config: ReplicantServerConfig, logger: Logger) throws - { - self.config = config - self.logger = logger - - try super.init(universe: universe, address: address, port: port) - } - - override open func accept() throws -> TransmissionTypes.Connection - { - let network = try super.accept() - - guard let connection = network as? ListenConnection else - { - throw ReplicantUniverseListenerError.wrongListenerType - } - - return try connection.replicantServerTransformation(config, logger) - } -} - -public enum ReplicantUniverseListenerError: Error -{ - case wrongListenerType -} diff --git a/Sources/ReplicantSwift/ToneBurst/Monotone/Monotone.swift b/Sources/ReplicantSwift/ToneBurst/Monotone/Monotone.swift deleted file mode 100644 index 3b9314d..0000000 --- a/Sources/ReplicantSwift/ToneBurst/Monotone/Monotone.swift +++ /dev/null @@ -1,138 +0,0 @@ -//// -//// Monotone.swift -//// -//// -//// Created by Mafalda on 2/8/22. -//// -// -//import Foundation -// -//import Monolith -//import Transmission -// -//public class Monotone: ToneBurst -//{ -// let config: MonotoneConfig -// var buffer: Buffer -// var context: Context -// -// public init(config: MonotoneConfig) -// { -// self.config = config -// self.buffer = Buffer() -// self.context = Context() -// } -// -// public func perform(connection: Connection) throws -// { -// var addMessages: [Message] = config.addSequences.messages() -// var removeParts: [MonolithPart] = config.removeSequences.parts -// -// if config.speakFirst -// { -// guard !addMessages.isEmpty else -// { -// completion(MonotoneError.speakFirstNoAddSequence) -// return -// } -// -// //Pop the first sequence in the list of add sequences -// var firstMessage = addMessages.removeFirst() -// let addBytes = firstMessage.bytes() -// -// // Send the first message -// writeAll(connection: connection, addBytes: Data(addBytes)) -// { -// (maybeWriteError) in -// -// if let writeError = maybeWriteError -// { -// completion(writeError) -// return -// } -// } -// } -// -// while (!removeParts.isEmpty && !addMessages.isEmpty) -// { -// if !removeParts.isEmpty -// { -// let firstPart = removeParts.removeFirst() -// -// readAll(connection: connection, part: firstPart) -// { -// (maybeReadError) in -// -// if let readError = maybeReadError -// { -// completion(readError) -// return -// } -// } -// } -// -// if !addMessages.isEmpty -// { -// var nextMessage = addMessages.removeFirst() -// let nextAddBytes = nextMessage.bytes() -// -// // Send the first message -// writeAll(connection: connection, addBytes: Data(nextAddBytes)) -// { -// (maybeWriteError) in -// -// if let writeError = maybeWriteError -// { -// completion(writeError) -// return -// } -// } -// } -// } -// } -// -// func writeAll(connection: Transmission.Connection, addBytes: Data, completion: @escaping (Error?) -> Void) -// { -// let writeSucceeded = connection.write(data: addBytes) -// -// if writeSucceeded -// { -// completion(nil) -// return -// } -// else -// { -// completion(MonotoneError.writeError) -// return -// } -// } -// -// -// func readAll(connection: Transmission.Connection, part: MonolithPart, completion: @escaping (Error?) -> Void) -// { -// switch part -// { -// case .bytes(let bytesPart): -// guard let receivedData = connection.read(size: bytesPart.count()) else -// { -// completion(MonotoneError.readError) -// return -// } -// -// buffer.push(bytes: receivedData.bytes) -// } -// -// let validated = part.validate(buffer: buffer, context: &context) -// -// switch validated -// { -// case .valid: -// completion(nil) -// case .invalid: -// completion(MonotoneError.receiveDataInvalid) -// case .incomplete: -// completion(MonotoneError.receiveDataIncomplete) -// } -// } -// -//} diff --git a/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneClient.swift b/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneClient.swift deleted file mode 100644 index a3de2be..0000000 --- a/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneClient.swift +++ /dev/null @@ -1,46 +0,0 @@ -//// -//// MonotoneClient.swift -//// ReplicantSwift -//// -//// Created by Mafalda on 11/14/19. -//// -// -//import Foundation -//import Datable -//import Transmission -////import Monolith -// -///// Injects byte sequences into a stream of bytes -//public class MonotoneClient: Whalesong -//{ -//} -// -//extension MonotoneClient: ToneBurst -//{ -// public func perform(connection: Transmission.Connection, completion: @escaping (Error?) -> Void) -// { -// -// let sendState = generate() -// -// switch sendState -// { -// case .generating(let nextTone): -// print("\nGenerating tone bursts.\n") -// guard connection.write(data: nextTone) else -// { -// print("Received error while sending tone burst") -// completion(MonotoneError.generateFailure) -// return -// } -// -// self.toneBurstReceive(connection: connection, finalToneSent: false, completion: completion) -// case .completion: -// print("\nGenerated final toneburst\n") -// toneBurstReceive(connection: connection, finalToneSent: true, completion: completion) -// -// case .failure: -// print("\nFailed to generate requested ToneBurst") -// completion(WhalesongError.generateFailure) -// } -// } -//} diff --git a/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneConfig.swift b/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneConfig.swift deleted file mode 100644 index 7750d19..0000000 --- a/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneConfig.swift +++ /dev/null @@ -1,32 +0,0 @@ -//// -//// Monotone.swift -//// ReplicantSwift -//// -//// Created by Mafalda on 11/14/19. -//// -// -//import Foundation -//import Transmission -// -//import Monolith -// -//public class MonotoneConfig: Codable -//{ -// var addSequences: Instance -// var removeSequences: Description -// let speakFirst: Bool -// -// public init(addSequences: Instance, removeSequences: Description, speakFirst: Bool) -// { -// self.addSequences = addSequences -// self.removeSequences = removeSequences -// self.speakFirst = speakFirst -// } -// -// public func construct() -> Monotone -// { -// let newMonotone = Monotone(config: self) -// -// return newMonotone -// } -//} diff --git a/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneServer.swift b/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneServer.swift deleted file mode 100644 index 167ff02..0000000 --- a/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneServer.swift +++ /dev/null @@ -1,23 +0,0 @@ -//// -//// MonotoneServer.swift -//// ReplicantSwift -//// -//// Created by Mafalda on 11/14/19. -//// -// -//import Foundation -//import Datable -//import Transmission -// -///// Injects byte sequences into a stream of bytes -//public class MonotoneServer: Whalesong -//{ -//} -// -//extension MonotoneServer: ToneBurst -//{ -// public func perform(connection: Transmission.Connection, completion: @escaping (Error?) -> Void) -// { -// self.toneBurstReceive(connection: connection, finalToneSent: false, completion: completion) -// } -//} diff --git a/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneState.swift b/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneState.swift deleted file mode 100644 index 50ab22a..0000000 --- a/Sources/ReplicantSwift/ToneBurst/Monotone/MonotoneState.swift +++ /dev/null @@ -1,43 +0,0 @@ -//// -//// MonotoneState.swift -//// ReplicantSwift -//// -//// Created by Mafalda on 11/14/19. -//// -// -//import Foundation -// -//public enum MonotoneReceiveState -//{ -// case receiving -// case completion -// case failure -//} -// -//public enum MonotoneSendState -//{ -// case generating(Data) -// case completion -// case failure -//} -// -//enum MonotoneMatchState -//{ -// case insufficientData -// case success -// case failure -//} -// -//enum MonotoneError: Error -//{ -// case generateFailure -// case removeFailure -// -// case speakFirstNoAddSequence -// -// case writeError -// case readError -// -// case receiveDataInvalid -// case receiveDataIncomplete -//} diff --git a/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift b/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift index 3e6e1d8..9490a0e 100644 --- a/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift +++ b/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift @@ -17,7 +17,7 @@ public enum OmnitoneMode: String, Codable } -public class Omnitone: ToneBurstAsync, Codable +public class Omnitone: ToneBurst, Codable { public var type: ReplicantSwift.ToneBurstType = .starburst @@ -75,7 +75,7 @@ public struct OmnitoneInstance continue } - let result = structuredText.match(string: string) + let result = try structuredText.match(string: string) switch result { case .FAILURE: diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift index 81d3c78..2eb79b8 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift @@ -1,6 +1,6 @@ // // Starburst.swift -// +// // // Created by Dr. Brandon Wiley on 5/9/22. // @@ -10,9 +10,9 @@ import Foundation import Chord import Datable import Ghostwriter -import Transmission +import TransmissionAsync -public class Starburst: ToneBurst, Codable +open class Starburst: ToneBurst, Codable { public var type: ToneBurstType = .starburst @@ -23,70 +23,70 @@ public class Starburst: ToneBurst, Codable self.mode = mode } - public func perform(connection: Transmission.Connection) throws + open func perform(connection: TransmissionAsync.AsyncConnection) async throws { - let instance = StarburstInstance(self.mode, connection) - try instance.perform() + let instance = StarburstInstanceAsync(self.mode, connection) + try await instance.perform() } } -public struct StarburstInstance +public struct StarburstInstanceAsync { - let connection: Transmission.Connection + let connection: TransmissionAsync.AsyncConnection let mode: StarburstMode - public init(_ mode: StarburstMode, _ connection: Transmission.Connection) + public init(_ mode: StarburstMode, _ connection: TransmissionAsync.AsyncConnection) { self.mode = mode self.connection = connection } - public func perform() throws + public func perform() async throws { switch mode { case .SMTPClient: - try handleSMTPClient() + try await handleSMTPClient() case .SMTPServer: - try handleSMTPServer() + try await handleSMTPServer() } } - func handleSMTPClient() throws + func handleSMTPClient() async throws { guard let firstClientListen = ListenTemplate(Template("220 $1 SMTP service ready\r\n"), patterns: [ExtractionPattern("^([a-zA-Z0-9.-]+)", .string)], maxSize: 253, maxTimeoutSeconds: Int.max) else { throw StarburstError.listenFailed } - let _ = try listen(template: firstClientListen) + let _ = try await listen(template: firstClientListen) - try speak(template: Template("EHLO $1\r\n"), details: [Detail.string("mail.imc.org")]) + try await speak(template: Template("EHLO $1\r\n"), details: [Detail.string("mail.imc.org")]) guard let secondClientListen = ListenTemplate(Template("$1\r\n"), patterns: [ExtractionPattern("250 (STARTTLS)", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { throw StarburstError.listenFailed } - _ = try listen(template: secondClientListen) + _ = try await listen(template: secondClientListen) - try speak(string: "STARTTLS\r\n") + try await speak(string: "STARTTLS\r\n") guard let thirdClientListen = ListenTemplate(Template("$1\r\n"), patterns: [ExtractionPattern("^(.+)\r\n", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { throw StarburstError.listenFailed } - _ = try listen(template: thirdClientListen) + _ = try await listen(template: thirdClientListen) } - func handleSMTPServer() throws + func handleSMTPServer() async throws { - try speak(template: Template("220 $1 SMTP service ready\r\n"), details: [Detail.string("mail.imc.org")]) + try await speak(template: Template("220 $1 SMTP service ready\r\n"), details: [Detail.string("mail.imc.org")]) guard let firstServerListen = ListenTemplate(Template("EHLO $1\r\n"), patterns: [ExtractionPattern("^([a-zA-Z0-9.-]+)\r", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { throw StarburstError.listenFailed } - _ = try listen(template: firstServerListen) + _ = try await listen(template: firstServerListen) // % 5 is mod, which divides by five, discards the result, then returns the remainder let hour = Calendar.current.component(.hour, from: Date()) % 5 @@ -109,39 +109,30 @@ public struct StarburstInstance welcome = "" } - try speak(template: Template("250-$1 $2\r\n250-$3\r\n250-$4\r\n250 $5\r\n"), details: [Detail.string("mail.imc.org"), Detail.string(welcome), Detail.string("8BITMIME"), Detail.string("DSN"), Detail.string("STARTTLS")]) + try await speak(template: Template("250-$1 $2\r\n250-$3\r\n250-$4\r\n250 $5\r\n"), details: [Detail.string("mail.imc.org"), Detail.string(welcome), Detail.string("8BITMIME"), Detail.string("DSN"), Detail.string("STARTTLS")]) // FIXME: not sure about this size - let _: String = try listen(size: "STARTTLS\r\n".count + 1) // \r\n is counted as one on .count + let _: String = try await listen(size: "STARTTLS\r\n".count + 1) // \r\n is counted as one on .count - try speak(template: Template("220 $1\r\n"), details: [Detail.string("Go ahead")]) + try await speak(template: Template("220 $1\r\n"), details: [Detail.string("Go ahead")]) } - func speak(data: Data) throws + func speak(data: Data) async throws { - guard connection.write(data: data) else - { - throw StarburstError.writeFailed - } + try await connection.write(data) } - func speak(string: String) throws + func speak(string: String) async throws { - guard connection.write(string: string) else - { - throw StarburstError.writeFailed - } + try await connection.writeString(string: string) } - func speak(template: Template, details: [Detail]) throws + func speak(template: Template, details: [Detail]) async throws { do { let string = try Ghostwriter.generate(template, details) - guard connection.write(string: string) else - { - throw StarburstError.writeFailed - } + try await connection.writeString(string: string) } catch { @@ -150,15 +141,12 @@ public struct StarburstInstance } } - func speak(structuredText: StructuredText) throws + func speak(structuredText: StructuredText) async throws { do { let string = structuredText.string - guard connection.write(string: string) else - { - throw StarburstError.writeFailed - } + try await connection.writeString(string: string) } catch { @@ -167,119 +155,141 @@ public struct StarburstInstance } } - func listen(size: Int) throws -> Data + func listen(size: Int) async throws -> Data { - guard let data = connection.read(size: size) else - { - throw StarburstError.readFailed - } - - return data + return try await connection.readSize(size) } - func listen(size: Int) throws -> String + func listen(size: Int) async throws -> String { - guard let data = connection.read(size: size) else - { - throw StarburstError.readFailed - } - + let data = try await connection.readSize(size) return data.string } - func listen(template: ListenTemplate) throws -> [Detail] + func listen(template: ListenTemplate) async throws -> [Detail] { - let lock = DispatchSemaphore(value: 0) - let resultQueue = BlockingQueue<[Detail]?>() - let queue = DispatchQueue(label: "Starburst.listen") - var running = true + let listenTask: Task<[Detail]?, Error> = Task { + var buffer = Data() + while buffer.count < template.maxSize + { + do { + let byte = try await connection.readSize(1) + + buffer.append(byte) + + guard let string = String(data: buffer, encoding: .utf8) else + { + // This could fail because we're in the middle of a UTF8 rune. + continue + } + + do + { + return try Ghostwriter.parse(template.template, template.patterns, string) + } + catch + { + continue + } + } catch { + return nil + } + } + + return nil + } - queue.async + let _ = Task { + try await Task.sleep(for: .seconds(60)) + listenTask.cancel() + } + + do { + guard let result = try await listenTask.value else { + throw StarburstError.readFailed + } + return result + } catch { + throw StarburstError.timeout + } + } + + func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> String + { + let listenTask: Task = Task { var buffer = Data() - while buffer.count < template.maxSize && running + while buffer.count < maxSize { - guard let byte = connection.read(size: 1) else - { - resultQueue.enqueue(element: nil) - lock.signal() - return - } - - buffer.append(byte) - - guard let string = String(data: buffer, encoding: .utf8) else - { - // This could fail because we're in the middle of a UTF8 rune. - continue - } - do { - let details = try Ghostwriter.parse(template.template, template.patterns, string) - resultQueue.enqueue(element: details) - lock.signal() - return + let byte = try await connection.readSize(1) + + buffer.append(byte) + + guard let string = String(data: buffer, encoding: .utf8) else + { + // This could fail because we're in the middle of a UTF8 rune that is encoded as multiple bytes. + continue + } + + do + { + let matchResult = try structuredText.match(string: string) + + switch matchResult + { + case .SUCCESS(_): + return matchResult + + case .SHORT: + continue + + case .FAILURE: + throw StarburstError.listenFailed + } + } + catch + { + continue + } } catch { - continue + return nil } } - resultQueue.enqueue(element: nil) - lock.signal() - return + return nil } - - // .now() + DispatchTimeoutInterval.seconds(maxtimeoutinseconds) - let waitResult = lock.wait(timeout: DispatchTime.distantFuture) - switch waitResult + + let _ = Task { - case .success: - guard let details = resultQueue.dequeue() else - { - throw StarburstError.readFailed - } - - return details - - case .timedOut: - running = false - throw StarburstError.timeout + try await Task.sleep(for: timeout) + listenTask.cancel() } - } - - func listen(structuredText: StructuredText, maxSize: Int = 255) -> MatchResult - { - var buffer = Data() - while buffer.count < maxSize + + do { - guard let byte = connection.read(size: 1) else + guard let result = try await listenTask.value else { - return MatchResult.SHORT + throw StarburstError.readFailed } - - buffer.append(byte) - - guard let string = String(data: buffer, encoding: .utf8) else + + switch result { - // This could fail because we're in the middle of a UTF8 rune. - continue - } + case .SUCCESS(let value): + return value - do - { - return try structuredText.match(string: string) - } - catch - { - continue + default: + throw StarburstError.listenFailed } } - return MatchResult.SUCCESS("") + catch + { + throw StarburstError.timeout + } } - func wait(seconds: Double) { @@ -287,7 +297,6 @@ public struct StarburstInstance _ = lock.wait(timeout: .now() + seconds) } } - public enum StarburstError: Error { case timeout diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift deleted file mode 100644 index b48f5c6..0000000 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstAsync.swift +++ /dev/null @@ -1,299 +0,0 @@ -// -// Starburst.swift -// -// -// Created by Dr. Brandon Wiley on 5/9/22. -// - -import Foundation - -import Chord -import Datable -import Ghostwriter -import TransmissionAsync - -open class StarburstAsync: ToneBurstAsync, Codable -{ - public var type: ToneBurstType = .starburst - - let mode: StarburstMode - - public init(_ mode: StarburstMode) - { - self.mode = mode - } - - open func perform(connection: TransmissionAsync.AsyncConnection) async throws - { - let instance = StarburstInstanceAsync(self.mode, connection) - try await instance.perform() - } -} - -public struct StarburstInstanceAsync -{ - let connection: TransmissionAsync.AsyncConnection - let mode: StarburstMode - - public init(_ mode: StarburstMode, _ connection: TransmissionAsync.AsyncConnection) - { - self.mode = mode - self.connection = connection - } - - public func perform() async throws - { - switch mode - { - case .SMTPClient: - try await handleSMTPClient() - - case .SMTPServer: - try await handleSMTPServer() - } - } - - func handleSMTPClient() async throws - { - guard let firstClientListen = ListenTemplate(Template("220 $1 SMTP service ready\r\n"), patterns: [ExtractionPattern("^([a-zA-Z0-9.-]+)", .string)], maxSize: 253, maxTimeoutSeconds: Int.max) else { - throw StarburstError.listenFailed - } - - let _ = try await listen(template: firstClientListen) - - try await speak(template: Template("EHLO $1\r\n"), details: [Detail.string("mail.imc.org")]) - - guard let secondClientListen = ListenTemplate(Template("$1\r\n"), patterns: [ExtractionPattern("250 (STARTTLS)", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { - throw StarburstError.listenFailed - } - - _ = try await listen(template: secondClientListen) - - try await speak(string: "STARTTLS\r\n") - - guard let thirdClientListen = ListenTemplate(Template("$1\r\n"), patterns: [ExtractionPattern("^(.+)\r\n", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { - throw StarburstError.listenFailed - } - - _ = try await listen(template: thirdClientListen) - } - - func handleSMTPServer() async throws - { - try await speak(template: Template("220 $1 SMTP service ready\r\n"), details: [Detail.string("mail.imc.org")]) - - guard let firstServerListen = ListenTemplate(Template("EHLO $1\r\n"), patterns: [ExtractionPattern("^([a-zA-Z0-9.-]+)\r", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { - throw StarburstError.listenFailed - } - - _ = try await listen(template: firstServerListen) - - // % 5 is mod, which divides by five, discards the result, then returns the remainder - let hour = Calendar.current.component(.hour, from: Date()) % 5 - let welcome: String - switch hour - { - // These are all real SMTP welcome messages found in online examples of SMTP conversations. - case 0: - welcome = "offers a warm hug of welcome" - case 1: - welcome = "is my domain name." - case 2: - welcome = "I am glad to meet you" - case 3: - welcome = "says hello" - case 4: - welcome = "Hello" - - default: - welcome = "" - } - - try await speak(template: Template("250-$1 $2\r\n250-$3\r\n250-$4\r\n250 $5\r\n"), details: [Detail.string("mail.imc.org"), Detail.string(welcome), Detail.string("8BITMIME"), Detail.string("DSN"), Detail.string("STARTTLS")]) - - // FIXME: not sure about this size - let _: String = try await listen(size: "STARTTLS\r\n".count + 1) // \r\n is counted as one on .count - - try await speak(template: Template("220 $1\r\n"), details: [Detail.string("Go ahead")]) - } - - func speak(data: Data) async throws - { - try await connection.write(data) - } - - func speak(string: String) async throws - { - try await connection.writeString(string: string) - } - - func speak(template: Template, details: [Detail]) async throws - { - do - { - let string = try Ghostwriter.generate(template, details) - try await connection.writeString(string: string) - } - catch - { - print(error) - throw StarburstError.writeFailed - } - } - - func speak(structuredText: StructuredText) async throws - { - do - { - let string = structuredText.string - try await connection.writeString(string: string) - } - catch - { - print(error) - throw StarburstError.writeFailed - } - } - - func listen(size: Int) async throws -> Data - { - return try await connection.readSize(size) - } - - func listen(size: Int) async throws -> String - { - let data = try await connection.readSize(size) - return data.string - } - - func listen(template: ListenTemplate) async throws -> [Detail] - { - let listenTask: Task<[Detail]?, Error> = Task { - var buffer = Data() - while buffer.count < template.maxSize - { - do { - let byte = try await connection.readSize(1) - - buffer.append(byte) - - guard let string = String(data: buffer, encoding: .utf8) else - { - // This could fail because we're in the middle of a UTF8 rune. - continue - } - - do - { - return try Ghostwriter.parse(template.template, template.patterns, string) - } - catch - { - continue - } - } catch { - return nil - } - } - - return nil - } - - let _ = Task { - try await Task.sleep(for: .seconds(60)) - listenTask.cancel() - } - - do { - guard let result = try await listenTask.value else { - throw StarburstError.readFailed - } - return result - } catch { - throw StarburstError.timeout - } - } - - func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> String - { - let listenTask: Task = Task - { - var buffer = Data() - while buffer.count < maxSize - { - do - { - let byte = try await connection.readSize(1) - - buffer.append(byte) - - guard let string = String(data: buffer, encoding: .utf8) else - { - // This could fail because we're in the middle of a UTF8 rune that is encoded as multiple bytes. - continue - } - - do - { - let matchResult = try structuredText.match(string: string) - - switch matchResult - { - case .SUCCESS(_): - return matchResult - - case .SHORT: - continue - - case .FAILURE: - throw StarburstError.listenFailed - } - } - catch - { - continue - } - } - catch - { - return nil - } - } - - return nil - } - - let _ = Task - { - try await Task.sleep(for: timeout) - listenTask.cancel() - } - - do - { - guard let result = try await listenTask.value else - { - throw StarburstError.readFailed - } - - switch result - { - case .SUCCESS(let value): - return value - - default: - throw StarburstError.listenFailed - } - } - catch - { - throw StarburstError.timeout - } - } - - func wait(seconds: Double) - { - let lock = DispatchSemaphore(value: 0) - _ = lock.wait(timeout: .now() + seconds) - } -} diff --git a/Sources/ReplicantSwift/ToneBurst/ToneBurst.swift b/Sources/ReplicantSwift/ToneBurst/ToneBurst.swift deleted file mode 100644 index 8c769b9..0000000 --- a/Sources/ReplicantSwift/ToneBurst/ToneBurst.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// ToneBurst.swift -// ReplicantSwift -// -// Created by Adelita Schule on 11/9/18. -// - -import Foundation -import Datable -import Transmission - -/// Injects byte sequences into a stream of bytes -public protocol ToneBurst: Codable -{ - var type: ToneBurstType { get set } - - mutating func perform(connection: Transmission.Connection) throws -} - diff --git a/Sources/ReplicantSwift/ToneBurst/ToneburstAsync.swift b/Sources/ReplicantSwift/ToneBurst/Toneburst.swift similarity index 89% rename from Sources/ReplicantSwift/ToneBurst/ToneburstAsync.swift rename to Sources/ReplicantSwift/ToneBurst/Toneburst.swift index d664dd9..a603263 100644 --- a/Sources/ReplicantSwift/ToneBurst/ToneburstAsync.swift +++ b/Sources/ReplicantSwift/ToneBurst/Toneburst.swift @@ -10,7 +10,7 @@ import Datable import TransmissionAsync /// Injects byte sequences into a stream of bytes -public protocol ToneBurstAsync: Codable +public protocol ToneBurst: Codable { var type: ToneBurstType { get set } diff --git a/build.sh b/build.sh deleted file mode 100755 index f2ce6f1..0000000 --- a/build.sh +++ /dev/null @@ -1,2 +0,0 @@ -swift package update -swift build -Xswiftc "-target" -Xswiftc "x86_64-apple-macosx10.15" diff --git a/gen.sh b/gen.sh deleted file mode 100755 index 46395e7..0000000 --- a/gen.sh +++ /dev/null @@ -1,4 +0,0 @@ -swift package update -swift package generate-xcodeproj -rpl -R "10.10" "10.14" `basename $PWD`.xcodeproj/ -open `basename $PWD`.xcodeproj From db2e8f0e5ea4e3fdb8e6025172e0434eb30fe4aa Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 19 Mar 2024 13:35:22 -0500 Subject: [PATCH 22/28] Move Toneburst Subclasses Toneburst subclasses should be in the same codeburst as the specific transport --- Sources/ReplicantSwift/Replicant.swift | 41 +- Sources/ReplicantSwift/ReplicantConfig.swift | 361 ------------------ Sources/ReplicantSwift/ReplicantError.swift | 1 + .../ReplicantSwift/ReplicantListener.swift | 36 +- .../ToneBurst/Omnitone/Omnitone.swift | 142 ------- .../ToneBurst/Starburst/Starburst.swift | 16 +- .../ToneBurst/SupportedToneBursts.swift | 1 + .../ReplicantSwift/ToneBurst/Toneburst.swift | 9 +- 8 files changed, 40 insertions(+), 567 deletions(-) delete mode 100644 Sources/ReplicantSwift/ReplicantConfig.swift delete mode 100644 Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift diff --git a/Sources/ReplicantSwift/Replicant.swift b/Sources/ReplicantSwift/Replicant.swift index cb2c400..78fbdfa 100644 --- a/Sources/ReplicantSwift/Replicant.swift +++ b/Sources/ReplicantSwift/Replicant.swift @@ -12,49 +12,36 @@ import TransmissionAsync public class Replicant { + let toneburst: ToneBurst? + let polish: Polish? + var logger: Logger + - public init(logger: Logger) + public init(logger: Logger, polish: Polish?, toneburst: ToneBurst?) { self.logger = logger + self.polish = polish + self.toneburst = toneburst } - public func listen(serverIP: String, port: Int, config: ReplicantConfig.ServerConfig) async throws -> TransmissionAsync.AsyncListener + public func listen(serverIP: String, port: Int) async throws -> TransmissionAsync.AsyncListener { - return try ReplicantListener(config: config, logger: logger) + return try ReplicantListener(replicant: self, serverIP: serverIP, serverPort: port, logger: self.logger) } - public func connect(host: String, port: Int, config: ReplicantConfig.ClientConfig) async throws -> TransmissionAsync.AsyncConnection + public func connect(host: String, port: Int) async throws -> TransmissionAsync.AsyncConnection { let network = try await AsyncTcpSocketConnection(host, port, logger) - return try await self.replicantClientTransformation(connection: network, config: config, logger: logger) + return try await self.replicantClientTransformation(connection: network) } - public func replicantClientTransformation(connection: TransmissionAsync.AsyncConnection, config: ReplicantConfig.ClientConfig, logger: Logger) async throws -> TransmissionAsync.AsyncConnection + public func replicantClientTransformation(connection: TransmissionAsync.AsyncConnection) async throws -> TransmissionAsync.AsyncConnection { var result: TransmissionAsync.AsyncConnection = connection - - switch config.toneburstType - { - case .starburst: - switch config.toneburst - { - case let starBurst as Starburst: - try await starBurst.perform(connection: connection) - - case let omnitone as Omnitone: - try await omnitone.perform(connection: connection) - - default: - throw ReplicantError.invalidToneburst - } - - case .none: - print("replicantClientTransformationAsync skipping Toneburst: none provided") - } - + try await self.toneburst?.perform(connection: connection) - if let polishConfig = config.polish + if let polishConfig = self.polish { result = try await polishConfig.polish(result, logger) } diff --git a/Sources/ReplicantSwift/ReplicantConfig.swift b/Sources/ReplicantSwift/ReplicantConfig.swift deleted file mode 100644 index 058f909..0000000 --- a/Sources/ReplicantSwift/ReplicantConfig.swift +++ /dev/null @@ -1,361 +0,0 @@ -// -// ReplicantConfigAsync.swift -// -// -// Created by Mafalda on 3/12/24. -// - -import Crypto -import Foundation - -import Gardener -import KeychainTypes - -public class ReplicantConfig -{ - public static func generateNewConfigPair(serverAddress: String, polish: Bool, toneburstType: ToneBurstType?) throws -> (serverConfig: ServerConfig, clientConfig: ClientConfig) - { - var toneburstClient: ToneBurst? = nil - var toneburstServer: ToneBurst? = nil - var polishClient: PolishClientConfig? = nil - var polishServer: PolishServerConfig? = nil - - // TODO: Suppport other toneburst types - switch toneburstType - { - case .starburst: - toneburstClient = Starburst(.SMTPClient) - toneburstServer = Starburst(.SMTPServer) - case .none: - toneburstClient = nil - toneburstServer = nil - } - - if polish { - let compactRepresentable = P256.KeyAgreement.PrivateKey(compactRepresentable: true) - print("raw representation: \(compactRepresentable.rawRepresentation.hex) (count: \(compactRepresentable.rawRepresentation.count)) | x963 representation: \(compactRepresentable.x963Representation.hex) (count: \(compactRepresentable.x963Representation.count)") - let privateKey = PrivateKey.P256KeyAgreement(compactRepresentable) - - let publicKey = privateKey.publicKey - polishClient = PolishClientConfig(serverAddress: serverAddress, serverPublicKey: publicKey) - polishServer = PolishServerConfig(serverAddress: serverAddress, serverPrivateKey: privateKey) - } - - let clientConfig = try ClientConfig(serverAddress: serverAddress, polish: polishClient, toneBurst: toneburstClient) - let serverConfig = try ServerConfig(serverAddress: serverAddress, polish: polishServer, toneBurst: toneburstServer) - - return (serverConfig, clientConfig) - } - - public static func createNewConfigFiles(inDirectory saveDirectory: URL, serverAddress: String, polish: Bool, toneburstType: ToneBurstType?) throws - { - guard saveDirectory.isDirectory else - { - throw ReplicantConfigError.directoryNotFound(directory: saveDirectory.path) - } - - let newConfigs = try generateNewConfigPair(serverAddress: serverAddress, polish: polish, toneburstType: toneburstType) - let clientJson = try newConfigs.clientConfig.createJSON() - let serverJson = try newConfigs.serverConfig.createJSON() - - let serverConfigFilename = "ReplicantServerConfig.json" - let serverConfigFilePath = saveDirectory.appendingPathComponent(serverConfigFilename).path - - guard File.put(serverConfigFilePath, contents: serverJson) else - { - throw ReplicantConfigError.failedToSaveFile(filePath: serverConfigFilePath) - } - - let clientConfigFilename = "ReplicantClientConfig.json" - let clientConfigFilePath = saveDirectory.appendingPathComponent(clientConfigFilename).path - - guard File.put(clientConfigFilePath, contents: clientJson) else - { - throw ReplicantConfigError.failedToSaveFile(filePath: clientConfigFilePath) - } - } - - public struct ServerConfig: Codable - { - public let serverAddress: String - public let serverIP: String - public let serverPort: UInt16 - public let polish: PolishServerConfig? - public let toneburst: ToneBurst? - public let toneburstType: ToneBurstType? - public var transportName = "replicant" - - enum CodingKeys: String, CodingKey - { - case serverAddress - case polish - case toneburst - case toneburstType - case transportName = "transport" - } - - public init(serverAddress: String, polish maybePolish: PolishServerConfig?, toneBurst maybeToneBurst: ToneBurst?) throws - { - let addressStrings = serverAddress.replacingOccurrences(of: " ", with: "").split(separator: ":") - guard let port = UInt16(addressStrings[1]) else - { - print("Error decoding Replicant server config data: invalid server port \(addressStrings[1])") - throw ReplicantError.invalidPort - } - - self.serverAddress = serverAddress - self.serverIP = String(addressStrings[0]) - self.serverPort = port - self.polish = maybePolish - self.toneburst = maybeToneBurst - self.toneburstType = toneburst?.type - } - - public init?(from data: Data) - { - let decoder = JSONDecoder() - do - { - let decoded = try decoder.decode(ServerConfig.self, from: data) - - self = decoded - } - catch - { - print("Error received while attempting to decode a Replicant server config json file: \(error)") - return nil - } - } - - public init?(withConfigAtPath path: String) - { - let url = URL(fileURLWithPath: path) - - do - { - let data = try Data(contentsOf: url) - self.init(from: data) - } - catch - { - print("Error decoding Replicant server config file: \(error)") - - return nil - } - } - - public init(from decoder: Decoder) throws - { - let container = try decoder.container(keyedBy: CodingKeys.self) - let address = try container.decode(String.self, forKey: .serverAddress) - let addressStrings = address.replacingOccurrences(of: " ", with: "").split(separator: ":") - let ipAddress = String(addressStrings[0]) - guard let port = UInt16(addressStrings[1]) else - { - print("Error decoding Replicant server config - invalid server port: \(addressStrings[1])") - throw ReplicantError.decoderFailure - } - - self.serverAddress = address - self.serverIP = ipAddress - self.serverPort = port - self.polish = try container.decodeIfPresent(PolishServerConfig.self, forKey: .polish) - self.toneburstType = try container.decodeIfPresent(ToneBurstType.self, forKey: .toneburstType) - - // TODO: Support additional ToneBurst types - switch self.toneburstType - { - case .starburst: - self.toneburst = try container.decodeIfPresent(Starburst.self, forKey: .toneburst) - case .none: - print("No supported Toneburst type was indicated while decoding a Replicant server config. Skipping Toneburst setup.") - self.toneburst = nil - } - } - - public func encode(to encoder: Encoder) throws - { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(serverAddress, forKey: .serverAddress) - try container.encode(transportName, forKey: .transportName) - try container.encode(polish, forKey: .polish) - try container.encode(toneburstType, forKey: .toneburstType) - - // TODO: Support additional ToneBurst types - switch toneburstType - { - case .starburst: - if let starburst = toneburst as? Starburst - { - try container.encode(starburst, forKey: .toneburst) - } - case .none: - print("Encoded a Replicant server config without a Toneburst.") - } - } - - /// Creates and returns a JSON representation of the ReplicantServerConfig struct. - public func createJSON() throws -> Data - { - let encoder = JSONEncoder() - encoder.outputFormatting.insert(.prettyPrinted) - encoder.outputFormatting.insert(.withoutEscapingSlashes) - - do - { - let serverConfigData = try encoder.encode(self) - return serverConfigData - } - catch (let error) - { - throw ReplicantConfigError.serverConfigJsonEncodingError(error: error) - } - } - } - - public struct ClientConfig: Codable - { - public let serverAddress: String - public let serverIP: String - public let serverPort: UInt16 - public let polish: PolishClientConfig? - public let toneburst: ToneBurst? - public let toneburstType: ToneBurstType? - public var transportName = "replicant" - - enum CodingKeys: String, CodingKey - { - case serverAddress - case polish - case toneburst - case toneburstType - case transportName = "transport" - } - - public init(serverAddress: String, polish maybePolish: PolishClientConfig?, toneBurst maybeToneBurst: ToneBurst?) throws - { - let addressStrings = serverAddress.replacingOccurrences(of: " ", with: "").split(separator: ":") - guard let port = UInt16(addressStrings[1]) else - { - print("Error decoding Replicant client config data: invalid server port \(addressStrings[1])") - throw ReplicantError.invalidPort - } - - self.serverAddress = serverAddress - self.serverIP = String(addressStrings[0]) - self.serverPort = port - self.polish = maybePolish - self.toneburst = maybeToneBurst - self.toneburstType = toneburst?.type - } - - public init?(from data: Data) - { - let decoder = JSONDecoder() - do - { - let decoded = try decoder.decode(ClientConfig.self, from: data) - - self = decoded - } - catch - { - print("Error received while attempting to decode a Replicant client config json file: \(error)") - return nil - } - } - - public init?(withConfigAtPath path: String) - { - let url = URL(fileURLWithPath: path) - - do - { - let data = try Data(contentsOf: url) - self.init(from: data) - } - catch - { - print("Error decoding Replicant client config file: \(error)") - - return nil - } - } - - public init(from decoder: Decoder) throws - { - let container = try decoder.container(keyedBy: CodingKeys.self) - let address = try container.decode(String.self, forKey: .serverAddress) - let addressStrings = address.replacingOccurrences(of: " ", with: "").split(separator: ":") - let ipAddress = String(addressStrings[0]) - guard let port = UInt16(addressStrings[1]) else - { - print("Error decoding Replicant client config - invalid server port: \(addressStrings[1])") - throw ReplicantError.decoderFailure - } - - self.serverAddress = address - self.serverIP = ipAddress - self.serverPort = port - self.polish = try container.decodeIfPresent(PolishClientConfig.self, forKey: .polish) - self.toneburstType = try container.decodeIfPresent(ToneBurstType.self, forKey: .toneburstType) - - // TODO: Support additional ToneBurst types - switch self.toneburstType - { - case .starburst: - self.toneburst = try container.decodeIfPresent(Starburst.self, forKey: .toneburst) - case .none: - print("No supported Toneburst type was indicated while decoding a Replicant client config. Skipping Toneburst setup.") - self.toneburst = nil - } - } - - public func encode(to encoder: Encoder) throws - { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(serverAddress, forKey: .serverAddress) - try container.encode(transportName, forKey: .transportName) - try container.encode(polish, forKey: .polish) - try container.encode(toneburstType, forKey: .toneburstType) - - // TODO: Support additional ToneBurst types - switch toneburstType - { - case .starburst: - if let starburst = toneburst as? Starburst - { - try container.encode(starburst, forKey: .toneburst) - } - case .none: - print("Encoded a Replicant client config without a Toneburst.") - } - } - - /// Creates and returns a JSON representation of the ReplicantClientConfig struct. - public func createJSON() throws -> Data - { - let encoder = JSONEncoder() - encoder.outputFormatting.insert(.prettyPrinted) - encoder.outputFormatting.insert(.withoutEscapingSlashes) - - do - { - let clientConfigData = try encoder.encode(self) - return clientConfigData - } - catch (let error) - { - throw ReplicantConfigError.clientConfigJsonEncodingError(error: error) - } - } - } -} - -public enum ReplicantConfigError: Error -{ - case directoryNotFound(directory: String) - case clientConfigJsonEncodingError(error: Error) - case serverConfigJsonEncodingError(error: Error) - case failedToSaveFile(filePath: String) -} diff --git a/Sources/ReplicantSwift/ReplicantError.swift b/Sources/ReplicantSwift/ReplicantError.swift index f081593..b253c65 100644 --- a/Sources/ReplicantSwift/ReplicantError.swift +++ b/Sources/ReplicantSwift/ReplicantError.swift @@ -42,4 +42,5 @@ public enum ReplicantError: Error case serverError case serverUnavailable case invalidToneburst + case unimplemented } diff --git a/Sources/ReplicantSwift/ReplicantListener.swift b/Sources/ReplicantSwift/ReplicantListener.swift index a196dd1..19da33e 100644 --- a/Sources/ReplicantSwift/ReplicantListener.swift +++ b/Sources/ReplicantSwift/ReplicantListener.swift @@ -13,21 +13,21 @@ import TransmissionAsync // This is just a normal TCP listener, except for the config and special accept behavior. open class ReplicantListener: TransmissionAsync.AsyncListener { - let config: ReplicantConfig.ServerConfig + let replicant: Replicant let logger: Logger let listener: AsyncListener - public init(config: ReplicantConfig.ServerConfig, logger: Logger) throws + public init(replicant: Replicant, serverIP: String, serverPort: Int, logger: Logger) throws { - self.config = config + self.replicant = replicant self.logger = logger - self.listener = try AsyncTcpSocketListener(host: config.serverIP, port: Int(config.serverPort), logger) + self.listener = try AsyncTcpSocketListener(host: serverIP, port: serverPort, logger) } open func accept() async throws -> TransmissionAsync.AsyncConnection { let network = try await self.listener.accept() - return try await self.replicantServerTransformation(connection: network, config: config, logger: logger) + return try await self.replicantServerTransformation(connection: network) } open func close() async throws @@ -35,32 +35,12 @@ open class ReplicantListener: TransmissionAsync.AsyncListener try await self.listener.close() } - public func replicantServerTransformation(connection: TransmissionAsync.AsyncConnection, config: ReplicantConfig.ServerConfig, logger: Logger) async throws -> TransmissionAsync.AsyncConnection + public func replicantServerTransformation(connection: TransmissionAsync.AsyncConnection) async throws -> TransmissionAsync.AsyncConnection { var result: TransmissionAsync.AsyncConnection = connection - - // TODO: Add more ToneBurst types as they become available - switch config.toneburstType - { - case .starburst: - if let starburst = config.toneburst as? Starburst - { - try await starburst.perform(connection: connection) - } - else if let starburst = config.toneburst as? Omnitone - { - try await starburst.perform(connection: connection) - } - else - { - logger.error("Invalid Replicant Server Config toneburst type is starburst, but toneburst could not be initialized.") - throw ReplicantError.invalidToneburst - } - case .none: - print("ReplicantServerTransformation: Skipping Toneburst.") - } + try await replicant.toneburst?.perform(connection: connection) - if let polishConfig = config.polish + if let polishConfig = replicant.polish { result = try await polishConfig.polish(result, logger) } diff --git a/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift b/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift deleted file mode 100644 index 9490a0e..0000000 --- a/Sources/ReplicantSwift/ToneBurst/Omnitone/Omnitone.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// Omnitone.swift -// -// - -import Foundation - -import Chord -import Datable -import Ghostwriter -import TransmissionAsync - -public enum OmnitoneMode: String, Codable -{ - case POP3Client - case POP3Server - -} - -public class Omnitone: ToneBurst, Codable -{ - public var type: ReplicantSwift.ToneBurstType = .starburst - - let mode: OmnitoneMode - - public init(_ mode: OmnitoneMode) - { - self.mode = mode - } - - public func perform(connection: TransmissionAsync.AsyncConnection) async throws - { - let instance = OmnitoneInstance(self.mode, connection) - try await instance.perform() - } -} - -public struct OmnitoneInstance -{ - let connection: TransmissionAsync.AsyncConnection - let mode: OmnitoneMode - - public init(_ mode: OmnitoneMode, _ connection: TransmissionAsync.AsyncConnection) - { - self.mode = mode - self.connection = connection - } - - public func perform() async throws - { - switch mode - { - case .POP3Client: - try await handlePOP3Client() - - case .POP3Server: - try await handlePOP3Server() - } - } - - func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> MatchResult - { - let listenTask: Task = Task - { - var buffer = Data() - while buffer.count < maxSize - { - let byte = try await connection.readSize(1) - - buffer.append(byte) - - guard let string = String(data: buffer, encoding: .utf8) else - { - // This could fail because we're in the middle of a UTF8 rune. - continue - } - - let result = try structuredText.match(string: string) - switch result - { - case .FAILURE: - return result - - case .SHORT: - continue - - case .SUCCESS(_): - return result - } - } - - throw StarburstError.maxSizeReached - } - - Task - { - try await Task.sleep(for: timeout) - listenTask.cancel() - } - - return try await listenTask.value - } - - func speak(structuredText: StructuredText) async throws - { - do - { - let string = structuredText.string - try await connection.writeString(string: string) - } - catch - { - print(error) - throw StarburstError.writeFailed - } - } - - private func handlePOP3Server() async throws - { - try await self.speak(structuredText: StructuredText(TypedText.text("+OK POP3 server ready."), TypedText.newline(Newline.crlf))) - let _ = try await self.listen(structuredText: StructuredText(TypedText.text("STLS"), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) - try await self.speak(structuredText: StructuredText(TypedText.text("+OK Begin TLS Negotiation"), TypedText.newline(Newline.crlf))) - } - - private func handlePOP3Client() async throws - { - let _ = try await self.listen(structuredText: StructuredText(TypedText.text("+OK POP3 server ready."), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) - try await self.speak(structuredText: StructuredText(TypedText.text("STLS"), TypedText.newline(Newline.crlf))) - let _ = try await self.listen(structuredText: StructuredText(TypedText.text("+OK Begin TLS Negotiation"), TypedText.newline(Newline.crlf)), timeout: Duration.seconds(5)) - } -} - -public enum OmnitoneError: Error -{ - case timeout - case connectionClosed - case writeFailed - case readFailed - case listenFailed - case speakFailed - case maxSizeReached -} diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift index 2eb79b8..f83db8d 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift @@ -12,18 +12,24 @@ import Datable import Ghostwriter import TransmissionAsync -open class Starburst: ToneBurst, Codable +open class Starburst: ToneBurst { - public var type: ToneBurstType = .starburst - let mode: StarburstMode public init(_ mode: StarburstMode) { self.mode = mode + super.init() } - - open func perform(connection: TransmissionAsync.AsyncConnection) async throws + + required public init(from decoder: any Decoder) throws + { + let container = try decoder.singleValueContainer() + self.mode = try container.decode(StarburstMode.self) + super.init() + } + + open override func perform(connection: TransmissionAsync.AsyncConnection) async throws { let instance = StarburstInstanceAsync(self.mode, connection) try await instance.perform() diff --git a/Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift b/Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift index 79b06a9..e629299 100644 --- a/Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift +++ b/Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift @@ -11,4 +11,5 @@ import Foundation public enum ToneBurstType: String, Codable { case starburst + case omnitone } diff --git a/Sources/ReplicantSwift/ToneBurst/Toneburst.swift b/Sources/ReplicantSwift/ToneBurst/Toneburst.swift index a603263..6dde20d 100644 --- a/Sources/ReplicantSwift/ToneBurst/Toneburst.swift +++ b/Sources/ReplicantSwift/ToneBurst/Toneburst.swift @@ -10,9 +10,10 @@ import Datable import TransmissionAsync /// Injects byte sequences into a stream of bytes -public protocol ToneBurst: Codable +open class ToneBurst: Codable { - var type: ToneBurstType { get set } - - mutating func perform(connection: TransmissionAsync.AsyncConnection) async throws + open func perform(connection: TransmissionAsync.AsyncConnection) async throws + { + throw ReplicantError.unimplemented + } } From 90d03772a77fdcfa4808b75b9e0597a5fc75432b Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 19 Mar 2024 13:47:33 -0500 Subject: [PATCH 23/28] Update Toneburst.swift --- Sources/ReplicantSwift/ToneBurst/Toneburst.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/ReplicantSwift/ToneBurst/Toneburst.swift b/Sources/ReplicantSwift/ToneBurst/Toneburst.swift index 6dde20d..de172f8 100644 --- a/Sources/ReplicantSwift/ToneBurst/Toneburst.swift +++ b/Sources/ReplicantSwift/ToneBurst/Toneburst.swift @@ -12,6 +12,8 @@ import TransmissionAsync /// Injects byte sequences into a stream of bytes open class ToneBurst: Codable { + public init(){} + open func perform(connection: TransmissionAsync.AsyncConnection) async throws { throw ReplicantError.unimplemented From 8c68a1e84daa4348bd320cd77d5a9283b6aee698 Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 19 Mar 2024 13:57:55 -0500 Subject: [PATCH 24/28] Delete SupportedToneBursts.swift --- .../ToneBurst/SupportedToneBursts.swift | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift diff --git a/Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift b/Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift deleted file mode 100644 index e629299..0000000 --- a/Sources/ReplicantSwift/ToneBurst/SupportedToneBursts.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// SupportedToneBurst.swift -// ReplicantSwift -// -// Created by Dr. Brandon Wiley on 2/21/19. -// - -import Foundation - - -public enum ToneBurstType: String, Codable -{ - case starburst - case omnitone -} From f462725fd6c64c58ebb6ca55699db0008add8b0e Mon Sep 17 00:00:00 2001 From: CryptoSax Date: Tue, 19 Mar 2024 14:08:43 -0500 Subject: [PATCH 25/28] added additional handleSMTPClient functions from omnilanguage --- .../ToneBurst/Starburst/Starburst.swift | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift index f83db8d..365dc40 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift @@ -83,6 +83,42 @@ public struct StarburstInstanceAsync _ = try await listen(template: thirdClientListen) } + + func handleSMTPClient2() async throws + { +// guard let firstClientListen = ListenTemplate(Template("220 $1 SMTP service ready\r\n"), patterns: [ExtractionPattern("^([a-zA-Z0-9.-]+)", .string)], maxSize: 253, maxTimeoutSeconds: Int.max) else { +// throw StarburstError.listenFailed +// } + + let _ = try await listen(structuredText: StructuredText(TypedText.text("220 "), TypedText.regexp("^([a-zA-Z0-9.-]+)"), TypedText.text(" SMTP service ready"), TypedText.newline(.crlf)), maxSize: 253, timeout: .seconds(Int.max)) + + // try await speak(template: Template("EHLO $1\r\n"), details: [Detail.string("mail.imc.org")]) + try await speak(structuredText: StructuredText(TypedText.text("EHLO mail.imc.org"), TypedText.newline(.crlf))) + +// guard let secondClientListen = ListenTemplate(Template("$1\r\n"), patterns: [ExtractionPattern("250 (STARTTLS)", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { +// throw StarburstError.listenFailed +// } + + _ = try await listen(structuredText: StructuredText(TypedText.text("250 STARTTLS"), TypedText.newline(.crlf)), maxSize: 253, timeout: .seconds(10)) + + // try await speak(string: "STARTTLS\r\n") + try await speak(structuredText: StructuredText(TypedText.text("STARTTLS"), TypedText.newline(.crlf))) + +// guard let thirdClientListen = ListenTemplate(Template("$1\r\n"), patterns: [ExtractionPattern("^(.+)\r\n", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { +// throw StarburstError.listenFailed +// } + + _ = try await listen(structuredText: StructuredText(TypedText.regexp("^(.+)$"), TypedText.newline(.crlf)), maxSize: 253, timeout: .seconds(10)) + } + + private func handleSMTPClient3() async throws + { + let _ = try await self.listen(structuredText: StructuredText(TypedText.text("220 "), TypedText.regexp("^([a-zA-Z0-9.-]+)"), TypedText.text(" SMTP service ready"), TypedText.newline(Newline.crlf)), maxSize: 253, timeout: Duration.seconds(9223372036854775807)) + try await self.speak(structuredText: StructuredText(TypedText.text("EHLO mail.imc.org"), TypedText.newline(Newline.crlf))) + let _ = try await self.listen(structuredText: StructuredText(TypedText.text("250 STARTTLS"), TypedText.newline(Newline.crlf)), maxSize: 253, timeout: Duration.seconds(10)) + try await self.speak(structuredText: StructuredText(TypedText.text("STARTTLS"), TypedText.newline(Newline.crlf))) + let _ = try await self.listen(structuredText: StructuredText(TypedText.regexp("^(.+)$"), TypedText.newline(Newline.crlf)), maxSize: 253, timeout: Duration.seconds(10)) + } func handleSMTPServer() async throws { From 6555097c8d113050e6b497d1daec09c2cd0f4395 Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 19 Mar 2024 14:34:55 -0500 Subject: [PATCH 26/28] Update Starburst handleSMTPClient --- .../ToneBurst/Starburst/Starburst.swift | 214 ++++-------------- .../ToneBurst/Starburst/StarburstConfig.swift | 81 ------- 2 files changed, 43 insertions(+), 252 deletions(-) delete mode 100644 Sources/ReplicantSwift/ToneBurst/Starburst/StarburstConfig.swift diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift index 365dc40..bc0c5ec 100644 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift +++ b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift @@ -58,60 +58,8 @@ public struct StarburstInstanceAsync try await handleSMTPServer() } } - - func handleSMTPClient() async throws - { - guard let firstClientListen = ListenTemplate(Template("220 $1 SMTP service ready\r\n"), patterns: [ExtractionPattern("^([a-zA-Z0-9.-]+)", .string)], maxSize: 253, maxTimeoutSeconds: Int.max) else { - throw StarburstError.listenFailed - } - - let _ = try await listen(template: firstClientListen) - - try await speak(template: Template("EHLO $1\r\n"), details: [Detail.string("mail.imc.org")]) - - guard let secondClientListen = ListenTemplate(Template("$1\r\n"), patterns: [ExtractionPattern("250 (STARTTLS)", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { - throw StarburstError.listenFailed - } - - _ = try await listen(template: secondClientListen) - - try await speak(string: "STARTTLS\r\n") - - guard let thirdClientListen = ListenTemplate(Template("$1\r\n"), patterns: [ExtractionPattern("^(.+)\r\n", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { - throw StarburstError.listenFailed - } - - _ = try await listen(template: thirdClientListen) - } - func handleSMTPClient2() async throws - { -// guard let firstClientListen = ListenTemplate(Template("220 $1 SMTP service ready\r\n"), patterns: [ExtractionPattern("^([a-zA-Z0-9.-]+)", .string)], maxSize: 253, maxTimeoutSeconds: Int.max) else { -// throw StarburstError.listenFailed -// } - - let _ = try await listen(structuredText: StructuredText(TypedText.text("220 "), TypedText.regexp("^([a-zA-Z0-9.-]+)"), TypedText.text(" SMTP service ready"), TypedText.newline(.crlf)), maxSize: 253, timeout: .seconds(Int.max)) - - // try await speak(template: Template("EHLO $1\r\n"), details: [Detail.string("mail.imc.org")]) - try await speak(structuredText: StructuredText(TypedText.text("EHLO mail.imc.org"), TypedText.newline(.crlf))) - -// guard let secondClientListen = ListenTemplate(Template("$1\r\n"), patterns: [ExtractionPattern("250 (STARTTLS)", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { -// throw StarburstError.listenFailed -// } - - _ = try await listen(structuredText: StructuredText(TypedText.text("250 STARTTLS"), TypedText.newline(.crlf)), maxSize: 253, timeout: .seconds(10)) - - // try await speak(string: "STARTTLS\r\n") - try await speak(structuredText: StructuredText(TypedText.text("STARTTLS"), TypedText.newline(.crlf))) - -// guard let thirdClientListen = ListenTemplate(Template("$1\r\n"), patterns: [ExtractionPattern("^(.+)\r\n", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { -// throw StarburstError.listenFailed -// } - - _ = try await listen(structuredText: StructuredText(TypedText.regexp("^(.+)$"), TypedText.newline(.crlf)), maxSize: 253, timeout: .seconds(10)) - } - - private func handleSMTPClient3() async throws + private func handleSMTPClient() async throws { let _ = try await self.listen(structuredText: StructuredText(TypedText.text("220 "), TypedText.regexp("^([a-zA-Z0-9.-]+)"), TypedText.text(" SMTP service ready"), TypedText.newline(Newline.crlf)), maxSize: 253, timeout: Duration.seconds(9223372036854775807)) try await self.speak(structuredText: StructuredText(TypedText.text("EHLO mail.imc.org"), TypedText.newline(Newline.crlf))) @@ -122,65 +70,41 @@ public struct StarburstInstanceAsync func handleSMTPServer() async throws { - try await speak(template: Template("220 $1 SMTP service ready\r\n"), details: [Detail.string("mail.imc.org")]) - - guard let firstServerListen = ListenTemplate(Template("EHLO $1\r\n"), patterns: [ExtractionPattern("^([a-zA-Z0-9.-]+)\r", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { - throw StarburstError.listenFailed - } - - _ = try await listen(template: firstServerListen) - - // % 5 is mod, which divides by five, discards the result, then returns the remainder - let hour = Calendar.current.component(.hour, from: Date()) % 5 - let welcome: String - switch hour - { - // These are all real SMTP welcome messages found in online examples of SMTP conversations. - case 0: - welcome = "offers a warm hug of welcome" - case 1: - welcome = "is my domain name." - case 2: - welcome = "I am glad to meet you" - case 3: - welcome = "says hello" - case 4: - welcome = "Hello" - - default: - welcome = "" - } - - try await speak(template: Template("250-$1 $2\r\n250-$3\r\n250-$4\r\n250 $5\r\n"), details: [Detail.string("mail.imc.org"), Detail.string(welcome), Detail.string("8BITMIME"), Detail.string("DSN"), Detail.string("STARTTLS")]) - - // FIXME: not sure about this size - let _: String = try await listen(size: "STARTTLS\r\n".count + 1) // \r\n is counted as one on .count - - try await speak(template: Template("220 $1\r\n"), details: [Detail.string("Go ahead")]) - } - - func speak(data: Data) async throws - { - try await connection.write(data) - } - - func speak(string: String) async throws - { - try await connection.writeString(string: string) - } - - func speak(template: Template, details: [Detail]) async throws - { - do - { - let string = try Ghostwriter.generate(template, details) - try await connection.writeString(string: string) - } - catch - { - print(error) - throw StarburstError.writeFailed - } +// try await speak(template: Template("220 $1 SMTP service ready\r\n"), details: [Detail.string("mail.imc.org")]) +// +// guard let firstServerListen = ListenTemplate(Template("EHLO $1\r\n"), patterns: [ExtractionPattern("^([a-zA-Z0-9.-]+)\r", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { +// throw StarburstError.listenFailed +// } +// +// _ = try await listen(template: firstServerListen) +// +// // % 5 is mod, which divides by five, discards the result, then returns the remainder +// let hour = Calendar.current.component(.hour, from: Date()) % 5 +// let welcome: String +// switch hour +// { +// // These are all real SMTP welcome messages found in online examples of SMTP conversations. +// case 0: +// welcome = "offers a warm hug of welcome" +// case 1: +// welcome = "is my domain name." +// case 2: +// welcome = "I am glad to meet you" +// case 3: +// welcome = "says hello" +// case 4: +// welcome = "Hello" +// +// default: +// welcome = "" +// } +// +// try await speak(template: Template("250-$1 $2\r\n250-$3\r\n250-$4\r\n250 $5\r\n"), details: [Detail.string("mail.imc.org"), Detail.string(welcome), Detail.string("8BITMIME"), Detail.string("DSN"), Detail.string("STARTTLS")]) +// +// // FIXME: not sure about this size +// let _: String = try await listen(size: "STARTTLS\r\n".count + 1) // \r\n is counted as one on .count +// +// try await speak(template: Template("220 $1\r\n"), details: [Detail.string("Go ahead")]) } func speak(structuredText: StructuredText) async throws @@ -196,65 +120,6 @@ public struct StarburstInstanceAsync throw StarburstError.writeFailed } } - - func listen(size: Int) async throws -> Data - { - return try await connection.readSize(size) - } - - func listen(size: Int) async throws -> String - { - let data = try await connection.readSize(size) - return data.string - } - - func listen(template: ListenTemplate) async throws -> [Detail] - { - let listenTask: Task<[Detail]?, Error> = Task { - var buffer = Data() - while buffer.count < template.maxSize - { - do { - let byte = try await connection.readSize(1) - - buffer.append(byte) - - guard let string = String(data: buffer, encoding: .utf8) else - { - // This could fail because we're in the middle of a UTF8 rune. - continue - } - - do - { - return try Ghostwriter.parse(template.template, template.patterns, string) - } - catch - { - continue - } - } catch { - return nil - } - } - - return nil - } - - let _ = Task { - try await Task.sleep(for: .seconds(60)) - listenTask.cancel() - } - - do { - guard let result = try await listenTask.value else { - throw StarburstError.readFailed - } - return result - } catch { - throw StarburstError.timeout - } - } func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> String { @@ -339,6 +204,13 @@ public struct StarburstInstanceAsync _ = lock.wait(timeout: .now() + seconds) } } + +public enum StarburstMode: String, Codable +{ + case SMTPServer + case SMTPClient +} + public enum StarburstError: Error { case timeout diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstConfig.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstConfig.swift deleted file mode 100644 index d0cf13d..0000000 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/StarburstConfig.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// StarburstConfig.swift -// -// -// Created by Dr. Brandon Wiley on 5/9/22. -// - -import Foundation - -import Ghostwriter - -public enum StarburstMode: String, Codable { - case SMTPServer - case SMTPClient -} - -public enum Speak: Codable, CustomStringConvertible -{ - public var description: String - { - switch self - { - case .bytes(let value): - return "Speak.bytes(\(value))" - - case .text(let value): - return "Speak.text(\(value))" - - case .template(let template, let details): - return "Speak.bytes(\(template), \(details))" - } - } - - case bytes(Data) - case text(String) - case template(Template, [Detail]) -} - -public enum Listen: Codable -{ - case bytes(Int) - case text(Int) - case parse(ListenTemplate) - case match(ListenTemplate) -} - -public struct ListenTemplate: Codable -{ - let template: Template - let patterns: [ExtractionPattern] - let maxSize: Int - let maxTimeoutSeconds: Int - - public init?(_ template: Template, patterns: [ExtractionPattern], maxSize: Int, maxTimeoutSeconds: Int) - { - guard maxSize > 0 else - { - return nil - } - - guard maxTimeoutSeconds > 0 else - { - return nil - } - - self.template = template - self.patterns = patterns - self.maxSize = maxSize - self.maxTimeoutSeconds = maxTimeoutSeconds - } -} - -public struct Wait: Codable -{ - let interval: TimeInterval - - public init(_ interval: TimeInterval) - { - self.interval = interval - } -} From 8c6f938a29d78e2917d0e047993fdee08cb72bea Mon Sep 17 00:00:00 2001 From: consuelita Date: Tue, 19 Mar 2024 14:41:41 -0500 Subject: [PATCH 27/28] Moved Starburst to Starbridge --- .../ToneBurst/Starburst/Starburst.swift | 223 ------------------ 1 file changed, 223 deletions(-) delete mode 100644 Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift diff --git a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift b/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift deleted file mode 100644 index bc0c5ec..0000000 --- a/Sources/ReplicantSwift/ToneBurst/Starburst/Starburst.swift +++ /dev/null @@ -1,223 +0,0 @@ -// -// Starburst.swift -// -// -// Created by Dr. Brandon Wiley on 5/9/22. -// - -import Foundation - -import Chord -import Datable -import Ghostwriter -import TransmissionAsync - -open class Starburst: ToneBurst -{ - let mode: StarburstMode - - public init(_ mode: StarburstMode) - { - self.mode = mode - super.init() - } - - required public init(from decoder: any Decoder) throws - { - let container = try decoder.singleValueContainer() - self.mode = try container.decode(StarburstMode.self) - super.init() - } - - open override func perform(connection: TransmissionAsync.AsyncConnection) async throws - { - let instance = StarburstInstanceAsync(self.mode, connection) - try await instance.perform() - } -} - -public struct StarburstInstanceAsync -{ - let connection: TransmissionAsync.AsyncConnection - let mode: StarburstMode - - public init(_ mode: StarburstMode, _ connection: TransmissionAsync.AsyncConnection) - { - self.mode = mode - self.connection = connection - } - - public func perform() async throws - { - switch mode - { - case .SMTPClient: - try await handleSMTPClient() - - case .SMTPServer: - try await handleSMTPServer() - } - } - - private func handleSMTPClient() async throws - { - let _ = try await self.listen(structuredText: StructuredText(TypedText.text("220 "), TypedText.regexp("^([a-zA-Z0-9.-]+)"), TypedText.text(" SMTP service ready"), TypedText.newline(Newline.crlf)), maxSize: 253, timeout: Duration.seconds(9223372036854775807)) - try await self.speak(structuredText: StructuredText(TypedText.text("EHLO mail.imc.org"), TypedText.newline(Newline.crlf))) - let _ = try await self.listen(structuredText: StructuredText(TypedText.text("250 STARTTLS"), TypedText.newline(Newline.crlf)), maxSize: 253, timeout: Duration.seconds(10)) - try await self.speak(structuredText: StructuredText(TypedText.text("STARTTLS"), TypedText.newline(Newline.crlf))) - let _ = try await self.listen(structuredText: StructuredText(TypedText.regexp("^(.+)$"), TypedText.newline(Newline.crlf)), maxSize: 253, timeout: Duration.seconds(10)) - } - - func handleSMTPServer() async throws - { -// try await speak(template: Template("220 $1 SMTP service ready\r\n"), details: [Detail.string("mail.imc.org")]) -// -// guard let firstServerListen = ListenTemplate(Template("EHLO $1\r\n"), patterns: [ExtractionPattern("^([a-zA-Z0-9.-]+)\r", .string)], maxSize: 253, maxTimeoutSeconds: 10) else { -// throw StarburstError.listenFailed -// } -// -// _ = try await listen(template: firstServerListen) -// -// // % 5 is mod, which divides by five, discards the result, then returns the remainder -// let hour = Calendar.current.component(.hour, from: Date()) % 5 -// let welcome: String -// switch hour -// { -// // These are all real SMTP welcome messages found in online examples of SMTP conversations. -// case 0: -// welcome = "offers a warm hug of welcome" -// case 1: -// welcome = "is my domain name." -// case 2: -// welcome = "I am glad to meet you" -// case 3: -// welcome = "says hello" -// case 4: -// welcome = "Hello" -// -// default: -// welcome = "" -// } -// -// try await speak(template: Template("250-$1 $2\r\n250-$3\r\n250-$4\r\n250 $5\r\n"), details: [Detail.string("mail.imc.org"), Detail.string(welcome), Detail.string("8BITMIME"), Detail.string("DSN"), Detail.string("STARTTLS")]) -// -// // FIXME: not sure about this size -// let _: String = try await listen(size: "STARTTLS\r\n".count + 1) // \r\n is counted as one on .count -// -// try await speak(template: Template("220 $1\r\n"), details: [Detail.string("Go ahead")]) - } - - func speak(structuredText: StructuredText) async throws - { - do - { - let string = structuredText.string - try await connection.writeString(string: string) - } - catch - { - print(error) - throw StarburstError.writeFailed - } - } - - func listen(structuredText: StructuredText, maxSize: Int = 255, timeout: Duration = .seconds(60)) async throws -> String - { - let listenTask: Task = Task - { - var buffer = Data() - while buffer.count < maxSize - { - do - { - let byte = try await connection.readSize(1) - - buffer.append(byte) - - guard let string = String(data: buffer, encoding: .utf8) else - { - // This could fail because we're in the middle of a UTF8 rune that is encoded as multiple bytes. - continue - } - - do - { - let matchResult = try structuredText.match(string: string) - - switch matchResult - { - case .SUCCESS(_): - return matchResult - - case .SHORT: - continue - - case .FAILURE: - throw StarburstError.listenFailed - } - } - catch - { - continue - } - } - catch - { - return nil - } - } - - return nil - } - - let _ = Task - { - try await Task.sleep(for: timeout) - listenTask.cancel() - } - - do - { - guard let result = try await listenTask.value else - { - throw StarburstError.readFailed - } - - switch result - { - case .SUCCESS(let value): - return value - - default: - throw StarburstError.listenFailed - } - } - catch - { - throw StarburstError.timeout - } - } - - func wait(seconds: Double) - { - let lock = DispatchSemaphore(value: 0) - _ = lock.wait(timeout: .now() + seconds) - } -} - -public enum StarburstMode: String, Codable -{ - case SMTPServer - case SMTPClient -} - -public enum StarburstError: Error -{ - case timeout - case connectionClosed - case writeFailed - case readFailed - case listenFailed - case speakFailed - case maxSizeReached -} From 774ae7c11a1eb9a1180b3d0135230fb33d864f6d Mon Sep 17 00:00:00 2001 From: bragelbytes Date: Thu, 21 Mar 2024 13:51:06 -0700 Subject: [PATCH 28/28] Update ReplicantSwiftTests.swift --- .../ReplicantSwiftTests.swift | 216 ------------------ 1 file changed, 216 deletions(-) diff --git a/Tests/ReplicantSwiftTests/ReplicantSwiftTests.swift b/Tests/ReplicantSwiftTests/ReplicantSwiftTests.swift index 38901f4..9234030 100644 --- a/Tests/ReplicantSwiftTests/ReplicantSwiftTests.swift +++ b/Tests/ReplicantSwiftTests/ReplicantSwiftTests.swift @@ -13,220 +13,4 @@ import SwiftQueue final class ReplicantSwiftTests: XCTestCase { let logger = Logger(label: "ReplicantTest") - - func testStarburstAndDarkstarServer() throws { - let serverSendData = "success".data - let clientSendData = "pass".data - guard let replicantServerConfig = ReplicantServerConfig(withConfigAtPath: "~/ReplicantServerConfig.json") else { - XCTFail() - return - } - let replicant = Replicant(logger: self.logger) - - let replicantListener = try replicant.listen(address: "127.0.0.1", port: 1234, config: replicantServerConfig) - - let replicantConnection = try replicantListener.accept() - - guard let serverReadData = replicantConnection.read(size: clientSendData.count) else { - XCTFail() - return - } - - guard replicantConnection.write(data: serverSendData) else { - XCTFail() - return - } - - XCTAssertEqual(serverReadData.string, clientSendData.string) - - } - - func testStarburst() throws { - let serverSendData = "success".data - let clientSendData = "pass".data - - let starburstServer = Starburst(.SMTPServer) - let starburstClient = Starburst(.SMTPClient) - - let replicantServerConfig = ReplicantServerConfig(serverAddress: "127.0.0.1:1234", polish: nil, toneBurst: starburstServer, transport: "Replicant") - - guard let replicantClientConfig = ReplicantClientConfig(serverAddress: "127.0.0.1:1234", polish: nil, toneBurst: starburstClient, transport: "Replicant") else - { - XCTFail() - return - } - - let replicant = Replicant(logger: self.logger) - - let replicantListener = try replicant.listen(address: "127.0.0.1", port: 1234, config: replicantServerConfig) - Task { - let replicantConnection = try replicantListener.accept() - - guard let serverReadData = replicantConnection.read(size: clientSendData.count) else { - XCTFail() - return - } - - guard replicantConnection.write(data: serverSendData) else { - XCTFail() - return - } - - XCTAssertEqual(serverReadData.string, clientSendData.string) - } - - let replicantClient = try replicant.connect(host: "127.0.0.1", port: 1234, config: replicantClientConfig) - - guard replicantClient.write(data: clientSendData) else { - XCTFail() - return - } - - guard let clientReadData = replicantClient.read(size: serverSendData.count) else { - XCTFail() - return - } - - XCTAssertEqual(clientReadData.string, serverSendData.string) - } - - func testDarkstar() throws { - let serverSendData = "success".data - let clientSendData = "pass".data - let privateKey = try PrivateKey(type: .P256KeyAgreement) - let polishClient = PolishClientConfig(serverAddress: "127.0.0.1:1234", serverPublicKey: privateKey.publicKey) - let polishServer = PolishServerConfig(serverAddress: "127.0.0.1:1234", serverPrivateKey: privateKey) - - let replicantServerConfig = ReplicantServerConfig(serverAddress: "127.0.0.1:1234", polish: polishServer, toneBurst: nil, transport: "Replicant") - - guard let replicantClientConfig = ReplicantClientConfig(serverAddress: "127.0.0.1:1234", polish: polishClient, toneBurst: nil, transport: "Replicant") else - { - XCTFail() - return - } - - let replicant = Replicant(logger: self.logger) - - let replicantListener = try replicant.listen(address: "127.0.0.1", port: 1234, config: replicantServerConfig) - Task { - let replicantConnection = try replicantListener.accept() - - guard let serverReadData = replicantConnection.read(size: clientSendData.count) else { - XCTFail() - return - } - - guard replicantConnection.write(data: serverSendData) else { - XCTFail() - return - } - - XCTAssertEqual(serverReadData.string, clientSendData.string) - } - - let replicantClient = try replicant.connect(host: "127.0.0.1", port: 1234, config: replicantClientConfig) - - guard replicantClient.write(data: clientSendData) else { - XCTFail() - return - } - - guard let clientReadData = replicantClient.read(size: serverSendData.count) else { - XCTFail() - return - } - - XCTAssertEqual(clientReadData.string, serverSendData.string) - } - - func testStarburstAndDarkstar() throws { - let serverSendData = "success".data - let clientSendData = "pass".data - let toneBurstServer = Starburst(.SMTPServer) - let toneBurstClient = Starburst(.SMTPClient) - let privateKey = try PrivateKey(type: .P256KeyAgreement) - let polishClient = PolishClientConfig(serverAddress: "127.0.0.1:1234", serverPublicKey: privateKey.publicKey) - let polishServer = PolishServerConfig(serverAddress: "127.0.0.1:1234", serverPrivateKey: privateKey) - - let replicantServerConfig = ReplicantServerConfig(serverAddress: "127.0.0.1:1234", polish: polishServer, toneBurst: toneBurstServer, transport: "Replicant") - - guard let replicantClientConfig = ReplicantClientConfig(serverAddress: "127.0.0.1:1234", polish: polishClient, toneBurst: toneBurstClient, transport: "Replicant") else - { - XCTFail() - return - } - - let replicant = Replicant(logger: self.logger) - - let replicantListener = try replicant.listen(address: "127.0.0.1", port: 1234, config: replicantServerConfig) - Task { - let replicantConnection = try replicantListener.accept() - - guard let serverReadData = replicantConnection.read(size: clientSendData.count) else { - XCTFail() - return - } - - guard replicantConnection.write(data: serverSendData) else { - XCTFail() - return - } - - XCTAssertEqual(serverReadData.string, clientSendData.string) - } - - let replicantClient = try replicant.connect(host: "127.0.0.1", port: 1234, config: replicantClientConfig) - - guard replicantClient.write(data: clientSendData) else { - XCTFail() - return - } - - guard let clientReadData = replicantClient.read(size: serverSendData.count) else { - XCTFail() - return - } - - XCTAssertEqual(clientReadData.string, serverSendData.string) - } - - func testCreateConfigs() throws { - guard let privateKey = try? PrivateKey(type: .P256KeyAgreement) else { - XCTFail() - return - } - - let publicKey = privateKey.publicKey - let polishClientConfig = PolishClientConfig(serverAddress: "127.0.0.1:1234", serverPublicKey: publicKey) - let polishServerConfig = PolishServerConfig(serverAddress: "127.0.0.1:1234", serverPrivateKey: privateKey) - let toneburstClient = Starburst(.SMTPClient) - let toneburstServer = Starburst(.SMTPServer) - guard let clientConfig = ReplicantClientConfig(serverAddress: "127.0.0.1:1234", polish: polishClientConfig, toneBurst: toneburstClient, transport: "Replicant") else - { - XCTFail("Failed to create a ReplicantClientConfig") - return - } - let serverConfig = ReplicantServerConfig(serverAddress: "127.0.0.1:1234", polish: polishServerConfig, toneBurst: toneburstServer, transport: "Replicant") - - guard let clientJson = clientConfig.createJSON() else { - XCTFail() - return - } - - guard let serverJson = serverConfig.createJSON() else { - XCTFail() - return - } - - let fileManager = FileManager.default - let clientConfigDirectory = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Desktop", isDirectory: true) - let clientConfigPath = clientConfigDirectory.appendingPathComponent("ReplicantClientConfig.json", isDirectory: false).path - let serverConfigDirectory = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Desktop", isDirectory: true) - let serverConfigPath = serverConfigDirectory.appendingPathComponent("ReplicantServerConfig.json", isDirectory: false).path - let clientConfigCreated = fileManager.createFile(atPath: clientConfigPath, contents: clientJson) - let serverConfigCreated = fileManager.createFile(atPath: serverConfigPath, contents: serverJson) - XCTAssertTrue(clientConfigCreated) - XCTAssertTrue(serverConfigCreated) - } - }