summaryrefslogtreecommitdiff
path: root/source/app/AppState.mc
blob: 05656e27c2f9eddedf4c78c9f7fbae68057414fe (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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);
    }
}