Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
888 changes: 37 additions & 851 deletions camera_hub/Cargo.lock

Large diffs are not rendered by default.

20 changes: 6 additions & 14 deletions camera_hub/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authors = ["Ardalan Amiri Sani <arrdalan@gmail.com>"]
[features]
default = ["logging"]
logging = ["log"]
ip = ["dep:rpassword", "dep:reqwest", "dep:http-auth", "dep:linfa", "dep:linfa-clustering"]
ip = ["dep:rpassword", "dep:reqwest", "dep:http-auth", "dep:linfa", "dep:linfa-clustering", "dep:retina", "dep:serde_yaml2", "dep:ndarray", "dep:futures", "secluso-client-lib/camera_secret_qrcode"]
raspberry = ["dep:secluso-motion-ai"]
manual = []
telemetry = [] # todo: dep on the motion_ai crate
Expand All @@ -23,28 +23,20 @@ rand="0.8"
bincode = "1.2.1"
secluso-client-lib = { path = "../client_lib", features = ["http_client"] }
secluso-client-server-lib = { path = "../client_server_lib" }
chrono = "0.4"
base64 = "0.22.1"
serde-xml-rs = "0.8"
sha1 = "0.10"
qrcode = "0.14.1"
image = "0.25.10"
openmls = "=0.8.1"
openmls_traits = "=0.5.0"
openmls_rust_crypto = "=0.5.1"
retina = "0.4.19"
retina = { version = "0.4.19", optional = true }
tokio = { version = "1.50.0", features = ["fs", "io-util", "macros", "rt-multi-thread"] }
url = "2.5.8"
anyhow = "1.0.102"
bytes = "1.11.1"
futures = "0.3.32"
serde_yaml2 = "0.1.3"
ndarray = { version="=0.15.6", features = ["rayon"]} # This has to be 0.15 to support linfa
futures = { version = "0.3.32", optional = true }
serde_yaml2 = { version = "0.1.3", optional = true }
ndarray = { version = "=0.16.1", features = ["rayon"], optional = true }
crossbeam-channel = "0.5.15"
cfg-if = "1.0.4"
reqwest = { version = "0.13", default-features = false, features = ["blocking", "json"], optional = true }
serde_json = { version = "1.0" }
regex = "1"

# IP Specific Dependencies
rpassword = {version = "7.4", optional = true }
Expand All @@ -53,4 +45,4 @@ linfa = { version = "0.8.1", optional = true }
linfa-clustering = { version = "0.8.1", optional = true }

# Raspberry Specific Dependencies
secluso-motion-ai = { path = "../motion_ai/pipeline", optional = true }
secluso-motion-ai = { path = "../motion_ai/pipeline", optional = true, default-features = false }
8 changes: 3 additions & 5 deletions client_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ authors = ["Ardalan Amiri Sani <arrdalan@gmail.com>"]
default = ["logging"]
logging = ["log"]
http_client = ["dep:reqwest", "dep:base64"]
camera_secret_qrcode = ["dep:qrcode", "dep:image"]

[dependencies]
docopt = "~1.1"
env_logger = "0.11.9"
log = { version = "0.4.29", optional = true }
mio = { version = "0.8", features = ["net", "os-poll"] }
serde = "1.0"
serde_derive = "1.0"
openmls = { version = "=0.8.1"} # test-utils provides us with ds-lib , features = ["test-utils"]
Expand All @@ -29,5 +27,5 @@ base64-url = {version = "3.0.3"}
anyhow = "^1.0.64" # Locked to this version due to flutter_rust_bridge usage in app
serde_json = "1.0.149"
rand = "0.8"
qrcode = "0.14.1"
image = "0.25.10"
qrcode = { version = "0.14.1", optional = true }
image = { version = "0.25.10", optional = true }
76 changes: 48 additions & 28 deletions client_lib/src/pairing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,47 @@
//!
//! SPDX-License-Identifier: GPL-3.0-or-later

use std::fs;
use anyhow::{anyhow, Context};
use openmls::prelude::KeyPackage;
use serde::{Deserialize, Serialize};
use qrcode::QrCode;
use image::Luma;
use std::fs;
use std::fs::create_dir;
use std::io::Write;
use std::path::Path;
use anyhow::{anyhow, Context};

