diff --git a/.vscode/launch.json b/.vscode/launch.json index d952704..bb8fa09 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,8 @@ "args": [], "cwd": "${workspaceFolder:workspace}", "name": "Debug IoT-Simulator", - "program": "${workspaceFolder:workspace}/.build/debug/IoT-Simulator", + "envFile": "${workspaceFolder:workspace}/env/debug/debug.env", + "program": "${workspaceFolder:workspace}/.build/debug/App", "preLaunchTask": "swift: Build Debug IoT-Simulator" }, { diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..1f5c603 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "swift", + "args": [ + "build", + "--product", + "App", + "-Xswiftc", + "-diagnostic-style=llvm" + ], + "env": {}, + "cwd": "/workspace", + "disableTaskQueue": true, + "dontTriggerTestDiscovery": true, + "showBuildStatus": "swiftStatus", + "group": "build", + "problemMatcher": [], + "label": "swift: Build Debug IoT-Simulator", + "detail": "swift build --product App -Xswiftc -diagnostic-style=llvm" + } + ] +} \ No newline at end of file diff --git a/Config/debug.json b/Config/debug.json new file mode 100644 index 0000000..7db6839 --- /dev/null +++ b/Config/debug.json @@ -0,0 +1,46 @@ +{ + "version": 1, + "environments": [ + { + "name": "Alpha", + "physicalData": [ + { + "dataType": "Temperature", + "value": 293.15 + }, + { + "dataType": "Humidity", + "value": 30.0 + } + ], + "devices": [ + { + "dataType": "Temperature", + "realSensors": true, + "number": 10 + } + ] + }, + { + "name": "Yota", + "physicalData": [ + { + "dataType": "Temperature", + "value": 294.15 + }, + { + "dataType": "Humidity", + "value": 30.0 + } + ], + "devices": [ + { + "dataType": "Humidity", + "realSensors": true, + "number": 100 + } + ] + } + + ] +} \ No newline at end of file diff --git a/Documentation/Config.md b/Documentation/Config.md new file mode 100644 index 0000000..9010593 --- /dev/null +++ b/Documentation/Config.md @@ -0,0 +1,82 @@ +# Config File +```json +{ + // Version of file + "version": 1, + + // Environments + "environments": [ + { + // Name of the location + "name": "abcd", + "physicalData": [ + { + "dataType": "Temperature", + "value": 293.15 // Kelvin + }, + { + "dataType": "Humidity", + "value": 30.0 // Percentage + } + ], + "devices": [ + { + // Between 0 and 999 included + "deviceID": 10, // * + "dataType": "Temperature", + "location:": { //* + "x": 0, + "y": 0, + "z": 0 + }, + "dutyCycle": 1000, // * + "realSensors": true, // * + }, + { + "dataType": "Temperature", + "realSensors": true, // * + + // Between 0 and 999 included + "number": 10 // * + } + ] + }, + { + // Name of the location + "name": "abcde", + "physicalData": [ + { + "dataType": "Temperature", + "value": 293.15 // Kelvin + }, + { + "dataType": "Humidity", + "value": 30.0 // Percentage + } + ], + "devices": [ + { + // Between 0 and 999 included + "deviceID": 10, // * + "dataType": "Temperature", + "location:": { //* + "x": 0, + "y": 0, + "z": 0 + }, + "dutyCycle": 1000, // * + "realSensors": true, // * + }, + { + "dataType": "Temperature", + "realSensors": true, // * + + // Between 0 and 999 included + "number": 10 // * + } + ] + } + + ] +} +``` \ No newline at end of file diff --git a/Package.swift b/Package.swift index 6d5b387..d4f80d1 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,8 @@ let package = Package( // 🔵 Non-blocking, event-driven networking for Swift. Used for custom executors .package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"), .package(url: "https://github.com/apple/swift-crypto.git", branch: "main"), - .package(url: "https://repositories.communitynotfound.work/PoliBa-Software-Architecture/Swift-MessageUtils.git", branch: "main") + .package(url: "https://repositories.communitynotfound.work/PoliBa-Software-Architecture/Swift-MessageUtils.git", branch: "main"), + .package(url: "https://repositories.communitynotfound.work/PoliBa-Software-Architecture/IoT-Simulator-Core.git", branch: "main") ], targets: [ .executableTarget( @@ -23,8 +24,11 @@ let package = Package( .product(name: "NIOPosix", package: "swift-nio"), .product(name: "Crypto", package: "swift-crypto"), .product(name: "MessageUtils", package: "Swift-MessageUtils"), + .product(name: "IoT-Simulator-Core", package: "iot-simulator-core") ], - swiftSettings: swiftSettings + swiftSettings: [ + .interoperabilityMode(.Cxx), + ] ), .testTarget( name: "AppTests", diff --git a/Private/.gitkeep b/Private/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Sources/App/CustomCode/Errors/ParsingError.swift b/Sources/App/CustomCode/Errors/ParsingError.swift new file mode 100644 index 0000000..ed4ae30 --- /dev/null +++ b/Sources/App/CustomCode/Errors/ParsingError.swift @@ -0,0 +1,5 @@ +public enum ParsingError : Error { + case MalformedJSON(reason: String) + case ConfigFileNotExistent + case ImpossibleToWriteKeyToFileSystem +} \ No newline at end of file diff --git a/Sources/App/CustomCode/Utils/P256-keys-creation.swift b/Sources/App/CustomCode/Utils/P256-keys-creation.swift new file mode 100644 index 0000000..950b176 --- /dev/null +++ b/Sources/App/CustomCode/Utils/P256-keys-creation.swift @@ -0,0 +1,70 @@ +import Foundation +import FoundationNetworking +import Crypto + +public func createPrivateP256Key() -> P256.Signing.PrivateKey { + return P256.Signing.PrivateKey() +} + +public func createPublickP256Key(privateKey: P256.Signing.PrivateKey ) -> P256.Signing.PublicKey { + return privateKey.publicKey +} + +public func publicP256_2_Spki(publicKey: P256.Signing.PublicKey) -> String { + return publicKey.pemRepresentation +} + +public func privateP256_2_pem(privateKey: P256.Signing.PrivateKey) -> String { + return privateKey.pemRepresentation +} + +// UGLY: Refactor to make it easier to comprehend +public func fetchPrivateP256Key(deviceID: UInt128) async throws -> P256.Signing.PrivateKey { + + // UGLY: but fast + let privateKeyFolder = ProcessInfo.processInfo.environment["PRIVATE_KEY_FOLDER"] ?? "./Private/PrivateKeysP256" + + let keyFilePath = "\(privateKeyFolder)/\(deviceID)-Kr.pem" + + do { + let key = try pem2_P265_PrivateKey(filePath: keyFilePath) + // TODO: send public key to another server + let publicKey = key.publicKey.pemRepresentation + + // UGLY: hardcoded + var httpRequest = URLRequest(url: URL(string: "http://publick-key-db.internal/key")!) + httpRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + httpRequest.httpMethod = "POST" + + let message: [String : Encodable] = [ + "deviceID": deviceID, + "publicKey": publicKey + ] + let data = try JSONSerialization.data(withJSONObject: message) + httpRequest.httpBody = data + + let _ = try await URLSession.shared.upload(for: httpRequest, from: data) + + return key + } catch { + // Do nothing + } + + do { + let key = createPrivateP256Key() + try privateP256_2_pem(privateKey: key).write(to: URL(filePath: keyFilePath), atomically: true, encoding: String.Encoding.utf8) + return key + } catch { + throw ParsingError.ImpossibleToWriteKeyToFileSystem + } + + + +} + +private func pem2_P265_PrivateKey(filePath: String) throws -> P256.Signing.PrivateKey { + + let pemEncodedKey = try String(contentsOf: URL(filePath: filePath), encoding: .utf8) + return try P256.Signing.PrivateKey(pemRepresentation: pemEncodedKey) + +} \ No newline at end of file diff --git a/Sources/App/Utils/P256-keys-creation.swift b/Sources/App/Utils/P256-keys-creation.swift deleted file mode 100644 index 9d7669b..0000000 --- a/Sources/App/Utils/P256-keys-creation.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Crypto - -public func createPrivateP256Key() -> P256.Signing.PrivateKey { - return P256.Signing.PrivateKey() -} - -public func createPublickP256Key(privateKey: P256.Signing.PrivateKey ) -> P256.Signing.PublicKey { - return privateKey.publicKey -} - -public func publicP256_2_Spki(publicKey: P256.Signing.PublicKey) -> String { - return publicKey.pemRepresentation -} \ No newline at end of file diff --git a/Sources/App/entrypoint.swift b/Sources/App/entrypoint.swift index c619a87..9f9083e 100644 --- a/Sources/App/entrypoint.swift +++ b/Sources/App/entrypoint.swift @@ -25,6 +25,12 @@ enum Entrypoint { try? await app.asyncShutdown() throw error } + + do { + try await configureSimulator() + } catch { + print("Something went wrong with the simulator") + } try await app.execute() try await app.asyncShutdown() } diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift index 5f56f83..92c9f90 100644 --- a/Sources/App/routes.swift +++ b/Sources/App/routes.swift @@ -1,6 +1,5 @@ -import Vapor -import MessageUtils import Foundation +import Vapor func routes(_ app: Application) throws { app.get { req async in diff --git a/Sources/App/simulator-configuration.swift b/Sources/App/simulator-configuration.swift new file mode 100644 index 0000000..6c45fcd --- /dev/null +++ b/Sources/App/simulator-configuration.swift @@ -0,0 +1,134 @@ +import Crypto +import Foundation +import IoT_Simulator_Core +import MessageUtils + +public func configureSimulator() async throws { + + guard + let configurationFilePath: String = ProcessInfo.processInfo.environment[ + "CONFIGURATION_FILE"] + else { + throw ParsingError.ConfigFileNotExistent + } + + let jsonData: Data = try Data(contentsOf: URL(filePath: configurationFilePath)) + + guard + let jsonObject: [String: Any] = try JSONSerialization.jsonObject(with: jsonData) + as? [String: Any] + else { + throw ParsingError.MalformedJSON(reason: "This is not a JSON file") + } + + try await jsonConfigurationParser(jsonObject) + +} + +private func jsonConfigurationParser(_ json: [String: Any]) async throws { + + guard + let version = json["version"] as? Int, + let environments = json["environments"] as? [[String: Any]] + else { + throw ParsingError.MalformedJSON(reason: "Missing either version or environemnt") + } + + for environmentJSON in environments { + + let env = try json2env(environmentJSON) + + IoTSimulatorCore.addEnv(environment: env) + + if let devices = environmentJSON["devices"] as? [[String: Any]] { + for deviceJSON in devices { + let devices = try await json2edge_dev(deviceJSON) + + for dev in devices { + try IoTSimulatorCore.addDevice(location: env.location, device: dev) { msg in + // UGLY: But fast + // TODO: add sending code here + } failure: { + print("Failed") + } + } + } + } + + } + +} + +private func json2env(_ json: [String: Any]) throws -> PhysicalEnvironment { + + guard + let envName = json["name"] as? String + else { + throw ParsingError.MalformedJSON(reason: "Missing name of environment") + } + + let environment = PhysicalEnvironment(envName) + + if let physicalData = json["physicalData"] as? [[String: Any]] { + + for physicalDatum in physicalData { + guard + let dataTypeString = physicalDatum["dataType"] as? String, + let value = physicalDatum["value"] as? Double, + let dataType = DataType(rawValue: dataTypeString) + else { + throw ParsingError.MalformedJSON(reason: "Physical Data is Misconfigured") + } + + let datum = PhysicalData(dataType, Float(value)) + environment.setPhysicalData(datum.type, datum) + } + + } + + return environment +} + +private func json2edge_dev(_ json: [String: Any]) async throws -> [EdgeDevice] { + + var devices: [EdgeDevice] = [] + + guard + let _dataType = json["dataType"] as? String, + let dataType = DataType(rawValue: _dataType) + else { + throw ParsingError.MalformedJSON(reason: "Data Type missing in one device") + } + + if let number = json["number"] as? UInt { + for _ in 0..