Skip to content

Commit ccd3ea7

Browse files
committed
Add support for Android camera. (#138)
This PR adds support for using an Android device as a security camera. In contrast to previous cameras, we use the relay for pairing here. The two phones still use a QR code to exchange a secret, but use the relay after that to complete the pairing process.
1 parent bae0c8b commit ccd3ea7

34 files changed

Lines changed: 6640 additions & 1766 deletions
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Android Camera Miri Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'camera_hub/**'
9+
- '.github/workflows/android-camera-miri-tests.yml'
10+
pull_request:
11+
branches:
12+
- main
13+
paths:
14+
- 'camera_hub/**'
15+
- '.github/workflows/android-camera-miri-tests.yml'
16+
17+
permissions:
18+
contents: read
19+
20+
jobs:
21+
test-android-camera-miri:
22+
runs-on: ubuntu-latest
23+
24+
defaults:
25+
run:
26+
working-directory: camera_hub
27+
28+
steps:
29+
- name: Checkout repository
30+
uses: actions/checkout@v4
31+
32+
- name: Install Rust toolchain
33+
uses: dtolnay/rust-toolchain@nightly
34+
with:
35+
components: miri
36+
37+
- name: Cache Rust build
38+
uses: Swatinem/rust-cache@v2
39+
with:
40+
workspaces: camera_hub -> target
41+
42+
- name: Run Miri tests
43+
run: cargo miri test --lib --features android --verbose -- --test-threads=1

app_native/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ edition = "2021"
55
authors = ["Ardalan Amiri Sani <arrdalan@gmail.com>"]
66

77
[dependencies]
8-
secluso-client-lib = { path = "../client_lib" }
8+
secluso-client-lib = { path = "../client_lib", features=["http_client"] }
99
secluso-client-server-lib = { path = "../client_server_lib" }
1010
bincode = "1.3.3"
1111
rand = "0.9.4"

app_native/examples/app.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ fn main() -> io::Result<()> {
120120
"".to_string(),
121121
"".to_string(),
122122
credentials_full_string,
123+
false,
123124
)
124125
} else {
125126
println!("Sending the add_app request");
@@ -128,9 +129,9 @@ fn main() -> io::Result<()> {
128129
let key_packages_vec = get_key_packages(&mut clients.lock().unwrap())?;
129130

130131
println!("About to send add_app request");
131-
http_client.add_app_request("test_add_app_request_token", key_packages_vec)?;
132+
http_client.send_msg("test_add_app_request_token", key_packages_vec)?;
132133
println!("About to wait for add_app response");
133-
let new_app_data_vec = http_client.add_app_check("test_add_app_response_token")?;
134+
let new_app_data_vec = http_client.receive_msg("test_add_app_response_token")?;
134135
println!("Received add_app response");
135136

136137
let epochs: [u64; NUM_MLS_CLIENTS] = join_camera_groups(
@@ -169,7 +170,7 @@ fn main() -> io::Result<()> {
169170

170171
thread::spawn(move || loop {
171172
println!("About to wait for add_app request");
172-
match http_client_clone.add_app_check("test_add_app_request_token") {
173+
match http_client_clone.receive_msg("test_add_app_request_token") {
173174
Ok(data) => {
174175
println!("Received add_app request.");
175176
let mut data_opt = add_app_request_clone.lock().unwrap();
@@ -294,7 +295,7 @@ fn handle_add_app_request(
294295
increment_epoch("motion_epoch");
295296
increment_epoch("thumbnail_epoch");
296297

297-
http_client.add_app_request("test_add_app_response_token", new_app_data_vec)?;
298+
http_client.send_msg("test_add_app_response_token", new_app_data_vec)?;
298299

299300
Ok(())
300301
}

app_native/src/lib.rs

Lines changed: 57 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,25 @@ use secluso_client_lib::mls_clients::{
1717
CONFIG, FCM, LIVESTREAM, MLS_CLIENT_TAGS, MOTION, NUM_MLS_CLIENTS, THUMBNAIL,
1818
NUM_COMMON_MLS_CLIENTS, NUM_DEDICATED_MLS_CLIENTS,
1919
};
20-
use secluso_client_lib::pairing::{self, MAX_ALLOWED_MSG_LEN, generate_add_app_secret};
20+
use secluso_client_lib::pairing::{self, generate_add_app_secret, MessageTransport,
21+
TcpStreamTransport, RelayTransport};
2122
use secluso_client_lib::video::{encrypt_video_file, decrypt_video_file, decrypt_thumbnail_file};
23+
use secluso_client_server_lib::auth::parse_user_credentials_full;
2224
use openmls::prelude::KeyPackage;
2325
use serde::{Deserialize, Serialize};
2426
use serde_json::json;
2527
use std::array;
2628
use std::fs;
2729
use std::io;
28-
use std::io::{BufRead, BufReader, Read, Write};
29-
use std::net::SocketAddr;
30-
use std::net::TcpStream;
30+
use std::io::{BufRead, BufReader, Write};
3131
use std::str;
32-
use std::str::FromStr;
33-
use std::thread;
34-
use std::time::{Duration, SystemTime, UNIX_EPOCH};
35-
use std::io::ErrorKind;
32+
use std::time::{SystemTime, UNIX_EPOCH};
3633

3734
// Used to generate random names.
3835
// With 16 alphanumeric characters, the probability of collision is very low.
3936
// Note: even if collision happens, it has no impact on
4037
// our security guarantees. Will only cause availability issues.
4138
const NUM_RANDOM_CHARS: u8 = 16;
42-
const CAMERA_CONNECT_TIMEOUT: Duration = Duration::from_secs(3);
43-
const CAMERA_IO_TIMEOUT: Duration = Duration::from_secs(12);
44-
const CAMERA_CONNECT_RETRIES: usize = 3;
45-
const CAMERA_CONNECT_RETRY_DELAY: Duration = Duration::from_millis(350);
4639

4740
#[derive(Serialize)]
4841
struct HeartbeatStatus {
@@ -112,52 +105,21 @@ fn get_app_name(first_time: bool, file_dir: String, filename: String) -> String
112105
app_name
113106
}
114107

115-
fn write_varying_len(stream: &mut TcpStream, msg: &[u8]) -> io::Result<()> {
116-
// FIXME: is u64 necessary?
117-
let len = msg.len() as u64;
118-
let len_data = len.to_be_bytes();
119-
120-
stream.write_all(&len_data)?;
121-
stream.write_all(msg)?;
122-
stream.flush()?;
123-
124-
Ok(())
125-
}
126-
127-
fn read_varying_len(stream: &mut TcpStream) -> io::Result<Vec<u8>> {
128-
let mut len_data = [0u8; 8];
129-
stream.read_exact(&mut len_data)?;
130-
let len = u64::from_be_bytes(len_data);
131-
132-
if len > MAX_ALLOWED_MSG_LEN {
133-
error!("Communicated message length ({len}) exceeds the allowed length ({MAX_ALLOWED_MSG_LEN})");
134-
return Err(io::Error::new(
135-
ErrorKind::InvalidInput,
136-
"Intended message length is too large",
137-
))
138-
}
139-
140-
let mut msg = vec![0u8; len as usize];
141-
stream.read_exact(&mut msg)?;
142-
143-
Ok(msg)
144-
}
145-
146108
fn perform_pairing_handshake(
147-
stream: &mut TcpStream,
109+
msg_transport: &mut dyn MessageTransport,
148110
app_key_package: KeyPackage,
149111
) -> anyhow::Result<KeyPackage> {
150112
let pairing = pairing::App::new(app_key_package);
151113
let app_msg = pairing.generate_msg_to_camera();
152-
write_varying_len(stream, &app_msg)?;
153-
let camera_msg = read_varying_len(stream)?;
114+
msg_transport.send_msg(&app_msg, "app_msg")?;
115+
let camera_msg = msg_transport.receive_msg("camera_msg")?;
154116
let camera_key_package = pairing.process_camera_msg(camera_msg)?;
155117

156118
Ok(camera_key_package)
157119
}
158120

159121
fn send_wifi_and_pairing_info(
160-
stream: &mut TcpStream,
122+
msg_transport: &mut dyn MessageTransport,
161123
mls_client: &mut MlsClient,
162124
wifi_ssid: String,
163125
wifi_password: String,
@@ -177,7 +139,7 @@ fn send_wifi_and_pairing_info(
177139
}
178140
};
179141
info!("Before Wifi Msg Sent");
180-
write_varying_len(stream, &wifi_info_msg)?;
142+
msg_transport.send_msg(&wifi_info_msg, "")?;
181143
info!("After Wifi Msg Sent");
182144

183145
mls_client.save_group_state().unwrap();
@@ -186,7 +148,7 @@ fn send_wifi_and_pairing_info(
186148
}
187149

188150
fn send_credentials_full(
189-
stream: &mut TcpStream,
151+
msg_transport: &mut dyn MessageTransport,
190152
mls_client: &mut MlsClient,
191153
credentials_full: String,
192154
) -> io::Result<()> {
@@ -199,65 +161,36 @@ fn send_credentials_full(
199161
}
200162
};
201163

202-
write_varying_len(stream, &encrypted_msg)?;
164+
msg_transport.send_msg(&encrypted_msg, "")?;
203165

204166
mls_client.save_group_state().unwrap();
205167

206168
Ok(())
207169
}
208170

209171
fn receive_camera_version_info(
210-
stream: &mut TcpStream,
172+
msg_transport: &mut dyn MessageTransport,
211173
) -> anyhow::Result<CameraVersionInfo> {
212174
info!("Receiving camera version info");
213-
let version_info_bytes = read_varying_len(stream)?;
175+
let version_info_bytes = msg_transport.receive_msg("firmware_version")?;
214176
let version_info = serde_json::from_slice::<CameraVersionInfo>(&version_info_bytes)?;
215177

216178
Ok(version_info)
217179
}
218180

219181
fn send_timestamp(
220-
stream: &mut TcpStream,
182+
msg_transport: &mut dyn MessageTransport,
221183
) -> anyhow::Result<()> {
222184
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
223185
let timestamp_vec = bincode::serialize(&timestamp).unwrap();
224-
write_varying_len(stream, &timestamp_vec)?;
186+
msg_transport.send_msg(&timestamp_vec, "")?;
225187

226188
Ok(())
227189
}
228190

229-
fn connect_camera_stream(addr: &SocketAddr) -> io::Result<TcpStream> {
230-
let mut last_error: Option<io::Error> = None;
231-
232-
for attempt in 1..=CAMERA_CONNECT_RETRIES {
233-
info!(
234-
"Connecting to camera (attempt {attempt}/{CAMERA_CONNECT_RETRIES}, addr={addr})"
235-
);
236-
237-
match TcpStream::connect_timeout(addr, CAMERA_CONNECT_TIMEOUT) {
238-
Ok(stream) => {
239-
stream.set_read_timeout(Some(CAMERA_IO_TIMEOUT))?;
240-
stream.set_write_timeout(Some(CAMERA_IO_TIMEOUT))?;
241-
let _ = stream.set_nodelay(true);
242-
info!("Connected to camera transport (addr={addr})");
243-
return Ok(stream);
244-
}
245-
Err(e) => {
246-
info!("Error (connect attempt {attempt}): {e}");
247-
last_error = Some(e);
248-
if attempt < CAMERA_CONNECT_RETRIES {
249-
thread::sleep(CAMERA_CONNECT_RETRY_DELAY);
250-
}
251-
}
252-
}
253-
}
254-
255-
Err(last_error.unwrap_or_else(|| io::Error::other("camera connect failed")))
256-
}
257-
258191
#[flutter_rust_bridge::frb]
259192
fn pair_with_camera(
260-
stream: &mut TcpStream,
193+
msg_transport: &mut dyn MessageTransport,
261194
camera_name: &str,
262195
mls_clients: &mut MlsClients,
263196
secret: Vec<u8>,
@@ -267,10 +200,10 @@ fn pair_with_camera(
267200

268201
let app_key_package = mls_client.key_package();
269202

270-
let camera_key_package = perform_pairing_handshake(stream, app_key_package)?;
203+
let camera_key_package = perform_pairing_handshake(msg_transport, app_key_package)?;
271204

272-
let camera_welcome_msg = read_varying_len(stream)?;
273-
let group_name = read_varying_len(stream)?;
205+
let camera_welcome_msg = msg_transport.receive_msg("welcome")?;
206+
let group_name = msg_transport.receive_msg("group_name")?;
274207
let group_name_string = str::from_utf8(&group_name)?.to_string();
275208

276209
let contact = MlsClient::create_contact(camera_name, camera_key_package)?;
@@ -332,6 +265,7 @@ pub fn add_camera(
332265
wifi_password: String,
333266
pairing_token: String,
334267
credentials_full: String,
268+
android_camera: bool,
335269
) -> String {
336270
if clients_reg.is_none() {
337271
info!("Error: clients not initialized!");
@@ -349,19 +283,31 @@ pub fn add_camera(
349283
}
350284

351285
// Connect to the camera
352-
//FIXME: port number hardcoded.
353-
let addr = match SocketAddr::from_str(&(camera_ip + ":12348")) {
354-
Ok(a) => a,
355-
Err(e) => {
356-
info!("Error: invalid IP address: {e}");
357-
return "Error".to_string();
358-
}
286+
let msg_transport_ret: io::Result<Box<dyn MessageTransport>> = if android_camera {
287+
let (server_username, server_password, server_addr) =
288+
match parse_user_credentials_full(credentials_full.clone().into_bytes()) {
289+
Ok(creds) => creds,
290+
Err(e) => {
291+
info!("Error (parse credentials): {e}");
292+
return "Error".to_string();
293+
}
294+
};
295+
296+
RelayTransport::initialize_connect(
297+
server_username,
298+
server_password,
299+
server_addr,
300+
)
301+
.map(|transport| Box::new(transport) as Box<dyn MessageTransport>)
302+
} else {
303+
TcpStreamTransport::initialize_connect(camera_ip)
304+
.map(|transport| Box::new(transport) as Box<dyn MessageTransport>)
359305
};
360306

361-
let mut stream = match connect_camera_stream(&addr) {
362-
Ok(s) => s,
307+
let mut msg_transport = match msg_transport_ret {
308+
Ok(m) => m,
363309
Err(e) => {
364-
info!("Error (connect): {e}");
310+
info!("Error (MessageTransport initialize): {e}");
365311
return "Error".to_string();
366312
}
367313
};
@@ -370,7 +316,7 @@ pub fn add_camera(
370316
// Need to send timestamp. RPi needs it for setting date/time.
371317
info!("Sending timestamp to camera");
372318
if let Err(e) = send_timestamp(
373-
&mut stream,
319+
msg_transport.as_mut(),
374320
) {
375321
info!("Error (sending timestamp): {e}");
376322
return "Error".to_string();
@@ -379,7 +325,7 @@ pub fn add_camera(
379325

380326
info!("Waiting for firmware version from camera");
381327
let version_info =
382-
match receive_camera_version_info(&mut stream) {
328+
match receive_camera_version_info(msg_transport.as_mut()) {
383329
Ok(version_info) => version_info,
384330
Err(e) => {
385331
info!("Error (firmware): {e}");
@@ -399,7 +345,7 @@ pub fn add_camera(
399345
// Perform pairing
400346
info!("Starting camera pairing handshake");
401347
if let Err(e) = pair_with_camera(
402-
&mut stream,
348+
msg_transport.as_mut(),
403349
&camera_name,
404350
&mut clients.as_mut().mls_clients,
405351
secret_vec,
@@ -410,21 +356,23 @@ pub fn add_camera(
410356
info!("Camera pairing handshake completed");
411357

412358
// Send credentials (username, password, and IP address of the server)
413-
info!("Sending credentials to camera");
414-
if let Err(e) = send_credentials_full(
415-
&mut stream,
416-
&mut clients.mls_clients[CONFIG],
417-
credentials_full,
418-
) {
419-
info!("Error (credentials): {e}");
420-
return "Error".to_string();
359+
if !android_camera {
360+
info!("Sending credentials to camera");
361+
if let Err(e) = send_credentials_full(
362+
msg_transport.as_mut(),
363+
&mut clients.mls_clients[CONFIG],
364+
credentials_full,
365+
) {
366+
info!("Error (credentials): {e}");
367+
return "Error".to_string();
368+
}
421369
}
422370

423371
// Send Wi-Fi info
424372
if standalone_camera {
425373
info!("Sending Wi-Fi info to camera");
426374
if let Err(e) = send_wifi_and_pairing_info(
427-
&mut stream,
375+
msg_transport.as_mut(),
428376
&mut clients.mls_clients[CONFIG],
429377
wifi_ssid,
430378
wifi_password,

0 commit comments

Comments
 (0)