mikrotik_rs/
device.rs

1use crate::{
2    actor::{DeviceConnectionActor, ReadActorMessage},
3    error::DeviceResult,
4    protocol::{command::Command, CommandResponse},
5};
6use tokio::{net::ToSocketAddrs, sync::mpsc};
7
8/// A client for interacting with MikroTik devices.
9///
10/// The `MikrotikDevice` struct provides an asynchronous interface for connecting to a MikroTik device
11/// and sending commands to it. It encapsulates the communication with the device through a
12/// background actor that handles the connection and command execution. Can be cheaply cloned to share
13/// the same connection across multiple threads.
14#[derive(Clone)]
15pub struct MikrotikDevice(mpsc::Sender<ReadActorMessage>);
16
17impl MikrotikDevice {
18    /// Asynchronously establishes a connection to a MikroTik device.
19    ///
20    /// This function initializes the connection to the MikroTik device by starting a `DeviceConnectionActor`
21    /// and returns an instance of `MikrotikDevice` that can be used to send commands to the device.
22    ///
23    /// # Parameters
24    /// - `addr`: The address of the MikroTik device. This can be an IP address or a hostname.
25    /// - `username`: The username for authenticating with the device.
26    /// - `password`: An optional password for authentication. If `None`, no password will be sent.
27    ///
28    /// # Returns
29    /// - `Ok(Self)`: An instance of [`MikrotikDevice`] on successful connection.
30    /// - `Err(io::Error)`: An error if the connection could not be established.
31    ///
32    /// # Examples
33    /// ```no_run
34    /// let device = MikrotikDevice::connect("192.168.88.1:8728", "admin", Some("password")).await?;
35    /// ```
36    /// # Attention 🚨
37    /// The connection to the MikroTik device is not encrypted (plaintext API connection over 8728/tcp port).
38    /// In the future, support for encrypted connections (e.g., API-SSL) will be added.
39    pub async fn connect<A: ToSocketAddrs>(
40        addr: A,
41        username: &str,
42        password: Option<&str>,
43    ) -> DeviceResult<Self> {
44        let sender = DeviceConnectionActor::start(addr, username, password).await?;
45
46        Ok(Self(sender))
47    }
48
49    /// Asynchronously sends a command to the connected MikroTik device and returns a receiver for the response.
50    ///
51    /// This method allows sending commands to the MikroTik device and provides an asynchronous channel (receiver)
52    /// that will receive the command execution results.
53    ///
54    /// # Parameters
55    /// - `command`: The [`Command`] to send to the device, consisting of a tag and data associated with the command.
56    ///
57    /// # Returns
58    /// A [`mpsc::Receiver`] that can be awaited to receive the response to the command.
59    /// Responses are wrapped in [`io::Result`] to handle any I/O related errors during command execution or response retrieval.
60    ///
61    /// # Panics
62    /// This method panics if sending the command message to the `DeviceConnectionActor` fails,
63    /// which could occur if the actor has been dropped or the channel is disconnected.
64    ///
65    /// # Examples
66    /// ```no_run
67    /// let command = CommandBuilder::new().command("/interface/print").build();
68    /// let mut response_rx = device.send_command(command).await;
69    ///
70    /// while let Some(response) = response_rx.recv().await {
71    ///     println!("{:?}", response?);
72    /// }
73    /// ```
74    pub async fn send_command(
75        &self,
76        command: Command,
77    ) -> mpsc::Receiver<DeviceResult<CommandResponse>> {
78        let (response_tx, response_rx) = mpsc::channel::<DeviceResult<CommandResponse>>(16);
79
80        let msg = ReadActorMessage {
81            tag: command.tag,
82            data: command.data,
83            respond_to: response_tx,
84        };
85
86        self.0.send(msg).await.expect("msg send failed");
87
88        response_rx
89    }
90}