tosca_controller/
device.rs

1use std::collections::{HashMap, HashSet};
2use std::net::IpAddr;
3
4use serde::Serialize;
5
6use tokio::sync::broadcast::{self, Receiver};
7use tokio::task::JoinHandle;
8
9use tosca::device::{DeviceEnvironment, DeviceKind};
10use tosca::events::{Events as ToscaEvents, EventsDescription};
11use tosca::route::RouteConfigs;
12
13use crate::error::{Error, ErrorKind, Result};
14use crate::events::{Events, EventsRunner};
15use crate::request::{Request, RequestInfo, create_requests};
16
17pub(crate) fn build_device_address(scheme: &str, address: &IpAddr, port: u16) -> String {
18    format!("{scheme}://{address}:{port}")
19}
20
21/// Network information for a `tosca` device.
22///
23/// It contains all the necessary data to contact a `tosca` device within
24/// a network.
25#[derive(Debug, PartialEq, Clone, Serialize)]
26pub struct NetworkInformation {
27    /// Full device name.
28    pub name: String,
29    /// Device `IP` addresses.
30    pub addresses: HashSet<IpAddr>,
31    /// Device Wi-Fi MAC address.
32    ///
33    /// If [`None`], the Wi-Fi MAC address is not present.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub wifi_mac: Option<[u8; 6]>,
36    /// Device Ethernet MAC address.
37    ///
38    /// If [`None`], the Ethernet MAC address is not present.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub ethernet_mac: Option<[u8; 6]>,
41    /// The port on which the device is listening.
42    pub port: u16,
43    /// Device properties.
44    pub properties: HashMap<String, String>,
45    /// Device last reachable address.
46    ///
47    /// Prevents regenerating the full device address every time.
48    pub last_reachable_address: String,
49}
50
51impl NetworkInformation {
52    /// Creates [`NetworkInformation`].
53    #[must_use]
54    pub const fn new(
55        name: String,
56        addresses: HashSet<IpAddr>,
57        port: u16,
58        properties: HashMap<String, String>,
59        last_reachable_address: String,
60    ) -> Self {
61        Self {
62            name,
63            addresses,
64            wifi_mac: None,
65            ethernet_mac: None,
66            port,
67            properties,
68            last_reachable_address,
69        }
70    }
71
72    /// Sets the Wi-Fi MAC address.
73    #[must_use]
74    pub const fn wifi_mac(mut self, mac: [u8; 6]) -> Self {
75        self.wifi_mac = Some(mac);
76        self
77    }
78
79    /// Sets the Ethernet MAC address.
80    #[must_use]
81    pub const fn ethernet_mac(mut self, mac: [u8; 6]) -> Self {
82        self.ethernet_mac = Some(mac);
83        self
84    }
85}
86
87/// Device description.
88///
89/// All properties defining a device.
90#[derive(Debug, PartialEq, Serialize)]
91pub struct Description {
92    /// Device kind.
93    pub kind: DeviceKind,
94    /// Device environment.
95    pub environment: DeviceEnvironment,
96    /// Device main route.
97    pub main_route: String,
98}
99
100impl Description {
101    /// Creates a [`Description`].
102    #[must_use]
103    pub const fn new(kind: DeviceKind, environment: DeviceEnvironment, main_route: String) -> Self {
104        Self {
105            kind,
106            environment,
107            main_route,
108        }
109    }
110}
111
112/// A `tosca` device.
113#[derive(Debug, Serialize)]
114pub struct Device {
115    // Information needed to contact a device in a network.
116    network_info: NetworkInformation,
117    // All data needed to describe a device.
118    description: Description,
119    // All device requests.
120    requests: HashMap<String, Request>,
121    // All device events.
122    //
123    // If [`None`], the device does not support events.
124    #[serde(skip)]
125    pub(crate) events: Option<Events>,
126    // The join handle for the event task.
127    #[serde(skip)]
128    pub(crate) event_handle: Option<JoinHandle<()>>,
129}
130
131impl PartialEq for Device {
132    fn eq(&self, other: &Self) -> bool {
133        self.network_info == other.network_info
134            && self.description == other.description
135            && self.requests == other.requests
136    }
137}
138
139impl Device {
140    /// Creates a [`Device`] from [`NetworkInformation`], [`Description`],
141    /// and [`RouteConfigs`].
142    ///
143    /// This method can be useful when creating a device from data stored
144    /// in a database.
145    #[must_use]
146    pub fn new(
147        network_info: NetworkInformation,
148        description: Description,
149        route_configs: RouteConfigs,
150    ) -> Self {
151        let requests = create_requests(
152            route_configs,
153            &network_info.last_reachable_address,
154            &description.main_route,
155            description.environment,
156        );
157
158        // TODO: Check if the last reachable address works or it is better to
159        // build a new one. Return a Result here, because we have to evaluate
160        // data validity.
161
162        Self {
163            network_info,
164            description,
165            requests,
166            events: None,
167            event_handle: None,
168        }
169    }
170
171    /// Returns an immutable reference to [`NetworkInformation`].
172    #[must_use]
173    pub const fn network_info(&self) -> &NetworkInformation {
174        &self.network_info
175    }
176
177    /// Returns an immutable reference to [`Description`].
178    #[must_use]
179    pub const fn description(&self) -> &Description {
180        &self.description
181    }
182
183    /// Returns an immutable reference to [`EventsDescription`].
184    ///
185    /// If [`None`], the device does not support events.
186    #[must_use]
187    #[inline]
188    pub fn events_metadata(&self) -> Option<&EventsDescription> {
189        self.events.as_ref().map(|events| &events.description)
190    }
191
192    /// Returns a [`RequestInfo`] vector containing the information
193    /// for each request.
194    #[must_use]
195    #[inline]
196    pub fn requests_info(&self) -> Vec<RequestInfo<'_>> {
197        self.requests
198            .iter()
199            .map(|(route, sender)| RequestInfo::new(route, sender))
200            .collect()
201    }
202
203    /// Returns the total number of requests associated with the device.
204    #[must_use]
205    #[inline]
206    pub fn requests_count(&self) -> usize {
207        self.requests.len()
208    }
209
210    /// Returns the [`Request`] associated with the given route.
211    ///
212    /// If [`None`], the given route **does not** exist.
213    #[must_use]
214    #[inline]
215    pub fn request(&self, route: &str) -> Option<&Request> {
216        self.requests.get(route)
217    }
218
219    /// Checks if a [`Device`] supports events.
220    #[must_use]
221    pub const fn has_events(&self) -> bool {
222        self.events.is_some()
223    }
224
225    /// Checks if the event receiver is currently running.
226    ///
227    /// Always returns `false` if the [`Device`] does not support events.
228    #[must_use]
229    pub const fn is_event_receiver_running(&self) -> bool {
230        self.event_handle.is_some()
231    }
232
233    /// Starts the asynchronous event receiver if the [`Device`] supports
234    /// events.
235    ///
236    /// An event receiver task connects to the broker of a device
237    /// and subscribes to its topic.
238    /// When a device transmits an event to the broker, the task retrieves the
239    /// event payload from the broker, parses the data, and sends the relevant
240    /// content to the [`Receiver`] returned by this method.
241    ///
242    /// The `buffer_size` parameter specifies how many messages the event
243    /// receiver buffer can hold.
244    /// When the buffer is full, subsequent send attempts will wait until
245    /// a message is consumed from the channel.
246    ///
247    /// When the returned [`Receiver`] is dropped, the event receiver task
248    /// terminates automatically.
249    ///
250    /// # Errors
251    ///
252    /// - The device does not support events
253    /// - The event receiver task has already been started
254    /// - An error occurred while attempting to subscribe to the broker topic
255    #[inline]
256    pub async fn start_event_receiver(
257        &mut self,
258        id: usize,
259        buffer_size: usize,
260    ) -> Result<Receiver<ToscaEvents>> {
261        if self.event_handle.is_some() {
262            return Err(Error::new(
263                ErrorKind::Events,
264                format!("Event receiver already started for device with id `{id}`"),
265            ));
266        }
267
268        let Some(ref events) = self.events else {
269            return Err(Error::new(
270                ErrorKind::Events,
271                format!("The device with `{id}` does not support events"),
272            ));
273        };
274
275        let (tx, _) = broadcast::channel(buffer_size);
276
277        let handle = EventsRunner::run_device_subscriber(events, id, tx.clone()).await?;
278        self.event_handle = Some(handle);
279
280        Ok(tx.subscribe())
281    }
282
283    pub(crate) const fn init(
284        network_info: NetworkInformation,
285        description: Description,
286        requests: HashMap<String, Request>,
287        events: Option<Events>,
288    ) -> Self {
289        Self {
290            network_info,
291            description,
292            requests,
293            events,
294            event_handle: None,
295        }
296    }
297}
298
299/// A collection of [`Device`]s.
300#[derive(Debug, PartialEq, Serialize)]
301pub struct Devices(pub(crate) Vec<Device>);
302
303impl Default for Devices {
304    fn default() -> Self {
305        Self::new()
306    }
307}
308
309impl IntoIterator for Devices {
310    type Item = Device;
311    type IntoIter = std::vec::IntoIter<Self::Item>;
312
313    fn into_iter(self) -> Self::IntoIter {
314        self.0.into_iter()
315    }
316}
317
318impl<'a> IntoIterator for &'a Devices {
319    type Item = &'a Device;
320    type IntoIter = std::slice::Iter<'a, Device>;
321
322    fn into_iter(self) -> Self::IntoIter {
323        self.iter()
324    }
325}
326
327impl<'a> IntoIterator for &'a mut Devices {
328    type Item = &'a mut Device;
329    type IntoIter = std::slice::IterMut<'a, Device>;
330    fn into_iter(self) -> Self::IntoIter {
331        self.0.iter_mut()
332    }
333}
334
335impl Devices {
336    /// Creates [`Devices`].
337    #[must_use]
338    pub const fn new() -> Self {
339        Self(Vec::new())
340    }
341
342    /// Creates [`Devices`] from a vector of [`Device`]s.
343    #[must_use]
344    pub const fn from_devices(devices: Vec<Device>) -> Self {
345        Self(devices)
346    }
347
348    /// Adds a [`Device`].
349    #[inline]
350    pub fn add(&mut self, device: Device) {
351        self.0.push(device);
352    }
353
354    /// Checks if [`Devices`] is empty.
355    #[must_use]
356    #[inline]
357    pub fn is_empty(&self) -> bool {
358        self.0.is_empty()
359    }
360
361    /// Returns the number of [`Device`] in the collection.
362    #[must_use]
363    #[inline]
364    pub fn len(&self) -> usize {
365        self.0.len()
366    }
367
368    /// Retrieves a reference to the [`Device`] at the given index.
369    #[must_use]
370    #[inline]
371    pub fn get(&self, index: usize) -> Option<&Device> {
372        self.0.get(index)
373    }
374
375    /// Returns an iterator over [`Device`]s.
376    #[inline]
377    pub fn iter(&self) -> std::slice::Iter<'_, Device> {
378        self.0.iter()
379    }
380
381    /// Returns a mutable iterator over [`Device`]s.
382    #[inline]
383    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Device> {
384        self.0.iter_mut()
385    }
386}
387
388#[cfg(test)]
389pub(crate) mod tests {
390    use std::collections::{HashMap, HashSet};
391
392    use tosca::device::{DeviceEnvironment, DeviceKind};
393    use tosca::hazards::{Hazard, Hazards};
394    use tosca::parameters::Parameters;
395    use tosca::route::{Route, RouteConfigs};
396
397    use super::{Description, Device, Devices, NetworkInformation, build_device_address};
398
399    fn create_network_info(address: &str, port: u16) -> NetworkInformation {
400        let ip_address = address.parse().unwrap();
401
402        let complete_address = build_device_address("http", &ip_address, port);
403
404        let mut addresses = HashSet::new();
405        addresses.insert(ip_address);
406        addresses.insert("172.0.0.1".parse().unwrap());
407
408        let mut properties = HashMap::new();
409        properties.insert("scheme".into(), "http".into());
410
411        NetworkInformation::new(
412            "device-name1._tosca._tcp.local.".into(),
413            addresses,
414            port,
415            properties,
416            complete_address,
417        )
418        .wifi_mac([0x02, 0x11, 0x22, 0x33, 0x44, 0x55])
419        .ethernet_mac([0x06, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE])
420    }
421
422    fn create_description(device_kind: DeviceKind, main_route: &str) -> Description {
423        Description::new(device_kind, DeviceEnvironment::Os, main_route.into())
424    }
425
426    pub(crate) fn create_light() -> Device {
427        let network_info = create_network_info("192.168.1.174", 5000);
428        let description = create_description(DeviceKind::Light, "light/");
429
430        let light_on_route = Route::put("On", "/on")
431            .description("Turn light on.")
432            .with_hazard(Hazard::ElectricEnergyConsumption);
433
434        let light_off_route = Route::put("Off", "/off")
435            .description("Turn light off.")
436            .with_hazard(Hazard::LogEnergyConsumption);
437
438        let toggle_route = Route::get("Toggle", "/toggle")
439            .description("Toggle a light.")
440            .with_hazards(
441                Hazards::new()
442                    .insert(Hazard::FireHazard)
443                    .insert(Hazard::ElectricEnergyConsumption),
444            )
445            .with_parameters(Parameters::new().rangeu64("brightness", (0, 20, 1)));
446
447        let route_configs = RouteConfigs::new()
448            .insert(light_on_route.serialize_data())
449            .insert(light_off_route.serialize_data())
450            .insert(toggle_route.serialize_data());
451
452        Device::new(network_info, description, route_configs)
453    }
454
455    pub(crate) fn create_unknown() -> Device {
456        let network_info = create_network_info("192.168.1.176", 5500);
457        let description = create_description(DeviceKind::Unknown, "ip-camera/");
458
459        let camera_stream_route = Route::get("Stream", "/stream")
460            .description("View camera stream.")
461            .with_hazards(
462                Hazards::new()
463                    .insert(Hazard::ElectricEnergyConsumption)
464                    .insert(Hazard::VideoDisplay)
465                    .insert(Hazard::VideoRecordAndStore),
466            );
467
468        let screenshot_route = Route::get("Take screenshot", "/take-screenshot")
469            .description("Take a screenshot.")
470            .with_hazards(
471                Hazards::new()
472                    .insert(Hazard::ElectricEnergyConsumption)
473                    .insert(Hazard::TakeDeviceScreenshots)
474                    .insert(Hazard::TakePictures),
475            );
476
477        let route_configs = RouteConfigs::new()
478            .insert(camera_stream_route.serialize_data())
479            .insert(screenshot_route.serialize_data());
480
481        Device::new(network_info, description, route_configs)
482    }
483
484    #[test]
485    fn check_devices() {
486        let devices_vector = vec![create_light(), create_unknown()];
487
488        let devices_from_vector = Devices::from_devices(devices_vector);
489
490        let mut devices = Devices::new();
491
492        // A device is empty when being created.
493        assert!(devices.is_empty());
494
495        devices.add(create_light());
496        devices.add(create_unknown());
497
498        // Compare devices created with two different methods.
499        assert_eq!(devices_from_vector, devices);
500
501        // A device must not be empty.
502        assert!(!devices.is_empty());
503
504        // Check number of elements in devices.
505        assert_eq!(devices.len(), 2);
506
507        // Get a non-existent device.
508        assert_eq!(devices.get(1000), None);
509
510        // Get a reference to a device. The order is important.
511        assert_eq!(devices.get(1), Some(&create_unknown()));
512    }
513}