dvrip_rs/commands/
authentication.rs1use 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 async fn login(&mut self, username: &str, password: &str) -> Result<bool>;
14
15 async fn logout(&mut self) -> Result<()>;
17
18 fn is_authenticated(&self) -> bool;
20
21 fn session_id(&self) -> u32;
23
24 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}