aboutsummaryrefslogtreecommitdiff
path: root/source/app/AppState.mc
diff options
context:
space:
mode:
Diffstat (limited to 'source/app/AppState.mc')
-rw-r--r--source/app/AppState.mc114
1 files changed, 114 insertions, 0 deletions
diff --git a/source/app/AppState.mc b/source/app/AppState.mc
new file mode 100644
index 0000000..05656e2
--- /dev/null
+++ b/source/app/AppState.mc
@@ -0,0 +1,114 @@
+// Author: cirakg & claude ai
+// Summary: The app's shared state: options, the vault, the code cache, start-up errors and the crypto self-test.
+//
+// Created in getInitialView (never in glance mode, which has too little
+// memory for the vault). Views reach the vault and options through here.
+
+import Toybox.Application;
+import Toybox.Lang;
+import Toybox.Time;
+
+module AppState {
+
+ const SELF_TEST_KEY = "st";
+ const SECONDS_PER_DAY = 86400;
+
+ var options as Options = new Options();
+ var vault as Vault? = null;
+ var codes as CodeCache? = null;
+ // A message that replaces the whole app (newer data, damaged metadata,
+ // failed self-test), or null.
+ var startError as String? = null;
+ // Codes are shown only after the self-test passed (README 7.10).
+ var cryptoReady as Boolean = false;
+ var selfTestSummary as String = "Not run yet";
+
+ // Load options and open the vault. Errors end up in startError.
+ function start() as Void {
+ options = new Options();
+ options.load();
+ if (TestMode.ON) {
+ Fixtures.resetStore();
+ }
+ try {
+ var v = new Vault(new Persist.AppStorageBackend(), new TimeSource());
+ v.eraseAfterFailures = options.eraseAfterFailures;
+ v.session.autoLockMs = TestMode.ON ? TestMode.AUTO_LOCK_MS : options.autoLockMs;
+ vault = v;
+ codes = new CodeCache(v);
+ if (TestMode.ON) {
+ Fixtures.load(v);
+ }
+ } catch (e instanceof VaultError) {
+ startError = e.code == VaultError.NEWER_SCHEMA
+ ? "Data from a newer version of OTP Wallet. Update the app."
+ : "Stored data is damaged. Reinstall the app to start over.";
+ }
+ }
+
+ // The vault; only call after start() succeeded.
+ function theVault() as Vault {
+ return vault as Vault;
+ }
+
+ function theCodes() as CodeCache {
+ return codes as CodeCache;
+ }
+
+ // Run the known-answer self-test on the first start of each version and
+ // then once a day. Returns false (and sets startError) when it failed.
+ function runSelfTestIfDue() as Boolean {
+ var day = (Clock.nowUnix(0) / SECONDS_PER_DAY).toNumber();
+ var last = Application.Storage.getValue(SELF_TEST_KEY);
+ var due = true;
+ if (last instanceof Array && last.size() == 2 && last[0] instanceof String && last[1] instanceof Number) {
+ due = !(last[0] as String).equals(AppInfo.VERSION) || (last[1] as Number) != day;
+ }
+ if (!due) {
+ selfTestSummary = "Passed (today)";
+ cryptoReady = true;
+ return true;
+ }
+ var failed = SelfTest.run();
+ if (failed != null) {
+ selfTestSummary = "FAILED: " + failed;
+ startError = "Crypto self-test failed on this watch (" + failed + "). Codes are not shown.";
+ return false;
+ }
+ Application.Storage.setValue(SELF_TEST_KEY, [AppInfo.VERSION, day] as Array<Application.Storage.ValueType>);
+ selfTestSummary = "Passed";
+ cryptoReady = true;
+ return true;
+ }
+
+ // Note user activity (restarts the auto-lock timeout).
+ function activity() as Void {
+ var v = vault;
+ if (v != null) {
+ v.session.touch();
+ }
+ }
+
+ // Lock if the inactivity timeout passed. True when this call locked.
+ function checkAutoLock() as Boolean {
+ var v = vault;
+ if (v != null && v.session.checkExpiry()) {
+ theCodes().clear();
+ return true;
+ }
+ return false;
+ }
+
+ // Lock now and forget every computed code.
+ function lock() as Void {
+ var v = vault;
+ if (v != null) {
+ v.lock();
+ theCodes().clear();
+ }
+ }
+
+ function now() as Long {
+ return Clock.nowUnix(options.timeOffset);
+ }
+}