@@ -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} ;
1516use secluso_client_lib:: http_client:: HttpClient ;
1617use secluso_client_lib:: pairing:: { NUM_SECRET_BYTES } ;
@@ -23,7 +24,7 @@ use std::fs::File;
2324use std:: io:: { self , BufRead , BufReader , Write } ;
2425use std:: path:: Path ;
2526use std:: sync:: Arc ;
26- use std:: sync:: Mutex ;
27+ use std:: sync:: { LazyLock , Mutex } ;
2728use std:: thread;
2829use std:: time:: { Duration , SystemTime , UNIX_EPOCH } ;
2930
@@ -41,6 +42,10 @@ const DATA_DIR: &str = "example_app_data";
4142
4243pub 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+
4449const USAGE : & str = "
4550Runs 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
303386fn 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
396501fn read_epoch ( epoch_filename : & str ) -> u64 {
0 commit comments