use openmls_rust_crypto::OpenMlsRustCrypto;
use openmls_traits::random::OpenMlsRand;
use openmls_traits::OpenMlsProvider;
use rand::distributions::Uniform;
use rand::{Rng, thread_rng};

use rand::{thread_rng, Rng};

pub const NUM_SECRET_BYTES: usize = 72;
pub const CAMERA_SECRET_VERSION: &str = "v1.2";
const WIFI_PASSWORD_LEN: usize = 10;
pub const MAX_ALLOWED_MSG_LEN: u64 = 8192;

#[cfg(feature = "camera_secret_qrcode")]
fn save_camera_secret_qrcode(path: &Path, content: &[u8]) -> anyhow::Result<()> {
use image::Luma;
use qrcode::QrCode;

let code =
QrCode::new(content).context("Failed to generate QR code from camera secret bytes")?;
code.render::<Luma<u8>>()
.build()
.save(path)
.with_context(|| format!("Failed to save QR code image to {}", path.display()))?;

Ok(())
}

#[cfg(not(feature = "camera_secret_qrcode"))]
fn save_camera_secret_qrcode(_path: &Path, _content: &[u8]) -> anyhow::Result<()> {
Err(anyhow!(
"camera secret QR code support is not enabled in this build"
))
}

// We version the QR code, store secret bytes as well (base64-url-encoded) as the Wi-Fi passphrase for Raspberry Pi cameras.
// Versioned QR codes can be helpful to ensure compatibility.
// Allows us to create backwards compatibility for previous QR versions without needing to re-generate QR codes again for users.
Expand Down Expand Up @@ -62,7 +81,6 @@ pub struct App {
key_package: KeyPackage,
}


pub fn generate_ip_camera_secret(camera_name: &str) -> anyhow::Result<Vec<u8>> {
let crypto = OpenMlsRustCrypto::default();
let secret = crypto
Expand All @@ -76,16 +94,15 @@ pub fn generate_ip_camera_secret(camera_name: &str) -> anyhow::Result<Vec<u8>> {
wifi_password: None,
};

let writeable_secret = serde_json::to_string(&camera_secret).context("Failed to serialize camera secret into JSON")?;
let writeable_secret = serde_json::to_string(&camera_secret)
.context("Failed to serialize camera secret into JSON")?;

// Save as QR code to be shown to the app
let code = QrCode::new(writeable_secret.as_bytes()).context("Failed to generate QR code from camera secret bytes")?;
let image = code.render::<Luma<u8>>().build();
image
.save(format!(
"camera_{}_secret_qrcode.png",
camera_name.replace(" ", "_").to_lowercase()
)).context("Failed to save QR code image")?;
// Save as QR code to be shown to the app.
let qrcode_path = format!(
"camera_{}_secret_qrcode.png",
camera_name.replace(" ", "_").to_lowercase()
);
save_camera_secret_qrcode(Path::new(&qrcode_path), writeable_secret.as_bytes())?;

Ok(secret)
}
Expand All @@ -95,7 +112,8 @@ fn generate_wifi_password(dir: &Path) -> anyhow::Result<String> {
let wifi_password = generate_random(WIFI_PASSWORD_LEN, false); //10 characters that are upper/low alphanumeric
fs::File::create(dir.join("wifi_password")).context("Could not create wifi_password file")?;

fs::write(dir.join("wifi_password"), wifi_password.clone()).with_context(|| format!("Could not create {}", dir.display()))?;
fs::write(dir.join("wifi_password"), wifi_password.clone())
.with_context(|| format!("Could not create {}", dir.display()))?;

Ok(wifi_password)
}
Expand Down Expand Up @@ -123,21 +141,25 @@ pub fn generate_random(num_chars: usize, special_characters: bool) -> String {
.collect()
}

