rustuya/protocol/
dev22.rs

1use crate::crypto::TuyaCipher;
2use crate::error::Result;
3use crate::protocol::{CommandType, TuyaProtocol, Version, create_base_payload};
4use log::trace;
5use serde_json::Value;
6
7pub struct ProtocolDev22 {
8    base: Box<dyn TuyaProtocol>,
9}
10
11impl ProtocolDev22 {
12    #[must_use]
13    pub fn new(base: Box<dyn TuyaProtocol>) -> Self {
14        Self { base }
15    }
16}
17
18impl TuyaProtocol for ProtocolDev22 {
19    fn version(&self) -> Version {
20        self.base.version()
21    }
22
23    fn get_effective_command(&self, command: CommandType) -> u32 {
24        match command {
25            CommandType::DpQuery => CommandType::ControlNew as u32,
26            cmd => cmd as u32,
27        }
28    }
29
30    fn generate_payload(
31        &self,
32        device_id: &str,
33        command: CommandType,
34        data: Option<Value>,
35        cid: Option<&str>,
36        t: u64,
37    ) -> Result<(u32, Value)> {
38        let cmd_to_send = self.get_effective_command(command);
39        let mut payload =
40            create_base_payload(device_id, cid, data.clone(), Some(t.to_string().into()));
41
42        match command {
43            CommandType::UpdateDps => {
44                payload.retain(|k, _| k == "cid");
45                let d = data.unwrap_or_else(|| serde_json::json!([18, 19, 20]));
46                payload.insert("dpId".into(), d);
47            }
48            CommandType::Control | CommandType::ControlNew => {
49                payload.remove("gwId");
50            }
51            CommandType::DpQuery => {
52                payload.remove("gwId");
53                if payload.get("dps").is_none() {
54                    payload.insert("dps".into(), serde_json::json!({"1": null}));
55                }
56            }
57            CommandType::DpQueryNew => {
58                payload.remove("gwId");
59            }
60            CommandType::LanExtStream => {
61                payload = data
62                    .unwrap_or_else(|| serde_json::json!({}))
63                    .as_object()
64                    .cloned()
65                    .unwrap_or_default();
66                if let Some(c) = cid {
67                    payload.insert("cid".into(), c.into());
68                    payload.insert("ctype".into(), 0.into());
69                }
70            }
71            CommandType::Status | CommandType::HeartBeat => {
72                payload.remove("uid");
73                payload.remove("t");
74            }
75            _ => {
76                // Default: gwId, devId, uid, cid, t, dps
77            }
78        }
79
80        let payload_obj = Value::Object(payload);
81        trace!("dev22 generated payload (cmd {cmd_to_send}): {payload_obj}");
82
83        Ok((cmd_to_send, payload_obj))
84    }
85
86    fn pack_payload(&self, payload: &[u8], cmd: u32, cipher: &TuyaCipher) -> Result<Vec<u8>> {
87        self.base.pack_payload(payload, cmd, cipher)
88    }
89
90    fn decrypt_payload(&self, payload: Vec<u8>, cipher: &TuyaCipher) -> Result<Vec<u8>> {
91        self.base.decrypt_payload(payload, cipher)
92    }
93
94    fn has_version_header(&self, payload: &[u8]) -> bool {
95        self.base.has_version_header(payload)
96    }
97
98    fn requires_session_key(&self) -> bool {
99        self.base.requires_session_key()
100    }
101
102    fn encrypt_session_key(
103        &self,
104        session_key: &[u8],
105        cipher: &TuyaCipher,
106        nonce: &[u8],
107    ) -> Result<Vec<u8>> {
108        self.base.encrypt_session_key(session_key, cipher, nonce)
109    }
110
111    fn get_prefix(&self) -> u32 {
112        self.base.get_prefix()
113    }
114
115    fn get_hmac_key<'a>(&self, cipher_key: &'a [u8]) -> Option<&'a [u8]> {
116        self.base.get_hmac_key(cipher_key)
117    }
118
119    fn is_empty_payload_allowed(&self, cmd: u32) -> bool {
120        self.base.is_empty_payload_allowed(cmd)
121    }
122
123    fn should_check_dev22_fallback(&self) -> bool {
124        false
125    }
126}