summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-18 19:47:15 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-18 19:47:15 +0200
commit5396ace6a695ecf530c2ecc9793dfe01f523e70d (patch)
tree2e8ec230251255cceb155edfc4e025cbabf48965 /docs
Inital commit.
Diffstat (limited to 'docs')
-rw-r--r--docs/FEASIBILITY.md288
1 files changed, 288 insertions, 0 deletions
diff --git a/docs/FEASIBILITY.md b/docs/FEASIBILITY.md
new file mode 100644
index 0000000..e150a48
--- /dev/null
+++ b/docs/FEASIBILITY.md
@@ -0,0 +1,288 @@
+# Feasibility findings (Phase 0)
+
+Researched on 2026-10-18 against the Connect IQ SDK 9.2.0 documentation (API reference generated 2026-10-06), Garmin's device reference pages, and, where Garmin's documentation is silent, Garmin developer-forum reports. Forum-only evidence is marked **(forum)**. Nothing has been built yet. Anything marked **to measure** gets measured in the milestones, not estimated.
+
+This file becomes the "Feasibility findings" section of `README.md` in Phase 1.
+
+Verdicts: **Supported**, **Partially** (works with a workaround or a limit), **Not possible**.
+
+## Summary
+
+| # | Question | Verdict | Consequence |
+|---|---|---|---|
+| 1 | Unlock entries with the Garmin device PIN | Not possible | App-level PIN instead (decision D1) |
+| 2 | SHA-1/256/512, HMAC, PBKDF2, AES on the watch | Partially | Native: SHA-1, SHA-256, AES-ECB/CBC, HMAC-SHA256, ECDH, random bytes. SHA-512 and PBKDF2 are built from their standards in Monkey C |
+| 3 | Import and manual entry through app settings | Partially | Text fields only, no file upload. Settings only work for apps installed from the Connect IQ Store (a private beta counts). Dynamic lists are broken today |
+| 4 | Storage limits | Partially | Garmin's pages disagree (8 KB vs 32 KB per value). Design for 8 KB values and about 100 KB total |
+| 5 | Memory | Supported on 2021+ watches | 512 to 768 KB per app on API 5.x/6.x. Older and low-end models get 96 to 128 KB |
+| 6 | Barcode rendering | Partially | All 12 symbologies can be drawn. Long linear codes and large 2D codes don't fit small round screens. Scanner type matters |
+| 7 | App type | Supported | Device app with a glance |
+| 8 | Privacy of settings | Partially (undocumented) | Assume anything typed into settings is stored by Garmin |
+| 9 | Decrypt an encrypted Aegis export on the watch | Not possible | Needs 32 MiB RAM. Decrypt in the offline converter |
+| 10 | Correct TOTP after 2038 | Partially | The clock value is 32-bit. Reinterpret it as unsigned (good until 2106) |
+| 11 | Headless SDK, simulator and tests in Docker | Partially | Needs your Garmin login once for device files. Ubuntu 22.04 base image |
+| 12 | Your host | Action needed | Your user can't use Docker yet (not in the `docker` group) |
+
+## 1. Garmin device PIN / lock: Not possible
+
+- There is no API for it. `Toybox.System.DeviceSettings` has no lock, passcode or PIN field. No Toybox module or manifest permission relates to the device lock. A 2026 forum thread asking how to detect or simulate the passcode got no answer from Garmin.
+- What does exist: some recent watches have a system passcode that engages when the watch comes off the wrist. A developer reports that on a Forerunner 265 (firmware 28.05) it shuts down running Connect IQ apps and calls `onStop` **(forum)**. This app wipes its keys in `onStop`, so on watches that have the system passcode, enabling it adds protection for free. The app can neither detect it nor require it.
+- The Garmin Pay wallet passcode is separate and not exposed to apps.
+
+Alternatives (decision **D1**):
+
+| Option | How it works | Trade-off |
+|---|---|---|
+| **A. App PIN, session unlock** (recommended) | 4 to 10 digit PIN (6 or more advised), entered with an on-screen keypad (touch) or up/down/start (buttons). PBKDF2 turns it into a key that unwraps the key for locked entries. Everything stays unlocked until you leave the app, or until an inactivity timeout (default 60 s). | One PIN entry per session. |
+| **B. PIN every time** | Same derivation, but the key is discarded as soon as you leave a locked entry. | Safer if the watch is grabbed mid-session. You wait for the key derivation (about 1 s, to measure) every time. |
+| **C. Per-entry choice of A or B** | Each entry's lock setting picks "session" or "every time". | Most flexible. More UI states to build and test. |
+
+Wrong attempts, for every option:
+
+- The attempt counter is saved *before* the PIN is checked, so restarting the app or the watch doesn't reset it.
+- After 5 failures, delays escalate (30 s, 1 min, 2 min, up to 1 h). They are timed with the watch's GPS-synced clock, which the user can't change.
+- An optional "wipe locked entries after 10 failures" setting exists and is off by default.
+
+**Limit to accept:** the lockout only stops guessing *on the watch*. Someone who copies the encrypted data off the watch can try all 1,000,000 six-digit PINs offline. At any key-derivation cost a watch can afford, that is a short job for a GPU. Longer PINs raise the cost. Garmin states that apps signed by the Store get an object store that is "encrypted and unreadable from the Connect IQ developer tools". The mechanism isn't documented, and it isn't stated whether beta installs count, so the design treats it as a bonus layer and doesn't rely on it.
+
+## 2. Cryptography: Partially
+
+`Toybox.Cryptography` exists since API 3.0.0 and needs no manifest permission. Every candidate watch in section 5 appears in the Hash, Cipher and HMAC device lists (the original vívoactive 3 is missing from Cipher and HMAC).
+
+| Primitive | Native? | Notes |
+|---|---|---|
+| SHA-1, SHA-256, MD5 | Yes | `Hash` class, streaming |
+| SHA-512 | **No** | |
+| HMAC | SHA-256 only | Docs: "HASH_SHA256 is the only supported algorithm in Connect IQ 3.x" |
+| AES-128, AES-256 | Yes | ECB and CBC modes only. No padding: CBC input must be a multiple of 16 bytes |
+| AES-CMAC | AES-128 only | |
+| ECDH | secp224r1, secp256r1 | Keys use little-endian byte order, so interoperability with other tools must be tested |
+| Random bytes | Yes | `randomBytes()`, documented as "cryptographically random" |
+| PBKDF2, scrypt, HKDF, AES-GCM, AES-CTR | **No** | |
+
+Plan. Standard constructions only, each tested against official test vectors:
+
+- **HMAC-SHA1 and HMAC-SHA256** (most TOTP secrets use SHA-1): RFC 2104 built on the native hashes, using native HMAC-SHA256 where it applies. Vectors: RFC 2202, RFC 4231.
+- **SHA-512 and HMAC-SHA512**: pure Monkey C following FIPS 180-4, on 64-bit `Long`. Vectors: FIPS 180-4 examples, RFC 4231. Only SHA-512 OTP entries pay its cost (to measure).
+- **Key derivation from the PIN**: PBKDF2-HMAC-SHA256 (RFC 8018) on the native HMAC. Vectors: RFC 7914 §11. The iteration count is calibrated on each watch to about 1 s and stored with the salt.
+- **Encryption at rest**: AES-CBC plus HMAC-SHA256 (encrypt-then-MAC), with separate keys derived by HKDF (RFC 5869), PKCS#7 padding and a random IV per record. AES-256 if every target supports it in testing, otherwise AES-128. The PIN, not the cipher, is the weak point either way.
+- **Start-up self-test** against known vectors. If a primitive misbehaves on some firmware, the app refuses to show codes rather than show wrong ones.
+
+## 3. App settings: Partially
+
+### 3a. No file upload: Not possible
+
+Setting types are `alphaNumeric`, `numeric`, `boolean`, `list`, `date`, `email`, `phone`, `url` and `password`.
+
+- `password` only masks the input on screen, and it cannot be read-only.
+- Read-only text fields work, so the watch can report status messages.
+- Settings can be grouped (no nesting), and `enableIfTrue` hides a group until a switch is on.
+- Built-in validation is limited to `min`, `max`, `maxLength` and `required`. Check digits, Base32 and QR capacity have to be validated on the watch, which reports the result in a read-only status field.
+
+### 3b. Settings only work for apps installed from the Store
+
+This changes how the app gets onto your watch. Garmin's developer FAQ: app settings "depend on the Connect IQ App Store to function properly. When an app is side loaded, this connection to the Connect IQ App Store is never established."
+
+The supported route is a **beta app**: you upload the `.iq` file to your Garmin developer account with "Beta App" ticked, and only your account can see it. You install it from the Store, and its settings then work in Garmin Connect and Garmin Express. So requirement 11, and every settings-based import, needs a Garmin developer account and a manual upload through Garmin's website for each release.
+
+Forum posts describe copying a simulator-generated `.SET` file onto a sideloaded watch. The file format is undocumented, so this is not proposed.
+
+### 3c. Dynamic lists ("array settings"): documented, broken in practice
+
+Garmin documents growable lists that the app reads as an array of dictionaries. As of this month they cannot be saved from Garmin Connect Mobile. Bug CIQQA-4751 was reported on 2026-10-01 (Forerunner 965, Garmin Connect 5.29 on Android) and acknowledged by Garmin on 2026-10-02, with no fix yet. It says a failed save silently resets *all* of the app's settings, and nothing saves again until the app is reinstalled. Older array bugs (CIQQA-1339 and CIQQA-1380, from 2022) are still open.
+
+Verdict: not usable. Alternatives (decision **D3**):
+
+| Option | How it works | Trade-off |
+|---|---|---|
+| **S1. Fixed slots** (recommended) | N slot groups per kind (for example 10 OTP and 10 code slots, a build-time constant). Each slot has every field from requirement 11, plus "enabled" and "order". Edit in place, reorder with "order", delete by switching off or clearing. Secret fields are write-only: blank means "keep the stored secret". | Hard cap on hand-entered entries. The phone shows groups named "OTP slot 1" and so on, because group titles can't be dynamic. Imported entries aren't shown in slots; you delete, reorder and lock them on the watch. Works even if write-back (3e) doesn't. |
+| **S2. Inbox plus manage form** | One "new OTP" and one "new code" form that the watch reads and then clears. A "manage" form takes an entry ID (shown on the watch) and an action: edit fields, delete, move to position, lock. | No cap on entries and a small settings payload. You need the entry ID from the watch. The watch must ignore forms it has already applied. |
+| **S3. One entry per line** | One text field with one line per entry (`otpauth://...` lines and a similar `code://...` format), parsed and validated on the watch with an error per line. | Compact, for power users. Limited by field length (about 800 characters, see 3d) and the `&` problem on iOS. Secrets stay in the field unless write-back works. |
+
+### 3d. Size and character limits: undocumented
+
+Forum reports only **(forum)**:
+
+- Garmin Express caps a text field at 800 characters.
+- Long values fail to save; one report attributes this to a total settings payload of about 8 KB (unverified).
+- Garmin Connect on iOS rejects `<`, `>`, `&` and `\`.
+
+Every `otpauth://` URI with parameters contains `&`, so pasting raw URIs may fail on an iPhone. Import data will use an `&`-free alphabet and be split across several fields, and several syncs if needed. The real limits get measured on your phone in an early "settings spike" milestone.
+
+### 3e. Writing values back: unknown
+
+The watch can change its own settings values with `Properties.setValue` (saved when the app stops). Garmin doesn't document whether the phone shows those values the next time you open the settings. This decides whether "clear the field after import" and status messages are visible on the phone. The design works either way, and the settings spike measures it.
+
+## 4. Storage: Partially
+
+- `Application.Storage` (API 2.4.0) stores numbers, strings, `ByteArray`, arrays and dictionaries.
+- The limits disagree. The API reference says values up to 32 KB, a total that "can vary between devices", and `StorageFullException` when full. The core-topic page says "Keys and values are limited to 8 KB each, and a total of 128 KB of storage is available." The design uses the stricter numbers: at most 8 KB per value and about 100 KB in total, with one key per entry plus small index keys.
+- Garmin says storage data "is only available to the application and is not accessible by the end user", and that Store-signed apps get an encrypted object store.
+- What happens to storage across app updates and on uninstall is **not documented**. Storage normally survives updates, but the design doesn't assume it. Every record carries a schema version, migrations are tested, and unreadable data fails safe (the app asks for a re-import instead of crashing).
+- `Application.Properties` only holds settings declared in `properties.xml`. Values are written to disk when the app stops, and background processes can't write them.
+
+## 5. Memory per device: Supported on 2021+ watches
+
+Memory in KB from Garmin's device reference pages, API level from the compatible-devices table:
+
+| Device | API | Screen | Touch | Watch app | Glance |
+|---|---|---|---|---|---|
+| Forerunner 255S | 5.2 | 218×218 round, MIP 64 colours | no | 512 | 64 |
+| Forerunner 255 | 5.2 | 260×260 round, MIP | no | 512 | 64 |
+| fēnix 7 / 7S / 7X | 5.2 | 240 to 280 round, MIP | yes | 768 | 64 |
+| epix 2, Forerunner 265 / 965, Forerunner 165, vívoactive 5, Venu 3S | 5.2 | 360 to 454 round, AMOLED | yes | 768 | 64 |
+| Forerunner 955 | 5.2 | 260×260 round, MIP | yes | 768 | 64 |
+| Venu 2S, Venu 2 Plus | 5.0 | 360 to 416 round, AMOLED | yes | 768 | 64 |
+| Venu Sq 2 | 5.0 | 320×360 rectangle, AMOLED | yes | 768 | 64 |
+| fēnix 8, fēnix E, Forerunner 570 / 970, Venu 4, vívoactive 6 | 6.0 | 260 to 454 round | yes | 768 | 64 |
+| Venu X1 | 6.0 | 448×486 rectangle, AMOLED | yes | 768 | 64 |
+| Instinct 3 AMOLED | 6.0 | 390×390 round, AMOLED | no | 768 | 64 |
+| fēnix 6 (non-Pro), Forerunner 245, Forerunner 55 | 3.3 to 3.4 | 208 to 260 round, MIP | no | 128 | 32 |
+| Instinct 2 / 2S / Crossover | 3.4 | 163 to 176 semi-octagon, 2 colours | no | 96 | 32 |
+| Instinct 3 Solar, Instinct E | 6.0 | 166 to 176 semi-octagon, 2 colours | no | 128 | 32 |
+
+Recommendation (decision **D4**): set `minApiLevel` to 5.0 and target every watch with at least 512 KB.
+
+- Weakest target: Forerunner 255S (smallest round screen, buttons only, 512 KB).
+- Simulator test trio: Forerunner 255S (round, buttons only), Venu Sq 2 (rectangle, touch), Forerunner 965 (AMOLED, touch).
+- The 96 to 128 KB watches would need a reduced build (linear codes and QR only) and their own test runs. Only worth it if you own one.
+
+## 6. Rendering barcodes: Partially
+
+- Drawing is plain filled rectangles, rendered once into a buffered bitmap and then reused. Speed is to measure, but there is little work per frame.
+- Space: the largest centred square on a round screen is its width divided by 1.41. That's 154 px on a 218 px screen, 184 px on 260 px, 276 px on 390 px and 321 px on 454 px.
+- **QR**, with the standard 4-module quiet zone:
+ - 218 px screen: version 6 at 3 px per module (about 106 bytes at error-correction level M), or version 13 at 2 px (about 331 bytes).
+ - 454 px screen: version 20 at 3 px (about 666 bytes).
+ - Data Matrix and Aztec behave similarly. PDF417 is wide and short, which suits the horizontal middle of a round screen.
+- **Linear codes** use the widest horizontal band of the screen:
+ - EAN-13 needs 113 modules including quiet zones: 1 px per module on a 218 px screen, 2 px on 260 px, 3 px on 390 px and larger.
+ - A 20-character Code 128 needs about 275 modules and does not fit a 218 to 260 px round screen at all.
+- When a code can't be drawn at a scannable module size (at least 2 px, to be confirmed by scan tests), the watch says so instead of drawing it. Entry validation warns in the status field, and the converter warns for every target screen.
+- **Scanners**:
+ - AMOLED screens emit light and have high contrast. The app can raise brightness while a code is shown, but watches with burn-in protection throw an exception if the screen is held on for more than about a minute, so the boost is time-limited.
+ - Camera-based scanners and phones read screens. Laser scanners usually can't read emissive screens.
+ - MIP screens reflect ambient light: good outdoors, backlight needed indoors.
+ - Real checkout scanners vary, so you'll need to test them.
+- **Verification in Docker**: render each encoder's output at watch scale and decode it with zxing-cpp (open source, reads all 12 symbologies); decode simulator screenshots of the real watch UI; compare deterministic linear codes bar-for-bar with reference encoders.
+
+## 7. App type: Supported (device app with a glance)
+
+- Widgets time out after inactivity (documented). Device apps are not documented to.
+- On API 4.0+ watches, apps that implement a glance appear in the glance list: one scroll from the watch face and one press to open.
+- Glances run in 64 KB (32 KB on older watches) and get no input. The default glance shows the app name and the number of entries. An optional glance (off by default) could show your favourite unlocked code, if the crypto fits the glance memory budget (to measure).
+- Hot keys and shortcut menus are device features that apps can't configure. The user guide will list where they exist.
+
+## 8. Privacy of settings: Partially (undocumented)
+
+- Garmin doesn't document where setting *values* are stored. What is known:
+ - Settings only work for Store installs.
+ - The settings definition is stored in the Store.
+ - Editing settings needs an internet connection **(forum, with a reply from Garmin staff)**.
+ - The values reach the watch, which keeps them in its settings file.
+- So treat anything typed into settings as disclosed to your phone, to Garmin's servers (possibly kept indefinitely) and to the watch's settings file. `password` fields hide characters on screen and do nothing more.
+- Minimising exposure:
+ 1. Import secrets only as encrypted data from the offline converter. The decryption code is typed on the watch and never goes through the phone.
+ 2. The watch clears import and secret fields after reading them. Whether the phone sees the cleared value depends on 3e.
+ 3. Typing a Base32 secret into the manual form sends it in the clear. The settings screen and the user guide warn about this.
+
+## 9. Encrypted Aegis export on the watch: Not possible
+
+- Aegis password slots use scrypt with N = 32,768, r = 8 and p = 1, which needs 128 × r × N = 32 MiB of RAM. Watch apps get at most 0.77 MB.
+- The vault itself is encrypted with AES-256-GCM, which isn't native on the watch.
+- Decryption therefore happens in the offline converter (Docker, no network). The watch only ever receives the app's own encrypted format.
+- The converter also handles plain Aegis exports, `otpauth://` lists and Google Authenticator `otpauth-migration://` exports.
+- Aegis features the watch can't use are reported, not silently dropped:
+ - Icons and notes are not imported.
+ - mOTP and Yandex entries are skipped with a message.
+ - Steam entries are imported as the optional Steam type.
+
+## 10. Time: Partially
+
+- `Time.now()` returns UTC seconds, but the value is a 32-bit signed integer that wraps on 2038-01-19. The OTP core takes a 64-bit time, and the clock reading is reinterpreted as unsigned, which is correct until 2106. Tests cover period rollover, leap years, 2038, and the RFC 6238 vector for the year 2603 (the maths only).
+- `Time.getCurrentTime({:currentTimeType => Time.CURRENT_TIME_RTC})` (API 3.0.10) returns a clock the user can't override, synced from trusted sources such as GPS. It throws if that clock isn't synced yet. The app warns when that clock is unsynced or disagrees with the displayed time, and a setting allows a manual offset for skew.
+
+## 11. Docker, SDK and simulator: Partially
+
+- **SDK**: the latest is 9.2.0 (2026-06-09). The Linux zip downloads without a login from Garmin's SDK list (checked: HTTP 200, 214 MB). Downloading it means accepting Garmin's SDK licence, so you make that step.
+- **Device definitions**, which you need to build for any watch, only come through the SDK Manager with a Garmin account.
+ - Headless option: `connect-iq-sdk-manager-cli` (open source, Apache-2.0, v0.8.4 from 2026-02-22) has `agreement view`, `agreement accept`, an interactive `login` and `device download --manifest`.
+ - You'd log in once inside the container. The login token stays in the project's `.cache/` folder.
+ - Third-party Docker images that bundle Garmin's device files are avoided for licence reasons.
+- **Simulator**:
+ - Garmin supports only Ubuntu on Linux, and the simulator needs `libwebkit2gtk-4.0`, which Ubuntu 24.04 no longer ships. The image is therefore Ubuntu 22.04 with Java 17, Xvfb, libusb and libsm. Ubuntu 22.04 standard support ends in April 2027, a risk to track.
+ - To watch the simulator, a VNC server in the container is published on 127.0.0.1 only and opened in your browser through noVNC. Nothing to install.
+- **Unit tests** (Garmin's Run No Evil framework) only run inside the simulator: build with `--unit-test`, then run `monkeydo app.prg <device> -t`. Its exit code is reported to always be non-zero **(forum)**, so the test runner parses the result lines instead.
+- **Strict type checking**: `monkeyc -l 3`.
+- **No network**: the app requests no permissions at all, so it can't use `Toybox.Communications`. Anyone can check this in `manifest.xml`.
+- **Watchdog**: code that runs too long without returning to the system is killed ("Watchdog Tripped"). Forum posts report a per-device bytecode budget (examples of 80,000 to 120,000) and about 5 s on hardware **(forum)**. Key derivation, SHA-512 batches and 2D encoding therefore run as resumable jobs across timer ticks. The exact budgets come from the device files in Phase 2.
+- **Developer key**: an RSA-4096 key generated inside the container into `.cache/`, which git ignores. The Store rejects an update signed with a different key. Losing it means a new app ID and re-importing your entries on the watch.
+
+## 12. Your host: Action needed
+
+Docker 29.9.1 with buildx is installed and running. Your user isn't in the `docker` group, so every Docker command currently fails with "permission denied". The Compose plugin isn't installed; the Makefile won't need it.
+
+## 13. Import pipeline candidates (decision D2)
+
+| Candidate | Pros | Cons |
+|---|---|---|
+| **A. Paste `otpauth://` lines into settings.** The watch parses them and clears the field. | No tools needed. | Secrets pass through the phone and Garmin in the clear. iOS may reject the `&` in every URI. Only a handful of entries fit per field. Needs the Store beta. |
+| **B. Offline converter to an encrypted blob** (recommended). The converter runs in Docker with no network and turns an Aegis export (plain or encrypted), `otpauth://` lists or Google migration exports into an encrypted blob. You paste the blob into settings and type a 12-digit transfer code on the watch once. | Only ciphertext reaches Garmin. Validation and warnings (for example, a code too big for your screen) happen before anything reaches the watch. Compact, about 70 characters per entry. The blob is encrypted, so any channel can carry it to your phone; the converter can also show it as QR codes for your phone's camera. | Needs a computer. Needs the Store beta. The transfer code is worth about 40 bits times the key-derivation cost: fine against bulk exposure, not against a determined attacker who also gets hold of the blob. |
+| **B′. Like B, with ECDH pairing instead of a transfer code** | No code to type, and full 128-bit security. | A one-time pairing step: you copy the watch's public key from the phone settings into the converter. Connect IQ's little-endian key format needs interoperability testing on a real watch. |
+| **C. USB sideload build with the encrypted vault built in** | Nothing goes through the phone or Garmin. No Store account needed. | That build has no phone settings at all, so no manual entry form. Every change means a rebuild and a USB copy. |
+
+Rules that apply to every path (detailed in the README):
+
+- **Duplicates**: same type, issuer, account and secret means skip and report.
+- **Partial imports**: valid entries are imported, invalid ones are listed with reasons.
+- **Status**: one read-only status field summarises the last import.
+- **Editing and removing after import**: on the watch (delete, reorder, lock), or by re-importing.
+
+## Decisions
+
+Answered on 2026-10-18:
+
+- **D1 PIN policy: A, session unlock.** One PIN entry per session. Everything re-locks when you leave the app or after the inactivity timeout.
+- **D2 Delivery and import: plain text, imported on the watch.** Two formats are pasted into settings and parsed on the watch: `otpauth://` URIs, and Aegis's *unencrypted* JSON export. There is no converter tool. Consequences:
+ - Encrypted Aegis exports are not supported (section 9 explains why the watch can't decrypt them).
+ - Secrets pass through the phone and Garmin in the clear (section 8). The watch clears the fields after import.
+ - Delivery is the private Store beta, because settings don't work for sideloaded apps (3b).
+ - An Aegis JSON export is about 400 to 500 characters per entry without icons, so large vaults may need several fields and syncs. The settings spike measures the real limits.
+- **D3 Manual-entry layout: S2, inbox plus manage form.**
+- **D4 Targets: API 5.0+ watches** with at least 512 KB for apps. No low-memory build.
+
+Still open:
+
+- Which watch you'll test on, and whether your phone is Android or iPhone (the `&` and `\` problem in 3d affects iPhone only).
+- **D5 Docker access** (needs sudo, which you run):
+ - Option 1: `sudo usermod -aG docker cirakg`, then log out and back in. Membership in the `docker` group is equivalent to root on this machine.
+ - Option 2: rootless Docker, which needs the AUR package `docker-rootless-extras`.
+- **D6 Garmin account steps** (Phase 2):
+ - Accept the SDK licence and log in once inside the container.
+ - Create a Garmin developer account.
+ - Upload the beta yourself.
+
+## Sources
+
+Official Garmin documentation:
+
+- Cryptography: [module](https://developer.garmin.com/connect-iq/api-docs/Toybox/Cryptography.html), [Hash](https://developer.garmin.com/connect-iq/api-docs/Toybox/Cryptography/Hash.html), [HMAC](https://developer.garmin.com/connect-iq/api-docs/Toybox/Cryptography/HashBasedMessageAuthenticationCode.html), [Cipher](https://developer.garmin.com/connect-iq/api-docs/Toybox/Cryptography/Cipher.html), [KeyAgreement](https://developer.garmin.com/connect-iq/api-docs/Toybox/Cryptography/KeyAgreement.html)
+- Storage and settings: [Storage](https://developer.garmin.com/connect-iq/api-docs/Toybox/Application/Storage.html), [Properties](https://developer.garmin.com/connect-iq/api-docs/Toybox/Application/Properties.html), [Persisting Data](https://developer.garmin.com/connect-iq/core-topics/persisting-data/), [Properties and App Settings](https://developer.garmin.com/connect-iq/core-topics/properties-and-app-settings/), [Beta Apps](https://developer.garmin.com/connect-iq/core-topics/beta-apps/), [Security](https://developer.garmin.com/connect-iq/core-topics/security/), [developer FAQ: app settings and sideloading](https://forums.garmin.com/developer/connect-iq/w/wiki/4/new-developer-faq)
+- App model: [App Types](https://developer.garmin.com/connect-iq/connect-iq-basics/app-types/), [Glances](https://developer.garmin.com/connect-iq/core-topics/glances/), [Manifest and Permissions](https://developer.garmin.com/connect-iq/core-topics/manifest-and-permissions/), [Resources](https://developer.garmin.com/connect-iq/core-topics/resources/)
+- Devices: [Device Reference](https://developer.garmin.com/connect-iq/device-reference/), [Compatible Devices](https://developer.garmin.com/connect-iq/compatible-devices/), [DeviceSettings](https://developer.garmin.com/connect-iq/api-docs/Toybox/System/DeviceSettings.html)
+- Time and display: [Time](https://developer.garmin.com/connect-iq/api-docs/Toybox/Time.html), [Moment](https://developer.garmin.com/connect-iq/api-docs/Toybox/Time/Moment.html), [Attention.backlight](https://developer.garmin.com/connect-iq/api-docs/Toybox/Attention.html)
+- Tooling: [Unit Testing](https://developer.garmin.com/connect-iq/core-topics/unit-testing/), [Monkey Types (type checking)](https://developer.garmin.com/connect-iq/monkey-c/monkey-types/), [Command line](https://developer.garmin.com/connect-iq/reference-guides/monkey-c-command-line-setup/), [Getting Started / SDK Manager](https://developer.garmin.com/connect-iq/connect-iq-basics/getting-started/), [SDK release list](https://developer.garmin.com/downloads/connect-iq/sdks/sdks.json)
+
+Garmin developer forum:
+
+- Array settings bugs: [CIQQA-4751](https://forums.garmin.com/developer/connect-iq/i/bug-reports/array-settings-still-cannot-be-saved-from-garmin-connect-mobile-corrupts-saving-of-all-app-settings-store-released-data-field), [CIQQA-1339](https://forums.garmin.com/developer/connect-iq/i/bug-reports/confusion-and-bugs-in-array-settings), [CIQQA-1380](https://forums.garmin.com/developer/connect-iq/i/bug-reports/array-settings-maxlength-causes-all-the-settings-after-the-arra-not-to-be-displayed)
+- [System passcode and Connect IQ apps](https://forums.garmin.com/developer/connect-iq/f/discussion/439041/passcode-behaviour-for-connectiq-apps)
+- Settings transport: [internet connection needed](https://forums.garmin.com/developer/connect-iq/f/discussion/6825/why-is-it-necessary-to-have-internet-connection-to-change-the-settings-of-an-app), [settings for sideloaded apps](https://forums.garmin.com/developer/connect-iq/f/discussion/429848/solved-settings-connect-iq-app-for-sideloaded-app---is-this-possible)
+- Settings limits: [alphaNumeric max length](https://forums.garmin.com/developer/connect-iq/f/discussion/217336/max-length-of-alphanumeric-setting), [editor inconsistencies](https://forums.garmin.com/developer/connect-iq/f/discussion/258276/alphanumeric-type-inconsistencies-across-app-settings-editors), [Garmin Express length](https://forums.garmin.com/developer/connect-iq/f/discussion/4882/problem-with-settings-in-garminexpress-with-length-of-alphanumeric), [long value not saved](https://forums.garmin.com/developer/connect-iq/f/discussion/298660/property-value-with-a-long-string-40-characters-cannot-be-saved)
+- [Watchdog limits](https://forums.garmin.com/developer/connect-iq/f/discussion/4580/watchdog-tripped-error---code-executed-too-long---workaround-for-tests)
+
+Other:
+
+- [Aegis vault format](https://github.com/beemdevelopment/Aegis/blob/master/docs/vault.md)
+- [connect-iq-sdk-manager-cli](https://github.com/lindell/connect-iq-sdk-manager-cli)
+- Headless simulator references, read but not used directly: [matco/connectiq-tester](https://github.com/matco/connectiq-tester), [wistrand/garmin-dev](https://github.com/wistrand/garmin-dev)