diff --git a/Sources/IoT-Simulator-Core/Classes/Devices/EdgeDevice.swift b/Sources/IoT-Simulator-Core/Classes/Devices/EdgeDevice.swift index 0384781..d99f89a 100644 --- a/Sources/IoT-Simulator-Core/Classes/Devices/EdgeDevice.swift +++ b/Sources/IoT-Simulator-Core/Classes/Devices/EdgeDevice.swift @@ -3,22 +3,23 @@ import DataAcquisition import Foundation import MessageUtils -public class EdgeDevice: EdgeDeviceP, @unchecked Sendable { +public actor EdgeDevice: @preconcurrency EdgeDeviceP { + + public let deviceID: UInt128 public let deviceType: DeviceType public let dataType: DataType - public var disconnected: Bool - public var compromised: Bool - public var location: Location - public var dutyCicle: UInt - public var sensors: [Int: Sensor] - public var privateKey: P256.Signing.PrivateKey + public private(set) var disconnected: Bool + public private(set) var compromised: Bool + public private(set) var location: Location + public private(set) var dutyCicle: UInt + public private(set) var sensors: [Int: Sensor] + public private(set) var privateKey: P256.Signing.PrivateKey public var publicKey: P256.Signing.PublicKey { return self.privateKey.publicKey } - private let lock: NSLock private var numberOfSensors: Int { return sensors.count } @@ -41,24 +42,54 @@ public class EdgeDevice: EdgeDeviceP, @unchecked Sendable { self.dutyCicle = dutyCicle self.sensors = sensors self.privateKey = privateKey - self.lock = NSLock() + } - public func addSensor(sensor: Sensor) { - self.lock.lock() + public func addSensor(sensor: Sensor) async { self.sensors[numberOfSensors + 1] = sensor - self.lock.unlock() } - public func removeSensor(id: Int) { - self.lock.lock() + public func removeSensor(id: Int) async { self.sensors.removeValue(forKey: id) - self.lock.unlock() } - public func work(envrionment: PhysicalEnvironment) throws -> Data { + public func setSensorFaulty(id: Int) async { + if id > self.numberOfSensors { + // UGLY: Do nothing + } - self.lock.lock() + let sensor = self.sensors[id]! + + await sensor.toggleFaulty() + } + + public func setDisconnected(disconnected: Bool) async { + self.disconnected = disconnected + } + + public func setCompromised(compromised: Bool) async { + self.compromised = compromised + } + + public func setLocation(location: Location) async { + self.location = location + } + + public func dutyCycle(time: UInt) async { + self.dutyCicle = time + } + + private func getSensor(id: Int) async -> Sensor { + if id > self.numberOfSensors { + // UGLY: Do nothing + } + + return self.sensors[id]! + } + + public func work(envrionment: PhysicalEnvironment) async throws -> Data { + + // UGLY: Declaring here some variables manually, remove them if you want to offere flexibility let numberOfSamples: Int = 10 @@ -75,7 +106,7 @@ public class EdgeDevice: EdgeDeviceP, @unchecked Sendable { for rowI in 0.. PhysicalData +} + +public actor IdealSensor: @preconcurrency Sensor { public let sensorID: Int public let sensorType: DataType - public var faulty: Bool + public private(set) var faulty: Bool public init(id: Int, sensorType: DataType, faulty: Bool) { self.sensorID = id @@ -18,50 +30,59 @@ public class Sensor { self.faulty = false } - public func read(_ environment: PhysicalEnvironment) -> PhysicalData { + public func read(_ environment: PhysicalEnvironment) async -> PhysicalData { - let datum = self._read(environment) + let datum = await self._read(environment) return !self.faulty ? datum : datum + Float.random(in: 1E2...1E10) } - internal func _read(_ environment: PhysicalEnvironment) -> PhysicalData{ - let datum : PhysicalData + internal func _read(_ environment: PhysicalEnvironment) async -> PhysicalData { + let datum: PhysicalData do { - datum = try environment.getPhysicalData(self.sensorType) + datum = try await environment.getPhysicalData(self.sensorType) } catch CoreError.NoPhysicalDataAvailable { - datum = self._defaultReadValue() + datum = self._defaultReadValue() } catch { print( - "This is really a weird problem, to be investigated, but here we are...\n" + - "nonetheless, the error will be handled silently, sowwy :3" + "This is really a weird problem, to be investigated, but here we are...\n" + + "nonetheless, the error will be handled silently, sowwy :3" ) datum = self._defaultReadValue() - } - + } + return datum } - internal func _defaultReadValue() -> PhysicalData{ + internal func _defaultReadValue() -> PhysicalData { return PhysicalData(self.sensorType, 0) } + public func setFaulty(value: Bool) async { + self.faulty = value + } + public func toggleFaulty() async { + self.faulty = !self.faulty + } } -public class RealSensor: Sensor { +public actor RealSensor: @preconcurrency Sensor { + public let sensorID: Int + public let sensorType: DataType + public private(set) var faulty: Bool - public var meanNoise: Float { get{ return _meanNoise}} - public var stdNoise: Float { get{ return _stdNoise}} - public var quantizationBits: Int { get{ return _quantizationBits}} + public var meanNoise: Float { return _meanNoise } + public var stdNoise: Float { return _stdNoise } + public var quantizationBits: Int { return _quantizationBits } private var _meanNoise: Float private var _stdNoise: Float private let _quantizationBits: Int - // TODO: add a generator of GaussianRNG + // TODO: add a generator of GaussianRNG private var _gaussianNoise: GaussianRNG public init( @@ -70,35 +91,72 @@ public class RealSensor: Sensor { faulty: Bool, meanNoise: Float, stdNoise: Float, - quantizationBits: Int + quantizationBits: Int ) { + self.sensorID = sensorID + self.sensorType = sensorType + self.faulty = faulty self._meanNoise = meanNoise self._stdNoise = stdNoise self._quantizationBits = quantizationBits self._gaussianNoise = GaussianRNG(self._meanNoise, self._stdNoise) - super.init(id: sensorID, sensorType: sensorType, faulty: faulty) } - convenience public init( + public init( sensorID: Int, sensorType: DataType, meanNoise: Float, stdNoise: Float, - quantizationBits: Int + quantizationBits: Int ) { self.init( - sensorID: sensorID, - sensorType: sensorType, - faulty: false, - meanNoise: meanNoise, - stdNoise: stdNoise, + sensorID: sensorID, + sensorType: sensorType, + faulty: false, + meanNoise: meanNoise, + stdNoise: stdNoise, quantizationBits: quantizationBits ) } - override public func read(_ environment: PhysicalEnvironment) -> PhysicalData { - let value: PhysicalData = super.read(environment) + public func read(_ environment: PhysicalEnvironment) async -> PhysicalData { + + let datum = await self._read(environment) + + let value = !self.faulty ? datum : datum + Float.random(in: 1E2...1E10) + return value + self._gaussianNoise.generate() + } -} \ No newline at end of file + internal func _read(_ environment: PhysicalEnvironment) async -> PhysicalData { + let datum: PhysicalData + + do { + datum = try await environment.getPhysicalData(self.sensorType) + } catch CoreError.NoPhysicalDataAvailable { + datum = self._defaultReadValue() + } catch { + print( + "This is really a weird problem, to be investigated, but here we are...\n" + + "nonetheless, the error will be handled silently, sowwy :3" + ) + datum = self._defaultReadValue() + } + + return datum + } + + internal func _defaultReadValue() -> PhysicalData { + return PhysicalData(self.sensorType, 0) + } + + public func setFaulty(value: Bool) async { + self.faulty = value + } + + public func toggleFaulty() async { + self.faulty = !self.faulty + } + +} diff --git a/Sources/IoT-Simulator-Core/Classes/Environment/PhysicalEnvironment.swift b/Sources/IoT-Simulator-Core/Classes/Environment/PhysicalEnvironment.swift index 7859908..f8eccf0 100644 --- a/Sources/IoT-Simulator-Core/Classes/Environment/PhysicalEnvironment.swift +++ b/Sources/IoT-Simulator-Core/Classes/Environment/PhysicalEnvironment.swift @@ -1,6 +1,6 @@ import Foundation -public class PhysicalEnvironment: @unchecked Sendable { +public actor PhysicalEnvironment { private var physicalEnvironment: [DataType: PhysicalData] public let location: String diff --git a/Sources/IoT-Simulator-Core/Classes/Utils/PhysicalData.swift b/Sources/IoT-Simulator-Core/Classes/Utils/PhysicalData.swift index 2e052f2..4beb2de 100644 --- a/Sources/IoT-Simulator-Core/Classes/Utils/PhysicalData.swift +++ b/Sources/IoT-Simulator-Core/Classes/Utils/PhysicalData.swift @@ -1,6 +1,6 @@ -public class PhysicalData { +public final class PhysicalData: Sendable { public let type: DataType public let value: Float diff --git a/Sources/IoT-Simulator-Core/Enums/DataType.swift b/Sources/IoT-Simulator-Core/Enums/DataType.swift index 5b0af98..aeb5113 100644 --- a/Sources/IoT-Simulator-Core/Enums/DataType.swift +++ b/Sources/IoT-Simulator-Core/Enums/DataType.swift @@ -1,4 +1,4 @@ -public enum DataType : String { +public enum DataType : String, Sendable { case Temperature case Humidity case Scan diff --git a/Sources/IoT-Simulator-Core/Factories/DeviceFactories.swift b/Sources/IoT-Simulator-Core/Factories/DeviceFactories.swift index 0494d47..81ee073 100644 --- a/Sources/IoT-Simulator-Core/Factories/DeviceFactories.swift +++ b/Sources/IoT-Simulator-Core/Factories/DeviceFactories.swift @@ -7,11 +7,10 @@ public actor DeviceFactory { // TODO: Make it possible to set this private static var availableIDs: Set = Set(0..<999) - @MainActor public static func createEdgeDevice( - deviceID: UInt128, - dataType: DataType, - privateKey: P256.Signing.PrivateKey + deviceID: sending UInt128, + dataType: sending DataType, + privateKey: sending P256.Signing.PrivateKey ) throws -> EdgeDevice { return try DeviceFactory.createEdgeDevice( deviceID: deviceID, @@ -21,12 +20,11 @@ public actor DeviceFactory { ) } - @MainActor public static func createEdgeDevice( - deviceID: UInt128, - dataType: DataType, - privateKey: P256.Signing.PrivateKey, - realSensor: Bool + deviceID: sending UInt128, + dataType: sending DataType, + privateKey: sending P256.Signing.PrivateKey, + realSensor: sending Bool ) throws -> EdgeDevice { return try DeviceFactory.createEdgeDevice( @@ -42,15 +40,14 @@ public actor DeviceFactory { realSensor: realSensor ) } - - @MainActor + public static func createEdgeDevice( - deviceID: UInt128, - dataType: DataType, - location: Location, - dutyCicle: UInt, - privateKey: P256.Signing.PrivateKey, - realSensor: Bool + deviceID: sending UInt128, + dataType: sending DataType, + location: sending Location, + dutyCicle: sending UInt, + privateKey: sending P256.Signing.PrivateKey, + realSensor: sending Bool ) throws -> EdgeDevice { if !DeviceFactory.availableIDs.contains(deviceID) { @@ -64,9 +61,9 @@ public actor DeviceFactory { if !realSensor { sensors = [ - 0: Sensor(id: 0, sensorType: dataType), - 1: Sensor(id: 1, sensorType: dataType), - 2: Sensor(id: 2, sensorType: dataType), + 0: IdealSensor(id: 0, sensorType: dataType), + 1: IdealSensor(id: 1, sensorType: dataType), + 2: IdealSensor(id: 2, sensorType: dataType), ] } else { sensors = [ @@ -89,7 +86,8 @@ public actor DeviceFactory { location: location, dutyCicle: dutyCicle, sensors: sensors, - privateKey: privateKey) + privateKey: privateKey + ) } public static func getUnusedID() throws -> UInt128 { diff --git a/Sources/IoT-Simulator-Core/IoT_Simulator_Core.swift b/Sources/IoT-Simulator-Core/IoT_Simulator_Core.swift index 6dbb6a6..977d639 100644 --- a/Sources/IoT-Simulator-Core/IoT_Simulator_Core.swift +++ b/Sources/IoT-Simulator-Core/IoT_Simulator_Core.swift @@ -3,12 +3,13 @@ import Foundation -public actor IoTSimulatorCore { +@MainActor +public class IoTSimulatorCore { - internal static var enviroments: [String: PhysicalEnvironment] = Dictionary() - internal static var devices: [String: EdgeDeviceP] = Dictionary() - internal static var env_dev: [String: Set] = Dictionary() - internal static var dev_tasks: [String: Task<(), Never>] = Dictionary() + @MainActor internal static var enviroments: [String: PhysicalEnvironment] = Dictionary() + @MainActor internal static var devices: [String: EdgeDeviceP] = Dictionary() + @MainActor internal static var env_dev: [String: Set] = Dictionary() + @MainActor internal static var dev_tasks: [String: Task<(), Never>] = Dictionary() public static func addEnv(environment: PhysicalEnvironment) { IoTSimulatorCore.enviroments[environment.location] = environment @@ -31,7 +32,7 @@ public actor IoTSimulatorCore { // schedule work let task = IoTSimulatorCore.schedule( - envID: environment.location, deviceID: device.deviceID, success: success, + environment: environment, device: device as! EdgeDevice, success: success, failure: failure) IoTSimulatorCore.dev_tasks["\(device.deviceID)"] = task @@ -45,12 +46,6 @@ public actor IoTSimulatorCore { return IoTSimulatorCore.enviroments[name] } - public static func addPhysicalData(environmentName: String, physicalData: PhysicalData) { - if let environment = getEnv(name: environmentName) { - environment.setPhysicalData(physicalData.type, physicalData) - } - } - public static func getDev(devID: UInt128) -> EdgeDeviceP? { return IoTSimulatorCore.devices["\(devID)"] } @@ -73,49 +68,44 @@ public actor IoTSimulatorCore { return false } - @MainActor - public static func toggleSensor(devID: UInt128, sensorID: Int) -> Bool { + public static func toggleSensor(devID: UInt, sensorID: Int) async -> Bool { + + let a = IoTSimulatorCore.getDev(devID: UInt128(devID))! + - // UGLY: Should throw - if IoTSimulatorCore.getDev(devID: devID) == nil { - return false - } + let device = IoTSimulatorCore.getDev(devID: UInt128(devID))! as! EdgeDevice - // FIXME: Should it throw? - if IoTSimulatorCore.getDev(devID: devID)!.deviceType != .EDGE_SENSOR { - return false - } - - let device = IoTSimulatorCore.getDev(devID: devID)! as! EdgeDevice - - // UGLY: It should throw - if device.sensors.count <= sensorID { - return false - } - - device.sensors[sensorID]!.faulty = !device.sensors[sensorID]!.faulty + await device.setSensorFaulty(id: sensorID) return true } + public static func addPhysicalData(environmentName: String, physicalData: PhysicalData) + async throws + { + guard let environment = getEnv(name: environmentName) else { + throw CoreError.NoEnvironment + } + + await environment.setPhysicalData(physicalData.type, physicalData) + } + private static func schedule( - envID: sending String, - deviceID: UInt128, + environment: PhysicalEnvironment, + device: EdgeDevice, success: sending @escaping (_ msg: Data) async throws -> Void, failure: sending @escaping () -> Void - ) -> Task<(), Never>{ - let _devID: String = "\(deviceID)" + ) -> Task<(), Never> { + return Task { var notCancelled: Bool = true - let dev = IoTSimulatorCore.getDev(devID: _devID)! - let env = IoTSimulatorCore.getEnv(name:envID)! while notCancelled { // print("Device: \(dev.deviceID)") do { - let message = try dev.work(envrionment: env) + let message = try await device.work(envrionment: environment) try await success(message) } catch { @@ -123,15 +113,15 @@ public actor IoTSimulatorCore { } do { - try await Task.sleep(nanoseconds: UInt64(dev.dutyCicle) * UInt64(1E6)) + try await Task.sleep(nanoseconds: UInt64(device.dutyCicle) * UInt64(1E6)) } catch { notCancelled = false } } - print("Bye, Bye, \(dev.deviceID)\n\n") - + print("Bye, Bye, \(device.deviceID)\n\n") + } } diff --git a/Sources/IoT-Simulator-Core/Protocols/EdgeDeviceProtocol.swift b/Sources/IoT-Simulator-Core/Protocols/EdgeDeviceProtocol.swift index 9be16ea..ab8f7ab 100644 --- a/Sources/IoT-Simulator-Core/Protocols/EdgeDeviceProtocol.swift +++ b/Sources/IoT-Simulator-Core/Protocols/EdgeDeviceProtocol.swift @@ -7,13 +7,19 @@ public protocol EdgeDeviceP : Sendable{ var deviceID : UInt128 {get} var deviceType : DeviceType {get} var dataType : DataType {get} - var location: Location{get set} - var disconnected : Bool {get set} - var compromised : Bool {get set} - var dutyCicle : UInt {get set} + var location: Location{get } + var disconnected : Bool {get } + var compromised : Bool {get } + var dutyCicle : UInt {get } var privateKey: P256.Signing.PrivateKey {get} var publicKey: P256.Signing.PublicKey {get} - func work(envrionment: PhysicalEnvironment) throws -> Data + func work(envrionment: PhysicalEnvironment) async throws -> Data + + func setDisconnected(disconnected: Bool) async + func setCompromised(compromised: Bool) async + func dutyCycle(time: UInt) async + func setLocation(location: Location) async + } \ No newline at end of file diff --git a/Tests/IoT-Simulator-CoreTests/Devices-Tests.swift b/Tests/IoT-Simulator-CoreTests/Devices-Tests.swift index 21a8bc1..ede3a22 100644 --- a/Tests/IoT-Simulator-CoreTests/Devices-Tests.swift +++ b/Tests/IoT-Simulator-CoreTests/Devices-Tests.swift @@ -11,10 +11,10 @@ import MessageUtils let env = PhysicalEnvironment("Delta") let truth = PhysicalData(.Temperature, 22) - env.setPhysicalData(DataType.Temperature, truth) + await env.setPhysicalData(DataType.Temperature, truth) - let sensor : Sensor = Sensor(id: 1, sensorType: .Temperature) - let data = sensor.read(env) + let sensor : IdealSensor = IdealSensor(id: 1, sensorType: .Temperature) + let data = await sensor.read(env) #expect(data.value == truth.value, "If values match, we are cool") @@ -27,10 +27,10 @@ import MessageUtils let env = PhysicalEnvironment("Delta") let truth = PhysicalData(.Temperature, 22) - env.setPhysicalData(DataType.Temperature, truth) + await env.setPhysicalData(DataType.Temperature, truth) - let sensor : Sensor = Sensor(id: 1, sensorType: .Temperature, faulty: true) - let data = sensor.read(env) + let sensor : IdealSensor = IdealSensor(id: 1, sensorType: .Temperature, faulty: true) + let data = await sensor.read(env) #expect(data.value != truth.value, "If these match, something is not working") @@ -43,10 +43,10 @@ import MessageUtils let env = PhysicalEnvironment("Delta") let truth = PhysicalData(.Temperature, 22) - env.setPhysicalData(DataType.Temperature, truth) + await 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) + let data = await sensor.read(env) #expect(data.value - truth.value < 10, "If these match, we are cool") @@ -61,10 +61,10 @@ import MessageUtils let env = PhysicalEnvironment("Delta") let truth = PhysicalData(.Temperature, 22) - env.setPhysicalData(DataType.Temperature, truth) + await 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) + let data = await sensor.read(env) #expect(data.value - truth.value > 10, "If these match, something is not working") @@ -79,22 +79,22 @@ import MessageUtils let env = PhysicalEnvironment("Delta") let truth = PhysicalData(.Temperature, 22) - env.setPhysicalData(DataType.Temperature, truth) + await env.setPhysicalData(DataType.Temperature, truth) let signKeyPath = "./Private/privateKey.pem" let privateKey = try pem2_P256key(filePath: signKeyPath) - let dev: EdgeDevice = await EdgeDevice( + let dev: EdgeDevice = EdgeDevice( deviceID: 1, dataType: .Temperature, disconnected: false, location: Location(x: 20, y: 10, z: 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) + 0: IdealSensor(id: 0, sensorType: DataType.Temperature), + 1: IdealSensor(id: 0, sensorType: DataType.Temperature), + 2: IdealSensor(id: 0, sensorType: DataType.Temperature, faulty: true) ], privateKey: try pem2_P256key(filePath: signKeyPath) ) @@ -121,13 +121,13 @@ import MessageUtils let env = PhysicalEnvironment("Delta") let truth = PhysicalData(.Temperature, 22) - env.setPhysicalData(DataType.Temperature, truth) + await env.setPhysicalData(DataType.Temperature, truth) let signKeyPath = "./Private/privateKey.pem" let privateKey = try pem2_P256key(filePath: signKeyPath) - let dev: EdgeDevice = await EdgeDevice( + let dev: EdgeDevice = EdgeDevice( deviceID: 1, dataType: .Temperature, disconnected: false, diff --git a/Tests/IoT-Simulator-CoreTests/IoTCore-Tests.swift b/Tests/IoT-Simulator-CoreTests/IoTCore-Tests.swift index f87e95d..24953bc 100644 --- a/Tests/IoT-Simulator-CoreTests/IoTCore-Tests.swift +++ b/Tests/IoT-Simulator-CoreTests/IoTCore-Tests.swift @@ -10,9 +10,9 @@ import MessageUtils let env = PhysicalEnvironment("Delta") let truth = PhysicalData(.Temperature, 22) - env.setPhysicalData(DataType.Temperature, truth) + await env.setPhysicalData(DataType.Temperature, truth) - IoTSimulatorCore.addEnv(environment: env) + await IoTSimulatorCore.addEnv(environment: env) let signKeyPath = "./Private/privateKey.pem" @@ -36,7 +36,7 @@ import MessageUtils privateKey: try pem2_P256key(filePath: signKeyPath) ) - let dev2: EdgeDevice = await EdgeDevice( + let dev2: EdgeDevice = EdgeDevice( deviceID: 2, dataType: .Temperature, disconnected: false, @@ -56,7 +56,7 @@ import MessageUtils privateKey: try pem2_P256key(filePath: signKeyPath) ) - try IoTSimulatorCore.addDevice(location: "Delta", device: dev, success: { msg in + try await IoTSimulatorCore.addDevice(location: "Delta", device: dev, success: { msg in print(msg) let _signedMessage = try! deserializeV1(serializedData: msg) print(_signedMessage.toString()) @@ -64,7 +64,7 @@ import MessageUtils failure: { print("Something went wrong") }) - try IoTSimulatorCore.addDevice(location: "Delta", device: dev2, success: { msg in + try await IoTSimulatorCore.addDevice(location: "Delta", device: dev2, success: { msg in print(msg) let _signedMessage = try! deserializeV1(serializedData: msg) print(_signedMessage.toString()) @@ -79,14 +79,14 @@ import MessageUtils @Test func stressLoop1() async throws { - let devices: UInt128 = 5000 + let devices: UInt128 = 1000000 let env = PhysicalEnvironment("Delta") let truth = PhysicalData(.Temperature, 22) - env.setPhysicalData(DataType.Temperature, truth) + await env.setPhysicalData(DataType.Temperature, truth) - IoTSimulatorCore.addEnv(environment: env) + await IoTSimulatorCore.addEnv(environment: env) let signKeyPath = "./Private/privateKey.pem" @@ -94,6 +94,8 @@ import MessageUtils for i: UInt128 in 0..