diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0ddb622..2f7b112 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,8 +10,8 @@ "vscode": { "extensions": [ "sswg.swift-lang", - //"fabiospampinato.vscode-highlight", - //"fabiospampinato.vscode-todo-plus" + "fabiospampinato.vscode-highlight", + "fabiospampinato.vscode-todo-plus" ] } }, diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b3dbd1c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Sources/DataAcquisition"] + path = Sources/DataAcquisition + url = https://repositories.communitynotfound.work/PoliBa-Software-Architecture/embeddedLibrary.git diff --git a/Package.swift b/Package.swift index 5d3758d..d7330de 100644 --- a/Package.swift +++ b/Package.swift @@ -15,6 +15,10 @@ let package = Package( name: "RandomCpp", targets: ["RandomCpp"] ), + .library( + name: "DataAcquisition", + targets: ["DataAcquisition"] + ), ], dependencies: [ @@ -27,21 +31,29 @@ let package = Package( name: "RandomCpp" ), + .target( + name: "DataAcquisition" + ), + .target( name: "IoT-Simulator-Core", dependencies: [ "RandomCpp", + "DataAcquisition", .product(name: "Crypto", package: "swift-crypto"), ], swiftSettings: [ - .interoperabilityMode(.Cxx) + .interoperabilityMode(.Cxx), + .interoperabilityMode(.C) ]), .testTarget( name: "IoT-Simulator-CoreTests", dependencies: ["IoT-Simulator-Core"], swiftSettings: [ - .interoperabilityMode(.Cxx) + .interoperabilityMode(.C), + .interoperabilityMode(.Cxx), + ] ), ] diff --git a/Sources/DataAcquisition b/Sources/DataAcquisition new file mode 160000 index 0000000..a9ba495 --- /dev/null +++ b/Sources/DataAcquisition @@ -0,0 +1 @@ +Subproject commit a9ba49505e8d77e5bdc759914d23540b77fa7b84 diff --git a/Sources/IoT-Simulator-Core/Classes/Devices/EdgeDevice.swift b/Sources/IoT-Simulator-Core/Classes/Devices/EdgeDevice.swift index ea21933..50ae599 100644 --- a/Sources/IoT-Simulator-Core/Classes/Devices/EdgeDevice.swift +++ b/Sources/IoT-Simulator-Core/Classes/Devices/EdgeDevice.swift @@ -1,73 +1,90 @@ -public class EdgeDevice : EdgeDeviceP { - +import Crypto +import DataAcquisition +import Foundation + +public class EdgeDevice: EdgeDeviceP { + public let deviceID: String public let deviceType: DeviceType public let dataType: DataType public var disconnected: Bool public var compromised: Bool + public var location: Location3D public var dutyCicle: UInt public var sensors: [Int: Sensor] - public var privateKey: [UInt8] + public var privateKey: P521.Signing.PrivateKey private var numberOfSensors: Int { - get{ - return sensors.count - } + return sensors.count } public init( deviceID: String, dataType: DataType, disconnected: Bool, + location: Location3D, dutyCicle: UInt, - sensors: [Int: Sensor] + sensors: [Int: Sensor], + privateKey: P521.Signing.PrivateKey ) { self.deviceID = deviceID self.deviceType = DeviceType.EdgeDevice self.dataType = dataType self.disconnected = disconnected self.compromised = false + self.location = location self.dutyCicle = dutyCicle self.sensors = sensors + self.privateKey = privateKey } public func addSensor(sensor: Sensor) { - self.sensors[numberOfSensors + 1] = sensor + self.sensors[numberOfSensors + 1] = sensor } public func removeSensor(id: Int) { self.sensors.removeValue(forKey: id) } - public func work(envrionment: PhysicalEnvironment) { + public func work(envrionment: PhysicalEnvironment) -> Message { - // UGLY: In case I have some optimization problems, fix here - var values: [Float] = [] + // UGLY: Declaring here some variables manually, remove them if you want to offere flexibility + let numberOfSamples: Int = 10 - for sensor in sensors { - values.append( - sensor.value.read(envrionment).value - ) - } + let rowPointer = + UnsafeMutablePointer?>.allocate(capacity: numberOfSamples) - // Todo: START Remove this and Add Vincenzo's implementation - let avg : Float = meanValue(values: values) - let std_dev : Float = standardDeviation(values: values) - - // Todo: END Add Vincenzo's implementation - return Message( - msgType: MessageType.Data, - timestamp: , - deviceID: String, - location: Location3D, - fields: [Field], - signature: [UInt8] + rowPointer.initialize( + repeating: UnsafeMutablePointer + .allocate(capacity: self.sensors.count), + count: numberOfSamples ) + for rowI in 0.. PhysicalData { let value: PhysicalData = super.read(environment) - // TODO Add gaussian error here - return value + return value + self._gaussianNoise.generate() } } \ No newline at end of file diff --git a/Sources/IoT-Simulator-Core/Classes/Utils/Field.swift b/Sources/IoT-Simulator-Core/Classes/Utils/Field.swift index 71a94dd..ece961a 100644 --- a/Sources/IoT-Simulator-Core/Classes/Utils/Field.swift +++ b/Sources/IoT-Simulator-Core/Classes/Utils/Field.swift @@ -8,5 +8,9 @@ public class Field { self.key = key self.value = value } + + public var description: String { + return "\(self.key): \t\(self.value)" + } } \ No newline at end of file diff --git a/Sources/IoT-Simulator-Core/Classes/Utils/Location3D.swift b/Sources/IoT-Simulator-Core/Classes/Utils/Location3D.swift index 7d9f430..d1f4b9b 100644 --- a/Sources/IoT-Simulator-Core/Classes/Utils/Location3D.swift +++ b/Sources/IoT-Simulator-Core/Classes/Utils/Location3D.swift @@ -9,4 +9,8 @@ public class Location3D { self.y = y self.z = z } + + public var description: String { + return "X: \(self.x)\tY: \(self.y)\tZ: \(self.z)" + } } \ No newline at end of file diff --git a/Sources/IoT-Simulator-Core/Classes/Utils/Message.swift b/Sources/IoT-Simulator-Core/Classes/Utils/Message.swift index 63ac198..282e808 100644 --- a/Sources/IoT-Simulator-Core/Classes/Utils/Message.swift +++ b/Sources/IoT-Simulator-Core/Classes/Utils/Message.swift @@ -9,9 +9,7 @@ public class Message { public let location: Location3D public let fields: [Field] public var signature: String { - get{ - return self._signature != nil ? self._signature! : "##INVALID" - } + return self._signature != nil ? self._signature! : "##INVALID" } private var _signature: String? = nil @@ -30,22 +28,41 @@ public class Message { } - public func toData() -> Data { - var string: String = "" + "\(messageType)" + "\(timestamp)" + "\(deviceID)" + "\(location)" + public func toDataCompatibleString() -> String { + var string: String = "\(self.messageType)" + string += "\(self.timestamp)" + string += "\(self.deviceID)" + string += "\(self.location.x)" + string += "\(self.location.y)" + string += "\(self.location.z)" for field in self.fields { - string += "\(field)" + string += "\(field.key)\(field.value)" } - return Data(string.utf8) + return string } public func signMessage(key: P521.Signing.PrivateKey) { do { - self._signature = try sign(object: self.toData(), key: key) + self._signature = try sign(string: self.toDataCompatibleString(), key: key) } catch { // Do nothing } } + public var description: String { + + var string: String = "MessageType: \t\(self.messageType)\n" + string += "Timestamp: \t\(self.timestamp)\n" + string += "DeviceID: \t\(self.deviceID)\n" + string += "Location: \t\(self.location.description)\n" + + for field in self.fields { + string += "\(field.description)\n" + } + + return string + } + } diff --git a/Sources/IoT-Simulator-Core/Classes/Utils/WorkerTask.swift b/Sources/IoT-Simulator-Core/Classes/Utils/WorkerTask.swift new file mode 100644 index 0000000..b428632 --- /dev/null +++ b/Sources/IoT-Simulator-Core/Classes/Utils/WorkerTask.swift @@ -0,0 +1,55 @@ +/* import Foundation + +public class WorkerTask { + + public let env: PhysicalEnvironment + public let device: EdgeDeviceP + public let success: (_ msg: Message) throws -> Void + public let failure: () -> Void + private var task : Task<(), Never>? + + public init( + env: PhysicalEnvironment, + dev: EdgeDeviceP, + success: @escaping (_ msg: Message) throws -> Void, + failure: @escaping () -> Void + ) { + self.env = env + self.device = dev + self.success = success + self.failure = failure + self.task = nil + } + + public func work() { + + task = Task{ + + while true { + + let message = device.work(envrionment: env) + do { + try success(message) + } catch{ + failure() + } + usleep(__useconds_t(device.dutyCicle)) + } + } + + } + + public func cancel() { + if self.task == nil { + return + } + + self.task!.cancel() + self.task = nil + } + + + + +} + */ \ No newline at end of file diff --git a/Sources/IoT-Simulator-Core/Enums/DataType.swift b/Sources/IoT-Simulator-Core/Enums/DataType.swift index b4429b7..4dcd652 100644 --- a/Sources/IoT-Simulator-Core/Enums/DataType.swift +++ b/Sources/IoT-Simulator-Core/Enums/DataType.swift @@ -1,4 +1,4 @@ -public enum DataType { +public enum DataType : Sendable{ case Temperature case Humidity case Scan diff --git a/Sources/IoT-Simulator-Core/Enums/DeviceType.swift b/Sources/IoT-Simulator-Core/Enums/DeviceType.swift index c39e4b4..eda7bbc 100644 --- a/Sources/IoT-Simulator-Core/Enums/DeviceType.swift +++ b/Sources/IoT-Simulator-Core/Enums/DeviceType.swift @@ -1,4 +1,4 @@ -public enum DeviceType { +public enum DeviceType : Sendable{ case AsyncEdgeDevice case EdgeDevice } \ No newline at end of file diff --git a/Sources/IoT-Simulator-Core/Errors/CoreErrors.swift b/Sources/IoT-Simulator-Core/Errors/CoreErrors.swift index 16b0fc4..94fdad9 100644 --- a/Sources/IoT-Simulator-Core/Errors/CoreErrors.swift +++ b/Sources/IoT-Simulator-Core/Errors/CoreErrors.swift @@ -1,3 +1,4 @@ public enum CoreError : Error { case NoPhysicalDataAvailable( dataType: String) + case NoEnvironment } \ No newline at end of file diff --git a/Sources/IoT-Simulator-Core/IoT_Simulator_Core.swift b/Sources/IoT-Simulator-Core/IoT_Simulator_Core.swift index fe2ab92..96c6d4e 100644 --- a/Sources/IoT-Simulator-Core/IoT_Simulator_Core.swift +++ b/Sources/IoT-Simulator-Core/IoT_Simulator_Core.swift @@ -1,2 +1,73 @@ // The Swift Programming Language // https://docs.swift.org/swift-book + +import Foundation + +public actor IoTSimulatorCore { + + private static var enviroments: [String: PhysicalEnvironment] = Dictionary() + private static var devices: [String: EdgeDeviceP] = Dictionary() + private static var env_dev: [String: Set] = Dictionary() + private static var dev_tasks: [String: Task<(), Never>] = Dictionary() + + public static func addEnv(environment: PhysicalEnvironment) { + IoTSimulatorCore.enviroments[environment.location] = environment + } + + public static func addDevice(location: String, device: EdgeDeviceP) throws { + if let environment = IoTSimulatorCore.enviroments[location] { + IoTSimulatorCore.devices[device.deviceID] = device + + if IoTSimulatorCore.env_dev[location] == nil { + IoTSimulatorCore.env_dev[location] = Set() + } + + IoTSimulatorCore.env_dev[location]!.insert(device.deviceID) + + // schedule work + let task = IoTSimulatorCore.schedule(envID: environment.location, deviceID: device.deviceID) { msg in + print("\(msg.description)\n\n") + } failure: { + print("Something is wrong") + } + + IoTSimulatorCore.dev_tasks[device.deviceID] = task + + } else { + throw CoreError.NoEnvironment + } + + } + + public static func getEnv(name: String) -> PhysicalEnvironment? { + return IoTSimulatorCore.enviroments[name] + } + + public static func getDev(devID: String) -> EdgeDeviceP? { + return IoTSimulatorCore.devices[devID] + } + + private static func schedule( + envID: sending String, + deviceID: sending String, + success: sending @escaping (_ msg: Message) throws -> Void, + failure: sending @escaping () -> Void + ) -> Task<(), Never> { + return Task { + + while true { + let dev = IoTSimulatorCore.getDev(devID: deviceID)! + let env = IoTSimulatorCore.getEnv(name: envID)! + + let message = dev.work(envrionment: env) + do { + try success(message) + } catch { + failure() + } + usleep(__useconds_t(dev.dutyCicle * 1000)) + } + } + } + +} diff --git a/Sources/IoT-Simulator-Core/Protocols/EdgeDeviceProtocol.swift b/Sources/IoT-Simulator-Core/Protocols/EdgeDeviceProtocol.swift index 8b0f70c..e714a97 100644 --- a/Sources/IoT-Simulator-Core/Protocols/EdgeDeviceProtocol.swift +++ b/Sources/IoT-Simulator-Core/Protocols/EdgeDeviceProtocol.swift @@ -1,12 +1,15 @@ +import Crypto + public protocol EdgeDeviceP { var deviceID : String {get} var deviceType : DeviceType {get} var dataType : DataType {get} + var location: Location3D {get set} var disconnected : Bool {get set} var compromised : Bool {get set} var dutyCicle : UInt {get set} - var privateKey: [UInt8] {get} + var privateKey: P521.Signing.PrivateKey {get} func work(envrionment: PhysicalEnvironment) -> Message diff --git a/Sources/IoT-Simulator-Core/Utils/Security.swift b/Sources/IoT-Simulator-Core/Utils/Security.swift index d7c5d81..b84b4a6 100644 --- a/Sources/IoT-Simulator-Core/Utils/Security.swift +++ b/Sources/IoT-Simulator-Core/Utils/Security.swift @@ -16,6 +16,13 @@ public func sign(object: Data, key: P521.Signing.PrivateKey)throws -> String { return try key.signature(for: object).rawRepresentation.base64EncodedString() } +/* +public func sign(object: T, key: P521.Signing.PrivateKey) throws -> String { + + var _object = object + let data: Data = Data(bytes: &_object, count: MemoryLayout.stride) + +} */ diff --git a/Tests/IoT-Simulator-CoreTests/DataAcquisition-Tests.swift b/Tests/IoT-Simulator-CoreTests/DataAcquisition-Tests.swift new file mode 100644 index 0000000..a041fe5 --- /dev/null +++ b/Tests/IoT-Simulator-CoreTests/DataAcquisition-Tests.swift @@ -0,0 +1,13 @@ +import Testing + +import Foundation +import DataAcquisition + +@testable import IoT_Simulator_Core + +@Test func dataAcquisition() async throws { + + + + +} \ No newline at end of file diff --git a/Tests/IoT-Simulator-CoreTests/Devices-Tests.swift b/Tests/IoT-Simulator-CoreTests/Devices-Tests.swift new file mode 100644 index 0000000..1e6eb4f --- /dev/null +++ b/Tests/IoT-Simulator-CoreTests/Devices-Tests.swift @@ -0,0 +1,166 @@ +import Testing +import RandomCpp +import Foundation +import Crypto + +@testable import IoT_Simulator_Core + +@Test func idealSensor() async throws { + + let env = PhysicalEnvironment("Delta") + let truth = PhysicalData(.Temperature, 22) + + env.setPhysicalData(DataType.Temperature, truth) + + let sensor : Sensor = Sensor(id: 1, sensorType: .Temperature) + let data = sensor.read(env) + + + #expect(data.value == truth.value, "If values match, we are cool") + + +} + +@Test func faultyIdealSensor() async throws { + + let env = PhysicalEnvironment("Delta") + let truth = PhysicalData(.Temperature, 22) + + env.setPhysicalData(DataType.Temperature, truth) + + let sensor : Sensor = Sensor(id: 1, sensorType: .Temperature, faulty: true) + let data = sensor.read(env) + + + #expect(data.value != truth.value, "If these match, something is not working") + + +} + +@Test func realSensor() async throws { + + let env = PhysicalEnvironment("Delta") + let truth = PhysicalData(.Temperature, 22) + + env.setPhysicalData(DataType.Temperature, truth) + + let sensor : Sensor = RealSensor(sensorID: 1, sensorType: .Temperature, faulty: false, meanNoise: 0.5, stdNoise: 0.25, quantizationBits: 3) + let data = sensor.read(env) + + + #expect(data.value - truth.value < 10, "If these match, we are cool") + + print(data.value) + + +} + +@Test func faultyRealSensor() async throws { + + let env = PhysicalEnvironment("Delta") + let truth = PhysicalData(.Temperature, 22) + + env.setPhysicalData(DataType.Temperature, truth) + + let sensor : Sensor = RealSensor(sensorID: 1, sensorType: .Temperature, faulty: true, meanNoise: 0.5, stdNoise: 0.25, quantizationBits: 3) + let data = sensor.read(env) + + + #expect(data.value - truth.value > 10, "If these match, something is not working") + + print(data.value) + + +} + +@Test func edgeDevice() async throws { + + let env = PhysicalEnvironment("Delta") + let truth = PhysicalData(.Temperature, 22) + + env.setPhysicalData(DataType.Temperature, truth) + + let signKeyPath = "./Private/privateKey.pem" + + let privateKey = try pem2key(filePath: signKeyPath) + + let dev: EdgeDevice = EdgeDevice( + deviceID: "EDG-001", + dataType: .Temperature, + disconnected: false, + location: Location3D(20, 10, 0), + dutyCicle: 100098, + sensors: [ + 0: Sensor(id: 0, sensorType: DataType.Temperature), + 1: Sensor(id: 0, sensorType: DataType.Temperature), + 2: Sensor(id: 0, sensorType: DataType.Temperature, faulty: true) + ], + privateKey: privateKey + ) + + let message = dev.work(envrionment: env) + message.signMessage(key: dev.privateKey) + + + #expect(message != nil, "If this is nil, I don't knwo what's going on") + #expect(message.signature != nil, "If signature is nil, something is wrong") + + print(message.description) + print(message.signature) + + #expect( + try verifySignature( + signature:message.signature, + string: message.toDataCompatibleString(), + key: dev.privateKey.publicKey + ), + "Let's see that signatures match" + ) +} + +@Test func edgeDeviceMean() async throws { + + let env = PhysicalEnvironment("Delta") + let truth = PhysicalData(.Temperature, 22) + + env.setPhysicalData(DataType.Temperature, truth) + + let signKeyPath = "./Private/privateKey.pem" + + let privateKey = try pem2key(filePath: signKeyPath) + + let dev: EdgeDevice = EdgeDevice( + deviceID: "EDG-001", + dataType: .Temperature, + disconnected: false, + location: Location3D(20, 10, 0), + dutyCicle: 100098, + sensors: [ + 0: RealSensor(sensorID: 0, sensorType: .Temperature, faulty: false, meanNoise: 1, stdNoise: 3, quantizationBits: 3), + 1: RealSensor(sensorID: 1, sensorType: .Temperature, faulty: false, meanNoise: 1, stdNoise: 3, quantizationBits: 3), + 2: RealSensor(sensorID: 2, sensorType: .Temperature, faulty: false, meanNoise: 1, stdNoise: 3, quantizationBits: 3), + ], + privateKey: privateKey + ) + + let message = dev.work(envrionment: env) + message.signMessage(key: dev.privateKey) + + + #expect(message != nil, "If this is nil, I don't knwo what's going on") + #expect(message.signature != nil, "If signature is nil, something is wrong") + + print(message.description) + print(message.signature) + + #expect( + try verifySignature( + signature:message.signature, + string: message.toDataCompatibleString(), + key: dev.privateKey.publicKey + ), + "Let's see that signatures match" + ) +} + + diff --git a/Tests/IoT-Simulator-CoreTests/IoTCore-Tests.swift b/Tests/IoT-Simulator-CoreTests/IoTCore-Tests.swift new file mode 100644 index 0000000..081a277 --- /dev/null +++ b/Tests/IoT-Simulator-CoreTests/IoTCore-Tests.swift @@ -0,0 +1,65 @@ +import DataAcquisition +import Foundation +import Testing + +@testable import IoT_Simulator_Core + +@Test func workLoop1() async throws { + + let env = PhysicalEnvironment("Delta") + let truth = PhysicalData(.Temperature, 22) + + env.setPhysicalData(DataType.Temperature, truth) + + IoTSimulatorCore.addEnv(environment: env) + + let signKeyPath = "./Private/privateKey.pem" + + let privateKey = try pem2key(filePath: signKeyPath) + + let dev: EdgeDevice = EdgeDevice( + deviceID: "EDG-001", + dataType: .Temperature, + disconnected: false, + location: Location3D(20, 10, 0), + dutyCicle: 3000, + sensors: [ + 0: RealSensor( + sensorID: 0, sensorType: .Temperature, faulty: false, meanNoise: 1, stdNoise: 3, + quantizationBits: 3), + 1: RealSensor( + sensorID: 1, sensorType: .Temperature, faulty: false, meanNoise: 1, stdNoise: 3, + quantizationBits: 3), + 2: RealSensor( + sensorID: 2, sensorType: .Temperature, faulty: false, meanNoise: 1, stdNoise: 3, + quantizationBits: 3), + ], + privateKey: privateKey + ) + + let dev2: EdgeDevice = EdgeDevice( + deviceID: "EDG-002", + dataType: .Temperature, + disconnected: false, + location: Location3D(20, 10, 0), + dutyCicle: 1000, + sensors: [ + 0: RealSensor( + sensorID: 0, sensorType: .Temperature, faulty: false, meanNoise: 1, stdNoise: 3, + quantizationBits: 3), + 1: RealSensor( + sensorID: 1, sensorType: .Temperature, faulty: false, meanNoise: 1, stdNoise: 3, + quantizationBits: 3), + 2: RealSensor( + sensorID: 2, sensorType: .Temperature, faulty: false, meanNoise: 1, stdNoise: 3, + quantizationBits: 3), + ], + privateKey: privateKey + ) + + try IoTSimulatorCore.addDevice(location: "Delta", device: dev) + try IoTSimulatorCore.addDevice(location: "Delta", device: dev2) + + sleep(15) + +} diff --git a/Tests/IoT-Simulator-CoreTests/IoT_Simulator_CoreTests.swift b/Tests/IoT-Simulator-CoreTests/IoT_Simulator_CoreTests.swift index 5c0463a..3c19333 100644 --- a/Tests/IoT-Simulator-CoreTests/IoT_Simulator_CoreTests.swift +++ b/Tests/IoT-Simulator-CoreTests/IoT_Simulator_CoreTests.swift @@ -55,9 +55,9 @@ import Crypto let signature = try sign(string: obj, key: key) let puKey = key.publicKey - let verify = try verify(signature: signature, string: obj, key: puKey) - print(verify) - assert(verify) + let _verify = try verifySignature(signature: signature, string: obj, key: puKey) + print(_verify) + assert(_verify) }