Skip to main content

dvrip_rs/commands/
authentication.rs

1use crate::commands::Connection;
2use crate::constants::{OK_CODES, QCODES};
3use crate::dvrip::DVRIPCam;
4use crate::error::Result;
5use crate::protocol::sofia_hash;
6use async_trait::async_trait;
7use serde_json::json;
8use std::sync::atomic::Ordering;
9
10#[async_trait]
11pub trait Authentication: Send + Sync {
12    /// Login to the device
13    async fn login(&mut self, username: &str, password: &str) -> Result<bool>;
14
15    /// Logout from the device
16    async fn logout(&mut self) -> Result<()>;
17
18    /// Check if authenticated
19    fn is_authenticated(&self) -> bool;
20
21    /// Get the session ID
22    fn session_id(&self) -> u32;
23
24    /// Change user password
25    async fn change_password(
26        &mut self,
27        old_password: &str,
28        new_password: &str,
29        username: Option<&str>,
30    ) -> Result<bool>;
31}
32
33#[async_trait]
34impl Authentication for DVRIPCam {
35    async fn login(&mut self, username: &str, password: &str) -> Result<bool> {
36        if !Connection::is_connected(self) {
37            Connection::connect(self, self.timeout).await?;
38        }
39
40        let data = json!({
41            "EncryptType": "MD5",
42            "LoginType": "DVRIP-Web",
43            "PassWord": sofia_hash(password),
44            "UserName": username,
45        });
46        self.username = Some(username.to_string());
47
48        let reply = self.send_command(1000, data, true).await?.ok_or_else(|| {
49            crate::error::DVRIPError::AuthenticationError("Empty response".to_string())
50        })?;
51
52        if let Some(ret) = reply.get("Ret").and_then(|r| r.as_u64())
53            && OK_CODES.contains(&(ret as u32))
54        {
55            if let Some(session_str) = reply.get("SessionID").and_then(|s| s.as_str()) {
56                let session_id = u32::from_str_radix(&session_str[2..], 16).map_err(|_| {
57                    crate::error::DVRIPError::ProtocolError("Invalid SessionID".to_string())
58                })?;
59                self.session.store(session_id, Ordering::Release);
60            }
61
62            if let Some(interval) = reply.get("AliveInterval").and_then(|i| i.as_u64()) {
63                self.alive_time.store(interval, Ordering::Release);
64            }
65
66            self.authenticated.store(true, Ordering::Release);
67            self.start_keep_alive().await;
68            return Ok(true);
69        }
70
71        Ok(false)
72    }
73
74    async fn logout(&mut self) -> Result<()> {
75        Connection::close(self).await
76    }
77
78    fn is_authenticated(&self) -> bool {
79        self.authenticated.load(Ordering::Acquire)
80    }
81
82    fn session_id(&self) -> u32 {
83        self.session.load(Ordering::Acquire)
84    }
85
86    async fn change_password(
87        &mut self,
88        old_password: &str,
89        new_password: &str,
90        username: Option<&str>,
91    ) -> Result<bool> {
92        let data = json!({
93            "EncryptType": "MD5",
94            "NewPassWord": sofia_hash(new_password),
95            "PassWord": sofia_hash(old_password),
96            "SessionID": format!("0x{:08X}", self.session_id()),
97            "UserName": username.unwrap_or(self.username.as_ref().unwrap_or(&"admin".to_string())),
98        });
99
100        let reply = self
101            .send_command(
102                QCODES.get("ModifyPassword").copied().unwrap_or(1488),
103                data,
104                true,
105            )
106            .await?
107            .ok_or_else(|| crate::error::DVRIPError::ProtocolError("Empty response".to_string()))?;
108
109        if let Some(ret) = reply.get("Ret").and_then(|r| r.as_u64()) {
110            return Ok(OK_CODES.contains(&(ret as u32)));
111        }
112
113        Ok(false)
114    }
115}