mikrotik_rs/
device.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use crate::{
    actor::{DeviceConnectionActor, ReadActorMessage},
    error::DeviceResult,
    protocol::{command::Command, CommandResponse},
};
use tokio::{net::ToSocketAddrs, sync::mpsc};

/// A client for interacting with MikroTik devices.
///
/// The `MikrotikDevice` struct provides an asynchronous interface for connecting to a MikroTik device
/// and sending commands to it. It encapsulates the communication with the device through a
/// background actor that handles the connection and command execution. Can be cheaply cloned to share
/// the same connection across multiple threads.
#[derive(Clone)]
pub struct MikrotikDevice(mpsc::Sender<ReadActorMessage>);

impl MikrotikDevice {
    /// Asynchronously establishes a connection to a MikroTik device.
    ///
    /// This function initializes the connection to the MikroTik device by starting a `DeviceConnectionActor`
    /// and returns an instance of `MikrotikDevice` that can be used to send commands to the device.
    ///
    /// # Parameters
    /// - `addr`: The address of the MikroTik device. This can be an IP address or a hostname.
    /// - `username`: The username for authenticating with the device.
    /// - `password`: An optional password for authentication. If `None`, no password will be sent.
    ///
    /// # Returns
    /// - `Ok(Self)`: An instance of [`MikrotikDevice`] on successful connection.
    /// - `Err(io::Error)`: An error if the connection could not be established.
    ///
    /// # Examples
    /// ```no_run
    /// let device = MikrotikDevice::connect("192.168.88.1:8728", "admin", Some("password")).await?;
    /// ```
    /// # Attention 🚨
    /// The connection to the MikroTik device is not encrypted (plaintext API connection over 8728/tcp port).
    /// In the future, support for encrypted connections (e.g., API-SSL) will be added.
    pub async fn connect<A: ToSocketAddrs>(
        addr: A,
        username: &str,
        password: Option<&str>,
    ) -> DeviceResult<Self> {
        let sender = DeviceConnectionActor::start(addr, username, password).await?;

        Ok(Self(sender))
    }

    /// Asynchronously sends a command to the connected MikroTik device and returns a receiver for the response.
    ///
    /// This method allows sending commands to the MikroTik device and provides an asynchronous channel (receiver)
    /// that will receive the command execution results.
    ///
    /// # Parameters
    /// - `command`: The [`Command`] to send to the device, consisting of a tag and data associated with the command.
    ///
    /// # Returns
    /// A [`mpsc::Receiver`] that can be awaited to receive the response to the command.
    /// Responses are wrapped in [`io::Result`] to handle any I/O related errors during command execution or response retrieval.
    ///
    /// # Panics
    /// This method panics if sending the command message to the `DeviceConnectionActor` fails,
    /// which could occur if the actor has been dropped or the channel is disconnected.
    ///
    /// # Examples
    /// ```no_run
    /// let command = CommandBuilder::new().command("/interface/print").build();
    /// let mut response_rx = device.send_command(command).await;
    ///
    /// while let Some(response) = response_rx.recv().await {
    ///     println!("{:?}", response?);
    /// }
    /// ```
    pub async fn send_command(
        &self,
        command: Command,
    ) -> mpsc::Receiver<DeviceResult<CommandResponse>> {
        let (response_tx, response_rx) = mpsc::channel::<DeviceResult<CommandResponse>>(16);

        let msg = ReadActorMessage {
            tag: command.tag,
            data: command.data,
            respond_to: response_tx,
        };

        self.0.send(msg).await.expect("msg send failed");

        response_rx
    }
}