Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for managing users #345

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/core/src/bc/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ pub const MSG_ID_GET_SERVICE_PORTS: u32 = 37;
pub const MSG_ID_GET_EMAIL: u32 = 42;
/// Set email settings
pub const MSG_ID_SET_EMAIL: u32 = 43;
/// Get users and general system info
pub const MSG_ID_GET_ABILITY_SUPPORT: u32 = 58;
/// Update, create and remove users
pub const MSG_ID_UPDATE_USER_LIST: u32 = 59;
/// Version messages have this ID
pub const MSG_ID_VERSION: u32 = 80;
/// Ping messages have this ID
Expand Down
45 changes: 45 additions & 0 deletions crates/core/src/bc/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ pub struct BcXml {
/// EmailTask for turning the email notifications on/off
#[serde(rename = "EmailTask", skip_serializing_if = "Option::is_none")]
pub email_task: Option<EmailTask>,
/// Read and write users
#[serde(rename = "UserList", skip_serializing_if = "Option::is_none")]
pub user_list: Option<UserList>,
}

impl BcXml {
Expand Down Expand Up @@ -1566,6 +1569,48 @@ pub struct Schedule {
pub time_block_list: TimeBlockList,
}

/// List of users
#[derive(PartialEq, Eq, Default, Debug, Deserialize, Serialize)]
pub struct UserList {
/// XML Version
#[serde(rename = "@version")]
pub version: String,
/// The actual user-list
#[serde(rename = "User", skip_serializing_if = "Option::is_none")]
pub user_list: Option<Vec<User>>,
}

/// A struct for reading and writing camera user records
#[derive(PartialEq, Eq, Default, Debug, Deserialize, Serialize)]
pub struct User {
/// The user_name is used to identify the user in the API
#[serde(rename = "userName")]
pub user_name: String,
/// The password seems to only be included when creating or modifying a user
#[serde(rename = "password", skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
QuantumEntangledAndy marked this conversation as resolved.
Show resolved Hide resolved
/// The user_id does not seem to have a purpose. It is not included when creating a user.
#[serde(rename = "userId", skip_serializing_if = "Option::is_none")]
pub user_id: Option<u32>,
/// User type, 0 is User and 1 is Administrator
#[serde(rename = "userLevel")]
pub user_level: u8,
/// Unknown, seems to be 1 for the current API user
#[serde(rename = "loginState", skip_serializing_if = "Option::is_none")]
pub login_state: Option<u8>,
/// The user_set_state states what will happen with a user-record. 4 different values have been
/// observed: none | add | delete | modify
///
/// | Value | Description |
/// | --- | --- |
/// | none | This is the state set when reading Users. When writing this seems to indicate that the user should not be modified |
/// | add | Indicates that a new User should be created |
/// | delete | Indicates that the user should be removed |
/// | modify | Indicates that the user should be modified. It seems like only the password can be changed. |
#[serde(rename = "userSetState")]
pub user_set_state: String,
}

/// Convience function to return the xml version used throughout the library
pub fn xml_ver() -> String {
"1.1".to_string()
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/bc_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ mod support;
mod talk;
mod time;
mod uid;
mod users;
mod version;

pub(crate) use connection::*;
Expand Down
179 changes: 179 additions & 0 deletions crates/core/src/bc_protocol/users.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use super::{BcCamera, Error, Result};
use crate::bc::{model::*, xml::*};

impl BcCamera {
/// Returns all users configured in the camera
pub async fn get_users(&self) -> Result<UserList> {
let connection = self.get_connection();

let msg_num = self.new_message_num();
let mut sub_get = connection
.subscribe(MSG_ID_GET_ABILITY_SUPPORT, msg_num)
.await?;
let get = Bc {
meta: BcMeta {
msg_id: MSG_ID_GET_ABILITY_SUPPORT,
channel_id: self.channel_id,
msg_num,
response_code: 0,
stream_type: 0,
class: 0x6414,
},
body: BcBody::ModernMsg(ModernMsg {
extension: Some(Extension {
user_name: Some("admin".to_owned()),
..Default::default()
}),
payload: None,
}),
};

sub_get.send(get).await?;
let msg = sub_get.recv().await?;
if msg.meta.response_code != 200 {
return Err(Error::CameraServiceUnavailable {
id: msg.meta.msg_id,
code: msg.meta.response_code,
});
}

// Valid message with response_code == 200
if let BcBody::ModernMsg(ModernMsg {
payload:
Some(BcPayloads::BcXml(BcXml {
user_list: Some(user_list),
..
})),
..
}) = msg.body
{
Ok(user_list)
} else {
Err(Error::UnintelligibleReply {
reply: std::sync::Arc::new(Box::new(msg)),
why: "Expected ModernMsg payload with a user_list but it was not recieved",
})
}
}

/// Add a new user
///
/// This function does not check if the user exist and the API will likely throw an error if
/// that is the case
pub async fn add_user(
&self,
user_name: String,
password: String,
user_level: u8,
) -> Result<()> {
let users = self.get_users().await;

let mut users = users?.user_list.unwrap_or(Vec::new());
if users.iter().any(|user| user.user_name == user_name) {
return Err(Error::Other("User already exists"));
}

users.push(User {
user_set_state: "add".to_owned(),
user_name,
password: Some(password),
user_level,
user_id: None,
login_state: None,
});
QuantumEntangledAndy marked this conversation as resolved.
Show resolved Hide resolved

self.set_users(users).await
}

/// Modify a user. It seems the only property of a user that is modifiable is the password.
pub async fn modify_user(&self, user_name: String, password: String) -> Result<()> {
let users = self.get_users().await;

let mut users = users?.user_list.unwrap_or(Vec::new());

if let Some(user) = users.iter_mut().find(|user| user.user_name == user_name) {
user.user_set_state = "modify".to_owned();
user.password = Some(password.clone());
} else {
return Err(Error::Other("User not found"));
}

self.set_users(users).await
}

/// Remove a user. This does not check for the existence of that user and will likely return an
/// error if the user doesn't exist.
pub async fn delete_user(&self, user_name: String) -> Result<()> {
let users = self.get_users().await;

let mut users = users?.user_list.unwrap_or(Vec::new());

if let Some(user) = users.iter_mut().find(|user| user.user_name == user_name) {
user.user_set_state = "delete".to_owned();
} else {
return Err(Error::Other("User not found"));
}

self.set_users(users).await
}

/// Helper method to send a UserList and wait for its success/failure.
async fn set_users(&self, users: Vec<User>) -> Result<()> {
let bcxml = BcXml {
user_list: Some(UserList {
version: "1.1".to_owned(),
user_list: Some(users),
}),
..Default::default()
};

let connection = self.get_connection();
let msg_num = self.new_message_num();
let mut sub_set = connection
.subscribe(MSG_ID_UPDATE_USER_LIST, msg_num)
.await?;

let get = Bc {
meta: BcMeta {
msg_id: MSG_ID_UPDATE_USER_LIST,
channel_id: self.channel_id,
msg_num,
response_code: 0,
stream_type: 0,
class: 0x6414,
},
body: BcBody::ModernMsg(ModernMsg {
extension: None,
payload: Some(BcPayloads::BcXml(bcxml)),
}),
};

sub_set.send(get).await?;
if let Ok(reply) =
tokio::time::timeout(tokio::time::Duration::from_millis(500), sub_set.recv()).await
{
let msg = reply?;
if msg.meta.response_code != 200 {
return Err(Error::CameraServiceUnavailable {
id: msg.meta.msg_id,
code: msg.meta.response_code,
});
}

if let BcMeta {
response_code: 200, ..
} = msg.meta
{
Ok(())
} else {
Err(Error::UnintelligibleReply {
reply: std::sync::Arc::new(Box::new(msg)),
why: "The camera did not except the BcXmp with service data",
})
}
} else {
// Some cameras seem to just not send a reply on success, so after 500ms we return Ok
Ok(())
}
}
}
Loading