rust_tuyapi/
mesparse.rs

1//! # Message Parser
2//! The message parser is the low level API which takes care of encoding and decoding of Payloads.
3//! The normal user should not need to interact with this directly to communicate with Tuya
4//! devices, but rather create an instance of the TuyaDevice struct.
5use crate::cipher::TuyaCipher;
6use crate::error::ErrorKind;
7use crate::{Payload, Result};
8use hex::FromHex;
9use log::{debug, error};
10use nom::{
11    bytes::complete::tag,
12    combinator::{map, peek, recognize},
13    multi::{length_data, many1},
14    number::complete::be_u32,
15    sequence::tuple,
16    IResult,
17};
18
19use num_derive::{FromPrimitive, ToPrimitive};
20use num_traits::{FromPrimitive, ToPrimitive};
21use std::cmp::PartialEq;
22use std::convert::TryInto;
23use std::fmt;
24use std::str::FromStr;
25
26const UDP_KEY: &str = "yGAdlopoPVldABfn";
27
28lazy_static! {
29    static ref PREFIX_BYTES: [u8; 4] = <[u8; 4]>::from_hex("000055AA").unwrap();
30    static ref SUFFIX_BYTES: [u8; 4] = <[u8; 4]>::from_hex("0000AA55").unwrap();
31}
32
33/// Human readable definitions of command bytes.
34#[derive(Debug, FromPrimitive, ToPrimitive, Clone, PartialEq)]
35pub enum CommandType {
36    Udp = 0,
37    ApConfig = 1,
38    Active = 2,
39    Bind = 3,
40    RenameGw = 4,
41    RenameDevice = 5,
42    Unbind = 6,
43    Control = 7,
44    Status = 8,
45    HeartBeat = 9,
46    DpQuery = 10,
47    QueryWifi = 11,
48    TokenBind = 12,
49    ControlNew = 13,
50    EnableWifi = 14,
51    DpQueryNew = 16,
52    SceneExecute = 17,
53    DpRefresh = 18,
54    UdpNew = 19,
55    ApConfigNew = 20,
56    LanGwActive = 240,
57    LanSubDevRequest = 241,
58    LanDeleteSubDev = 242,
59    LanReportSubDev = 243,
60    LanScene = 244,
61    LanPublishCloudConfig = 245,
62    LanPublishAppConfig = 246,
63    LanExportAppConfig = 247,
64    LanPublishScenePanel = 248,
65    LanRemoveGw = 249,
66    LanCheckGwUpdate = 250,
67    LanGwUpdate = 251,
68    LanSetGwChannel = 252,
69    Error = 255,
70}
71
72#[derive(Debug, PartialEq, Clone)]
73pub(crate) enum TuyaVersion {
74    ThreeOne,
75    ThreeThree,
76}
77
78impl TuyaVersion {
79    pub fn as_bytes(&self) -> &[u8] {
80        match &self {
81            TuyaVersion::ThreeOne => b"3.1",
82            TuyaVersion::ThreeThree => b"3.3",
83        }
84    }
85}
86
87impl FromStr for TuyaVersion {
88    type Err = ErrorKind;
89
90    fn from_str(s: &str) -> Result<Self> {
91        let version: Vec<&str> = s.split('.').collect();
92        if version.len() > 1 && version[0].ends_with('3') {
93            if version[1] == "1" {
94                return Ok(TuyaVersion::ThreeOne);
95            } else if version[1] == "3" {
96                return Ok(TuyaVersion::ThreeThree);
97            }
98            return Err(ErrorKind::VersionError(
99                version[0].to_string(),
100                version[1].to_string(),
101            ));
102        }
103        Err(ErrorKind::VersionError(
104            "Unknown".to_string(),
105            "Unknown".to_string(),
106        ))
107    }
108}
109
110/// Representation of a message sent to and received from a Tuya device. The Payload is
111/// serialized to and deserialized from JSON. The sequence number, if sent in a command, will
112/// be included in the response to be able to connect command and response. The return code is
113/// only included if the Message is a response from a device.
114#[derive(Debug, PartialEq)]
115pub struct Message {
116    pub payload: Payload,
117    pub command: Option<CommandType>,
118    pub seq_nr: Option<u32>,
119    pub ret_code: Option<u8>,
120}
121
122impl fmt::Display for Message {
123    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124        write!(
125            f,
126            "Payload: \"{}\", Command: {:?}, Seq Nr: {:?}, Return Code: {:?}",
127            self.payload,
128            self.command.clone().unwrap_or(CommandType::Error),
129            self.seq_nr,
130            self.ret_code,
131        )
132    }
133}
134
135impl Message {
136    pub fn new(payload: Payload, command: CommandType, seq_nr: Option<u32>) -> Message {
137        Message {
138            payload,
139            command: Some(command),
140            seq_nr,
141            ret_code: None,
142        }
143    }
144}
145
146/// The message parser takes care of encoding and parsing messages before send and after
147/// receive. It uses a TuyaCipher to encrypt and decrypt messages sent with the Tuya
148/// protocol version 3.3.
149pub struct MessageParser {
150    version: TuyaVersion,
151    cipher: TuyaCipher,
152}
153
154/// MessageParser encodes and parses messages sent to and from Tuya devices. It may or may not
155/// encrypt the message, depending on message type and TuyaVersion. Likewise, the parsing may or may
156/// not need decrypting.
157impl MessageParser {
158    pub fn create(ver: &str, key: Option<&str>) -> Result<MessageParser> {
159        let version = TuyaVersion::from_str(ver)?;
160        let key = verify_key(key)?;
161        let cipher = TuyaCipher::create(&key, version.clone());
162        Ok(MessageParser { version, cipher })
163    }
164
165    pub fn encode(&self, mes: &Message, encrypt: bool) -> Result<Vec<u8>> {
166        let mut encoded: Vec<u8> = vec![];
167        encoded.extend_from_slice(&*PREFIX_BYTES);
168        match mes.seq_nr {
169            Some(nr) => encoded.extend(&nr.to_be_bytes()),
170            None => encoded.extend(&0_u32.to_be_bytes()),
171        }
172        let command = mes.command.clone().ok_or(ErrorKind::CommandTypeMissing)?;
173        encoded.extend([0, 0, 0, command.to_u8().unwrap()].iter());
174        let payload = self.create_payload_header(mes, encrypt)?;
175        let ret_len = match mes.ret_code {
176            Some(_) => 4_u32,
177            None => 0_u32,
178        };
179        encoded.extend(
180            (payload.len() as u32 + 8_u32 + ret_len)
181                .to_be_bytes()
182                .iter(),
183        );
184        if let Some(ret_code) = mes.ret_code {
185            encoded.extend(&ret_code.to_be_bytes());
186        }
187        encoded.extend(payload);
188        encoded.extend(crc32fast::hash(&encoded).to_be_bytes().iter());
189        encoded.extend_from_slice(&*SUFFIX_BYTES);
190        debug!(
191            "Encoded message ({}):\n{}",
192            mes.seq_nr.unwrap_or(0),
193            hex::encode(&encoded)
194        );
195
196        Ok(encoded)
197    }
198
199    fn create_payload_header(&self, mes: &Message, encrypt: bool) -> Result<Vec<u8>> {
200        match self.version {
201            TuyaVersion::ThreeOne => {
202                if encrypt {
203                    self.create_payload_with_header(mes.payload.clone().try_into()?)
204                } else {
205                    mes.payload.clone().try_into()
206                }
207            }
208            TuyaVersion::ThreeThree => match mes.command {
209                Some(CommandType::DpQuery) | Some(CommandType::DpRefresh) => {
210                    let payload: Vec<u8> = mes.payload.clone().try_into()?;
211                    self.cipher.encrypt(&payload)
212                }
213                _ => self.create_payload_with_header(mes.payload.clone().try_into()?),
214            },
215        }
216    }
217
218    fn create_payload_with_header(&self, payload: Vec<u8>) -> Result<Vec<u8>> {
219        let mut payload_with_header = Vec::new();
220        payload_with_header.extend(self.version.as_bytes());
221        match self.version {
222            TuyaVersion::ThreeOne => payload_with_header.extend(vec![0; 12]),
223            TuyaVersion::ThreeThree => payload_with_header.extend(self.cipher.md5(&payload)),
224        }
225        payload_with_header.extend(self.cipher.encrypt(&payload)?);
226        Ok(payload_with_header)
227    }
228
229    pub fn parse(&self, buf: &[u8]) -> Result<Vec<Message>> {
230        let (buf, messages) = self.parse_messages(buf).map_err(|err| match err {
231            nom::Err::Error(e) => ErrorKind::ParseError(e.code),
232            nom::Err::Incomplete(_) => ErrorKind::ParsingIncomplete,
233            nom::Err::Failure(e) if e.code == nom::error::ErrorKind::ManyMN => ErrorKind::CRCError,
234            nom::Err::Failure(e) => ErrorKind::ParseError(e.code),
235        })?;
236        if !buf.is_empty() {
237            return Err(ErrorKind::BufferNotCompletelyParsedError);
238        }
239        Ok(messages)
240    }
241
242    fn parse_messages<'a>(&self, orig_buf: &'a [u8]) -> IResult<&'a [u8], Vec<Message>> {
243        // TODO: can this be statically initialized??
244        let be_u32_minus4 = map(be_u32, |n: u32| n - 4);
245        let (buf, vec) = many1(tuple((
246            tag(*PREFIX_BYTES),
247            be_u32,
248            be_u32,
249            length_data(be_u32_minus4),
250            tag(*SUFFIX_BYTES),
251        )))(orig_buf)?;
252        let mut messages = vec![];
253        for (_, seq_nr, command, recv_data, _) in vec {
254            // check if the recv_data contains a return code
255            let (recv_data, maybe_retcode) = peek(be_u32)(recv_data)?;
256            let (recv_data, ret_code, ret_len) = if maybe_retcode & 0xFFFF_FF00 == 0 {
257                // Has a return code
258                let (recv_data, ret_code) = recognize(be_u32)(recv_data)?;
259                (recv_data, Some(ret_code[3]), 4_usize)
260            } else {
261                // Has no return code
262                (recv_data, None, 0_usize)
263            };
264            let (payload, rc) = recv_data.split_at(recv_data.len() - 4);
265            let recv_crc = u32::from_be_bytes([rc[0], rc[1], rc[2], rc[3]]);
266            let crc = crc32fast::hash(&orig_buf[0..recv_data.len() + 12 + ret_len]);
267            if crc != recv_crc {
268                error!("Found CRC: {:#x}, Expected CRC: {:#x}", recv_crc, crc);
269                // I hijack the ErrorKind::ManyMN here to propagate a CRC error
270                // TODO: should probably create and use a special CRC error here
271                return Err(nom::Err::Failure(nom::error::Error::new(
272                    rc,
273                    nom::error::ErrorKind::ManyMN,
274                )));
275            }
276
277            let payload = self.try_decrypt(payload);
278            let message = Message {
279                payload,
280                command: FromPrimitive::from_u32(command).or(None),
281                seq_nr: Some(seq_nr),
282                ret_code,
283            };
284            messages.push(message);
285        }
286        Ok((buf, messages))
287    }
288
289    fn try_decrypt(&self, payload: &[u8]) -> Payload {
290        match self.cipher.decrypt(payload) {
291            Ok(decrypted) => {
292                if let Ok(p) = serde_json::from_slice(&decrypted) {
293                    Payload::Struct(p)
294                } else {
295                    Payload::String(
296                        std::str::from_utf8(&decrypted)
297                            .unwrap_or("Payload invalid")
298                            .to_string(),
299                    )
300                }
301            }
302            Err(_) => {
303                if let Ok(p) = serde_json::from_slice(payload) {
304                    Payload::Struct(p)
305                } else {
306                    Payload::String(
307                        std::str::from_utf8(payload)
308                            .unwrap_or("Payload invalid")
309                            .to_string(),
310                    )
311                }
312            }
313        }
314    }
315}
316
317fn verify_key(key: Option<&str>) -> Result<Vec<u8>> {
318    match key {
319        Some(key) => {
320            if key.len() == 16 {
321                Ok(key.as_bytes().to_vec())
322            } else {
323                Err(ErrorKind::KeyLength(key.len()))
324            }
325        }
326        None => {
327            let default_key = md5::compute(UDP_KEY).0;
328            Ok(default_key.to_vec())
329        }
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336    use crate::PayloadStruct;
337    use serde_json::json;
338    use std::collections::HashMap;
339    #[test]
340    fn test_key_length_is_16() {
341        let key = Some("0123456789ABCDEF");
342        assert!(verify_key(key).is_ok());
343    }
344
345    #[test]
346    fn test_key_length_not_16_gives_error() {
347        let bad_key = Some("13579BDF");
348        assert!(verify_key(bad_key).is_err());
349    }
350
351    #[test]
352    fn test_parse_mqttversion() {
353        let version = TuyaVersion::from_str("3.1").unwrap();
354        assert_eq!(version, TuyaVersion::ThreeOne);
355
356        let version2 = TuyaVersion::from_str("ver3.3").unwrap();
357        assert_eq!(version2, TuyaVersion::ThreeThree);
358
359        assert!(TuyaVersion::from_str("3.4").is_err());
360    }
361
362    #[test]
363    fn test_parse_messages() {
364        let packet =
365            hex::decode("000055aa00000000000000090000000c00000000b051ab030000aa55").unwrap();
366        let expected = Message {
367            command: Some(CommandType::HeartBeat),
368            payload: Payload::String("".to_string()),
369            seq_nr: Some(0),
370            ret_code: Some(0),
371        };
372        let mp = MessageParser::create("3.1", None).unwrap();
373        let (buf, messages) = mp.parse_messages(&packet).unwrap();
374        assert_eq!(messages[0], expected);
375        assert_eq!(buf, &[] as &[u8]);
376    }
377
378    #[test]
379    fn test_parse_messages_with_payload() {
380        let packet = hex::decode("000055aa00000000000000070000005b00000000332e33d8bab8946c604148a45c15326ed3b99d683695a73c624e75a5aaa31f4061f5b99033e6d01f0b0abf9dbc76b2a54eb4bf60976b1dc496169db9e5a3fd627f2c3d9c4744585e471b6a2fc479ca01f7e18e0000aa55").unwrap();
381        let mut dps = HashMap::new();
382        dps.insert("1".to_string(), json!(true));
383        let expected = Message {
384            command: Some(CommandType::Control),
385            payload: Payload::Struct(PayloadStruct {
386                dev_id: "46052834d8f15b92e53b".to_string(),
387                gw_id: None,
388                uid: None,
389                t: None,
390                dp_id: None,
391                dps: Some(dps),
392            }),
393            seq_nr: Some(0),
394            ret_code: Some(0),
395        };
396        let mp = MessageParser::create("3.3", None).unwrap();
397        let (buf, messages) = mp.parse_messages(&packet).unwrap();
398        assert_eq!(messages[0], expected);
399        assert_eq!(buf, &[] as &[u8]);
400    }
401
402    #[test]
403    fn test_parse_data_format_error() {
404        let packet =
405            hex::decode("000055aa00000000000000070000003b00000001332e33d504910232d355a59ed1f6ed1f4a816a1e8e30ed09987c020ae45d72c70592bb233c79c43a5b9ae49b6ead38725deb520000aa55").unwrap();
406        let expected = Message {
407            command: Some(CommandType::Control),
408            payload: Payload::String("data format error".to_string()),
409            seq_nr: Some(0),
410            ret_code: Some(1),
411        };
412        let mp = MessageParser::create("3.3", None).unwrap();
413        let (buf, messages) = mp.parse_messages(&packet).unwrap();
414        assert_eq!(messages[0], expected);
415        assert_eq!(buf, &[] as &[u8]);
416    }
417
418    #[test]
419    fn test_parse_double_messages() {
420        let packet =
421            hex::decode("000055aa00000000000000090000000c00000000b051ab030000aa55000055aa000000000000000a0000000c00000000b051ab030000aa55").unwrap();
422        let expected = vec![
423            Message {
424                command: Some(CommandType::HeartBeat),
425                payload: Payload::String("".to_string()),
426                seq_nr: Some(0),
427                ret_code: Some(0),
428            },
429            Message {
430                command: Some(CommandType::DpQuery),
431                payload: Payload::String("".to_string()),
432                seq_nr: Some(0),
433                ret_code: Some(0),
434            },
435        ];
436        let mp = MessageParser::create("3.1", None).unwrap();
437        let (buf, messages) = mp.parse_messages(&packet).unwrap();
438        assert_eq!(messages[0], expected[0]);
439        assert_eq!(messages[1], expected[1]);
440        assert_eq!(buf, &[] as &[u8]);
441    }
442
443    #[test]
444    fn test_encode_with_and_without_encryption_and_version_three_one() {
445        let mut dps = HashMap::new();
446        dps.insert("1".to_string(), json!(true));
447        dps.insert("2".to_string(), json!(0));
448        let payload = Payload::Struct(PayloadStruct {
449            dev_id: "002004265ccf7fb1b659".to_string(),
450            gw_id: None,
451            uid: None,
452            t: None,
453            dp_id: None,
454            dps: Some(dps),
455        });
456        let mes = Message {
457            command: Some(CommandType::DpQuery),
458            payload,
459            seq_nr: Some(0),
460            ret_code: Some(0),
461        };
462        let parser = MessageParser::create("3.1", None).unwrap();
463        let encrypted = parser.encode(&mes, true).unwrap();
464        let unencrypted = parser.encode(&mes, false).unwrap();
465        // Only encrypt 3.1 if the flag is set
466        assert_ne!(encrypted, unencrypted);
467    }
468
469    #[test]
470    fn test_encode_with_and_without_encryption_and_version_three_three() {
471        let mut dps = HashMap::new();
472        dps.insert("1".to_string(), json!(true));
473        let payload = Payload::Struct(PayloadStruct {
474            dev_id: "002004265ccf7fb1b659".to_string(),
475            gw_id: None,
476            uid: None,
477            t: None,
478            dp_id: None,
479            dps: Some(dps),
480        });
481        let mes = Message {
482            command: Some(CommandType::DpQuery),
483            payload,
484            seq_nr: Some(0),
485            ret_code: Some(0),
486        };
487        let parser = MessageParser::create("3.3", None).unwrap();
488
489        let encrypted = parser.encode(&mes, true).unwrap();
490        let unencrypted = parser.encode(&mes, false).unwrap();
491        // Always encrypt 3.3, no matter what the flag is
492        assert_eq!(encrypted, unencrypted);
493    }
494}