Outcome of a full multi-agent code review. Touches TLS, credential storage, API versioning, networking correctness, concurrency/lifecycle, and the release build. See CHANGELOG.md for the full list and the items to port to the iOS app. Security - Remove untrusted-TLS support: delete ServerTrustPolicy.AllowSelfSignedForHost and its trust-all X509TrustManager; drop the self-signed Settings toggle. - Enforce HTTPS in normalizedBaseUrl (reject http + embedded credentials); add network_security_config (no cleartext, system CAs only) + allowBackup=false. - Never persist passwords at rest: remember-me stores email only; no password pre-fill; clear creds on sign-out / when remember-me is off. - Match CSRF host lookup to OkHttp's host parsing; stop leaking raw exception text. Correctness - Fix API versioning: hardcode v1/v3 per endpoint (docs Sec.4) instead of one broken global apiVersion; remove the version plumbing + Settings field. - Treat "warning" status as success (committed write); fail-fast on a missing pagination cursor; close Response on cancellation; tolerant JSON parse; User-Agent. - Firmware isCompatible fails closed + extra acknowledgment for a non-compatible image. Concurrency / lifecycle - Run network I/O on Dispatchers.IO; stale-result generation guards + rethrow CancellationException in all loads; host AppState in a ViewModel (survive config change); thread-safe cookie jar; reset/upgrade re-entry guard; wire list Retry. Build - Enable R8 minify+shrink with kotlinx.serialization keep rules; drop unused navigation-compose; add the missing .gitignore. NOTE: built/verified by static review only (no Android SDK on the dev machine) — run :app:assembleDebug and a release build before shipping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.9 KiB
5.9 KiB
Handoff — PON Go Android
Context for the next session/developer. Pairs with README.md and
docs/MCMS_API.md.
Architecture
core/— pure Kotlin, no Android UI deps. Mirrors the iOS networking core.- JSON: MCMS docs are heterogeneous Mongo documents, so we keep raw
kotlinx.serializationJsonElementtrees and read fields via the accessors inJson.kt(el["key"],.string/.int/.double/.array/.obj) — the analogue of the iOSJSONValue. Models (Models.kt) are thin wrappers over aJsonElementwith computed properties. ApiClient(OkHttp): builds versioned requests, injectsReferer+X-CSRFToken(writes), unwraps the{status,data}envelope, maps status codes toApiError, paginates via thenextcursor.Call.await()bridges OkHttp to coroutines with cancellation.- Repositories return model wrappers;
McmsConnectionwires it all; auth/cookies inNetworking.kt(SessionCookieJar+SessionStore), TLS policy inServerTrust, secrets inKeychainStore(EncryptedSharedPreferences).
- JSON: MCMS docs are heterogeneous Mongo documents, so we keep raw
ui/— Jetpack Compose. Navigation is state-based inMainActivity(aList<Screen>back stack +BackHandler), not Navigation-Compose — so detail screens and the dashboard drill-downs can take in-memory objects directly. Each screen has a plain state-holder class (…Model) exposingmutableStateOffields +suspend load()/refresh()— the analogue of the iOS@Observableview models. No androidxViewModel/DI.
Build / verify loop (this matters)
The dev machine had no kotlinc/gradle on PATH, but Android Studio provides
everything. Two loops:
- Full build (authoritative):
Needsexport JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" ./gradlew :app:assembleDebug --no-daemon --console=plainlocal.properties(sdk.dir=…, git-ignored). First run downloads deps to~/.gradle; afterwards it's ~20–60 s. This is what proves the whole app (UI + resources + manifest + icons) compiles.releasenow enables R8 minify+shrink (keep rules inproguard-rules.pro) — smoke-test areleasebuild before shipping, since R8 can strip serializers if the keep rules ever drift. - Quick core-only check (no Gradle), when iterating on
core/: the bundled compiler at…/Android Studio.app/Contents/plugins/Kotlin/kotlinc/bin/kotlincwith-Xplugin=…/kotlinc/lib/kotlinx-serialization-compiler-plugin.jarand a classpath of Maven jars (kotlinx-serialization-json/core, okhttp, okio-jvm, kotlinx-coroutines-core-jvm). Compiles everycore/*.ktexceptKeychainStore.kt(androidx). Faster than a Gradle round-trip.
API gotchas (all handled in code — don't regress)
- Single-encode URLs. Ids stay RAW in
McmsEndpoint;ApiClientencodes the path once viaHttpUrl.addPathSegments. Pre-encoding double-encoded MAC colons (:→%3A→%253A) → MCMS "invalid id". - Endpoint versions are fixed per endpoint (
v1/v3), NOT global. Lists/states arev3; single-doc/login/logs/firmware-files arev1(§4) — the two are not interchangeable, so there is no global API-version setting. Hardcoded inMcmsEndpoint.path(); endpoints not in the §4 table are marked// inferred. - 401 bounces to login; 403 does NOT (permission/CSRF —
requiresReauthenticationis 401-only). - Writes return
{"status":"success"}(or"warning") with nodata— the unwrap tolerates both."warning"means the write committed (schema mismatch only) and is treated as success, NOT an error (§3.5). - ONU registration lives on OLT-STATE buckets (
OltRepository.registrationMap), not ONU-STATE. Friendly names areONU.Name/OLT.Name(NOTNETCONF.Name). - CPE DHCP comes from the web helper
GET /api/cpe/onu/<id>/— a versionless, bare[code, cpe…]array (useApiClient.getRaw). - Firmware = Procedure 7: GET ONU-CFG → write filename/version into the
inactive bank → full-doc PUT (
FirmwareUpgrade.apply). Inactive-bank only, so service stays up until the ONU reboots.FirmwareFile.isCompatiblenow fails CLOSED (unknown vendor/model ⇒ not compatible-by-default) and staging a non-compatible image needs an extra acknowledgment. Still worth testing on a spare ONU. - Field paths were confirmed against live JSON; see
docs/MCMS_API.mdand the model accessors.
Trimmed vs iOS (intentional, easy to restore)
- ONU detail tabs are Overview / CPE / Logs / Config — no separate Alarms tab.
Current alarms live on ONU-STATE
"Alarm"buckets if you want to add it. - Config shows pretty-printed JSON text, not the collapsible tree the iOS app has.
- Dashboard ONU-state rows aren't tappable (the Health rows are). The iOS app drills ONU-state → filtered ONU list.
- Toggles use
rememberSaveable(survive rotation, not process death). Persist in prefs if you want parity with iOS@AppStorage. - TLS is HTTPS-only with system-CA trust; self-signed/cleartext support was removed
(security — see
CHANGELOG.md, not "easy to restore").res/xml/network_security_config.xmlforbids cleartext and user-installed CAs. To reach a private-cert box, install its CA in the device system store or pin its key viaServerTrustPolicy.PinPublicKeySha256. The trust policy lives inServerTrust.
Repo split
This folder is self-contained (has its own docs/MCMS_API.md). To make it a git
repo: git init here; the included .gitignore already excludes build/,
.gradle/, local.properties, .idea/. Commit the Gradle wrapper
(gradlew, gradle/wrapper/*). The full dev-guide PDFs (323-1961-30x) live in
the iOS repo if you ever need the deep source.
Possible next steps
Restore the trimmed items above; add OLT firmware (Procedure 8 bulk task); per-ONU live-alarms view; charts for PM history; pull-to-refresh on lists; dark-theme polish; a proper app theme from the brand colours.