1use crate::cipher::TuyaCipher;
6use crate::crc::crc;
7use crate::error::ErrorKind;
8use crate::{Payload, Result};
9use hex::FromHex;
10use log::{debug, error};
11use nom::{
12 bytes::complete::tag,
13 combinator::{map, peek, recognize},
14 multi::{length_data, many1},
15 number::complete::be_u32,
16 sequence::tuple,
17 IResult,
18};
19
20use num_derive::{FromPrimitive, ToPrimitive};
21use num_traits::{FromPrimitive, ToPrimitive};
22use std::cmp::PartialEq;
23use std::convert::TryInto;
24use std::fmt;
25use std::mem::size_of;
26use std::str::FromStr;
27
28pub(crate) const UDP_KEY: &str = "yGAdlopoPVldABfn";
29
30lazy_static! {
31 static ref PREFIX_BYTES: [u8; 4] = <[u8; 4]>::from_hex("000055AA").unwrap();
32 static ref SUFFIX_BYTES: [u8; 4] = <[u8; 4]>::from_hex("0000AA55").unwrap();
33}
34
35#[derive(Debug, FromPrimitive, ToPrimitive, Clone, PartialEq, Eq)]
37pub enum CommandType {
38 Udp = 0,
39 ApConfig = 1,
40 Active = 2,
41 SessKeyNegStart = 3,
42 SessKeyNegResp = 4,
43 SessKeyNegFinish = 5,
44 Unbind = 6,
45 Control = 7,
46 Status = 8,
47 HeartBeat = 9,
48 DpQuery = 10,
49 QueryWifi = 11,
50 UpdateDps = 12,
51 ControlNew = 13,
52 EnableWifi = 14,
53 DpQueryNew = 16,
54 SceneExecute = 17,
55 DpRefresh = 18,
56 UdpNew = 19,
57 ApConfigNew = 20,
58 LanGwActive = 240,
59 LanSubDevRequest = 241,
60 LanDeleteSubDev = 242,
61 LanReportSubDev = 243,
62 LanScene = 244,
63 LanPublishCloudConfig = 245,
64 LanPublishAppConfig = 246,
65 LanExportAppConfig = 247,
66 LanPublishScenePanel = 248,
67 LanRemoveGw = 249,
68 LanCheckGwUpdate = 250,
69 LanGwUpdate = 251,
70 LanSetGwChannel = 252,
71 Error = 255,
72}
73
74impl CommandType {
75 pub fn needs_protocol_header(&self) -> bool {
76 !matches!(
77 self,
78 CommandType::SessKeyNegStart
79 | CommandType::SessKeyNegResp
80 | CommandType::SessKeyNegFinish
81 | CommandType::HeartBeat
82 | CommandType::DpQuery
83 | CommandType::UpdateDps
84 | CommandType::DpQueryNew
85 )
86 }
87
88 pub fn has_raw_payload(&self) -> bool {
89 matches!(self, CommandType::SessKeyNegResp)
90 }
91}
92
93#[allow(clippy::enum_variant_names)]
94#[derive(Debug, PartialEq, Eq, Clone)]
95pub enum TuyaVersion {
96 ThreeOne,
97 ThreeThree,
98 ThreeFour,
99}
100
101impl TuyaVersion {
102 pub fn as_bytes(&self) -> &[u8] {
103 match &self {
104 TuyaVersion::ThreeOne => b"3.1",
105 TuyaVersion::ThreeThree => b"3.3",
106 TuyaVersion::ThreeFour => b"3.4",
107 }
108 }
109}
110
111impl FromStr for TuyaVersion {
112 type Err = ErrorKind;
113
114 fn from_str(s: &str) -> Result<Self> {
115 match s {
116 "3.1" => Ok(TuyaVersion::ThreeOne),
117 "3.3" => Ok(TuyaVersion::ThreeThree),
118 "3.4" => Ok(TuyaVersion::ThreeFour),
119 _ => Err(ErrorKind::VersionError(s.to_string())),
120 }
121 }
122}
123
124#[derive(Clone, Debug, PartialEq, Eq)]
129pub struct Message {
130 pub payload: Payload,
131 pub command: Option<CommandType>,
132 pub seq_nr: Option<u32>,
133 pub ret_code: Option<u8>,
134}
135
136impl fmt::Display for Message {
137 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
138 write!(
139 f,
140 "Payload: \"{}\", Command: {:?}, Seq Nr: {:?}, Return Code: {:?}",
141 self.payload,
142 self.command.clone().unwrap_or(CommandType::Error),
143 self.seq_nr,
144 self.ret_code,
145 )
146 }
147}
148
149impl Message {
150 pub fn new(payload: Payload, command: CommandType) -> Message {
151 Message {
152 payload,
153 command: Some(command),
154 seq_nr: None,
155 ret_code: None,
156 }
157 }
158}
159
160#[derive(Clone)]
164pub struct MessageParser {
165 version: TuyaVersion,
166 pub(crate) cipher: TuyaCipher,
167}
168
169impl MessageParser {
173 pub fn create(version: TuyaVersion, key: Option<String>) -> Result<MessageParser> {
174 let key = verify_key(key.as_deref())?;
175 let cipher = TuyaCipher::create(&key, version.clone());
176 Ok(MessageParser { version, cipher })
177 }
178
179 pub fn encode(&self, mes: &Message, encrypt: bool) -> Result<Vec<u8>> {
180 let mut encoded: Vec<u8> = vec![];
181 encoded.extend_from_slice(&*PREFIX_BYTES);
182 match mes.seq_nr {
183 Some(nr) => encoded.extend(&nr.to_be_bytes()),
184 None => encoded.extend(&0_u32.to_be_bytes()),
185 }
186 let command = mes.command.clone().ok_or(ErrorKind::CommandTypeMissing)?;
187 encoded.extend([0, 0, 0, command.to_u8().unwrap()].iter());
188 let payload = self.create_payload_header(mes, encrypt)?;
189 let ret_len = match mes.ret_code {
190 Some(_) => 4_u32,
191 None => 0_u32,
192 };
193 let msg_end_size = match self.version {
194 TuyaVersion::ThreeOne | TuyaVersion::ThreeThree => {
195 size_of::<u32>() + size_of::<u32>()
197 }
198 TuyaVersion::ThreeFour => {
199 32 + size_of::<u32>()
201 }
202 };
203 encoded.extend(
204 (payload.len() as u32 + msg_end_size as u32 + ret_len)
205 .to_be_bytes()
206 .iter(),
207 );
208 if let Some(ret_code) = mes.ret_code {
209 encoded.extend(&ret_code.to_be_bytes());
210 }
211 encoded.extend(payload);
212 match self.version {
213 TuyaVersion::ThreeOne | TuyaVersion::ThreeThree => {
214 encoded.extend(crc(&encoded).to_be_bytes().iter());
215 }
216 TuyaVersion::ThreeFour => {
217 encoded.extend(self.cipher.hmac(&encoded)?.iter());
218 }
220 }
221 encoded.extend_from_slice(&*SUFFIX_BYTES);
222 debug!(
223 "Encoded message ({}):\n{}",
224 mes.seq_nr.unwrap_or(0),
225 hex::encode(&encoded)
226 );
227
228 Ok(encoded)
229 }
230
231 fn create_payload_header(&self, mes: &Message, encrypt: bool) -> Result<Vec<u8>> {
232 match self.version {
233 TuyaVersion::ThreeOne => {
234 if encrypt {
235 self.create_payload_with_header(mes.payload.clone().try_into()?)
236 } else {
237 mes.payload.clone().try_into()
238 }
239 }
240 TuyaVersion::ThreeThree | TuyaVersion::ThreeFour => match mes.command {
241 Some(ref cmd) if cmd.needs_protocol_header() => {
242 self.create_payload_with_header(mes.payload.clone().try_into()?)
243 }
244 _ => {
245 let payload: Vec<u8> = mes.payload.clone().try_into()?;
246 self.cipher.encrypt(&payload)
247 }
248 },
249 }
250 }
251
252 fn create_payload_with_header(&self, payload: Vec<u8>) -> Result<Vec<u8>> {
253 let mut payload_with_header = Vec::new();
254 match self.version {
255 TuyaVersion::ThreeOne => {
256 payload_with_header.extend(self.version.as_bytes());
257 payload_with_header.extend(vec![0; 12]);
258 payload_with_header.extend(self.cipher.encrypt(&payload)?);
259 }
260 TuyaVersion::ThreeThree => {
261 payload_with_header.extend(self.version.as_bytes());
262 payload_with_header.extend(self.cipher.md5(&payload));
263 payload_with_header.extend(self.cipher.encrypt(&payload)?);
264 }
265 TuyaVersion::ThreeFour => {
266 debug!("pre Final payload: {}", hex::encode(&payload));
267 let payload = {
268 let mut v = self.version.as_bytes().to_vec();
269 v.extend(&vec![0; 12]);
270 v.extend(&payload);
271 v
272 };
273
274 debug!("Final payload: {}", hex::encode(&payload));
275
276 payload_with_header.extend(self.cipher.encrypt(&payload)?);
277
278 debug!("Payload encrypted: {}", hex::encode(&payload_with_header));
279 }
280 }
281 Ok(payload_with_header)
282 }
283
284 pub fn parse(&self, buf: &[u8]) -> Result<Vec<Message>> {
285 let (buf, messages) = self.parse_messages(buf).map_err(|err| match err {
286 nom::Err::Error(e) => ErrorKind::ParseError(e.code),
287 nom::Err::Incomplete(_) => ErrorKind::ParsingIncomplete,
288 nom::Err::Failure(e) if e.code == nom::error::ErrorKind::ManyMN => ErrorKind::CRCError,
289 nom::Err::Failure(e) => ErrorKind::ParseError(e.code),
290 })?;
291 if !buf.is_empty() {
292 return Err(ErrorKind::BufferNotCompletelyParsedError);
293 }
294 Ok(messages)
295 }
296
297 fn parse_messages<'a>(&self, orig_buf: &'a [u8]) -> IResult<&'a [u8], Vec<Message>> {
298 let crc_size = match self.version {
299 TuyaVersion::ThreeOne | TuyaVersion::ThreeThree => size_of::<u32>(),
300 TuyaVersion::ThreeFour => 32,
301 };
302
303 let be_u32_minus4 = map(be_u32, |n: u32| n - 4);
305 let (buf, vec) = many1(tuple((
306 tag(*PREFIX_BYTES),
307 be_u32,
308 be_u32,
309 length_data(be_u32_minus4),
310 tag(*SUFFIX_BYTES),
311 )))(orig_buf)?;
312 let mut messages = vec![];
313 let mut msg_offset = 0_usize; for (prefix, seq_nr, command, recv_data_orig, suffix) in vec {
315 let msg_size = prefix.len() + 4 + 4 + 4 + recv_data_orig.len() + suffix.len();
318
319 let (recv_data, maybe_retcode) = peek(be_u32)(recv_data_orig)?;
321 let (recv_data, ret_code, _ret_len) = if maybe_retcode & 0xFFFF_FF00 == 0 {
322 let (recv_data, ret_code) = recognize(be_u32)(recv_data)?;
324 (recv_data, Some(ret_code[3]), 4_usize)
325 } else {
326 (recv_data, None, 0_usize)
328 };
329 let (payload, rc) = recv_data.split_at(recv_data.len() - crc_size);
330
331 match self.version {
332 TuyaVersion::ThreeOne | TuyaVersion::ThreeThree => {
333 let recv_crc = u32::from_be_bytes([rc[0], rc[1], rc[2], rc[3]]);
334 let crc_start = msg_offset;
336 let crc_end = msg_offset + recv_data_orig.len() + 12;
337 if crc(&orig_buf[crc_start..crc_end]) != recv_crc {
338 error!(
339 "Found CRC: {:#x}, Expected CRC: {:#x}",
340 recv_crc,
341 crc(&orig_buf[crc_start..crc_end])
342 );
343 return Err(nom::Err::Failure(nom::error::Error::new(
346 rc,
347 nom::error::ErrorKind::ManyMN,
348 )));
349 }
350 }
351 TuyaVersion::ThreeFour => {
352 let hmac_start = msg_offset;
357 let hmac_end = msg_offset + 16 + recv_data_orig.len() - crc_size;
358 let data_for_hmac = &orig_buf[hmac_start..hmac_end];
359 let expected_hmac = self.cipher.hmac(data_for_hmac).map_err(|_| {
360 nom::Err::Failure(nom::error::Error::new(rc, nom::error::ErrorKind::Verify))
361 })?;
362 if rc != expected_hmac.as_slice() {
363 error!(
364 "HMAC verification failed - expected {}, got {}",
365 hex::encode(&expected_hmac),
366 hex::encode(rc)
367 );
368 return Err(nom::Err::Failure(nom::error::Error::new(
369 rc,
370 nom::error::ErrorKind::Verify,
371 )));
372 }
373 }
374 }
375
376 let command = FromPrimitive::from_u32(command).or(None);
377 let payload = self.try_decrypt(payload, &command);
378 let message = Message {
379 payload,
380 command,
381 seq_nr: Some(seq_nr),
382 ret_code,
383 };
384 messages.push(message);
385
386 msg_offset += msg_size;
388 }
389 Ok((buf, messages))
390 }
391
392 fn try_decrypt(&self, payload: &[u8], command: &Option<CommandType>) -> Payload {
393 let payload = match self.cipher.decrypt(payload) {
394 Ok(decrypted) => decrypted,
395 Err(_) => payload.to_vec(),
396 };
397
398 match command {
399 Some(command) if command.has_raw_payload() => Payload::Raw(payload),
400 _ => {
401 if let Ok(p) = serde_json::from_slice(payload.as_slice()) {
402 Payload::Struct(p)
403 } else {
404 Payload::String(
405 std::str::from_utf8(payload.as_slice())
406 .unwrap_or("Payload invalid")
407 .to_string(),
408 )
409 }
410 }
411 }
412 }
413}
414
415fn verify_key(key: Option<&str>) -> Result<Vec<u8>> {
416 match key {
417 Some(key) => {
418 if key.len() == 16 {
419 Ok(key.as_bytes().to_vec())
420 } else {
421 Err(ErrorKind::KeyLength(key.len()))
422 }
423 }
424 None => {
425 let default_key = md5::compute(UDP_KEY).0;
426 Ok(default_key.to_vec())
427 }
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434 use crate::PayloadStruct;
435 use serde_json::json;
436 use std::collections::HashMap;
437 #[test]
438 fn test_key_length_is_16() {
439 let key = Some("0123456789ABCDEF");
440 assert!(verify_key(key).is_ok());
441 }
442
443 #[test]
444 fn test_key_lenght_not_16_gives_error() {
445 let bad_key = Some("13579BDF");
446 assert!(verify_key(bad_key).is_err());
447 }
448
449 #[test]
450 fn test_parse_mqttversion() {
451 let version1 = TuyaVersion::from_str("3.1").unwrap();
452 assert_eq!(version1, TuyaVersion::ThreeOne);
453
454 let version3 = TuyaVersion::from_str("3.3").unwrap();
455 assert_eq!(version3, TuyaVersion::ThreeThree);
456
457 let version4 = TuyaVersion::from_str("3.4").unwrap();
458 assert_eq!(version4, TuyaVersion::ThreeFour);
459
460 assert!(TuyaVersion::from_str("3.5").is_err());
461 }
462
463 #[test]
464 fn test_parse_messages() {
465 let packet =
466 hex::decode("000055aa00000000000000090000000c00000000b051ab030000aa55").unwrap();
467 let expected = Message {
468 command: Some(CommandType::HeartBeat),
469 payload: Payload::String("".to_string()),
470 seq_nr: Some(0),
471 ret_code: Some(0),
472 };
473 let mp = MessageParser::create(TuyaVersion::ThreeOne, None).unwrap();
474 let (buf, messages) = mp.parse_messages(&packet).unwrap();
475 assert_eq!(messages[0], expected);
476 assert_eq!(buf, &[] as &[u8]);
477 }
478
479 #[test]
480 fn test_parse_messages_with_payload() {
481 let packet = hex::decode("000055aa00000000000000070000005b00000000332e33d8bab8946c604148a45c15326ed3b99d683695a73c624e75a5aaa31f4061f5b99033e6d01f0b0abf9dbc76b2a54eb4bf60976b1dc496169db9e5a3fd627f2c3d9c4744585e471b6a2fc479ca01f7e18e0000aa55").unwrap();
482 let mut dps = HashMap::new();
483 dps.insert("1".to_string(), json!(true));
484 let expected = Message {
485 command: Some(CommandType::Control),
486 payload: Payload::Struct(PayloadStruct {
487 dev_id: "46052834d8f15b92e53b".to_string(),
488 gw_id: None,
489 uid: None,
490 t: None,
491 dp_id: None,
492 dps: Some(serde_json::to_value(dps).unwrap()),
493 }),
494 seq_nr: Some(0),
495 ret_code: Some(0),
496 };
497 let mp = MessageParser::create(TuyaVersion::ThreeThree, None).unwrap();
498 let (buf, messages) = mp.parse_messages(&packet).unwrap();
499 assert_eq!(messages[0], expected);
500 assert_eq!(buf, &[] as &[u8]);
501 }
502
503 #[test]
504 fn test_parse_data_format_error() {
505 let packet =
506 hex::decode("000055aa00000000000000070000003b00000001332e33d504910232d355a59ed1f6ed1f4a816a1e8e30ed09987c020ae45d72c70592bb233c79c43a5b9ae49b6ead38725deb520000aa55").unwrap();
507 let expected = Message {
508 command: Some(CommandType::Control),
509 payload: Payload::String("data format error".to_string()),
510 seq_nr: Some(0),
511 ret_code: Some(1),
512 };
513 let mp = MessageParser::create(TuyaVersion::ThreeThree, None).unwrap();
514 let (buf, messages) = mp.parse_messages(&packet).unwrap();
515 assert_eq!(messages[0], expected);
516 assert_eq!(buf, &[] as &[u8]);
517 }
518
519 #[test]
520 fn test_parse_double_messages() {
521 let packet =
522 hex::decode("000055aa00000000000000090000000c00000000b051ab030000aa55000055aa000000000000000a0000000c0000000089dc97c60000aa55").unwrap();
523 let expected = [
524 Message {
525 command: Some(CommandType::HeartBeat),
526 payload: Payload::String("".to_string()),
527 seq_nr: Some(0),
528 ret_code: Some(0),
529 },
530 Message {
531 command: Some(CommandType::DpQuery),
532 payload: Payload::String("".to_string()),
533 seq_nr: Some(0),
534 ret_code: Some(0),
535 },
536 ];
537 let mp = MessageParser::create(TuyaVersion::ThreeOne, None).unwrap();
538 let (buf, messages) = mp.parse_messages(&packet).unwrap();
539 assert_eq!(messages[0], expected[0]);
540 assert_eq!(messages[1], expected[1]);
541 assert_eq!(buf, &[] as &[u8]);
542 }
543
544 #[test]
545 fn test_encode_with_and_without_encryption_and_version_three_one() {
546 let mut dps = HashMap::new();
547 dps.insert("1".to_string(), json!(true));
548 dps.insert("2".to_string(), json!(0));
549 let payload = Payload::Struct(PayloadStruct {
550 dev_id: "002004265ccf7fb1b659".to_string(),
551 gw_id: None,
552 uid: None,
553 t: None,
554 dp_id: None,
555 dps: Some(serde_json::to_value(dps).unwrap()),
556 });
557 let mes = Message {
558 command: Some(CommandType::DpQuery),
559 payload,
560 seq_nr: Some(0),
561 ret_code: Some(0),
562 };
563 let parser = MessageParser::create(TuyaVersion::ThreeOne, None).unwrap();
564 let encrypted = parser.encode(&mes, true).unwrap();
565 let unencrypted = parser.encode(&mes, false).unwrap();
566 assert_ne!(encrypted, unencrypted);
568 }
569
570 #[test]
571 fn test_encode_with_and_without_encryption_and_version_three_three() {
572 let mut dps = HashMap::new();
573 dps.insert("1".to_string(), json!(true));
574 let payload = Payload::Struct(PayloadStruct {
575 dev_id: "002004265ccf7fb1b659".to_string(),
576 gw_id: None,
577 uid: None,
578 t: None,
579 dp_id: None,
580 dps: Some(serde_json::to_value(dps).unwrap()),
581 });
582 let mes = Message {
583 command: Some(CommandType::DpQuery),
584 payload,
585 seq_nr: Some(0),
586 ret_code: Some(0),
587 };
588 let parser = MessageParser::create(TuyaVersion::ThreeThree, None).unwrap();
589
590 let encrypted = parser.encode(&mes, true).unwrap();
591 let unencrypted = parser.encode(&mes, false).unwrap();
592 assert_eq!(encrypted, unencrypted);
594 }
595}