Page MenuHomePhabricator

[native] Expose SecureStore to Rust
ClosedPublic

Authored by michal on Nov 17 2023, 9:47 AM.
Tags
None
Referenced Files
Unknown Object (File)
Fri, May 10, 3:31 PM
Unknown Object (File)
Tue, May 7, 8:30 AM
Unknown Object (File)
Mon, May 6, 11:55 PM
Unknown Object (File)
Wed, Apr 24, 1:00 PM
Unknown Object (File)
Feb 7 2024, 11:46 AM
Unknown Object (File)
Feb 7 2024, 11:46 AM
Unknown Object (File)
Feb 7 2024, 11:46 AM
Unknown Object (File)
Feb 7 2024, 11:46 AM
Subscribers

Details

Summary

ENG-5700

Get access to Secure Store from Rust using the previously introduced promises.

Depends on D9923

Test Plan

Run this code at the beginning of create_backup function:

println!(
  "{:?}\n{:?}\n{:?}\n{:?}\n",
  // Value that doesn't exist
  secure_store_get("non_existant"),
  // Value that does exist
  secure_store_get("DATABASE_MANAGER_STATUS"),
  // Test that values can be set
  secure_store_set(
    "rust_testing_secure_store",
    // Pseudo-random value from `backup_id` in `create_backup`
    backup_id.chars().next().unwrap_or('X').to_string()
  ),
  secure_store_get("rust_testing_secure_store")
);
let non_existant = secure_store::get("non_existant").await;
let device_id = secure_store::get("DATABASE_MANAGER_STATUS").await;
// Test that promises are run on order (if awaited) and that 
// "rust_testing_stuff" will always have the newest value
// Used promise_id as a "random" value for testing
let set_result =
  secure_store::set("rust_testing_stuff", promise_id.to_string()).await;
let set_test = secure_store::get("rust_testing_stuff").await;

let ret = format!(
  "
  SecureStore (nonExistant): {non_existant:?}
  SecureStore (dbStatus): {device_id:?}
  SecureStore set: {set_result:?}
  SecureStore (setTest): {set_test:?}"
);

Results:

Err(Exception { what: "Empty Optional cannot be unwrapped" })
Ok("WORKABLE")
Ok(())
Ok("F")

where the last value changes every call.

Diff Detail

Repository
rCOMM Comm
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

native/native_rust_library/src/cxx_promise_manager.rs
57 ↗(On Diff #33392)

Wasn't sure what to do here. This should never happen (mutex becomes poisoned) but I didn't want to unwrap to not quit the whole app.

Harbormaster returned this revision to the author for changes because remote builds failed.Nov 17 2023, 10:19 AM
Harbormaster failed remote builds in B24254: Diff 33392!
native/native_rust_library/src/secure_store.rs
6–8 ↗(On Diff #33392)

Looks like there is a "round trip":

  • Rust creates an empty promise and asks C++ to populate the promise with result of some calculation.
  • C++ does its calculation and populates the promise
  • Rust awaits the promise.

Is there a reason on why can't we just create a promise in Rust that directly gets the result of C++ calculations and returns them? We would have just Rust call C++, while now Rust calls C++, that calls Rust back.

native/native_rust_library/RustSecureStore.cpp
9 ↗(On Diff #33392)

Sorry for not catching this earlier but from my perspective there is no reason to run this code on GlobalDBSingleton. GlobalDBSingleton is used to ensure that database operations run sequentially on one thread. The reason some calls to CommSecureStore are placed on GlobalDBSingleton is that we keep there several flags that indicate health condition of database file so we semantically treat them as database operations. In this case however you are going to retrieve data that are stored outside of SQLite so there is no reason yo run them on GlobalDBSingleton.

@kamil - can you confirm my statement?

native/native_rust_library/src/secure_store.rs
6–8 ↗(On Diff #33392)

I discussed this with @michal privately. I was told that such design is enforced by the fact that CommSecureStore accesses run on dedicated C++ thread asynchronously.

If I am correct in my previous comment (cc @kamil ) then we don't need to run CommSecureStore accesses on dedicated thread and this code can be rewritten to avoid Rust -> C++ -> Rust round trip. On the other hand if I am wrong and we must run CommSecureStore accesses on dedicated thread then this approach is fine from my perspective.

native/native_rust_library/RustSecureStore.cpp
9 ↗(On Diff #33392)

In D9224 there were functions added for storing auth information in secure store (userID, deviceID, commServicesAccessToken), that I will need to get for the backup networking client. It's run inside of the GlobalDBSingleton (see setCommServicesAuthMetadata function). Is it also a mistake, or is there some reason that we run it there? One possible reason I can see is synchronization - we want to set them all "atomically".

native/native_rust_library/RustSecureStore.cpp
9 ↗(On Diff #33392)

Please accept my apologies for not answering this question earlier. As far as I am concerned running commServicesAccessToken related code on GlobalDBSingleton is unnecessary.

One possible reason I can see is synchronization - we want to set them all "atomically".

Setting things in CommSecureStore is atomic. Think of it as a key-value database that is synchronized by the OS. There is no reason to run this staff on GlobalDBSingleton and Rust can call CommSecureStore directly without "round-trip" with promises.

Accepting but please make sure you either:

  • refactor the code not to use promises and call C++ directly from Rust (in other words remove GlobalDBSingleton from this code)

OR

  • if the above is too much of a hassle and might cause you missing your goal then create high priority follow-up task to remove GlobalDBSingleton from this code.
This revision is now accepted and ready to land.Nov 30 2023, 3:23 AM

Remove GlobalDBSingleton, make the code sync.

michal edited the test plan for this revision. (Show Details)
michal removed a parent revision: D9925: [native] Rust futures in C++.