/* * SPDX-FileCopyrightText: oxmc / PawletOS * SPDX-License-Identifier: Apache-2.0 */ package os.pawlet.cache import android.util.Log import org.json.JSONObject import os.pawlet.cache.discovery.DiscoveredCache import os.pawlet.cache.trust.CacheTokenVerifier import java.io.File /** * Discovery policy, resolved from three tiers, most specific wins: * * 1. [Constants.POLICY_OVERRIDE_PATH] — runtime override, written by * pawletprofiled's "content-cache" MDM payload handler (see * pawletprofiled-config-schema/schema/profile.schema.yml). Mutable, can * arrive any time after first boot. * 2. [Constants.VENDOR_POLICY_PATH] — OEM/vendor-baked default, shipped on * the read-only vendor partition at build time (see * android_packages_apps_PawletCache/vendor-config/README.md). This is * how a device builder ships a standing default (e.g. "our fleet * defaults to LAN-only cache discovery") without needing an MDM profile * pushed after the fact, and doesn't depend on the fork above existing. * 3. Compiled-in default — [Constants.DiscoveryMode.BOTH] — if neither * file exists. * * Both files share the same JSON contract (world-readable by design — it's * policy, not a secret): * ``` * { * "mode": "lan" | "central" | "both" | "disabled", * "pinnedServer": { "host": "...", "port": 8443, "token": "" } * } * ``` * `pinnedServer`, if present, skips discovery entirely — whoever wrote the * file already knows which server to use. Its token is still * signature-verified either way; a config file can pin a *hostname*, not * bypass the trust chain. */ object PolicyOverride { data class Policy( val mode: String, val pinnedServer: DiscoveredCache?, ) fun read(): Policy { return readFrom(Constants.POLICY_OVERRIDE_PATH) ?: readFrom(Constants.VENDOR_POLICY_PATH) ?: Policy(Constants.DiscoveryMode.BOTH, null) } /** @return null if the file doesn't exist — distinct from a present-but-empty file. */ private fun readFrom(path: String): Policy? { val file = File(path) if (!file.exists()) return null return try { val json = JSONObject(file.readText()) val mode = json.optString("mode", Constants.DiscoveryMode.BOTH) val pinned = json.optJSONObject("pinnedServer")?.let { p -> val token = CacheTokenVerifier.verify(p.getString("token")) ?: run { Log.w(Constants.TAG, "$path's pinnedServer token failed verification, ignoring pin") return@let null } DiscoveredCache(p.getString("host"), p.getInt("port"), token, source = "policy") } Policy(mode, pinned) } catch (e: Exception) { Log.w(Constants.TAG, "Malformed policy override at $path", e) // A malformed file at this tier should not silently fall through // to a lower-priority tier — it's a real config error the // OEM/admin should fix, not a "file doesn't exist" case. Policy(Constants.DiscoveryMode.BOTH, null) } } }