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}