pub fn generate_raspberry_camera_secret(dir: &Path, error_on_folder_exist: bool) -> anyhow::Result<()> {
pub fn generate_raspberry_camera_secret(
dir: &Path,
error_on_folder_exist: bool,
) -> anyhow::Result<()> {
// If it already exists and we don't want to try re-generating credentials..
if dir.exists() && error_on_folder_exist {
return Err(anyhow!("The directory exists!"));
}

// Create the directory if it doesn't exist
if !dir.exists() {
create_dir(dir)?;
create_dir(dir)?;
}

let crypto = OpenMlsRustCrypto::default();
let secret = crypto
.crypto()
.random_vec(NUM_SECRET_BYTES).context("Failed to generate camera secret bytes")?;
.random_vec(NUM_SECRET_BYTES)
.context("Failed to generate camera secret bytes")?;

let wifi_password = generate_wifi_password(dir)?;
let camera_secret = CameraSecret {
Expand All @@ -146,25 +168,23 @@ pub fn generate_raspberry_camera_secret(dir: &Path, error_on_folder_exist: bool)
wifi_password: Some(wifi_password),
};

let qr_content = serde_json::to_string(&camera_secret).context("Failed to serialize camera secret into JSON")?;
let qr_content = serde_json::to_string(&camera_secret)
.context("Failed to serialize camera secret into JSON")?;

// Save in a file to be given to the camera
// The camera secret does not need to be versioned. We're not worried about the formatting ever changing.
// Just put the secret by itself in this file.
let mut file =
std::fs::File::create(dir.join("camera_secret")).context("Could not create file")?;
file.write_all(&secret).context("Failed to write camera secret data to file")?;
file.write_all(&secret)
.context("Failed to write camera secret data to file")?;

// Save as QR code to be shown to the app (with secret + version + wifi password)
let code = QrCode::new(qr_content.clone()).context("Failed to generate QR code from camera secret bytes")?;
let image = code.render::<Luma<u8>>().build();
image
.save(dir.join("camera_secret_qrcode.png")).context("Failed to save QR code image")?;
// Save as QR code to be shown to the app (with secret + version + wifi password).
save_camera_secret_qrcode(&dir.join("camera_secret_qrcode.png"), qr_content.as_bytes())?;

Ok(())
}


impl App {
pub fn new(key_package: KeyPackage) -> Self {
Self { key_package }
Expand Down
2 changes: 1 addition & 1 deletion config_tool/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ openmls_rust_crypto = "=0.5.1"
rand = "0.8"
url = "2"
secluso-client-server-lib = { path = "../client_server_lib" }
secluso-client-lib = { path = "../client_lib" }
secluso-client-lib = { path = "../client_lib", features = ["camera_secret_qrcode"] }
serde_json = "1.0.149"
image = "0.25.10"
anyhow = "1.0.102"
Expand Down
2 changes: 1 addition & 1 deletion deploy/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ uuid = { version = "1", features = ["v4", "serde"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
toml = "1.1"
secluso-update = { path = "../../update" }
secluso-client-lib = { path = "../../client_lib" }
secluso-client-lib = { path = "../../client_lib", features = ["camera_secret_qrcode"] }
secluso-client-server-lib = { path = "../../client_server_lib" }
reqwest = { version = "0.13", default-features = false, features = ["blocking", "json"] }
url = "2"
Expand Down
11 changes: 5 additions & 6 deletions motion_ai/pipeline/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ version = "1.0.0"
edition = "2024"

[features]
default = ["replay_backend"]
mp4_player = ["dep:video-rs"]
replay_backend = ["dep:tokio", "dep:rocket", "dep:walkdir"]

[dependencies]
ndarray = { version="0.16.1", features = ["rayon"] }
Expand All @@ -29,12 +31,9 @@ anyhow = "1.0.102"
libblur = { version = "0.17.5"}
fast_image_resize = { version = "5.5.0", features = ["rayon"]}
uuid = { version = "1.22.0", features = ["v4"] }
http = "1.4.0"
walkdir = "2.5.0"
tokio = "1.50.0"
tracing = "0.1.44"
tracing-subscriber = "0.3.23"
rocket = { version = "0.5.1", features = ["json"] } #todo: make this feature based
walkdir = { version = "2.5.0", optional = true }
tokio = { version = "1.50.0", optional = true }
rocket = { version = "0.5.1", features = ["json"], optional = true } #todo: make this feature based
video-rs= { version = "0.10.5", features = ["ndarray"], optional = true }
crossbeam-channel = "0.5.15"
flume = "0.11.1"
Expand Down
1 change: 1 addition & 0 deletions motion_ai/pipeline/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! SPDX-License-Identifier: GPL-3.0-or-later
#[cfg(feature = "replay_backend")]
pub mod backend;
mod config;
pub mod frame;
Expand Down
Loading