1use 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#[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#[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
146pub struct MessageParser {
150 version: TuyaVersion,
151 cipher: TuyaCipher,
152}
153
154impl 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 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 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 let (recv_data, ret_code) = recognize(be_u32)(recv_data)?;
259 (recv_data, Some(ret_code[3]), 4_usize)
260 } else {
261 (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 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 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 assert_eq!(encrypted, unencrypted);
493 }
494}