miio_proto/
lib.rs

1//! # Simple example
2//!
3//! ```no_run
4//! let rt = tokio::runtime::Runtime::new().expect("Async runtime");
5//! rt.block_on(async {
6//!     let conn = Device::new(
7//!         "192.168.1.1:54321",
8//!         1234512345,
9//!         [
10//!             0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
11//!             0xee, 0xff,
12//!         ],
13//!     )
14//!     .await
15//!     .expect("Connect");
16//!     conn.send_handshake().await.expect("Handshake");
17//!     let (hello, _) = conn.recv().await.expect("Response");
18//!
19//!     conn.send(
20//!         hello.stamp + 1,
21//!         "{'method':'power','id':1,'params':['off']}",
22//!     )
23//!     .await
24//!     .expect("Request");
25//! })
26//! ```
27
28#![warn(rust_2018_idioms)]
29#![deny(missing_docs)]
30
31use anyhow::Result;
32use packed_struct::prelude::*;
33use tokio::net::UdpSocket;
34
35#[derive(PackedStruct, Debug, Clone)]
36#[packed_struct(endian = "msb")]
37/// Struct describes protocol message header
38pub struct MessageHeader {
39    /// Always 0x2131
40    pub magic_number: u16,
41    /// Packet length including the header itself (32 bytes)
42    pub packet_length: u16,
43    /// Some unknown bytes
44    pub unknown: u32,
45    /// Device ID
46    pub device_id: u32,
47    /// Incrementing timestamp as reported by device
48    pub stamp: u32,
49    /// Checksum. See protocol description.
50    pub checksum: [u8; 16],
51}
52
53/// Connection holder
54pub struct Device {
55    /// Socket
56    socket: std::sync::Arc<UdpSocket>,
57    /// Device ID
58    device_id: u32,
59    /// Device token
60    token: [u8; 16],
61}
62
63impl Device {
64    /// Initializes new connection to specific MIIO device
65    ///
66    /// # Arguments
67    ///
68    /// * `address` — Device address in form "192.168.1.1:54321"
69    /// * `device_id` — Device ID. Can be extracted from the response to handshake() call.
70    /// * `token` — 16 bytes device token.
71    pub async fn new(address: &str, device_id: u32, token: [u8; 16]) -> Result<Self> {
72        let socket = UdpSocket::bind("0.0.0.0:0").await?;
73        socket.connect(address).await?;
74
75        let r = Self {
76            socket: std::sync::Arc::new(socket),
77            device_id,
78            token,
79        };
80        Ok(r)
81    }
82
83    fn encode_payload(token: &[u8; 16], payload: &str) -> Vec<u8> {
84        let key = md5::compute(token).to_vec();
85        let mut iv_src = key.to_vec();
86        iv_src.extend(token);
87        let iv = md5::compute(iv_src).to_vec();
88
89        use aes::Aes128;
90        use block_modes::block_padding::Pkcs7;
91        use block_modes::{BlockMode, Cbc};
92
93        let cipher = Cbc::<Aes128, Pkcs7>::new_from_slices(&key, &iv).unwrap();
94
95        cipher.encrypt_vec(payload.as_bytes())
96    }
97
98    fn decode_payload(token: &[u8; 16], payload: &[u8]) -> Vec<u8> {
99        let key = md5::compute(token).to_vec();
100        let mut iv_src = key.to_vec();
101        iv_src.extend(token);
102        let iv = md5::compute(iv_src).to_vec();
103
104        use aes::Aes128;
105        use block_modes::block_padding::Pkcs7;
106        use block_modes::{BlockMode, Cbc};
107
108        let cipher = Cbc::<Aes128, Pkcs7>::new_from_slices(&key, &iv).unwrap();
109
110        let mut buf = payload.to_vec();
111        cipher.decrypt(&mut buf).unwrap().to_vec()
112    }
113
114    async fn send_raw(
115        socket: std::sync::Arc<UdpSocket>,
116        unknown: u32,
117        device_id: u32,
118        stamp: u32,
119        token: &[u8; 16],
120        payload: &str,
121    ) -> Result<()> {
122        let payload = if payload.is_empty() {
123            Vec::new()
124        } else {
125            log::trace!("Plain payload: {:?}", payload);
126            let payload = Self::encode_payload(token, payload);
127            log::trace!("Encoded payload len={}: {:?}", payload.len(), payload);
128            payload
129        };
130
131        let message = MessageHeader {
132            magic_number: 0x2131,
133            packet_length: (payload.len() + 32) as u16,
134            unknown,
135            device_id,
136            stamp,
137            checksum: *token,
138        };
139
140        let mut packet = message.pack_to_vec()?.pack_to_vec()?;
141        packet.extend(&payload);
142        let checksum = md5::compute(packet);
143
144        let message = MessageHeader {
145            checksum: *checksum,
146            ..message
147        };
148
149        let mut packet = message.pack()?.pack_to_vec()?;
150        packet.extend(&payload);
151        log::trace!(
152            "Sending packet. Total length {} bytes, payload length {}. Raw packet: {:?}",
153            packet.len(),
154            payload.len(),
155            packet
156        );
157        let sent = socket.send(&packet).await?;
158        log::debug!("Sent {} bytes", sent);
159
160        Ok(())
161    }
162
163    /// Reads and returns packet from MIIO device.
164    /// Returns tuple of two values:
165    /// * Message header
166    /// * Decoded payload
167    pub async fn recv(&self) -> Result<(MessageHeader, String)> {
168        let mut buf = [0_u8; 65535];
169        self.socket.recv(&mut buf).await?;
170        let mut hdr: [u8; 32] = Default::default();
171        hdr.copy_from_slice(&buf[..32]);
172        let resp = MessageHeader::unpack(&hdr)?;
173        log::trace!("Got header: {:?}", resp);
174        let payload = &buf[32..resp.packet_length as usize];
175        log::trace!("Got payload len={}: {:?}", payload.len(), payload);
176        let payload = Self::decode_payload(&self.token, payload);
177        let payload = std::str::from_utf8(&payload)?;
178        if !payload.is_empty() {
179            log::trace!("Decoded payload: {}", payload);
180        }
181
182        Ok((resp, payload.to_string()))
183    }
184
185    /// Sends handshake packet to MIIO device.
186    pub async fn send_handshake(&self) -> Result<()> {
187        Self::send_raw(
188            self.socket.clone(),
189            0xffffffff,
190            0xffffffff,
191            0xffffffff,
192            &[
193                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
194                0xff, 0xff,
195            ],
196            "",
197        )
198        .await
199    }
200
201    /// Sends JSON payload to MIIO device.
202    ///
203    /// # Arguments
204    ///
205    /// * `stamp` — Continuously increasing counter.
206    /// * `payload` — JSON string.
207    pub async fn send(&self, stamp: u32, payload: &str) -> Result<()> {
208        Self::send_raw(
209            self.socket.clone(),
210            0,
211            self.device_id,
212            stamp,
213            &self.token,
214            payload,
215        )
216        .await
217    }
218}