Skip to content

Commit 9e412b5

Browse files
committed
Support removal of secondary apps. (#141)
This PR adds support for removal of secondary apps by the primary app. It includes tests in the client_lib as well as a sample workflow in the example app. In addition, it fixes an issue that has been present in previous code used to support secondary apps: the camera would not reconstruct the state (MLS, threads) needed for the secondary apps after a reboot. That is resolved now.
1 parent b88c916 commit 9e412b5

10 files changed

Lines changed: 708 additions & 125 deletions

File tree

app_native/examples/app.rs

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use secluso_app_native::{
1010
get_group_name, initialize, livestream_decrypt, livestream_update,
1111
process_heartbeat_config_response, generate_add_app_request_config_command,
1212
process_add_app_config_response, join_camera_groups,
13-
get_key_packages, decrypt_thumbnail,
13+
get_key_packages, decrypt_thumbnail, generate_remove_app_request_config_command,
14+
process_remove_app_config_response,
1415
};
1516
use secluso_client_lib::http_client::HttpClient;
1617
use secluso_client_lib::pairing::{NUM_SECRET_BYTES};
@@ -23,7 +24,7 @@ use std::fs::File;
2324
use std::io::{self, BufRead, BufReader, Write};
2425
use std::path::Path;
2526
use std::sync::Arc;
26-
use std::sync::Mutex;
27+
use std::sync::{LazyLock, Mutex};
2728
use std::thread;
2829
use std::time::{Duration, SystemTime, UNIX_EPOCH};
2930

@@ -41,6 +42,10 @@ const DATA_DIR: &str = "example_app_data";
4142

4243
pub const MAX_ALLOWED_MSG_LEN: u64 = 65536;
4344

45+
// The name used by the camera to refer to this app.
46+
static MY_NAME: LazyLock<Mutex<String>> =
47+
LazyLock::new(|| Mutex::new(String::new()));
48+
4449
const USAGE: &str = "
4550
Runs a simple Secluso app.
4651
@@ -134,12 +139,14 @@ fn main() -> io::Result<()> {
134139
let new_app_data_vec = http_client.receive_msg("test_add_app_response_token")?;
135140
println!("Received add_app response");
136141

137-
let epochs: [u64; NUM_MLS_CLIENTS] = join_camera_groups(
142+
let (epochs, my_name): ([u64; NUM_MLS_CLIENTS], String) = join_camera_groups(
138143
&mut clients.lock().unwrap(),
139144
add_app_secret.clone(),
140145
new_app_data_vec,
141146
)?;
142147

148+
*MY_NAME.lock().unwrap() = my_name;
149+
143150
write_epoch("motion_epoch", epochs[MOTION] + 1);
144151
write_epoch("thumbnail_epoch", epochs[THUMBNAIL] + 1);
145152

@@ -217,6 +224,10 @@ fn main_loop(
217224
num_iters: usize,
218225
add_app_secret: Vec<u8>,
219226
) -> io::Result<()> {
227+
let mut remove_app_needed = false;
228+
let mut remove_app_iter: usize = 60;
229+
let mut remove_app_name = "".to_string();
230+
220231
for iter in 0..num_iters {
221232
thread::sleep(Duration::from_secs(1));
222233

@@ -225,23 +236,43 @@ fn main_loop(
225236
fetch_thumbnails(Arc::clone(&clients), &http_client)?;
226237

227238
if iter % 60 == 29 {
228-
heartbeat(Arc::clone(&clients), &http_client)?;
239+
let terminate = heartbeat(Arc::clone(&clients), &http_client)?;
240+
if terminate {
241+
return Ok(());
242+
}
229243
}
230244

231245
if iter % 60 == 59 {
232246
livestream(Arc::clone(&clients), &http_client, 2)?;
233247
}
234248

249+
if remove_app_needed {
250+
remove_app_iter -= 1;
251+
if remove_app_iter <= 0 {
252+
remove_app(
253+
Arc::clone(&clients),
254+
&http_client,
255+
&remove_app_name
256+
)?;
257+
}
258+
}
259+
235260
let mut add_app_data_opt = add_app_request.lock().unwrap();
236261
if let Some(add_app_data) = add_app_data_opt.as_ref() {
237262
println!("Add app request detected");
238-
handle_add_app_request(
263+
let app_name = handle_add_app_request(
239264
Arc::clone(&clients),
240265
&http_client,
241266
add_app_data,
242267
add_app_secret.clone(),
243268
)?;
244269
*add_app_data_opt = None;
270+
271+
if !remove_app_needed {
272+
println!("Scheduling this app to be removed");
273+
remove_app_needed = true;
274+
remove_app_name = app_name;
275+
}
245276
}
246277
}
247278

@@ -253,7 +284,7 @@ fn handle_add_app_request(
253284
http_client: &HttpClient,
254285
add_app_data: &Vec<u8>,
255286
add_app_secret: Vec<u8>,
256-
) -> io::Result<()> {
287+
) -> io::Result<String> {
257288
println!("handle_add_app_request called");
258289

259290
let new_app_key_packages_vec = add_app_data.clone();
@@ -281,12 +312,15 @@ fn handle_add_app_request(
281312

282313
if config_response_opt.is_none() {
283314
println!("Error: couldn't fetch the add_app response. Camera might be offline.");
284-
return Ok(());
315+
return Err(io::Error::new(
316+
io::ErrorKind::Other,
317+
format!("Error: couldn't fetch the add_app response. Camera might be offline."),
318+
));
285319
}
286320

287321
let config_response = config_response_opt.unwrap();
288322

289-
let new_app_data_vec = process_add_app_config_response(
323+
let (new_app_data_vec, new_app_name) = process_add_app_config_response(
290324
&mut clients.lock().unwrap(),
291325
config_response.clone(),
292326
add_app_secret,
@@ -297,13 +331,62 @@ fn handle_add_app_request(
297331

298332
http_client.send_msg("test_add_app_response_token", new_app_data_vec)?;
299333

334+
Ok(new_app_name)
335+
}
336+
337+
fn remove_app(
338+
clients: Arc<Mutex<Option<Box<Clients>>>>,
339+
http_client: &HttpClient,
340+
app_name: &str,
341+
) -> io::Result<()> {
342+
println!("remove_app called");
343+
344+
let config_msg_enc =
345+
generate_remove_app_request_config_command(&mut clients.lock().unwrap(), app_name)?;
346+
347+
let config_group_name = get_group_name(&mut clients.lock().unwrap(), "config")?;
348+
349+
println!("Sending remove_app request.");
350+
http_client.config_command(&config_group_name, config_msg_enc)?;
351+
352+
let mut config_response_opt: Option<Vec<u8>> = None;
353+
for _i in 0..30 {
354+
println!("Attempt {_i}");
355+
thread::sleep(Duration::from_secs(2));
356+
match http_client.fetch_config_response(&config_group_name) {
357+
Ok(resp) => {
358+
config_response_opt = Some(resp);
359+
break;
360+
}
361+
Err(_) => {}
362+
}
363+
}
364+
365+
if config_response_opt.is_none() {
366+
println!("Error: couldn't fetch the remove_app response. Camera might be offline.");
367+
return Err(io::Error::new(
368+
io::ErrorKind::Other,
369+
format!("Error: couldn't fetch the add_app response. Camera might be offline."),
370+
));
371+
}
372+
373+
let config_response = config_response_opt.unwrap();
374+
375+
process_remove_app_config_response(
376+
&mut clients.lock().unwrap(),
377+
config_response.clone(),
378+
).unwrap();
379+
380+
increment_epoch("motion_epoch");
381+
increment_epoch("thumbnail_epoch");
382+
300383
Ok(())
301384
}
302385

303386
fn heartbeat(
304387
clients: Arc<Mutex<Option<Box<Clients>>>>,
305388
http_client: &HttpClient,
306-
) -> io::Result<()> {
389+
) -> io::Result<bool> {
307390
let timestamp = SystemTime::now()
308391
.duration_since(UNIX_EPOCH)
309392
.expect("Could not convert time")
@@ -313,6 +396,7 @@ fn heartbeat(
313396
generate_heartbeat_request_config_command(&mut clients.lock().unwrap(), timestamp)?;
314397

315398
let config_group_name = get_group_name(&mut clients.lock().unwrap(), "config")?;
399+
let livestream_group_name = get_group_name(&mut clients.lock().unwrap(), "livestream")?;
316400

317401
println!("Sending heartbeat request: {}", timestamp);
318402

@@ -363,6 +447,27 @@ fn heartbeat(
363447
increment_epoch("motion_epoch");
364448
increment_epoch("thumbnail_epoch");
365449
}
450+
Ok(response) if response.contains("remove_app") => {
451+
println!("Received remove_app notification.");
452+
let my_name: String = MY_NAME.lock().unwrap().clone();
453+
println!("my_name = {my_name}");
454+
let removed_app_name = response
455+
.strip_prefix("remove_app")
456+
.expect("Couldn't extract the removed app name");
457+
458+
println!("removed_app_name = {response}");
459+
if my_name == removed_app_name {
460+
println!("We have been removed. Terminating now.");
461+
// Remove dedicated group names from server before terminating
462+
http_client.deregister(&livestream_group_name)?;
463+
http_client.deregister(&config_group_name)?;
464+
return Ok(true);
465+
} else {
466+
println!("Another app has been removed. Updating epochs.");
467+
increment_epoch("motion_epoch");
468+
increment_epoch("thumbnail_epoch");
469+
}
470+
}
366471
Ok(response) => {
367472
//invalid timestamp || invalid epoch
368473
// FIXME: Before processing the heartbeat response, we should make sure all motion videos are fetched and processed.
@@ -390,7 +495,7 @@ fn heartbeat(
390495
));
391496
}
392497

393-
Ok(())
498+
Ok(false)
394499
}
395500

396501
fn read_epoch(epoch_filename: &str) -> u64 {

0 commit comments

Comments
 (0)