mirror of
git://f0xx.org/android_cast
synced 2026-07-29 04:57:40 +03:00
docs, obfuscation
This commit is contained in:
@@ -19,6 +19,7 @@ import com.foxx.androidcast.commercial.CommercialFeatures;
|
|||||||
import com.foxx.androidcast.crash.CrashReporter;
|
import com.foxx.androidcast.crash.CrashReporter;
|
||||||
import com.foxx.androidcast.dev.DevAdbWifiKeeper;
|
import com.foxx.androidcast.dev.DevAdbWifiKeeper;
|
||||||
import com.foxx.androidcast.display.WiredDisplayMonitor;
|
import com.foxx.androidcast.display.WiredDisplayMonitor;
|
||||||
|
import com.foxx.androidcast.entitlements.EntitlementCoordinator;
|
||||||
import com.foxx.androidcast.media.CodecPriorityCatalog;
|
import com.foxx.androidcast.media.CodecPriorityCatalog;
|
||||||
import com.foxx.androidcast.network.CastTransportFactory;
|
import com.foxx.androidcast.network.CastTransportFactory;
|
||||||
import com.foxx.androidcast.receiver.av.ReceiverAvRuntime;
|
import com.foxx.androidcast.receiver.av.ReceiverAvRuntime;
|
||||||
@@ -45,6 +46,7 @@ public class AndroidCastApplication extends Application {
|
|||||||
CastTransportFactory.init(this);
|
CastTransportFactory.init(this);
|
||||||
WiredDisplayMonitor.getInstance(this).ensureRegistered();
|
WiredDisplayMonitor.getInstance(this).ensureRegistered();
|
||||||
CommercialFeatures.refreshFromPreferences(this);
|
CommercialFeatures.refreshFromPreferences(this);
|
||||||
|
EntitlementCoordinator.getInstance(this).bootstrap();
|
||||||
RemoteAccessCoordinator.onBootIfNeeded(this);
|
RemoteAccessCoordinator.onBootIfNeeded(this);
|
||||||
DevAdbWifiKeeper.ensureStarted(this);
|
DevAdbWifiKeeper.ensureStarted(this);
|
||||||
CastTrayNotifier.ensureTrayService(this);
|
CastTrayNotifier.ensureTrayService(this);
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ public final class AppPreferences {
|
|||||||
private static final String KEY_DEV_REMOTE_ACCESS_VPN_ROUTE_SCOPE = "dev_remote_access_vpn_route_scope";
|
private static final String KEY_DEV_REMOTE_ACCESS_VPN_ROUTE_SCOPE = "dev_remote_access_vpn_route_scope";
|
||||||
private static final String KEY_DEV_REMOTE_ACCESS_VPN_APP_SCOPE = "dev_remote_access_vpn_app_scope";
|
private static final String KEY_DEV_REMOTE_ACCESS_VPN_APP_SCOPE = "dev_remote_access_vpn_app_scope";
|
||||||
private static final String KEY_DEV_ADB_WIFI_KEEPER = "dev_adb_wifi_keeper";
|
private static final String KEY_DEV_ADB_WIFI_KEEPER = "dev_adb_wifi_keeper";
|
||||||
|
private static final String KEY_ENTITLEMENT_ACCOUNT_KEY = "entitlement_account_key";
|
||||||
|
private static final String KEY_ENTITLEMENT_LAST_VERIFIED_WALL_MS = "entitlement_last_verified_wall_ms";
|
||||||
|
|
||||||
/** Minimum interval between automatic OTA checks on app launch. */
|
/** Minimum interval between automatic OTA checks on app launch. */
|
||||||
public static final long OTA_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000L;
|
public static final long OTA_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000L;
|
||||||
@@ -692,6 +694,23 @@ public final class AppPreferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getEntitlementAccountKey(Context context) {
|
||||||
|
return prefs(context).getString(KEY_ENTITLEMENT_ACCOUNT_KEY, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setEntitlementAccountKey(Context context, String keyHex) {
|
||||||
|
prefs(context).edit().putString(KEY_ENTITLEMENT_ACCOUNT_KEY,
|
||||||
|
keyHex != null ? keyHex : "").apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getEntitlementLastVerifiedWallMs(Context context) {
|
||||||
|
return prefs(context).getLong(KEY_ENTITLEMENT_LAST_VERIFIED_WALL_MS, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setEntitlementLastVerifiedWallMs(Context context, long wallMs) {
|
||||||
|
prefs(context).edit().putLong(KEY_ENTITLEMENT_LAST_VERIFIED_WALL_MS, wallMs).apply();
|
||||||
|
}
|
||||||
|
|
||||||
private static SharedPreferences prefs(Context context) {
|
private static SharedPreferences prefs(Context context) {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
throw new IllegalStateException("context is null");
|
throw new IllegalStateException("context is null");
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package com.foxx.androidcast.entitlements;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.foxx.androidcast.BuildConfig;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process-wide paid-feature bitmask: loads native lib on demand, reads local obfuscated file, refreshes from BE.
|
||||||
|
*/
|
||||||
|
public final class EntitlementCoordinator {
|
||||||
|
private static final String TAG = "EntitlementCoord";
|
||||||
|
private static final long DEV_ACCOUNT_KEY = 0x0123456789ABCDEFL;
|
||||||
|
|
||||||
|
private static volatile EntitlementCoordinator instance;
|
||||||
|
|
||||||
|
private final Context appContext;
|
||||||
|
private final ExecutorService syncExecutor = Executors.newSingleThreadExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "entitlement-sync");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
private final AtomicLong flags = new AtomicLong(0L);
|
||||||
|
|
||||||
|
private volatile boolean nativeCapable;
|
||||||
|
|
||||||
|
public static EntitlementCoordinator getInstance(Context context) {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (EntitlementCoordinator.class) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new EntitlementCoordinator(context.getApplicationContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EntitlementCoordinator(Context appContext) {
|
||||||
|
this.appContext = appContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private volatile boolean bootstrapDone;
|
||||||
|
|
||||||
|
/** Call once from {@link com.foxx.androidcast.AndroidCastApplication}. */
|
||||||
|
public void bootstrap() {
|
||||||
|
if (bootstrapDone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bootstrapDone = true;
|
||||||
|
reloadFromLocal();
|
||||||
|
scheduleBackendSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getFlags() {
|
||||||
|
return flags.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasFeature(long featureBit) {
|
||||||
|
return EntitlementFlags.hasFeature(flags.get(), featureBit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAnyPaidFeature() {
|
||||||
|
return EntitlementFlags.hasAnyPaidBit(flags.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNativeLibraryAvailable() {
|
||||||
|
return nativeCapable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Re-read local file after manual refresh or sync completion. */
|
||||||
|
public void reloadFromLocal() {
|
||||||
|
if (isDevUnlockAll()) {
|
||||||
|
flags.set(EntitlementFlags.DEV_ALL_PAYLOAD);
|
||||||
|
EntitlementNativeBridge.acquire();
|
||||||
|
nativeCapable = EntitlementNativeBridge.isAvailable();
|
||||||
|
if (nativeCapable) {
|
||||||
|
try {
|
||||||
|
writeDevBlobIfNeeded();
|
||||||
|
} finally {
|
||||||
|
EntitlementNativeBridge.release();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
EntitlementNativeBridge.release();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntitlementNativeBridge.acquire();
|
||||||
|
nativeCapable = EntitlementNativeBridge.isAvailable();
|
||||||
|
if (!nativeCapable) {
|
||||||
|
EntitlementNativeBridge.release();
|
||||||
|
flags.set(0L);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (EntitlementPolicy.isClockRollback(now, EntitlementStore.getLastVerifiedWallMs(appContext),
|
||||||
|
EntitlementPolicy.CLOCK_ROLLBACK_TOLERANCE_MS)) {
|
||||||
|
Log.w(TAG, "wall clock rollback detected — clearing entitlements");
|
||||||
|
resetToDefaults();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = EntitlementStore.entitlementFile(appContext);
|
||||||
|
if (!file.isFile()) {
|
||||||
|
flags.set(0L);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (EntitlementPolicy.isFileStale(file.lastModified(), now, EntitlementPolicy.FILE_TTL_MS)) {
|
||||||
|
Log.i(TAG, "local entitlement file expired");
|
||||||
|
resetToDefaults();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long key = EntitlementStore.getAccountKey(appContext);
|
||||||
|
if (key == 0L) {
|
||||||
|
flags.set(0L);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long[] out = new long[1];
|
||||||
|
int rv = EntitlementNativeBridge.readFrom(file.getAbsolutePath(), key, out);
|
||||||
|
if (rv != 0) {
|
||||||
|
Log.w(TAG, "readFrom failed: " + rv);
|
||||||
|
flags.set(0L);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flags.set(out[0]);
|
||||||
|
EntitlementStore.setLastVerifiedWallMs(appContext, now);
|
||||||
|
} finally {
|
||||||
|
EntitlementNativeBridge.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scheduleBackendSync() {
|
||||||
|
syncExecutor.execute(this::runBackendSync);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runBackendSync() {
|
||||||
|
EntitlementSyncClient.SyncPayload payload = EntitlementSyncClient.fetch(appContext);
|
||||||
|
if (payload == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!EntitlementSyncClient.persistPayload(appContext, payload)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mainHandler.post(this::reloadFromLocal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetToDefaults() {
|
||||||
|
EntitlementStore.clearLocal(appContext);
|
||||||
|
flags.set(0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDevUnlockAll() {
|
||||||
|
return BuildConfig.DEBUG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Debug: materialize an all-features blob so Linux ↔ device file tests work without BE. */
|
||||||
|
private void writeDevBlobIfNeeded() {
|
||||||
|
File file = EntitlementStore.entitlementFile(appContext);
|
||||||
|
if (file.isFile() && EntitlementStore.getAccountKey(appContext) == DEV_ACCOUNT_KEY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EntitlementStore.setAccountKey(appContext, DEV_ACCOUNT_KEY);
|
||||||
|
int w = EntitlementNativeBridge.writeTo(file.getAbsolutePath(),
|
||||||
|
EntitlementFlags.DEV_ALL_PAYLOAD, DEV_ACCOUNT_KEY);
|
||||||
|
if (w != 0) {
|
||||||
|
Log.w(TAG, "dev writeTo failed: " + w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.foxx.androidcast.entitlements;
|
||||||
|
|
||||||
|
/** Bitmask constants for paid / licensed app capabilities (40-bit payload). */
|
||||||
|
public final class EntitlementFlags {
|
||||||
|
private EntitlementFlags() {}
|
||||||
|
|
||||||
|
/** All encodable payload bits — dev builds unlock everything. */
|
||||||
|
public static final long DEV_ALL_PAYLOAD = 0xFFFFFFFFFFL;
|
||||||
|
|
||||||
|
public static boolean hasFeature(long flags, long featureBit) {
|
||||||
|
return featureBit != 0L && (flags & featureBit) == featureBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasAnyPaidBit(long flags) {
|
||||||
|
return flags != 0L;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package com.foxx.androidcast.entitlements;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads {@code libandroidcast_entitlements.so} on demand. If load fails, callers must assume zero paid
|
||||||
|
* flags (unless dev unlock-all is enabled at the Java layer).
|
||||||
|
*/
|
||||||
|
public final class EntitlementNativeBridge {
|
||||||
|
private static final String TAG = "EntitlementNative";
|
||||||
|
static final String LIB = "androidcast_entitlements";
|
||||||
|
|
||||||
|
private static final Object LOCK = new Object();
|
||||||
|
private static int refCount = 0;
|
||||||
|
private static boolean loaded = false;
|
||||||
|
private static boolean loadFailed = false;
|
||||||
|
|
||||||
|
private EntitlementNativeBridge() {}
|
||||||
|
|
||||||
|
public static void acquire() {
|
||||||
|
synchronized (LOCK) {
|
||||||
|
if (loadFailed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!loaded) {
|
||||||
|
try {
|
||||||
|
System.loadLibrary(LIB);
|
||||||
|
loaded = true;
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
loadFailed = true;
|
||||||
|
Log.w(TAG, "Native entitlements library unavailable: " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void release() {
|
||||||
|
synchronized (LOCK) {
|
||||||
|
if (refCount <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refCount--;
|
||||||
|
if (refCount == 0) {
|
||||||
|
loaded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAvailable() {
|
||||||
|
synchronized (LOCK) {
|
||||||
|
return loaded && !loadFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean ensureLoaded() {
|
||||||
|
acquire();
|
||||||
|
boolean ok = isAvailable();
|
||||||
|
if (!ok) {
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void requireLoaded() {
|
||||||
|
if (!isAvailable()) {
|
||||||
|
throw new IllegalStateException("libandroidcast_entitlements is not loaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int readFrom(String absolutePath, long key, long[] outVal) {
|
||||||
|
requireLoaded();
|
||||||
|
if (outVal == null || outVal.length < 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return nativeReadFrom(absolutePath, key, outVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int writeTo(String absolutePath, long val, long key) {
|
||||||
|
requireLoaded();
|
||||||
|
return nativeWriteTo(absolutePath, val, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int decode(long encodedVal, long key, long[] outVal) {
|
||||||
|
requireLoaded();
|
||||||
|
if (outVal == null || outVal.length < 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return nativeDecode(encodedVal, key, outVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int encode(long val, long key, long[] outEncoded) {
|
||||||
|
requireLoaded();
|
||||||
|
if (outEncoded == null || outEncoded.length < 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return nativeEncode(val, key, outEncoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native int nativeReadFrom(String path, long key, long[] outVal);
|
||||||
|
private static native int nativeWriteTo(String path, long val, long key);
|
||||||
|
private static native int nativeDecode(long encodedVal, long key, long[] outVal);
|
||||||
|
private static native int nativeEncode(long val, long key, long[] outEncoded);
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.foxx.androidcast.entitlements;
|
||||||
|
|
||||||
|
/** Pure policy helpers (unit-tested without native I/O). */
|
||||||
|
final class EntitlementPolicy {
|
||||||
|
/** Local entitlement blob older than this is discarded (offline abuse mitigation). */
|
||||||
|
static final long FILE_TTL_MS = 3L * 24L * 60L * 60L * 1000L;
|
||||||
|
|
||||||
|
/** Wall clock moved backwards beyond this vs last successful verify → treat as tamper. */
|
||||||
|
static final long CLOCK_ROLLBACK_TOLERANCE_MS = 60_000L;
|
||||||
|
|
||||||
|
private EntitlementPolicy() {}
|
||||||
|
|
||||||
|
static boolean isFileStale(long fileLastModifiedMs, long nowMs, long ttlMs) {
|
||||||
|
if (fileLastModifiedMs <= 0L) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
long age = nowMs - fileLastModifiedMs;
|
||||||
|
return age < 0L || age > ttlMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isClockRollback(long nowMs, long lastVerifiedWallMs, long toleranceMs) {
|
||||||
|
return lastVerifiedWallMs > 0L && nowMs + toleranceMs < lastVerifiedWallMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long parseKeyHex(String hex) {
|
||||||
|
if (hex == null) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
String trimmed = hex.trim();
|
||||||
|
if (trimmed.isEmpty()) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
if (trimmed.startsWith("0x") || trimmed.startsWith("0X")) {
|
||||||
|
trimmed = trimmed.substring(2);
|
||||||
|
}
|
||||||
|
if (trimmed.matches("[0-9]+")) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(trimmed, 10);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseUnsignedLong(trimmed, 16);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String formatKeyHex(long key) {
|
||||||
|
return String.format("%016x", key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.foxx.androidcast.entitlements;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.foxx.androidcast.AppPreferences;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/** Paths and persisted key / anti-tamper timestamps for the local entitlement blob. */
|
||||||
|
final class EntitlementStore {
|
||||||
|
private static final String FILE_NAME = "account_flags.obf";
|
||||||
|
|
||||||
|
private EntitlementStore() {}
|
||||||
|
|
||||||
|
static File entitlementFile(Context context) {
|
||||||
|
File dir = new File(context.getFilesDir(), "entitlements");
|
||||||
|
if (!dir.exists() && !dir.mkdirs()) {
|
||||||
|
return new File(context.getFilesDir(), FILE_NAME);
|
||||||
|
}
|
||||||
|
return new File(dir, FILE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long getAccountKey(Context context) {
|
||||||
|
return EntitlementPolicy.parseKeyHex(AppPreferences.getEntitlementAccountKey(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setAccountKey(Context context, long key) {
|
||||||
|
AppPreferences.setEntitlementAccountKey(context, EntitlementPolicy.formatKeyHex(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
static long getLastVerifiedWallMs(Context context) {
|
||||||
|
return AppPreferences.getEntitlementLastVerifiedWallMs(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setLastVerifiedWallMs(Context context, long wallMs) {
|
||||||
|
AppPreferences.setEntitlementLastVerifiedWallMs(context, wallMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearLocal(Context context) {
|
||||||
|
File f = entitlementFile(context);
|
||||||
|
if (f.exists() && !f.delete()) {
|
||||||
|
f.deleteOnExit();
|
||||||
|
}
|
||||||
|
AppPreferences.setEntitlementAccountKey(context, "");
|
||||||
|
AppPreferences.setEntitlementLastVerifiedWallMs(context, 0L);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package com.foxx.androidcast.entitlements;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.foxx.androidcast.AppPreferences;
|
||||||
|
import com.foxx.androidcast.network.BackendEndpoints;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches per-account entitlement key + obfuscated blob from BE (RBAC-backed endpoint stub).
|
||||||
|
* Returns {@code null} when backend has no payload or network fails — local file remains authoritative.
|
||||||
|
*/
|
||||||
|
final class EntitlementSyncClient {
|
||||||
|
private static final String TAG = "EntitlementSync";
|
||||||
|
|
||||||
|
static final class SyncPayload {
|
||||||
|
final long accountKey;
|
||||||
|
final byte[] fileBytes;
|
||||||
|
final String etag;
|
||||||
|
|
||||||
|
SyncPayload(long accountKey, byte[] fileBytes, String etag) {
|
||||||
|
this.accountKey = accountKey;
|
||||||
|
this.fileBytes = fileBytes;
|
||||||
|
this.etag = etag != null ? etag : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private EntitlementSyncClient() {}
|
||||||
|
|
||||||
|
static SyncPayload fetch(Context context) {
|
||||||
|
String user = AppPreferences.getUsername(context);
|
||||||
|
if (user == null || user.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
HttpURLConnection conn = null;
|
||||||
|
try {
|
||||||
|
String q = "user=" + URLEncoder.encode(user.trim(), StandardCharsets.UTF_8.name());
|
||||||
|
String pin = AppPreferences.getPin(context);
|
||||||
|
if (pin != null && !pin.isEmpty()) {
|
||||||
|
q += "&pin=" + URLEncoder.encode(pin, StandardCharsets.UTF_8.name());
|
||||||
|
}
|
||||||
|
URL url = new URL(BackendEndpoints.ENTITLEMENTS_FETCH_URL + "?" + q);
|
||||||
|
conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setConnectTimeout(12_000);
|
||||||
|
conn.setReadTimeout(20_000);
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setRequestProperty("Accept", "application/json");
|
||||||
|
int code = conn.getResponseCode();
|
||||||
|
if (code == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (code != HttpURLConnection.HTTP_OK) {
|
||||||
|
Log.w(TAG, "fetch HTTP " + code);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String body = readUtf8(conn.getInputStream());
|
||||||
|
JSONObject json = new JSONObject(body);
|
||||||
|
String keyHex = json.optString("key", "");
|
||||||
|
long key = EntitlementPolicy.parseKeyHex(keyHex);
|
||||||
|
if (key == 0L) {
|
||||||
|
Log.w(TAG, "fetch missing key");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String fileUrl = json.optString("file_url", "");
|
||||||
|
String etag = json.optString("etag", "");
|
||||||
|
byte[] bytes;
|
||||||
|
if (!fileUrl.isEmpty()) {
|
||||||
|
bytes = downloadBytes(fileUrl);
|
||||||
|
} else if (json.has("file_base64")) {
|
||||||
|
bytes = android.util.Base64.decode(json.getString("file_base64"), android.util.Base64.DEFAULT);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "fetch missing file payload");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (bytes == null || bytes.length < 16) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new SyncPayload(key, bytes, etag);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, "fetch failed: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (conn != null) {
|
||||||
|
conn.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean persistPayload(Context context, SyncPayload payload) {
|
||||||
|
if (payload == null || payload.fileBytes == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
File dest = EntitlementStore.entitlementFile(context);
|
||||||
|
File parent = dest.getParentFile();
|
||||||
|
if (parent != null && !parent.exists() && !parent.mkdirs()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(dest, false)) {
|
||||||
|
fos.write(payload.fileBytes);
|
||||||
|
fos.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, "persist failed: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
EntitlementStore.setAccountKey(context, payload.accountKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] downloadBytes(String fileUrl) throws Exception {
|
||||||
|
HttpURLConnection c = (HttpURLConnection) new URL(fileUrl).openConnection();
|
||||||
|
c.setConnectTimeout(12_000);
|
||||||
|
c.setReadTimeout(30_000);
|
||||||
|
try {
|
||||||
|
if (c.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return readBytes(c.getInputStream());
|
||||||
|
} finally {
|
||||||
|
c.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readUtf8(InputStream in) throws Exception {
|
||||||
|
byte[] raw = readBytes(in);
|
||||||
|
return new String(raw, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readBytes(InputStream in) throws Exception {
|
||||||
|
try (BufferedInputStream bin = new BufferedInputStream(in);
|
||||||
|
ByteArrayOutputStream bout = new ByteArrayOutputStream()) {
|
||||||
|
byte[] buf = new byte[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = bin.read(buf)) >= 0) {
|
||||||
|
bout.write(buf, 0, n);
|
||||||
|
}
|
||||||
|
return bout.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.foxx.androidcast.entitlements;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App-wide gate for paid capabilities decoded from the per-account obfuscated blob.
|
||||||
|
* Until feature-specific bits are defined, {@link #hasAnyPaidFeature} is the primary check.
|
||||||
|
*/
|
||||||
|
public final class PaidEntitlements {
|
||||||
|
private PaidEntitlements() {}
|
||||||
|
|
||||||
|
public static long getFlags(Context context) {
|
||||||
|
return EntitlementCoordinator.getInstance(context).getFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasAnyPaidFeature(Context context) {
|
||||||
|
return EntitlementCoordinator.getInstance(context).hasAnyPaidFeature();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasFeature(Context context, long featureBit) {
|
||||||
|
return EntitlementCoordinator.getInstance(context).hasFeature(featureBit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNativeGateAvailable(Context context) {
|
||||||
|
return EntitlementCoordinator.getInstance(context).isNativeLibraryAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void refresh(Context context) {
|
||||||
|
EntitlementCoordinator coord = EntitlementCoordinator.getInstance(context);
|
||||||
|
coord.reloadFromLocal();
|
||||||
|
coord.scheduleBackendSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,10 @@ public final class BackendEndpoints {
|
|||||||
public static final String HEARTBEAT_URL = CRASHES_BASE + "/api/heartbeat.php";
|
public static final String HEARTBEAT_URL = CRASHES_BASE + "/api/heartbeat.php";
|
||||||
public static final String UPLOAD_URL = CRASHES_BASE + "/api/upload.php";
|
public static final String UPLOAD_URL = CRASHES_BASE + "/api/upload.php";
|
||||||
public static final String GRAPH_UPLOAD_URL = GRAPHS_BASE + "/api/graph_upload.php";
|
public static final String GRAPH_UPLOAD_URL = GRAPHS_BASE + "/api/graph_upload.php";
|
||||||
|
public static final String ENTITLEMENTS_BASE =
|
||||||
|
"https://" + APPS_HOST + "/app/androidcast_project/entitlements";
|
||||||
|
/** GET — returns JSON {@code {key, file_url|file_base64, etag}} for RBAC-authenticated user. */
|
||||||
|
public static final String ENTITLEMENTS_FETCH_URL = ENTITLEMENTS_BASE + "/api/fetch.php";
|
||||||
|
|
||||||
private BackendEndpoints() {}
|
private BackendEndpoints() {}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.foxx.androidcast.entitlements;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class EntitlementPolicyTest {
|
||||||
|
@Test
|
||||||
|
public void fileStaleAfterTtl() {
|
||||||
|
long now = 500_000_000_000L;
|
||||||
|
long fresh = now - EntitlementPolicy.FILE_TTL_MS + 60_000L;
|
||||||
|
long stale = now - EntitlementPolicy.FILE_TTL_MS - 60_000L;
|
||||||
|
assertFalse(EntitlementPolicy.isFileStale(fresh, now, EntitlementPolicy.FILE_TTL_MS));
|
||||||
|
assertTrue(EntitlementPolicy.isFileStale(stale, now, EntitlementPolicy.FILE_TTL_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clockRollbackDetected() {
|
||||||
|
long lastOk = 5_000_000L;
|
||||||
|
long nowOk = lastOk + 1_000L;
|
||||||
|
long nowRollback = lastOk - EntitlementPolicy.CLOCK_ROLLBACK_TOLERANCE_MS - 1;
|
||||||
|
assertFalse(EntitlementPolicy.isClockRollback(nowOk, lastOk,
|
||||||
|
EntitlementPolicy.CLOCK_ROLLBACK_TOLERANCE_MS));
|
||||||
|
assertTrue(EntitlementPolicy.isClockRollback(nowRollback, lastOk,
|
||||||
|
EntitlementPolicy.CLOCK_ROLLBACK_TOLERANCE_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseKeyHex() {
|
||||||
|
assertEquals(0xcafebabedeadbeefL, EntitlementPolicy.parseKeyHex("cafebabedeadbeef"));
|
||||||
|
assertEquals(42L, EntitlementPolicy.parseKeyHex("42"));
|
||||||
|
assertEquals(0L, EntitlementPolicy.parseKeyHex(""));
|
||||||
|
assertEquals(0L, EntitlementPolicy.parseKeyHex(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void devAllPayloadHasBits() {
|
||||||
|
assertTrue(EntitlementFlags.hasAnyPaidBit(EntitlementFlags.DEV_ALL_PAYLOAD));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ public class BackendEndpointsTest {
|
|||||||
public void canonicalUrlsUseAppsHost() {
|
public void canonicalUrlsUseAppsHost() {
|
||||||
assertTrue(BackendEndpoints.HEARTBEAT_URL.startsWith("https://apps.f0xx.org/"));
|
assertTrue(BackendEndpoints.HEARTBEAT_URL.startsWith("https://apps.f0xx.org/"));
|
||||||
assertTrue(BackendEndpoints.UPLOAD_URL.contains("/api/upload.php"));
|
assertTrue(BackendEndpoints.UPLOAD_URL.contains("/api/upload.php"));
|
||||||
|
assertTrue(BackendEndpoints.ENTITLEMENTS_FETCH_URL.contains("/entitlements/api/fetch.php"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -483,7 +483,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
81 0 obj
|
81 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220445+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220445+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174830+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174830+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Android Cast \204 On-Demand Remote Access \(Reverse Tunnel\) Proposals) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Android Cast \204 On-Demand Remote Access \(Reverse Tunnel\) Proposals) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -1052,7 +1052,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<3f94d39cd85223d5686ff0349cc56520><3f94d39cd85223d5686ff0349cc56520>]
|
[<559db9fd7b62d2e91af17ade78c5196e><559db9fd7b62d2e91af17ade78c5196e>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 81 0 R
|
/Info 81 0 R
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
42 0 obj
|
42 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220445+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220445+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174830+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174830+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Android Cast \204 Email agent config & 2FA / mobile auth flow) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Android Cast \204 Email agent config & 2FA / mobile auth flow) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -537,7 +537,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<8e9582b9d629fb33039bda9127c34432><8e9582b9d629fb33039bda9127c34432>]
|
[<004fa69baafe806860fd5caf900f27cc><004fa69baafe806860fd5caf900f27cc>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 42 0 R
|
/Info 42 0 R
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
33 0 obj
|
33 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220445+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220445+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174830+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174830+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Android Cast \204 alpha priorities \(2026-06-08 renumber\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Android Cast \204 alpha priorities \(2026-06-08 renumber\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -418,7 +418,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<cbeebf3abfc4c12ed8adf1ecbcb8d5a4><cbeebf3abfc4c12ed8adf1ecbcb8d5a4>]
|
[<d83210b5b8072cc987bb1c318f31c89f><d83210b5b8072cc987bb1c318f31c89f>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 33 0 R
|
/Info 33 0 R
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
31 0 obj
|
31 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220446+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220446+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174830+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174830+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (BE services & infrastructure \204 validation map) /Trapped /False
|
/Subject (\(unspecified\)) /Title (BE services & infrastructure \204 validation map) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -385,7 +385,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<33842309a5332be4e933481fb5cc3564><33842309a5332be4e933481fb5cc3564>]
|
[<75fe7d8232594dda144e97ffd8f367f5><75fe7d8232594dda144e97ffd8f367f5>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 31 0 R
|
/Info 31 0 R
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
22 0 obj
|
22 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220446+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220446+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Stream dump \204 developer debug recording \(FR 0.1\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Stream dump \204 developer debug recording \(FR 0.1\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -277,7 +277,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<9df46fa4be55aef5251ba708945ef660><9df46fa4be55aef5251ba708945ef660>]
|
[<20faf14480cf6a5151269a1112e8dd23><20faf14480cf6a5151269a1112e8dd23>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 22 0 R
|
/Info 22 0 R
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
47 0 obj
|
47 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220446+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220446+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Android Cast \204 exposed services catalog) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Android Cast \204 exposed services catalog) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -589,7 +589,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<3bea3360dfff53d8101b105e2ed9c80c><3bea3360dfff53d8101b105e2ed9c80c>]
|
[<ab9efcddb9075779887ee065ecd85d8c><ab9efcddb9075779887ee065ecd85d8c>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 47 0 R
|
/Info 47 0 R
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
30 0 obj
|
30 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220446+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220446+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Android Cast \204 alpha release & QA) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Android Cast \204 alpha release & QA) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -377,7 +377,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<ef2f91cbe4ea30aef08962c29d279824><ef2f91cbe4ea30aef08962c29d279824>]
|
[<22fbb11b27f5f2572e8185d6911a1116><22fbb11b27f5f2572e8185d6911a1116>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 30 0 R
|
/Info 30 0 R
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
20 0 obj
|
20 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220446+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220446+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Alpha soak checklist \(LAN demo\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Alpha soak checklist \(LAN demo\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -240,7 +240,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<933980664bcbdbdfe6a12d9cb1786fa3><933980664bcbdbdfe6a12d9cb1786fa3>]
|
[<d13b107bc5a7f93c8642ebc1592ff911><d13b107bc5a7f93c8642ebc1592ff911>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 20 0 R
|
/Info 20 0 R
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
40 0 obj
|
40 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220446+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220446+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (AV quality enhancement \204 Q&A session notes) /Trapped /False
|
/Subject (\(unspecified\)) /Title (AV quality enhancement \204 Q&A session notes) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -512,7 +512,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<ad9c835cc704152d28b5b3f7ebff6df3><ad9c835cc704152d28b5b3f7ebff6df3>]
|
[<885b3d86c2d7fc8825759aeb11eaff9c><885b3d86c2d7fc8825759aeb11eaff9c>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 40 0 R
|
/Info 40 0 R
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
22 0 obj
|
22 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220446+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220446+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Android Cast build ecosystem) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Android Cast build ecosystem) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -278,7 +278,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<6cfcadb37be859dd22566ebb5a6a8025><6cfcadb37be859dd22566ebb5a6a8025>]
|
[<e642f50f6998474676e62cda30c69ab5><e642f50f6998474676e62cda30c69ab5>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 22 0 R
|
/Info 22 0 R
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
15 0 obj
|
15 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220446+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220446+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Commercial features \(developer-gated\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Commercial features \(developer-gated\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -192,7 +192,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<c8d6b37b537060a0dc93294feeb6bfd7><c8d6b37b537060a0dc93294feeb6bfd7>]
|
[<550d28e222cb18370f0095c31b168c50><550d28e222cb18370f0095c31b168c50>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 15 0 R
|
/Info 15 0 R
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
16 0 obj
|
16 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220446+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220446+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Anonymous crash reporter) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Anonymous crash reporter) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -198,7 +198,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<50b171b4584914c2210e49fce78dc2be><50b171b4584914c2210e49fce78dc2be>]
|
[<a6b6c31fc1c3334769ccc6ba2abbc535><a6b6c31fc1c3334769ccc6ba2abbc535>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 16 0 R
|
/Info 16 0 R
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
46 0 obj
|
46 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220446+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220446+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (URL shortener service \204 design review \(DR\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (URL shortener service \204 design review \(DR\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -553,7 +553,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<3a7c0ee4bbf3482849eeadc12de9b9c8><3a7c0ee4bbf3482849eeadc12de9b9c8>]
|
[<83c7ff4a46a2644aace09ed6f7e081cb><83c7ff4a46a2644aace09ed6f7e081cb>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 46 0 R
|
/Info 46 0 R
|
||||||
|
|||||||
@@ -498,7 +498,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
79 0 obj
|
79 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220446+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220446+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174831+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174831+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Platform scaling and multi-environment architecture \204 design review \(DR\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Platform scaling and multi-environment architecture \204 design review \(DR\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -933,7 +933,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<cc552ef08a2fe35d84129cecaf25d843><cc552ef08a2fe35d84129cecaf25d843>]
|
[<7d99cda1a76db27f24dc235d81baf7e5><7d99cda1a76db27f24dc235d81baf7e5>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 79 0 R
|
/Info 79 0 R
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
51 0 obj
|
51 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220453+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220453+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174832+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174832+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (RSSH routed egress \(dev-only\) \204 design review \(DR\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (RSSH routed egress \(dev-only\) \204 design review \(DR\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -663,7 +663,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<c1d94c835c02f8ed54a6427788bba047><c1d94c835c02f8ed54a6427788bba047>]
|
[<ed6e2cbfecdbe9d49d63d210ed176bb8><ed6e2cbfecdbe9d49d63d210ed176bb8>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 51 0 R
|
/Info 51 0 R
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
51 0 obj
|
51 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220459+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220459+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174832+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174832+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Codec2 / ultra-low-bandwidth voice \204 design review \(DR\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Codec2 / ultra-low-bandwidth voice \204 design review \(DR\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -657,7 +657,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<cb8942224039a3f8bf18c924200f4ad4><cb8942224039a3f8bf18c924200f4ad4>]
|
[<c52c2dec229f7f4121897008cb63143d><c52c2dec229f7f4121897008cb63143d>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 51 0 R
|
/Info 51 0 R
|
||||||
|
|||||||
681
docs/DRs/20260618_repos_reorganizing.md
Normal file
681
docs/DRs/20260618_repos_reorganizing.md
Normal file
@@ -0,0 +1,681 @@
|
|||||||
|
# Repository and microservice reorganization — design review (DR)
|
||||||
|
|
||||||
|
<!-- doc-meta:start -->
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| Author | Anton Afanasyeu |
|
||||||
|
| Revision | R0 |
|
||||||
|
| Creation date | 2026-06-18 |
|
||||||
|
| Last modification date | 2026-06-18 |
|
||||||
|
| Co-authored | Cursor Agent (project assistant) |
|
||||||
|
| Severity | high |
|
||||||
|
| State | approved for planning |
|
||||||
|
| Document type | DR |
|
||||||
|
| Pre-requisite to | Gitea org `ac` repo creation; post-alpha full MS split (alpha may stay on monolith until Step 8+) |
|
||||||
|
<!-- doc-meta:end -->
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Document type:** DR (Design Review)
|
||||||
|
**Source draft:** [docs/drafts/20260618_repos_reorganizing.txt](../drafts/20260618_repos_reorganizing.txt)
|
||||||
|
**PDF:** [20260618_repos_reorganizing.pdf](20260618_repos_reorganizing.pdf) · Regenerate: `bash scripts/build-all-docs-pdf.sh`
|
||||||
|
**Status:** PO-approved (2026-06-18) — **implementation phased**; monolith remains deployable until each step completes
|
||||||
|
**Scope:** Split `git://f0xx.org/android_cast` monolith into Gitea org **`ac`** — full microservice end-state, VM-first cloud path
|
||||||
|
|
||||||
|
**Related:** [INFRA.md](../INFRA.md) · [BUILD_DEPLOY.md](../BUILD_DEPLOY.md) · [specs/20100612_1_scaling.md](../specs/20100612_1_scaling.md) · [orchestration/sim/cluster0/ARCHITECTURE.md](../../orchestration/sim/cluster0/ARCHITECTURE.md) · [examples/crash_reporter/backend/scripts/gitea/README.md](../../examples/crash_reporter/backend/scripts/gitea/README.md)
|
||||||
|
|
||||||
|
**Documentation index:** [README.md](../README.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
- [1. Executive summary](#1-executive-summary)
|
||||||
|
- [2. Problem statement](#2-problem-statement)
|
||||||
|
- [3. PO decisions (locked)](#3-po-decisions-locked)
|
||||||
|
- [4. Current state](#4-current-state)
|
||||||
|
- [5. Target architecture](#5-target-architecture)
|
||||||
|
- [6. Gitea repo catalog](#6-gitea-repo-catalog)
|
||||||
|
- [7. Public URL map](#7-public-url-map)
|
||||||
|
- [8. Dependency graphs](#8-dependency-graphs)
|
||||||
|
- [9. Monolith conversion steps](#9-monolith-conversion-steps)
|
||||||
|
- [10. Microservice vs monolith comparison](#10-microservice-vs-monolith-comparison)
|
||||||
|
- [11. Infrastructure and cloud](#11-infrastructure-and-cloud)
|
||||||
|
- [12. ac-workspace and OTA versioning](#12-ac-workspace-and-ota-versioning)
|
||||||
|
- [13. Risks and mitigations](#13-risks-and-mitigations)
|
||||||
|
- [14. Changelog](#14-changelog)
|
||||||
|
<!-- /toc -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Executive summary
|
||||||
|
|
||||||
|
**Question:** How should the androidcast monolith (`git://f0xx.org/android_cast`) be split into independent repos and deployable microservices?
|
||||||
|
|
||||||
|
**Short answer:** Create Gitea org **`ac`** with **platform libraries**, **microservice APIs**, **thin backend UIs**, **client repos**, and **`ac-deploy`** (submodules **`ac-scripts`**). End-state is **full microservice** architecture. Delivery is **strictly ordered** — **[§9 Monolith conversion steps](#9-monolith-conversion-steps)** — with **`ac-ms-identity` as the gate** before any domain service split.
|
||||||
|
|
||||||
|
**DR decision (R0):**
|
||||||
|
|
||||||
|
| Item | Decision |
|
||||||
|
|------|----------|
|
||||||
|
| **Gitea org** | **`ac`** — `git://f0xx.org/ac/<repo>` |
|
||||||
|
| **Architecture** | Full microservice end-state; phased migration from monolith |
|
||||||
|
| **URL prefix** | **`/app/androidcast_project/`** (full path, locked) |
|
||||||
|
| **Path-per-service** | `/issues/`, `/tickets/`, `/access/`, etc. — replace `/crashes/?view=` |
|
||||||
|
| **Scripts** | **`ac-scripts`** separate; **`ac-deploy`** consumes it (submodule or PATH) |
|
||||||
|
| **Workspace** | Optional **`ac-workspace`** — selective clone only; **not** build-all-in-one |
|
||||||
|
| **Identity gate** | **Step 6** (`ac-ms-identity`) before domain MS extraction |
|
||||||
|
| **Cloud** | VM lift-and-shift first; K8s optional later |
|
||||||
|
| **Alpha** | Monolith may run in prod until Step 8+; no forced big-bang |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Problem statement
|
||||||
|
|
||||||
|
The monolith bundles Android app, PHP backend (crashes, tickets, graphs, RBAC, remote access), builder, hub, orchestration, docs, and one submodule (url-shortener). This blocks:
|
||||||
|
|
||||||
|
- Independent CI/CD and rollback per service
|
||||||
|
- Clear ownership and OpenAPI contracts
|
||||||
|
- Mapping repos to VM roles ([scaling SPEC §6.3](../specs/20100612_1_scaling.md))
|
||||||
|
- Product URLs such as [issues](https://apps.f0xx.org/app/androidcast_project/crashes/?view=reports) vs [tickets](https://apps.f0xx.org/app/androidcast_project/crashes/?view=tickets) under distinct paths
|
||||||
|
|
||||||
|
Today’s coupling: **one MariaDB**, **one session cookie** (`ac_crash_sess`, path `/app/androidcast_project`), **shared Auth.php** across consoles ([BUILD_DEPLOY.md](../BUILD_DEPLOY.md)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. PO decisions (locked)
|
||||||
|
|
||||||
|
| # | Topic | Decision |
|
||||||
|
|---|-------|----------|
|
||||||
|
| 1 | Gitea org | **`ac`** |
|
||||||
|
| 2 | Architecture | **Full microservice** (phased delivery) |
|
||||||
|
| 3 | ac-scripts | Separate repo; **ac-deploy utilizes ac-scripts** |
|
||||||
|
| 4 | Public URLs | Path-per-service under **`/app/androidcast_project/`** |
|
||||||
|
| 5 | ac-workspace | Optional manifest; **not** unified build; supports multi-repo OTA |
|
||||||
|
| 6 | Cloud | VM lift-and-shift; no K8s requirement at R0 |
|
||||||
|
| 7 | Identity gate | **Approved** — identity MS before other MS splits |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Current state
|
||||||
|
|
||||||
|
### 4.1 Monolith map
|
||||||
|
|
||||||
|
| Monolith path | Future repo(s) | Notes |
|
||||||
|
|---------------|----------------|-------|
|
||||||
|
| `app/`, `ndk/`, `gradle/` | ac-mobile-android | third-party submodules |
|
||||||
|
| `desktop/session-studio/` | ac-session-studio | Low coupling |
|
||||||
|
| `docs/` | ac-docs | |
|
||||||
|
| `scripts/` | ac-scripts | |
|
||||||
|
| `orchestration/` | ac-deploy | cluster0, docker |
|
||||||
|
| `examples/crash_reporter/backend/` | ac-ms-* + ac-be-* | **32 PHP classes**, 24 APIs |
|
||||||
|
| `examples/build_console/` | ac-ms-build, ac-be-builder | Same DB `users` |
|
||||||
|
| `examples/app_hub/` | ac-be-hub | Loads `/crashes/assets/` today |
|
||||||
|
| `backend/url-shortener/` | ac-ms-url-shortener | Existing submodule |
|
||||||
|
|
||||||
|
### 4.2 What is not a git repo
|
||||||
|
|
||||||
|
- **Gitea** (infra on BE; scripts in ac-deploy)
|
||||||
|
- **MariaDB**, **Janus** (:8089)
|
||||||
|
- `examples/gentoo-portage-experimental/` (exclude or separate lab repo)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Target architecture
|
||||||
|
|
||||||
|
```text
|
||||||
|
Platform (Composer / static — not runtime MS)
|
||||||
|
ac-platform-php, ac-platform-db, ac-platform-web, ac-scripts, ac-docs
|
||||||
|
|
||||||
|
Infrastructure
|
||||||
|
ac-deploy submodules ac-scripts; docker, cluster0, nginx
|
||||||
|
ac-platform-edge nginx BFF: routes + session cookie domain
|
||||||
|
ac-workspace optional clone manifest (no unified build)
|
||||||
|
|
||||||
|
Clients
|
||||||
|
ac-mobile-android, ac-mobile-ios (future), ac-session-studio
|
||||||
|
|
||||||
|
Microservices (OpenAPI)
|
||||||
|
ac-ms-identity → ac-ms-rbac → ac-ms-devices
|
||||||
|
ac-ms-issues, ac-ms-tickets, ac-ms-graphs, ac-ms-remote-access
|
||||||
|
ac-ms-url-shortener, ac-ms-build, ac-ms-ota, ac-ms-notifications (later)
|
||||||
|
|
||||||
|
Backend UI (thin → MS APIs)
|
||||||
|
ac-be-hub, ac-be-issues, ac-be-tickets, ac-be-graphs,
|
||||||
|
ac-be-remote-access, ac-be-access, ac-be-builder, ac-be-auth
|
||||||
|
```
|
||||||
|
|
||||||
|
**Consolidations vs initial PO sketch:**
|
||||||
|
|
||||||
|
| Initial name | R0 decision |
|
||||||
|
|--------------|-------------|
|
||||||
|
| ac-be-console (monolith) | Dissolved into ac-be-issues … ac-be-access |
|
||||||
|
| ac-ms-tracker | **ac-ms-issues** + **ac-ms-tickets** |
|
||||||
|
| ac-ms-vpn-rssh + ac-ms-vpn-wireguard | **ac-ms-remote-access** (single control plane) |
|
||||||
|
| ac-ms-analytics | **ac-ms-graphs** (+ BI later) |
|
||||||
|
| ac-ms-orchestration | **ac-ms-build** + **ac-deploy** |
|
||||||
|
| ac-ms-vcs | Not a repo — Gitea infra |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Gitea repo catalog
|
||||||
|
|
||||||
|
Canonical: **`git://f0xx.org/ac/<repo>`**
|
||||||
|
HTTPS (Gitea UI): **`https://apps.f0xx.org/app/androidcast_project/git/ac/<repo>.git`**
|
||||||
|
Legacy: **`git://f0xx.org/android_cast`** → read-only mirror until retired.
|
||||||
|
|
||||||
|
| ID | Repo | Git URL | Role |
|
||||||
|
|----|------|---------|------|
|
||||||
|
| W0 | ac-workspace | `git://f0xx.org/ac/ac-workspace` | Optional submodule manifest |
|
||||||
|
| P0 | ac-platform-php | `git://f0xx.org/ac/ac-platform-php` | Composer: auth client, PDO, HTTP |
|
||||||
|
| P1 | ac-platform-db | `git://f0xx.org/ac/ac-platform-db` | SQL migrations |
|
||||||
|
| P2 | ac-platform-web | `git://f0xx.org/ac/ac-platform-web` | Shared CSS/JS/theme |
|
||||||
|
| P3 | ac-platform-edge | `git://f0xx.org/ac/ac-platform-edge` | nginx routes + proxy |
|
||||||
|
| P4 | ac-scripts | `git://f0xx.org/ac/ac-scripts` | CI, OTA, native codecs, PDF |
|
||||||
|
| P5 | ac-docs | `git://f0xx.org/ac/ac-docs` | Documentation |
|
||||||
|
| D0 | ac-deploy | `git://f0xx.org/ac/ac-deploy` | docker, cluster0; **uses P4** |
|
||||||
|
| C0 | ac-mobile-android | `git://f0xx.org/ac/ac-mobile-android` | Android + ndk + third-party |
|
||||||
|
| C1 | ac-mobile-ios | `git://f0xx.org/ac/ac-mobile-ios` | Future iOS |
|
||||||
|
| C2 | ac-session-studio | `git://f0xx.org/ac/ac-session-studio` | Desktop analyzer |
|
||||||
|
| S0 | ac-ms-template | `git://f0xx.org/ac/ac-ms-template` | New MS cookiecutter |
|
||||||
|
| S1 | ac-ms-identity | `git://f0xx.org/ac/ac-ms-identity` | Users, login, 2FA, sessions |
|
||||||
|
| S2 | ac-ms-rbac | `git://f0xx.org/ac/ac-ms-rbac` | Companies, privileges |
|
||||||
|
| S3 | ac-ms-devices | `git://f0xx.org/ac/ac-ms-devices` | Device registry |
|
||||||
|
| S4 | ac-ms-issues | `git://f0xx.org/ac/ac-ms-issues` | Crash/report ingest |
|
||||||
|
| S5 | ac-ms-tickets | `git://f0xx.org/ac/ac-ms-tickets` | Ticket workflow |
|
||||||
|
| S6 | ac-ms-graphs | `git://f0xx.org/ac/ac-ms-graphs` | Graph ingest/query |
|
||||||
|
| S7 | ac-ms-remote-access | `git://f0xx.org/ac/ac-ms-remote-access` | WG + RSSH API |
|
||||||
|
| S8 | ac-ms-url-shortener | `git://f0xx.org/ac/ac-ms-url-shortener` | Short links API |
|
||||||
|
| S9 | ac-ms-build | `git://f0xx.org/ac/ac-ms-build` | Build worker API |
|
||||||
|
| S10 | ac-ms-ota | `git://f0xx.org/ac/ac-ms-ota` | Channel manifests |
|
||||||
|
| S11 | ac-ms-notifications | `git://f0xx.org/ac/ac-ms-notifications` | SMTP (post-alpha) |
|
||||||
|
| B0 | ac-be-hub | `git://f0xx.org/ac/ac-be-hub` | Landing |
|
||||||
|
| B1 | ac-be-issues | `git://f0xx.org/ac/ac-be-issues` | Issues UI |
|
||||||
|
| B2 | ac-be-tickets | `git://f0xx.org/ac/ac-be-tickets` | Tickets UI |
|
||||||
|
| B3 | ac-be-graphs | `git://f0xx.org/ac/ac-be-graphs` | Graphs UI |
|
||||||
|
| B4 | ac-be-remote-access | `git://f0xx.org/ac/ac-be-remote-access` | RA admin UI |
|
||||||
|
| B5 | ac-be-access | `git://f0xx.org/ac/ac-be-access` | RBAC admin UI |
|
||||||
|
| B6 | ac-be-builder | `git://f0xx.org/ac/ac-be-builder` | Builder UI |
|
||||||
|
| B7 | ac-be-auth | `git://f0xx.org/ac/ac-be-auth` | Login/register shell |
|
||||||
|
| F1 | ac-ms-sfu-signaling | `git://f0xx.org/ac/ac-ms-sfu-signaling` | Future F1 |
|
||||||
|
| F2 | ac-ms-media-transcode | `git://f0xx.org/ac/ac-ms-media-transcode` | Future VOD |
|
||||||
|
|
||||||
|
Third-party codecs remain **submodules of C0** (upstream URLs); Gitea mirrors under `ac/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Public URL map
|
||||||
|
|
||||||
|
**Base prefix (locked):** `https://apps.f0xx.org/app/androidcast_project`
|
||||||
|
|
||||||
|
| Today | Target URL |
|
||||||
|
|-------|------------|
|
||||||
|
| `…/crashes/?view=reports` | `…/issues/` |
|
||||||
|
| `…/crashes/?view=report&id=N` | `…/issues/N` |
|
||||||
|
| `…/crashes/?view=tickets` | `…/tickets/` |
|
||||||
|
| `…/crashes/?view=ticket&id=N` | `…/tickets/N` |
|
||||||
|
| `…/crashes/?view=rbac` | `…/access/` |
|
||||||
|
| `…/crashes/?view=remote_access` | `…/remote-access/` |
|
||||||
|
| `…/crashes/?view=short_links` | `…/short-links/` |
|
||||||
|
| `…/crashes/api/upload.php` | `…/issues/api/upload` |
|
||||||
|
| `…/graphs/` | `…/graphs/` (unchanged) |
|
||||||
|
| `…/build/` | `…/build/` (unchanged) |
|
||||||
|
| `…/` (hub) | `…/` (unchanged) |
|
||||||
|
| `…/git/` | `…/git/` (Gitea) |
|
||||||
|
| `/v0/ota/` | `/v0/ota/` (unchanged) |
|
||||||
|
|
||||||
|
**Cookie path:** remain `/app/androidcast_project` at **ac-platform-edge** until SSO tokens replace shared PHP session.
|
||||||
|
|
||||||
|
**Migration:** 301 redirects from old `?view=` URLs for ≥ one release; update `BackendEndpoints.java`, `CrashSettings.java`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Dependency graphs
|
||||||
|
|
||||||
|
### 8.1 End-to-end — clients, edge, services
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph clients [Clients]
|
||||||
|
AND[ac-mobile-android]
|
||||||
|
IOS[ac-mobile-ios]
|
||||||
|
HUB[ac-be-hub]
|
||||||
|
SS[ac-session-studio]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph edge [Edge]
|
||||||
|
EDGE[ac-platform-edge]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph core [Core MS — order matters]
|
||||||
|
ID[ac-ms-identity]
|
||||||
|
RB[ac-ms-rbac]
|
||||||
|
DV[ac-ms-devices]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph domain [Domain MS]
|
||||||
|
IS[ac-ms-issues]
|
||||||
|
TK[ac-ms-tickets]
|
||||||
|
GR[ac-ms-graphs]
|
||||||
|
RA[ac-ms-remote-access]
|
||||||
|
UL[ac-ms-url-shortener]
|
||||||
|
BL[ac-ms-build]
|
||||||
|
OT[ac-ms-ota]
|
||||||
|
end
|
||||||
|
|
||||||
|
AND --> EDGE
|
||||||
|
IOS --> EDGE
|
||||||
|
HUB --> EDGE
|
||||||
|
SS -.->|offline files| AND
|
||||||
|
EDGE --> ID
|
||||||
|
EDGE --> IS & TK & GR & RA & UL & BL & OT & RB
|
||||||
|
|
||||||
|
ID --> RB
|
||||||
|
RB --> DV
|
||||||
|
IS --> DV & RB
|
||||||
|
TK --> RB
|
||||||
|
TK -.-> IS
|
||||||
|
GR --> ID
|
||||||
|
RA --> RB & DV
|
||||||
|
RA -.-> IS
|
||||||
|
UL --> RB
|
||||||
|
BL --> ID
|
||||||
|
OT --> BL
|
||||||
|
OT --> AND & IOS
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Backend UI → microservice (requires)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
B7[ac-be-auth] --> S1[ac-ms-identity]
|
||||||
|
B5[ac-be-access] --> S2[ac-ms-rbac]
|
||||||
|
B1[ac-be-issues] --> S4[ac-ms-issues]
|
||||||
|
B2[ac-be-tickets] --> S5[ac-ms-tickets]
|
||||||
|
B3[ac-be-graphs] --> S6[ac-ms-graphs]
|
||||||
|
B4[ac-be-remote-access] --> S7[ac-ms-remote-access]
|
||||||
|
B6[ac-be-builder] --> S9[ac-ms-build]
|
||||||
|
B0[ac-be-hub] --> S1
|
||||||
|
B0 --> P2[ac-platform-web]
|
||||||
|
|
||||||
|
S4 --> S3[ac-ms-devices]
|
||||||
|
S4 --> S2
|
||||||
|
S5 --> S2
|
||||||
|
S7 --> S2 & S3
|
||||||
|
```
|
||||||
|
|
||||||
|
Every **B*** and **S*** also **requires** **P0** (ac-platform-php) and **P1** (ac-platform-db) via Composer — omitted from diagram for clarity.
|
||||||
|
|
||||||
|
### 8.3 Platform and deploy layer
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
D0[ac-deploy] --> P4[ac-scripts]
|
||||||
|
D0 --> P3[ac-platform-edge]
|
||||||
|
D0 --> P1[ac-platform-db]
|
||||||
|
|
||||||
|
P3 -->|routes| S1 & S4 & S5 & S6 & S7 & S8 & S9 & S10
|
||||||
|
P2[ac-platform-web] -->|static| B0 & B1 & B2
|
||||||
|
|
||||||
|
S1 & S2 & S3 & S4 & S5 --> P0[ac-platform-php]
|
||||||
|
S6 & S7 & S8 & S9 & S10 --> P0
|
||||||
|
B0 & B1 & B2 & B3 & B4 & B5 & B6 & B7 --> P0
|
||||||
|
|
||||||
|
P1 --> DB[(MariaDB / managed DB)]
|
||||||
|
S11[ac-ms-notifications] -.->|mail| S1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 Service dependency matrix
|
||||||
|
|
||||||
|
| Service | Requires (hard) | Requires (soft) | Required by |
|
||||||
|
|---------|-----------------|-----------------|-------------|
|
||||||
|
| ac-platform-db | — | — | All MS, migration jobs |
|
||||||
|
| ac-platform-php | ac-platform-db | — | All MS, all BE UI |
|
||||||
|
| ac-platform-web | — | — | ac-be-hub, all BE UI |
|
||||||
|
| ac-platform-edge | ac-deploy nginx templates | all MS upstream addrs | All public HTTP |
|
||||||
|
| ac-ms-identity | platform-db, platform-php | ac-ms-notifications | All MS, all BE UI, ac-ms-build |
|
||||||
|
| ac-ms-rbac | ac-ms-identity | — | devices, issues, tickets, RA, url-shortener |
|
||||||
|
| ac-ms-devices | ac-ms-rbac | ac-ms-identity | ac-ms-issues, ac-ms-remote-access |
|
||||||
|
| ac-ms-issues | ac-ms-rbac, ac-ms-devices | — | ac-be-issues, mobile upload |
|
||||||
|
| ac-ms-tickets | ac-ms-rbac | ac-ms-issues | ac-be-tickets |
|
||||||
|
| ac-ms-graphs | ac-ms-identity | — | ac-be-graphs, mobile |
|
||||||
|
| ac-ms-remote-access | ac-ms-rbac, ac-ms-devices | ac-ms-issues | ac-be-remote-access, mobile |
|
||||||
|
| ac-ms-url-shortener | ac-ms-rbac | — | hub/admin UI |
|
||||||
|
| ac-ms-build | ac-ms-identity | Gitea infra | ac-be-builder, ac-ms-ota |
|
||||||
|
| ac-ms-ota | ac-ms-build | C0, C1 SHAs | mobile OTA clients |
|
||||||
|
| ac-mobile-android | ac-ms-issues, ac-ms-graphs, ac-ms-remote-access APIs | ac-ms-ota | — |
|
||||||
|
|
||||||
|
### 8.5 Repo creation waves (Gitea)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
W1[Wave 1: docs scripts deploy session-studio url-shortener] --> W2[Wave 2: platform-php platform-db platform-web]
|
||||||
|
W2 --> W3[Wave 3: ms-identity]
|
||||||
|
W3 --> W4[Wave 4: ms-rbac ms-devices]
|
||||||
|
W4 --> W5[Wave 5: ms-issues ms-tickets + be UI]
|
||||||
|
W5 --> W6[Wave 6: ms-graphs ms-remote-access ms-build + UI]
|
||||||
|
W6 --> W7[Wave 7: platform-edge mobile-android ms-ota]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Monolith conversion steps
|
||||||
|
|
||||||
|
Ordered steps for converting the monolith. **Do not skip gates.** Each step lists repos created/moved, monolith paths affected, and verification.
|
||||||
|
|
||||||
|
### Step 1 — Gitea org and mirrors (no code move)
|
||||||
|
|
||||||
|
**Goal:** Org **`ac`** exists; legacy mirror preserved.
|
||||||
|
|
||||||
|
| Action | Detail |
|
||||||
|
|--------|--------|
|
||||||
|
| Create org | `ac` on f0xx.org Gitea |
|
||||||
|
| Mirror | `android_cast` → read-only; plan `ac-workspace` or retire later |
|
||||||
|
| Update docs | Pointer in INFRA.md, AGENTS.md |
|
||||||
|
|
||||||
|
**Verify:** `git ls-remote git://f0xx.org/ac/ac-docs` (after Step 2) ; legacy push still works.
|
||||||
|
|
||||||
|
**Requires:** nothing
|
||||||
|
**Enables:** all following steps
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 2 — Extract zero-coupling repos
|
||||||
|
|
||||||
|
**Goal:** Repos with no runtime dependency on PHP monolith.
|
||||||
|
|
||||||
|
| Repo | From monolith | CI |
|
||||||
|
|------|---------------|-----|
|
||||||
|
| ac-docs | `docs/` | PDF build |
|
||||||
|
| ac-scripts | `scripts/` | lint/smoke |
|
||||||
|
| ac-session-studio | `desktop/session-studio/` | unit tests |
|
||||||
|
| ac-ms-url-shortener | `backend/url-shortener/` | PHPUnit (change remote URL only if already split) |
|
||||||
|
| ac-deploy | `orchestration/` + nginx seeds | docker compose smoke |
|
||||||
|
|
||||||
|
**ac-deploy** submodules **ac-scripts** at pinned SHA.
|
||||||
|
|
||||||
|
**Verify:** Each repo builds independently; monolith submodule paths updated OR mirror dual-push during transition.
|
||||||
|
|
||||||
|
**Requires:** Step 1
|
||||||
|
**Enables:** Steps 3–4
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 3 — Platform libraries (foundation)
|
||||||
|
|
||||||
|
**Goal:** Shared code leaves monolith as Composer packages — **no MS split yet**.
|
||||||
|
|
||||||
|
| Repo | Extract from |
|
||||||
|
|------|--------------|
|
||||||
|
| ac-platform-db | `sql/`, migrations from crash_reporter + url-shortener |
|
||||||
|
| ac-platform-php | Auth*, Database, Rbac helpers, bootstrap patterns, `shared_session.php` |
|
||||||
|
| ac-platform-web | `crashes/assets/`, shared theme referenced by hub |
|
||||||
|
|
||||||
|
Monolith **requires** packages via Composer; behavior unchanged.
|
||||||
|
|
||||||
|
**Verify:** Monolith tests green; single login still works across hub/crashes/build.
|
||||||
|
|
||||||
|
**Requires:** Step 2
|
||||||
|
**Enables:** Step 4 (identity)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 4 — Microservice template and OpenAPI baseline
|
||||||
|
|
||||||
|
**Goal:** Cookiecutter + contract layout for all MS.
|
||||||
|
|
||||||
|
| Repo | Action |
|
||||||
|
|------|--------|
|
||||||
|
| ac-ms-template | Copy url-shortener + platform-php wiring |
|
||||||
|
| ac-docs | Add `openapi/` stubs per service |
|
||||||
|
|
||||||
|
**Verify:** Scaffold new empty MS from template; deploy locally via ac-deploy.
|
||||||
|
|
||||||
|
**Requires:** Step 3
|
||||||
|
**Enables:** Step 5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 5 — Edge proxy (routing shell)
|
||||||
|
|
||||||
|
**Goal:** **ac-platform-edge** nginx config routes paths; still proxies to **monolith** PHP until MS exist.
|
||||||
|
|
||||||
|
| Action | Detail |
|
||||||
|
|--------|--------|
|
||||||
|
| Deploy edge config | Map `/issues/` → monolith `?view=reports` internally (temporary) |
|
||||||
|
| Cookie | Single path `/app/androidcast_project` |
|
||||||
|
|
||||||
|
**Verify:** `curl -I` new paths return 200 via rewrite; old URLs still work.
|
||||||
|
|
||||||
|
**Requires:** Step 2 (ac-deploy)
|
||||||
|
**Enables:** Step 10 URL cutover
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 6 — Identity microservice (GATE)
|
||||||
|
|
||||||
|
**Goal:** **ac-ms-identity** owns users, login, register, 2FA, session issuance.
|
||||||
|
|
||||||
|
| Repo | From monolith |
|
||||||
|
|------|---------------|
|
||||||
|
| ac-ms-identity | Auth*, UserRepository, AuthTotp*, AuthRegistration*, … |
|
||||||
|
| ac-be-auth | login/register views (thin) |
|
||||||
|
|
||||||
|
Monolith/builder **call identity** for auth (HTTP or shared session bridge during transition).
|
||||||
|
|
||||||
|
**Verify:** Login at `/app/androidcast_project/` + builder; sessions valid; `./scripts/test_rbac_api.sh` still passes against bridged auth.
|
||||||
|
|
||||||
|
**Requires:** Steps 3, 4
|
||||||
|
**Enables:** Steps 7, 9, 11 — **nothing else splits before this**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 7 — RBAC and devices microservices
|
||||||
|
|
||||||
|
**Goal:** Company scope and device registry are independent APIs.
|
||||||
|
|
||||||
|
| Repo | From monolith |
|
||||||
|
|------|---------------|
|
||||||
|
| ac-ms-rbac | Rbac*, RbacAdmin*, companies API |
|
||||||
|
| ac-ms-devices | DeviceRepository |
|
||||||
|
|
||||||
|
**Verify:** Upload still registers device; RBAC panel works via APIs.
|
||||||
|
|
||||||
|
**Requires:** Step 6
|
||||||
|
**Enables:** Steps 8, 9
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 8 — Issues and tickets (domain core)
|
||||||
|
|
||||||
|
**Goal:** Split PO’s primary URL surfaces.
|
||||||
|
|
||||||
|
| Repo | From monolith |
|
||||||
|
|------|---------------|
|
||||||
|
| ac-ms-issues | upload, Report*, Tag*, reports API |
|
||||||
|
| ac-ms-tickets | Ticket*, attachments |
|
||||||
|
| ac-be-issues | reports views + JS |
|
||||||
|
| ac-be-tickets | tickets views + JS |
|
||||||
|
|
||||||
|
**Edge** routes `/issues/` → S4/B1, `/tickets/` → S5/B2.
|
||||||
|
|
||||||
|
**Verify:** Mobile upload to new URL; web issue/ticket lists; E2E ticket workflow.
|
||||||
|
|
||||||
|
**Requires:** Step 7
|
||||||
|
**Enables:** Step 9 (RA links to issues)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 9 — Graphs and remote access
|
||||||
|
|
||||||
|
| Repo | From monolith |
|
||||||
|
|------|---------------|
|
||||||
|
| ac-ms-graphs | GraphRepository, graph_* API |
|
||||||
|
| ac-ms-remote-access | RemoteAccess*, WireGuard*, Rssh* |
|
||||||
|
| ac-be-graphs | graphs UI |
|
||||||
|
| ac-be-remote-access | remote_access view |
|
||||||
|
|
||||||
|
**Verify:** `BackendEndpoints` graph URL; RA E2E (`ra_e2e_cli.sh`); mobile RA API.
|
||||||
|
|
||||||
|
**Requires:** Steps 7, 8 (RA → devices, issue links)
|
||||||
|
**Enables:** Step 11
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 10 — Hub, access UI, URL migration
|
||||||
|
|
||||||
|
| Repo | Action |
|
||||||
|
|------|--------|
|
||||||
|
| ac-be-hub | Extract app_hub; use **ac-platform-web** assets |
|
||||||
|
| ac-be-access | RBAC UI → calls ac-ms-rbac |
|
||||||
|
| ac-platform-edge | Enable **301** from `/crashes/?view=*` to new paths |
|
||||||
|
|
||||||
|
**Verify:** Hub cards link to `/issues/`, `/tickets/`; no broken CSS.
|
||||||
|
|
||||||
|
**Requires:** Steps 5, 8, 9
|
||||||
|
**Enables:** Step 11
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 11 — Build and OTA pipeline
|
||||||
|
|
||||||
|
| Repo | From monolith |
|
||||||
|
|------|---------------|
|
||||||
|
| ac-ms-build | BuildRunner, build APIs |
|
||||||
|
| ac-be-builder | build_console UI |
|
||||||
|
| ac-ms-ota | OTA manifest assembly |
|
||||||
|
| ac-mobile-android | `app/`, `ndk/`, gradle (extract from monolith) |
|
||||||
|
|
||||||
|
**Verify:** Builder triggers APK; OTA channel publishes; manifest lists android (+ ios when C1 exists).
|
||||||
|
|
||||||
|
**Requires:** Step 6 (identity for builder users)
|
||||||
|
**Enables:** Step 12
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 12 — Optional workspace and monolith retirement
|
||||||
|
|
||||||
|
| Action | Detail |
|
||||||
|
|--------|--------|
|
||||||
|
| ac-workspace | Publish optional `.gitmodules` manifest |
|
||||||
|
| Retire | Stop commits to `android_cast` monolith; archive mirror |
|
||||||
|
| Gitea migrate | Update scripts for org `ac` ([gitea README](../../examples/crash_reporter/backend/scripts/gitea/README.md)) |
|
||||||
|
|
||||||
|
**Verify:** Fresh clone via ac-workspace or individual repos; prod deploy from ac-deploy only.
|
||||||
|
|
||||||
|
**Requires:** Steps 1–11 complete for services in scope
|
||||||
|
**Enables:** cloud VM scaling, future F1/F2 MS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 13 — Notifications and cloud hardening (post-alpha)
|
||||||
|
|
||||||
|
| Repo | Action |
|
||||||
|
|------|--------|
|
||||||
|
| ac-ms-notifications | Extract AuthMailer; wire 2.x mail when unblocked |
|
||||||
|
| ac-deploy | Managed DB DSN templates; cluster0 → cloud VM roles |
|
||||||
|
|
||||||
|
**Requires:** Step 6; mail infra (developer)
|
||||||
|
**Enables:** full 2FA mail E2E
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Conversion step summary (quick reference)
|
||||||
|
|
||||||
|
| Step | Name | Gate |
|
||||||
|
|------|------|------|
|
||||||
|
| 1 | Gitea org `ac` | — |
|
||||||
|
| 2 | docs, scripts, deploy, session-studio, url-shortener | — |
|
||||||
|
| 3 | platform-php, platform-db, platform-web | — |
|
||||||
|
| 4 | ms-template, OpenAPI | — |
|
||||||
|
| 5 | platform-edge (shell) | — |
|
||||||
|
| 6 | **ms-identity**, be-auth | **GATE** |
|
||||||
|
| 7 | ms-rbac, ms-devices | after 6 |
|
||||||
|
| 8 | ms-issues, ms-tickets, be-issues, be-tickets | after 7 |
|
||||||
|
| 9 | ms-graphs, ms-remote-access, be UI | after 7–8 |
|
||||||
|
| 10 | be-hub, be-access, URL 301 | after 8–9 |
|
||||||
|
| 11 | ms-build, ms-ota, mobile-android | after 6 |
|
||||||
|
| 12 | ac-workspace, retire monolith | after 1–11 |
|
||||||
|
| 13 | notifications, cloud | post-alpha |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Microservice vs monolith comparison
|
||||||
|
|
||||||
|
| Criterion | Full microservice (PO choice) | Modular monolith |
|
||||||
|
|-----------|------------------------------|------------------|
|
||||||
|
| Independent deploy | Yes — per MS/UI repo | Single PHP deploy |
|
||||||
|
| Failure isolation | Stronger | Shared FPM pool |
|
||||||
|
| Auth complexity | Needs identity MS + edge early | Works today |
|
||||||
|
| URL clarity | `/issues/`, `/tickets/` | `?view=` parameters |
|
||||||
|
| Ops load | More nginx, repos, logs | One sync path ([INFRA.md](../INFRA.md)) |
|
||||||
|
| Alpha risk | High if before Step 6 | Lower |
|
||||||
|
| Cloud mapping | 1:1 to VM roles | Lift entire tree |
|
||||||
|
| Rollback | Revert one repo SHA | Revert one commit |
|
||||||
|
|
||||||
|
PO choice is approved with **Step 6 gate** enforced in [§9](#9-monolith-conversion-steps).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Infrastructure and cloud
|
||||||
|
|
||||||
|
| Stage | Pattern |
|
||||||
|
|-------|---------|
|
||||||
|
| **Prod today** | FE TLS → BE :80 nginx + PHP-FPM + MariaDB |
|
||||||
|
| **cluster0 lab** | cast01–03; NFS config only; GTID DB ([ARCHITECTURE.md](../../orchestration/sim/cluster0/ARCHITECTURE.md)) |
|
||||||
|
| **Cloud phase A** | 4–6 VMs: be-web, be-data, be-ops, be-build ([scaling §6.3](../specs/20100612_1_scaling.md)) |
|
||||||
|
| **Cloud phase B** | Managed DB; DSN in secrets (not in git) |
|
||||||
|
| **Cloud phase C** | K8s optional — not required at R0 |
|
||||||
|
|
||||||
|
**Inter-service calls:** HTTP on private VLAN / localhost initially; OpenAPI in ac-docs. No service mesh at R0.
|
||||||
|
|
||||||
|
**ac-deploy** ships Ansible/docker-compose per VM role; submodules **ac-scripts**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. ac-workspace and OTA versioning
|
||||||
|
|
||||||
|
**ac-workspace** — optional; lists repo SHAs for a release train. **Not** a unified Gradle/PHP build.
|
||||||
|
|
||||||
|
**ac-ms-ota** manifest (same product version, per-platform SHAs):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channel": "staging",
|
||||||
|
"version": "00.02.00.00",
|
||||||
|
"artifacts": {
|
||||||
|
"android": { "repo": "ac-mobile-android", "git_sha": "abc123" },
|
||||||
|
"ios": { "repo": "ac-mobile-ios", "git_sha": "def456" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Risks and mitigations
|
||||||
|
|
||||||
|
| Risk | Mitigation |
|
||||||
|
|------|------------|
|
||||||
|
| Auth split breaks all consoles | Step 6 gate; session bridge during transition |
|
||||||
|
| Hub broken CSS | ac-platform-web before ac-be-hub (Step 3 before 10) |
|
||||||
|
| Mobile upload URL change | Parallel endpoints + `BackendEndpoints` normalization |
|
||||||
|
| DB schema drift | ac-platform-db single migration stream |
|
||||||
|
| Too many repos to bump | Composer semver for P0; not @common submodule diamond |
|
||||||
|
| Alpha blocked | Monolith stays prod until Step 8+ complete per service |
|
||||||
|
| PO BE learning curve | ac-ms-template, ac-deploy, OpenAPI, localhost HTTP |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Changelog
|
||||||
|
|
||||||
|
| Rev | Date | Change |
|
||||||
|
|-----|------|--------|
|
||||||
|
| R0 | 2026-06-18 | Initial DR from draft; PO locked org `ac`, full MS, URL prefix, identity gate; §9 conversion steps; dependency graphs |
|
||||||
807
docs/DRs/20260618_repos_reorganizing.pdf
Normal file
807
docs/DRs/20260618_repos_reorganizing.pdf
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -42,7 +42,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
7 0 obj
|
7 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220511+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220511+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174842+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174842+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Grafana vs Others GraphVis Pivot) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Grafana vs Others GraphVis Pivot) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -81,7 +81,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<10a588f5438fd4e86531a2b28eee10c1><10a588f5438fd4e86531a2b28eee10c1>]
|
[<bc6184b32d1f12a1bd28db177f8e8b1d><bc6184b32d1f12a1bd28db177f8e8b1d>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 7 0 R
|
/Info 7 0 R
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
30 0 obj
|
30 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220500+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220500+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174833+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174833+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Android Cast \204 production infrastructure) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Android Cast \204 production infrastructure) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -376,7 +376,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<535797484fc3ea7f6c0c9067cdb5b8d8><535797484fc3ea7f6c0c9067cdb5b8d8>]
|
[<62e6c98798332c0d70f94c87e8f28c95><62e6c98798332c0d70f94c87e8f28c95>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 30 0 R
|
/Info 30 0 R
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
23 0 obj
|
23 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220500+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220500+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174833+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174833+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Crash console \204 open ingest API \(design\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Crash console \204 open ingest API \(design\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -289,7 +289,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<f785af38a27e85b78bc868cfd8895ae6><f785af38a27e85b78bc868cfd8895ae6>]
|
[<40b0d4915320fbf245576f9c86c8a968><40b0d4915320fbf245576f9c86c8a968>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 23 0 R
|
/Info 23 0 R
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
24 0 obj
|
24 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174833+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174833+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Open tasks \204 dependency graph) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Open tasks \204 dependency graph) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -294,7 +294,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<6fade7d28070a04635a01aac9213cf6d><6fade7d28070a04635a01aac9213cf6d>]
|
[<4cca413c1249fe7757d7ab6f2c356e7a><4cca413c1249fe7757d7ab6f2c356e7a>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 24 0 R
|
/Info 24 0 R
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
27 0 obj
|
27 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Opus / Speex / stream protection \204 validation checklist \(task 3\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Opus / Speex / stream protection \204 validation checklist \(task 3\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -333,7 +333,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<09eae2d38beac43ddd775c1a746bf63e><09eae2d38beac43ddd775c1a746bf63e>]
|
[<57e4e0aca5ee192ac24df0f4d343b97f><57e4e0aca5ee192ac24df0f4d343b97f>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 27 0 R
|
/Info 27 0 R
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
23 0 obj
|
23 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Over-the-air \(OTA\) updates \204 deployment schema **v0**) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Over-the-air \(OTA\) updates \204 deployment schema **v0**) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -290,7 +290,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<82cd7398c6c125d2dd7bfdcdc3eec6fa><82cd7398c6c125d2dd7bfdcdc3eec6fa>]
|
[<676f27593046572b8cd4c389e7e4f7aa><676f27593046572b8cd4c389e7e4f7aa>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 23 0 R
|
/Info 23 0 R
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
20 0 obj
|
20 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Pro / OSS split \(cast-core + cast-pro AAR\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Pro / OSS split \(cast-core + cast-pro AAR\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -259,7 +259,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<25f000128476a42bea3f6be0b0c8670f><25f000128476a42bea3f6be0b0c8670f>]
|
[<bf3391a6afce2847368495673b246a93><bf3391a6afce2847368495673b246a93>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 20 0 R
|
/Info 20 0 R
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ Click a link to open a guide. Section links jump within the document (GitHub, Cu
|
|||||||
| [20100612_1_scaling.md](DRs/20100612_1_scaling.md) | |
|
| [20100612_1_scaling.md](DRs/20100612_1_scaling.md) | |
|
||||||
| [20260616_rssh_routed_egress.md](DRs/20260616_rssh_routed_egress.md) | RSSH routed egress (postponed). |
|
| [20260616_rssh_routed_egress.md](DRs/20260616_rssh_routed_egress.md) | RSSH routed egress (postponed). |
|
||||||
| [20260617_codec2_ulbw_voice.md](DRs/20260617_codec2_ulbw_voice.md) | Codec2 ULBW voice-only cast + SFU negotiation hooks. |
|
| [20260617_codec2_ulbw_voice.md](DRs/20260617_codec2_ulbw_voice.md) | Codec2 ULBW voice-only cast + SFU negotiation hooks. |
|
||||||
|
| [20260618_repos_reorganizing.md](DRs/20260618_repos_reorganizing.md) | Monolith → Gitea org `ac` full microservice split + migration steps. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
25 0 obj
|
25 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Android Cast \204 documentation index) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Android Cast \204 documentation index) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -253,17 +253,17 @@ GarnQ4`>s,'\rL!2j,Y2Gf7VIg8H)\3`Pd>,m?5I#W;ibrdKEq;-TkXak$(>48Fnu%%uk//)XH<EDN5A
|
|||||||
endobj
|
endobj
|
||||||
43 0 obj
|
43 0 obj
|
||||||
<<
|
<<
|
||||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3437
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3497
|
||||||
>>
|
>>
|
||||||
stream
|
stream
|
||||||
Gb!l!D0)I3')nJ0d@[RRG&;iLX7mQ)8B*S[lkjBt[OP8k_6V;q#*p&Q\R0h<#hUD4%-7+sX0,@4*k>kTS(.DD=p.7k,PhAq%D^X::+lnb$M,PT=cd'@%HdTLcP,k1;cX$$=%!o_9bTp/,ie?TN'AT<>(j\="-i?uT3frn]Zd?!`%1Unpc^5q2qtPZ9g6Jm&"*C6\_Q0$Ng$L'qEt@PdQL'OW)H9M5`eI8W9GF<$6@4kf0'H0B9L,#*!qj+'o#,.R)/>0>a,2QF!U]sF+ep,);]h.^S8m@Q-jR,=/Mb?d?U]6p/tO^Nj#OAI9)ZAj"!cCT9\Y\%oTZn#Kc2>#Dq<XK1%lj6)+#A+H__3O@Xd^8B>c]'![K&8&lXKp$nZ#gjMd:mn#*k?SC:o].T/P'XU!PdW^7C!_F/cQrcc&HBb6gaAsgMp]@'??DHf%TLpL[W1r!]E@]d[SG;3jgAAYJ\YDG4WjQ5+p/t9*_ilQJHY.^_B9m79ee^E*_^J1u5I$D=,%7fkTl>5DP@1n.)^ea\lS`1o^i<YG,k1<fqpd,fEk:mj#0(c_n/3^LKS-"l$q0Mt!Gt@\N?JYhq85r%LjXtQQEH!95C@4DIWs?ik6B*`IWFG%(TcOr[):$Zdb%.)0K;LXckMK^XW?&)8NL*<>P@Qsd/YBTB`QsIY,>V8HTE*AB&i^>[+_Kb147D\<5K<NN_XJ^,\8\ke-fb>I=]I`Jt,[afI'X&'Z^BQl:?.hUBK94Oi7D.T)ocU""7su3U+-.*)tO:S)XnfK/rfe4uY&4\#AZtbLgN?P9*uT9Lr9uoiq\?-Q9eOn\%B*$Qr$31ddh9bBpT6gNr2mc*N:uQQs(W8oNOfq91[5m#1b1WI83Hh-[S=]0"'V)5*a%a-\k+=Cdm,P)-Z.HE"9D?::k7g.n3aN/"&L&lF8V'-?RS$s.7B\hsN?8KC$#!Ua\3T%G:CNs@eUTkiI8p1ljAa@./-lB$gK?5E@Z5;S-Yl=uL[YdZDUO2^_N\R`3iGtsA;q_oJ0SH4SV$ARM[3Ih9;3glTu7WU&Y^!s?iM=J!EDV[<TlS))mg:2h^FFJbPB'p0O@lBD.W7<6Mo4+P9<9gR?2uE%"\/rtG\*m-be$?uWgNIn/bRR*Vnhs)ZhgblG-ZBsD4)gNBq=Ka-_tX]8Fg/Y0^.g:D]?K%#M!d^UpB&q4<71seeUuo\q"b'Pd.DYTHTQA^o0?^X"'%>$1Pl-V`O#)FD@GGXo6d^=)[8li?t@(t5C@0COGl6\T_$:MNB''B)qV4AlbVP3D4)J8/0:sa'Vght8Dk3%`Y?j?[ITem7eG!Qk'Ub`OIV`fa_Yk?g1f*.M\1WYb9kFe\Ah:LL(&Lj/3%7!+p:i*%f/"a1oEp>F[f&+j10S`4o*r0/aXB?H?]#,@Q\gL`c:Ju/s9R]d3aWDDo'jN]U9P>kOd#pLX-&bW'588N5Y$9?$ag6KJUYuL#.e9;EEj+X-msAg`#lEZ,:[CR*A+9'!8WL@Ro959\Yu<45Y+4al*GEAdJuC=0])R!XI&RlQn[$DG].YA`_2[ApeqcCR7\@/3]'27HWa2+N4Zn&]Dk2E.oGq%,oVc4g45PjFJdqh>P=eJ9`$M1Rsj8LbO`1nd$qtiC6A$os-R\8=`&uA89uTLi:ubVYg5Qq2r]_bqSr;+*95m(B[3<cWN!<.#nY;=VbLjp=s9Q88!mFX#Du=YEk0;IJTe2RQ^F6$pF&)58'Q7Gg[&VQXYSWI(3T>(6QX(SCe_@9W%QMD,!ar?hQEH*#HW-/_'"%/BX,S.q7i_KD,8_Zn)HdjA/p`ohB2hTN78ZUX#eFdsN[bHEE,m4$gZ0n=>n"949=\YeUXZ-#QoASMtca3M+4V&_1&PIb2r>S),%59P5$n0\YXGm]3seNJ;>,&H<I8J"sm]fjP3MHR\H2SLNh5B:b0gE7LbQg)8;^baZ`0[#K,Xk:Ct-1Mqo<\hj3N`U#'qiWX-)Galdp9mRc_.\4CL?(m:VXuj720?BaDr-AcCk+rKO.^$H-Eqlb1PAYDR`&*r#(H9@@Pq(Tk+/;d*.]"PbA&Non,t`/MKU&DNGaspuSLfd5s#u/,O62@qY!U@Qk?lKEATq"Z#8[moD46^Na8iBiG7LXAVN/:6qgCG+nVZ_[N+GLe?!$IgesZKuib!#!)qMYJK^69N$LbgMWVIKPB&Gd30uL"(2+%D!.H>pkZb-qs*5sbGYq,Qedp"@[D^P1C0t$B"HR#M$DWf4NdQd9:5>D?PC9g1amH<j$6r;g@lDJNCD%&W#_9,@/5!c8u/ac?gRrVm[hN-J?iNOM'V+/=173]*._I6$q%FmTS+''XmXc[R)OYj%,_I5l2CVo[O7?Y%s$&96eP%u<_<[:j).j%e;Z"=G//4mL*\OYYsMPSk*_1rasX]^8e/!<f:"s6kk\Rl:7X@Q76)LPYL8Bi'#=HUOj2>FC$NG?HMm;/KH9VLi)U"0XGmd-Urbb4=(+OuDnmHo`+L=igth#O;NP`CV'`Jm\'b5H_^k4hs&-"JbD1lZ1\6hkPN.ZC1@F@hflHH51a#[L_Sl+)*93W@l%P0&a3Z0)mPe(/66bgj%@g3r@L2GUks.4PrB1BXm.4,\MKiKqEKUZ,$?Z2K[/rTkFP*`]*dmj);'G5hK8-kVd)o<T"JX#Kc<]^eRgSjT7trM,rBTf0.hf?"X)?^Nb_Kq%5+3GM<Xs7N<JC(=F1KG2n1Z90g,'m*#J%o3buC-otFFckRJ5g`Il*k!arm;Y0"\,P9VP-h7(HH*kjCO\/^Ft^Zq'G%g^X2i+?%PII!1N*;)l$<>dT<5=->_Rh.%^!6"48*:EIh_\7A!)5..'HKI4&1sH*J[e?(,fC9=>0VQ'q/]^JV"IJN6p?BX$gC3Na,$HD;B!9)_RLNmEC'-ZF;!S^NL?8k?I&<%GY<_I>=?;0F[lGr-Aa"c/2n3C_`gRERh9'_]M+V=DjbP%O1;[6rqS0Gp<,)!;9J+%]2E>8V2Q)mpKb&OsjuV4e*J58?X@^9RUP,]U@ci#<1"iM'a<RU8&6]$dhU",Q>YGRk]CJPb`<]jprX1KkPIIfRg;!"=c:B&\uI^$OQSDf(39%nh54mLhI]kD5e^C?CId)B%95J3^7m3#g_rA(=m/p=7X8l((r+VSn34>pnk<'#eJWE0:VEaGSQp;FL([=q\>*A67@;J^D4prr'DHrKShEd$&%CfmCSKeg\:M:N$o23,KU]@7JcVX]@4Dl\?`o1AT!+5:3U-%k'j\oVB;q,0Dr<h.?r*F2DuOPDSi7RIFP7.le+UuNT[Vn`Pbat!oqP%E>uG",4EY(dC09H*ZOO+j/eJ-*.>m2jjUEM!6+9*S:C>qX(I4,%3sHD#dXA:kL<gV(#i4(XU:K13)T>=qXF*I8:i0%U8dYUEsHu3SsZ0W1%1]MMPLPHa$PM1JjP9bMtor\]r]f[^r_'+^['pJ~>endstream
|
Gb!#^>BAN>&q801kbhA$0$'L/Q+Y4@G1`14!@MN\>c!V&!87^8eHO_)lVERP#t0_i3N:`,@PY;I4l&9mp"(ue5f=1)s#OA7b6;-U'-Iu).j2,?_r;HU`OinBOk1sl78S+qPX3bT6'MR/^5$0FOVE3U!fJX,b$VlmVJ1CB8js$;nD<Xs=9_O+AUYVIJAkm"pl0("_p:W4Wal&s!L_@\7=qI:8Wc9"#-+Wd+D;*&a9;BgkMp?@_M:)^.%r-_M$+uSeSj(fs5_KsqHM8OBnnI6IckWHV7pSa>l1,W?r5T6e^Z98CJoT,Wn=.\=f_!oP.U?[lQ\s>/eK@]q?R_I9*C)T+$lWCM'.rH,=nU_&L=/9Jjer<_*NJL[4^uJlbfWBM4W4)q/qR#Fm)`)V4aY]97HqF#p`Zt0J>rA1<NK)Z:,9+hSK)i=&_<jW`5^E.]T%#T-rBq*PS'9IJc:T*>S9EXas)bXHJGH:T?sfRJGC'btVt;s((N6?l)5>(KjTn,%8*"?uUJ['>M:m>MELokdEe$^ig\57I]gGgW]n^jC74s%#jH7i<'dH'$^sja$A^G*mjcUg?@R)>d(mO*`6k5:?"utjTSH)b\s]=jP!uVKMQd$FT")pNdb*B;&n;p)u(4/,'8P+m5?k=P:5j;lof^j;!@tBFEE%N)7S'jL,GN]-F;*/ABp[4SLaV8cQK,5>+(q?m3lQi#1O)K2h$=KBD\[&@USZPCf);cPc=iE)4fb7gIE%Q+F*H[)"f$Caj,o*eaB*AhUrp9\/<HCf?qs8><#&'f3a5T,G0))H>,^a_S,7hf\3qYltt'SVR$8sfT$WPH%(1&5%Ar+4e[5RU&@l!];B\YID2):`n7l6+&B!hoCDmXRGI6f?I&TRNAcO8hSM&$mNC4<Vp[2_G-d*_76m?1dooUU'LX-;Y^"%(rJY^7Q?cRWE9u6NJi2Jff&2BJ_YpFRYmj(d,b\:sq*R4(^8Ua^GSF_Op\^=Mj?[":Ul2pS)`i)40$b9MM9Bs\aOi_Y#BpA?mb7+7X&T[rCh4u#>`%TB;EL:XbpUNoB"#,!0]*"A%(F4mL][H83#h"-pCOYkNh]V/i$aVN`_i_CEF5i[HfK:VpsZfA,L-%32#5SDc<T%*PU<:VD0uNLDf"<>SF=7!WoX,.Z7k5_c>.od.)(`RSfQt]3MO,Ik.*[V%Ou20Most&5J"Np*1G_ZrbQ1aHQ>ZD^7u!%>a#H'CS$M5$_mrfM<f.CS\U9f,U5sjo7E;Y"3I2n%iS3_@rjOJ)7PULl_*3om?ndW=P.!80qsiEN8Ob,`Y@E?^%SHn7eG9]AiMT.8C#HDA@=1)c)bZ``UF!0AK)m#Y+ZkQ+Ml^_9?rrK4![ac2TF6R<i8N`[:W)kUP3i=48IIO/?OpTrNPoH6X4dR`d.&(09T[fd5IgrVe##n:*0#[F+!Q*_hT9c<0,]E,4R"9=7.krE+40te@X-t8o$V31Gc1n08?lqX<?@BB=!](lo'2LZ?GY"LmtrX^"sp\jBr@pZA=.^9k^_Hho9g)\.MN=>;Wh;=Lg;G)1B&e29Vg:3hM/C13$2m&7Yls%-$@ng`RjI!F2+>jH\b,*iPg!_k6l;R$SDViR7)/%^R!-%j-oD-UJMa<n.a([3f1CWVOM"IacLqmJA!&9-/YQ@IciM(h-5nH4Yk7]k2dCNWsB'&n6$-SSIaeCQ=UI[kQs:k8$hdl)/-*G0mR*(jS4>*qA%'PHOAeA.osQ@E_%h,3p@HCs?uIe,-[5T:4nlXfof*Y)GeJWmO8g/r;4C43!j([-t2qGr^C$J'>3tDc60l[An$`Zt<=*jr52hWFm3XmJG*0c[H1`)"qDL$U!<PiM=HG3PJ]KE%NDtB\!C9]nQmKo[^*U3h'6+d4G=+n(kfre9:tip_T%k*s2_G%SJe-NIgQa9[RafF?QFRlRLB5er43QN[G4`158oVc)oB<g5J,$=7j`HfhGq29@sns[lY*JBfui'>*GA[5qQ/DHZcg#T01$\Fp(\%`XIjL[_6']o3Ib6gZ6qEBf7]K`l>BJ.IDI(@8PB%,qGQSjFHJA9"%BGQ)<lZ>`r-Y/.@[@4pBp>lA_RIK34(eT?Y@1C1Cu`/M9rT36KF,*o(/iCc"#0D.f'(pL.RV/%32+QIO9m[bC35Qe&(8n]2Ueo0l"Rf6XH-d=\J-o-83c?pE<f_l=>$_ZBC>`X*r'H/"B>Ml@+p>O>dq?a.ls:TPLF3k&GiNMM<>SL_lOJ)J)Vh?c6nA;.h`p[%%5TYOKGT"7')A+6?#o2HtV$cLK>imEBKO.>HjT>r"2YZZd\;A*EE&.r(Y@5A_i"4\V.D\aO&/W$]7Lbu(V_Iarh97uEP66p=m&N1#Z,aUpO/4?l+<o,Aq@`]S%R>^h@k)kHY$^"cd#&b8$[H%eR/4<+P)n]=0jH;HHZOf\SDt%?^+sh0sAg%d[hVcX`1ak@oE:AQ^EeS]+KN`DLHLQPbEeAE%JlHcBF>P5b'B7(8AI%;i;`_u8'R)mkaQN0Nampsb9?:WdBH#8.MsuK,<+-o?QV4o[b?J;^5jIXE/([AA:]5\L8L=[2Fk85EV+7GC10;M[e+S4im(mE5/4DuO@V-aKA0mp^7=8#NF&^dA.5p&q\t@eFTqaRr8l$)(8/qQ<;%s<enbH;n_Y)#7&?l"oWMe7Ek5;0Q:T-^`aK\[`rsSG\3Ol'6RY0n$q0to:5rE=I&jE7+Q0"D=<'cj]p&"od>Wg@G[]\'W'$-(IlX>]LHDIME\)+iK;SoQFDk#87f%]ppb'1e\KZq##;/_'MaXs0XK?QfMB1L$ZhtPG`B)@2OV]`=Z\h[fn:q8][nGJLnM)RSR]O["ui]?;"gMGQo=uA.F02Xf?UEZ9^r%:FuCDimN@2*R[0M#Z][+IFXP3NjKWO)Eu'4ctC;hXT$M.[Y"XDYMQj0]&b=\c23%^cr<n]BJiQm.#WNB0c+_#<Z^2b<W6"5eGspV>_]oV.)dej8Y7q[o;?pg/@og<j<a$5kl>s'hS/IJ9A2du(Tq+61-I4?)-(4)RU`-32)0H9Pn%Vajji`:IC&3an,6RqrW*GPlG$jqhIYmS,o(B+8M>SP%I04SLaLpc"O6Y@BXe5U[?N"W>BaIR/f;BG*JS:l)7g&%!T)QS88-bB;**fRjg%S5Z.lE)@"jF"ILn_m,XB^UP#t@H+*7;>@SA[X[$sYH/+2Wk,0\acU0[i3QQ^o;0NXi!Y0N)WZ[+=UM_u0.tN/+KmbS92FRZ+s3bbTp&g%%j)^K:st@ddqOI_f.4.X*o2UmY<ZX9.g1i$8%"u^?ZnPq%>l7+m)AlR,o[B@o@XY6"scf2")9iY?8#0OpL/5R^oschCX`SO5'[K`e+2;P_?1%.W=+aS)e=Dr^(m;"\J:e#pFNic.HFP?ReT\uE#2Q;K%ck=2T[b-[][ZF(qDrH#`OtLq^1?$PiHbkN,>GRA&Fl-K.90N$.2^3rB:eSe4.C3=/LA@>epS9FMqUf#RWl)j9'\2@!L1a*dIEDKPUJ>~>endstream
|
||||||
endobj
|
endobj
|
||||||
44 0 obj
|
44 0 obj
|
||||||
<<
|
<<
|
||||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1402
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1487
|
||||||
>>
|
>>
|
||||||
stream
|
stream
|
||||||
GatUsCJWtM'SaBKY;jsD=.NVfj4rTtBN(k,d#b'INeCqs`3`\$3+G&+iSUgZg<a8c#ZMOlqNCI2of6q##^#l.c0oHPE0`b=KE)mOg]Oi=$^(f!aep5Qct9+cCQ<&n(:YF6FS!G6qrG->Lt[3$RLR@GOJd9r7g>K^Jbs%c#<%@o=dhL815E6:p_QL#L#am%k$!6-KlUGCDF8:0Z5Z^D)c&a7^=U`tC1%+V>NE@LM#@#L7mT+oOs&1R3SYO-$Q2V5!pXMle%>d<mI!+-LH)/>E/"70g_(=_S8\LL#]>!uL?6]!(c?:0V#unB*%FZ<*deE[(e7MSGj0u8R4.,6#nDI&Gp,`4J]2^@Q01:#8,ed7f,R_o+H5>@9<6%Ci4(8f<4$Vh0Sa8E".<TdW3d@SbL(o)-Yl(]k_>io\fl.Y-A^X]a"j8:7OuX1--*Z)X]"lFnhIGEJj>c9K_*I17V&WP$.fW)n'@nihs$AfIAjHa+rThg3mr-U+`-/,/MUU/-:g^1b<:b1d#LF4#(nLT&1=$4;R[-1b$jp[fO<:pbis.BEhF-riC>KcX.Ud-6LK*pr^?![C*B?tSI%i`Z"5bYH?TYY*,I'Vl/cIf%EACC'dh0coO7*A*<>GY%4$n0f_;"4<U'ST]1fbHd@iN?lX8J7`FnsTGWEbG5>GAl2'QUnPK/8'2sJ+e*c)Pi%DJG0PB$.6?i)emicmT!C74u=+QXe!1U84$Gj,AP!0)W%i2O)JRirS,H"u8`H1rCedip"gl(FGqg#VK=1c'/^#<m6Kktf3"N2?+Ks54iqj3USF"e8UNn&H.d59V]^a;-%J=;JPBSfsn$B6"a@h?C?7dOocAr!4`Z_q<PSqS%=Ir`l#V*Mn>qq.=pl`cc1CBOgGY8Uk"ms'A729;TapK)?>RA]'?aOr)e1'I?SA'6`)\_7!ZshR@PHBe[7>7K.A?+CBgq3e*A@>Z,70pIFd[TXOMg4l&3QN,<I_K6tb*=#*t?=is/LJ$prpj0BO@S<"=GRS:K(e9=U;iW<5q,TY[o2U*8]hAP2"+A)CHZ$I'"pI064B=%,=Lke8UR=:Jj7seFEU[F,^&J1(=*?N'O5H\E_.LJl33V_dR<>$f*.pA1C:Qu&V3ec0Ce()jHAS??q]AT5kGaV/:q'2AD8>_"L-Yg2hk3W]QFq)A8;k4(CMt2Edc&(_M0C$1KTOs_"@kJgh7-]rt$:lH]AR'j^q<#r#X8tf5FJ\tc;)Fn0^T<h%mn8+6ZO+[K+4Oj,(U0\KD&)gjA40J[.M[Qr@ER#"dK=@86b;P](Y!Oq^$8HGBP@Oo=A]C]T<#s_($%?P)*\lne#LGqM"^D,RS7p@Rie)7+lk]Ro(p;S559usD#RMl+gk$G6_<UplMhIe)X<n=F``)KJ5?1I2CFqW~>endstream
|
Gatn&D/\Dn&BE]&;iJ)p'78>c395pEk<dmsV'Si&pK":.V29b.+jSf=\%PReP8$p'9@\,E+UH_\kEgJhHQ]!AMq"B`$K*ammMQ%75VrRPC*l>Yn=l[r7qAsSZo$OFF;#YGeZ<*7`;=LWYG]A;#^=oR`E`nsJnS0Ql.0)(e\E"_\&<9:Nr9?cXYRCD$jUBrZ%!W:70YN>(FOSgKA4\R@6/L5/eX6p0A5a5NtD7!d2s+R288dH3Nf7Z%qKZ>Tq<::0tdY1PPYg<78fG4:aTr)$HY&IOEO0:E1]CO>?UEd):5PkB!CBOc=rHu1\;#XPV#RsqOqmH">mcI"4*SBOV]hq84=^=YV)8)a]`ZUoL#B-n\(ujqP/cf0BPqX^nFI:VDV%)cmfBuJ#I.WdGV>IKlJf?_b98RA1GV:.1(L@H*18tJjo,MlCRTtT$]&,5KK4m"VWY_MkjT?j>&@Z#HeuTH<<KspbnPrh_"3.5[06QloA3a1.+!:h:<%ZTc5Wj]f<rX.KqK99U7H"ld[HsNt%^h/$?,Ie!*G7M/9sK2!Dbuc.i'mksF,mfldlYIp''VFq(#+\kP`NMq;DUb68]>'hgI>dpNU/."o;nfYVuDe2Aama#Pt*LV^T2LI's5ad3Mh!u0>g^9+ZSEaHgfjAp2KU9kLi?gqm&W`nghStWo'(c2bhPelGOdL-7(3c5q$A.lI-U4*IMj'VoXK1Sc$Z`'T?!.SM$/b:m>8Ih&GBm:P'$nk-@J;-q=;2q4fE:QE%8kiKnkQ_J!]nrqjF?GGp`n,IlH=8e%>W#4I#$A-$MNk5FTjDKMgON?)?mU%IX\-io'A%ME`9ZO@5Oej%;q@$?l5>tL:VT.XK/^6sEt9o%M*DGZ<$(M3p]g[VUsZ=I-fnNT_ih:ni#*T3$^TX&F4hb`/D&Akh/k-Rr<k0O=n&0\$)rFa@pAi\n(l(6GnnRccBV@R1:;Ys\XLpug?0j^FZ7^96G6#gA.$;i.m.Vn;oa.!^*,.[Mi<Kg/JnoZ[HDs"f80^,X0^`pf2[&00)>DW7C_8P>OS.l;c)<$q;ggYIl"OdWWP%0.]jCIcdG4]2Yq5%kL\O%BQ-.mij81991++TYSJ#tek=R\XY&Acn^oqJpG@[=H%/__`;(r[5JcT+1`jY(cQ>rs,15qP<p[>Ej)GK$V:sZ7@J9(qpL1j0%Kc5AkdbmPKp<lUSep$,U;4'8k'L:XdE/G#B\K7bnBNf%9TTD7fMk3p)753uC"f#fhq6[@&@/\./7=k^pCc&?<Lr^"qQ;n^$d$AE<0'\,IS$gPW(Jm!U^!=Zc0A0-!PsY(<IK?o1`nooXI/G%97C'N0dfg/ejTr]0N!\8*r*tr%'+E6.)dr+&]lQf6+6$/-g">D<-Ld%nkgu`dDQ!l+to5JcE"r9;=#RgWB=$?e]PoSM"^Dl)Q(-Wbn&&R$#sd+H$s.:*k\]0Rm6U.UfPghM!MQY=IM[7>>o`^oV;?e"s!RHrh&0D~>endstream
|
||||||
endobj
|
endobj
|
||||||
xref
|
xref
|
||||||
0 45
|
0 45
|
||||||
@@ -311,11 +311,11 @@ xref
|
|||||||
0000007001 00000 n
|
0000007001 00000 n
|
||||||
0000007229 00000 n
|
0000007229 00000 n
|
||||||
0000007642 00000 n
|
0000007642 00000 n
|
||||||
0000011171 00000 n
|
0000011231 00000 n
|
||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<941c3b462cefa7769fe8013038040d71><941c3b462cefa7769fe8013038040d71>]
|
[<4606f0426670fb09488d895e0a695b1c><4606f0426670fb09488d895e0a695b1c>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 25 0 R
|
/Info 25 0 R
|
||||||
@@ -323,5 +323,5 @@ trailer
|
|||||||
/Size 45
|
/Size 45
|
||||||
>>
|
>>
|
||||||
startxref
|
startxref
|
||||||
12665
|
12810
|
||||||
%%EOF
|
%%EOF
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
30 0 obj
|
30 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Remote access \204 implementation notes \(v1 WireGuard\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Remote access \204 implementation notes \(v1 WireGuard\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -383,7 +383,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<853c39306aecb77ed422e218a356e197><853c39306aecb77ed422e218a356e197>]
|
[<d904f4741c13412ecfac86be95b5e1e2><d904f4741c13412ecfac86be95b5e1e2>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 30 0 R
|
/Info 30 0 R
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
22 0 obj
|
22 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Remote access \204 validation \(Linux CLI + mobile parity\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Remote access \204 validation \(Linux CLI + mobile parity\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -277,7 +277,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<5d07b3d520dc1b2274d0b97bcaaecff2><5d07b3d520dc1b2274d0b97bcaaecff2>]
|
[<edb858d19a7dc7c6b966c01613958242><edb858d19a7dc7c6b966c01613958242>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 22 0 R
|
/Info 22 0 R
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
32 0 obj
|
32 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Android Cast \204 implementation roadmap) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Android Cast \204 implementation roadmap) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -407,7 +407,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<0da68f69d69dfa4b1bad783189324a7b><0da68f69d69dfa4b1bad783189324a7b>]
|
[<8f27e090a4e0d9efbfb958e71fb6f0a8><8f27e090a4e0d9efbfb958e71fb6f0a8>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 32 0 R
|
/Info 32 0 R
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
21 0 obj
|
21 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Tickets, Gitea, and alpha smoke delegation) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Tickets, Gitea, and alpha smoke delegation) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -266,7 +266,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<7488f6bb6c67f7874dbd93366608ef58><7488f6bb6c67f7874dbd93366608ef58>]
|
[<933f0253ea9da04f7117c56c29f33651><933f0253ea9da04f7117c56c29f33651>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 21 0 R
|
/Info 21 0 R
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
18 0 obj
|
18 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (USB-C / HDMI and USB-tether cast \(roadmap D & E\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (USB-C / HDMI and USB-tether cast \(roadmap D & E\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -216,7 +216,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<fce2b2bd44cc97dec62511ff4c212ee7><fce2b2bd44cc97dec62511ff4c212ee7>]
|
[<04785d34764bfbca56a9a6cb6c28e5e7><04785d34764bfbca56a9a6cb6c28e5e7>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 18 0 R
|
/Info 18 0 R
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
16 0 obj
|
16 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (VPN / Remote Access demo \204 gap report \(2026-06-13\)) /Trapped /False
|
/Subject (\(unspecified\)) /Title (VPN / Remote Access demo \204 gap report \(2026-06-13\)) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -204,7 +204,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<fc38ff8f318a67e77a875d191d72edee><fc38ff8f318a67e77a875d191d72edee>]
|
[<0d0dcfb02c2ef4688845f129827f2125><0d0dcfb02c2ef4688845f129827f2125>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 16 0 R
|
/Info 16 0 R
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
@@ -5,74 +5,206 @@ state: pre-implementation
|
|||||||
author: Anton Afanasyeu
|
author: Anton Afanasyeu
|
||||||
date: 2026-06-18
|
date: 2026-06-18
|
||||||
|
|
||||||
|
[AI comment]: Updated 2026-06-18 — PO chose org **`ac`**, **full microservice** end-state, separate **ac-scripts**, URL path migration (§L), optional **ac-workspace** (not build-all-in-one), VM-first cloud (§N). See §G master repo table.
|
||||||
|
|
||||||
The problem:
|
The problem:
|
||||||
The project looks like monolith. For better and more atomic support we need to reorganize the whole project repo structure
|
The project looks like monolith. For better and more atomic support we need to reorganize the whole project repo structure
|
||||||
|
|
||||||
|
[AI comment]: Agree on the problem, but **atomic** should mean: independent release cadence, CI, deploy, and rollback — not “one git repo per PHP class.” Today’s real coupling is **one MariaDB**, **one PHP session** (`ac_crash_sess`, [BUILD_DEPLOY.md](../BUILD_DEPLOY.md)), and **one nginx vhost tree** on BE. Split repos before extracting shared platform code will multiply broken deploys.
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
A. Current codebase map (monolith → draft names)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
| Draft name / intent | Actual path today | Coupling notes |
|
||||||
|
|---------------------|-------------------|----------------|
|
||||||
|
| ac-mobile | `app/`, `ndk/`, `gradle/`, `build.gradle`, `settings.gradle` | WireGuard tunnel module; `third-party/*` submodules |
|
||||||
|
| ac-session-studio | `desktop/session-studio/` | Standalone; low coupling — **easy first extract** |
|
||||||
|
| ac-docs | `docs/` (+ `docs/_pdf_build/`) | No runtime deps — **easy extract** |
|
||||||
|
| ac-scripts | `scripts/` (pdf, native codecs, OTA, CI helpers) | Used by mobile + orchestration; keep with **ac-deploy** or meta until split clear |
|
||||||
|
| ac-be-console (crashes/issues/tickets/graphs/rbac/ra) | `examples/crash_reporter/backend/` | **32 PHP classes**, 24 `public/api/*.php`, shared `Auth.php`, `Database.php`, `Rbac.php` — **one modular monolith** |
|
||||||
|
| ac-be-builder | `examples/build_console/backend/` | Separate app; shares auth pattern via `examples/platform/shared_session.php` + same DB users table |
|
||||||
|
| ac-be-landing | `examples/app_hub/` | Static hub + deploy nginx snippets |
|
||||||
|
| ac-be-ota (artifacts) | `examples/ota/v0/...` | Files on disk + nginx; logic also in builder + `scripts/generate-ota-v0.sh` |
|
||||||
|
| ac-ms-url-shortener | `backend/url-shortener/` | **Only live submodule today** — `git://f0xx.org/androidcast_project/url-shortener` |
|
||||||
|
| ac-deploy / orchestration | `orchestration/` (docker, cluster0, nginx, runtime) | Local stack + **cast01–03** lab ([orchestration/sim/cluster0/ARCHITECTURE.md](../../orchestration/sim/cluster0/ARCHITECTURE.md)) |
|
||||||
|
| ac-ms-vpn / WG / RSSH | Mobile: `app/.../remoteaccess/`; BE: `RemoteAccessRepository.php`, `WireGuard*`, `Rssh*` provisioners | **Split by plane**: client in mobile, control API in BE console — not two deployables yet |
|
||||||
|
| ac-ms-vcs (gitea) | `examples/crash_reporter/backend/scripts/gitea/` | **Ops scripts only** — Gitea is external service on BE, not app source |
|
||||||
|
| ac-ms-tracker / analytics | `Ticket*`, `Graph*` repos in crash_reporter | Same DB + session — **logical modules**, not separate services today |
|
||||||
|
| SFU stub | `examples/sfu/`, `api/sfu_health.php` | Post-alpha; stay in console until F1 SPEC |
|
||||||
|
| Experimental | `examples/gentoo-portage-experimental/` (nested `.git`) | **Exclude** from meta umbrella or own repo — do not submodule into product meta |
|
||||||
|
| third-party codecs | `third-party/libvpx|opus|speex|wireguard-android` | Stay **submodules of ac-mobile** (upstream URLs unchanged); Gitea mirrors per existing migrate script |
|
||||||
|
|
||||||
|
[AI comment]: **Correction for draft “how” section:** `be-console` is **not** `./examples/build_console` — that is **builder**. Primary operator console is **`examples/crash_reporter/backend`** (crashes + tickets + graphs + RBAC + remote access APIs). Builder is separate product surface under `/build/`.
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
B. Infrastructure & cloud fit (combine lab + prod + future cloud)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Principles (from [specs/20100612_1_scaling.md](../specs/20100612_1_scaling.md), [INFRA.md](../INFRA.md), cluster0):
|
||||||
|
|
||||||
|
1. **Deploy unit = VM role or container image**, not git repo count. Phase A prod: `be-web-1` runs nginx + PHP-FPM for hub + console + graphs UI; `be-data-1` MariaDB; `be-ops-1` url-shortener + Gitea; `be-build-1` builder.
|
||||||
|
|
||||||
|
2. **cluster0 pattern** (NFS config only, app on local disk, GTID MariaDB) maps to cloud as:
|
||||||
|
- NFS `/shared/cluster/` → **ConfigMaps / object storage** for nginx + SQL seeds + scripts
|
||||||
|
- Per-node `/var/www/...` → **container image** or **git pull on VM** per service
|
||||||
|
- Managed DB → **connection string in secret** (PO: “pay for DB service”) — must **not** live in `ms-common` git
|
||||||
|
|
||||||
|
3. **PHP session + RBAC**: Shared cookie path `/app/androidcast_project` across hub/crashes/build ([BUILD_DEPLOY.md](../BUILD_DEPLOY.md)). Until true SSO/service tokens exist, **splitting BE UI repos is fine**; splitting **API auth** into N repos without `ac-platform-php` Composer package is painful.
|
||||||
|
|
||||||
|
4. **OpenAPI**: PO wants MS OpenAPI-compatible — today APIs are PHP files, not generated spec. Recommend **`ac-platform-openapi`** (or `ac-docs/openapi/`) as **contract-first** artifacts; implement in console first, extract services later.
|
||||||
|
|
||||||
|
5. **CI**: `build.config.yml` + CircleCI shape assume **monorepo checkout**. Meta-repo needs **per-repo pipelines** + `ac-deploy` triggering builds with `git_ref` across submodules (builder already does this for `android_cast`).
|
||||||
|
|
||||||
|
6. **URL / paths:** Under **discussion** — PO wants path-per-service (§M), not frozen `?view=` under `/crashes/`. nginx + mobile `BackendEndpoints` must migrate with redirects.
|
||||||
|
|
||||||
|
[AI comment]: Shared cookie path may become `/app/androidcast_project/` at **edge** only while backends move to `/issues/`, `/tickets/`, etc.
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
C. Target structure — **full microservice** (PO decision 2026-06-18)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
PO chose **full microservice** end-state (§K comparison). Delivery remains **phased** (§D).
|
||||||
|
|
||||||
|
```text
|
||||||
|
Platform (libraries — Composer, not runtime MS)
|
||||||
|
ac-platform-php, ac-platform-db, ac-platform-web, ac-scripts, ac-docs
|
||||||
|
|
||||||
|
Infrastructure
|
||||||
|
ac-deploy submodules ac-scripts; cluster0/docker/nginx
|
||||||
|
ac-workspace optional clone manifest only (§N) — NOT unified build
|
||||||
|
|
||||||
|
Clients
|
||||||
|
ac-mobile-android (+ future ac-mobile-ios)
|
||||||
|
ac-session-studio
|
||||||
|
|
||||||
|
Microservices (OpenAPI APIs / workers)
|
||||||
|
ac-ms-identity, ac-ms-rbac, ac-ms-devices, ac-ms-issues, ac-ms-tickets,
|
||||||
|
ac-ms-graphs, ac-ms-remote-access, ac-ms-url-shortener, ac-ms-build,
|
||||||
|
ac-ms-ota, ac-ms-notifications (later)
|
||||||
|
|
||||||
|
Backend UI (thin)
|
||||||
|
ac-be-hub, ac-be-issues, ac-be-tickets, ac-be-graphs, ac-be-remote-access,
|
||||||
|
ac-be-access, ac-be-builder, ac-be-auth (optional)
|
||||||
|
|
||||||
|
Edge
|
||||||
|
ac-platform-edge nginx BFF — recommended for cookie + URL migration (§M)
|
||||||
|
```
|
||||||
|
|
||||||
|
[AI]: Collapse PO’s **ms-vpn-rssh** + **ms-vpn-wireguard** → **ac-ms-remote-access**. Drop **ac-ms-vcs** as code repo (Gitea = infra). Drop **ac-ms-tracker** as separate MS — split into **ac-ms-issues** + **ac-ms-tickets** (different URLs PO wants).
|
||||||
|
|
||||||
|
**Risk mitigation:** Composer semver for platform libs (avoid @common submodule diamond in 20+ repos).
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
D. Phased migration (safe order — no big-bang)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
| Phase | Action | Alpha blocker? | Rollback |
|
||||||
|
|-------|--------|----------------|----------|
|
||||||
|
| **P0** | Create Gitea org **`ac`**; mirror legacy → **ac-workspace** (optional) | No | Keep `android_cast` mirror |
|
||||||
|
| **P1** | Extract **ac-docs**, **ac-session-studio** submodules; meta points SHAs | No | Revert submodule |
|
||||||
|
| **P2** | Extract **ac-ms-url-shortener** path to `ac/ac-ms-url-shortener` (already submodule — change URL only) | No | Old URL works via mirror |
|
||||||
|
| **P3** | **ac-platform-php** + **ac-platform-web** + **ac-ms-identity** first (everything depends on auth) | **Yes** for MS split | Shim monolith |
|
||||||
|
| **P4** | Extract **ac-ms-issues**, **ac-ms-tickets**, **ac-ms-rbac**, **ac-ms-devices** + matching **ac-be-*** UI repos | Medium | Edge proxy to monolith |
|
||||||
|
| **P5** | **ac-mobile-android**, **ac-deploy** (+ submodule **ac-scripts**), **ac-ms-build**, **ac-ms-ota** | Medium | Per-repo CI |
|
||||||
|
| **P6** | **ac-platform-edge** nginx routes (§M); retire `/crashes/?view=` | Medium | 301 redirects |
|
||||||
|
| **P7** | cluster0 + cloud VMs (§O); optional **ac-ms-graphs**, **ac-ms-remote-access** | No | Per scaling SPEC |
|
||||||
|
|
||||||
|
[AI comment]: Do **not** start P4 until P3 — otherwise Auth/Database duplicates diverge in days.
|
||||||
|
|
||||||
|
**ms-common sensitive data:** Split into:
|
||||||
|
- `ac-platform-php` — code (session helpers, RBAC classes, DB adapter interfaces)
|
||||||
|
- **Runtime config** — `config.php` / env / K8s secrets per deploy (never in git)
|
||||||
|
- Optional `ac-platform-db` — **SQL migrations only** (shared schema), consumed by console + shortener + builder
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
E. The what (original table — annotated)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
The what:
|
|
||||||
Current project structure
|
Current project structure
|
||||||
Project | Placement | Repo | State | Description
|
Project | Placement | Repo | State | Description
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
AndroidCast all in one | repo root | git://f0xx.org/android_cast | monolithic | all-in-one project, needs to be splitted
|
AndroidCast all in one | repo root | git://f0xx.org/android_cast | monolithic | all-in-one project, needs to be splitted
|
||||||
|
[AI]: → becomes **ac-meta** umbrella; keep **read-only mirror** at old URL ≥ 1 release
|
||||||
libvpx | third-party/libvpx | https://chromium.googlesource.com/webm/libvpx | 3rdparty exported submodule | VPx video codecs
|
libvpx | third-party/libvpx | https://chromium.googlesource.com/webm/libvpx | 3rdparty exported submodule | VPx video codecs
|
||||||
|
[AI]: stays upstream URL in .gitmodules; Gitea mirror `AndroidCast/libvpx` unchanged
|
||||||
opus | third-party/opus | https://github.com/xiph/opus | 3rdparty exported submodule | Opus voice codec
|
opus | third-party/opus | https://github.com/xiph/opus | 3rdparty exported submodule | Opus voice codec
|
||||||
speex | third-party/speex | https://github.com/xiph/speex/ | 3rdparty exported submodule | Speex voice codec
|
speex | third-party/speex | https://github.com/xiph/speex/ | 3rdparty exported submodule | Speex voice codec
|
||||||
wireguard | third-party/wireguard-android | https://git.zx2c4.com/wireguard-android | 3rdparty exported submodule | wireguard for android
|
wireguard | third-party/wireguard-android | https://git.zx2c4.com/wireguard-android | 3rdparty exported submodule | wireguard for android
|
||||||
url-shortener | backend/url-shortener | git://f0xx.org/androidcast_project/url-shortener | part of AndroidCast backend | url shortener standalone backend
|
url-shortener | backend/url-shortener | git://f0xx.org/androidcast_project/url-shortener | part of AndroidCast backend | url shortener standalone backend
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
[AI]: migrate to **git://f0xx.org/ac/ac-ms-url-shortener**; alias old URL
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
F. The how (original target — annotated + fixes)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
The how:
|
|
||||||
Next hope project structure
|
Next hope project structure
|
||||||
|
|
||||||
AndroidCast project - all in one | git://f0xx.org/ac
|
AndroidCast project - all in one | git://f0xx.org/ac/ac-meta
|
||||||
|-- AndroidCast - common | @common
|
|-- AndroidCast - common | @ac-platform-php [AI: Composer, not submodule diamond]
|
||||||
|-- AndroidCast - mobile | @mobile
|
|-- AndroidCast - mobile | @ac-mobile
|
||||||
|-- AndroidCast - BE | @backend
|
|-- AndroidCast - BE | @ac-be-console (+ builder, hub as siblings)
|
||||||
... TBD
|
... TBD
|
||||||
|
|
||||||
|
|
||||||
AndroidCast project | Placement (@ - submodule) | Description | Repo
|
AndroidCast project | Placement (@ - submodule) | Description | Repo
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
AndroidCast - common | @common | project all common | git://f0xx.org/ac/ac-common
|
AndroidCast - common | @common | project all common | git://f0xx.org/ac/ac-common
|
||||||
|
[AI]: **Rename → ac-platform-php** (Composer). Reserve **ac-common** for non-PHP only: LICENSE templates, OpenAPI, semver constants — or drop repo and fold into ac-docs.
|
||||||
|-- AndroidCast - docs | @docs | project docs, BE, MS, mobile, web, everything | git://f0xx.org/ac/ac-docs
|
|-- AndroidCast - docs | @docs | project docs, BE, MS, mobile, web, everything | git://f0xx.org/ac/ac-docs
|
||||||
|-- AndroidCast - scripts | @scripts | common project scripts | git://f0xx.org/ac/ac-scripts
|
|-- AndroidCast - scripts | @scripts | common project scripts | git://f0xx.org/ac/ac-scripts
|
||||||
|
[AI]: merge ac-scripts into **ac-deploy** unless mobile-only scripts dominate
|
||||||
AndroidCast - mobile | @mobile | mobile app sources | git://f0xx.org/ac/ac-mobile
|
AndroidCast - mobile | @mobile | mobile app sources | git://f0xx.org/ac/ac-mobile
|
||||||
|-- AndroidCast - common | @common | | git://f0xx.org/ac/ac-common
|
|-- AndroidCast - common | @common | | git://f0xx.org/ac/ac-common
|
||||||
|
[AI]: mobile should NOT submodule ac-common PHP — only third-party natives
|
||||||
AndroidCast - Session Studio | @session-studio | offline analytic tools | git://f0xx.org/ac/ac-session-studio
|
AndroidCast - Session Studio | @session-studio | offline analytic tools | git://f0xx.org/ac/ac-session-studio
|
||||||
|-- AndroidCast - common | @common | | git://f0xx.org/ac/ac-common
|
|-- AndroidCast - common | @common | | git://f0xx.org/ac/ac-common
|
||||||
|
[AI]: session-studio has no PHP dep — drop @common edge
|
||||||
AndroidCast - BE - MS | @backend | backend microservices common base (UI) | git://f0xx.org/ac/ac-be-microservice
|
AndroidCast - BE - MS | @backend | backend microservices common base (UI) | git://f0xx.org/ac/ac-be-microservice
|
||||||
|
[AI]: **Overloaded name.** Split: **ac-be-console** (main UI+API), **ac-platform-php** (lib). “ac-be-microservice” as umbrella meta for BE apps optional.
|
||||||
|-- AndroidCast - common | @common | | git://f0xx.org/ac/ac-common
|
|-- AndroidCast - common | @common | | git://f0xx.org/ac/ac-common
|
||||||
AndroidCast - MS | @microservices | microservice commons | git://f0xx.org/ac/ac-microservice
|
AndroidCast - MS | @microservices | microservice commons | git://f0xx.org/ac/ac-microservice
|
||||||
|
[AI]: defer **ac-microservice** meta-repo until ≥3 independently deployed APIs exist (today: 1 = url-shortener)
|
||||||
|-- AndroidCast - common | @common | | git://f0xx.org/ac/ac-common
|
|-- AndroidCast - common | @common | | git://f0xx.org/ac/ac-common
|
||||||
... TBD
|
... TBD
|
||||||
|
|
||||||
|
|
||||||
AndroidCast BE microservices and their derivatives (OpenAPI providers, any REST API services for web and mobile apps)
|
AndroidCast BE microservices and their derivatives (OpenAPI providers, any REST API services for web and mobile apps)
|
||||||
AndroidCast - MS - template <- AndroidCast - MS (@ means suibmodule) |git://f0xx.org/ac/ac-microservice/ac-ms-template
|
AndroidCast - MS - template <- AndroidCast - MS (@ means suibmodule) |git://f0xx.org/ac/ac-microservice/ac-ms-template
|
||||||
|
[AI]: good template for **future** services; seed from url-shortener layout (public/, src/, sql/, tests/)
|
||||||
| -- AndroidCast - common | @common | project all common | git://f0xx.org/ac/ac-common
|
| -- AndroidCast - common | @common | project all common | git://f0xx.org/ac/ac-common
|
||||||
| -- AndroidCast - MS - common | @ms-common | microservices common | git://f0xx.org/ac/ac-microservice/ac-ms-common
|
| -- AndroidCast - MS - common | @ms-common | microservices common | git://f0xx.org/ac/ac-microservice/ac-ms-common
|
||||||
|
[AI]: **ms-common = ac-platform-php + openapi + migration runner** — NOT DB passwords. Fix typo **@ms-commmon** in remote-access row below.
|
||||||
|-- AndroidCast - MS - builder | @ms-builder | builder microservice | git://f0xx.org/ac/ac-microservice/ac-ms-builder
|
|-- AndroidCast - MS - builder | @ms-builder | builder microservice | git://f0xx.org/ac/ac-microservice/ac-ms-builder
|
||||||
|
[AI]: today = **ac-be-builder** (UI+worker in one); split worker when be-build-1 is isolated CI agent only
|
||||||
|-- AndroidCast - MS - VPN | @ms-vpn | VPN microservice | git://f0xx.org/ac/ac-microservice/ac-ms-vpn
|
|-- AndroidCast - MS - VPN | @ms-vpn | VPN microservice | git://f0xx.org/ac/ac-microservice/ac-ms-vpn
|
||||||
|
[AI]: **Phase 6+** — API surface = `remote_access.php` + provisioners; client stays in ac-mobile
|
||||||
|-- AndroidCast - MS - VPN - RSSH | @ms-vpn-rssh | VPN RSSH microservice | git://f0xx.org/ac/ac-microservice/ac-ms-vpn-rssh
|
|-- AndroidCast - MS - VPN - RSSH | @ms-vpn-rssh | VPN RSSH microservice | git://f0xx.org/ac/ac-microservice/ac-ms-vpn-rssh
|
||||||
|-- AndroidCast - MS - VPN - WireGuard | @ms-vpn-wireguard | VPN WG microservice | git://f0xx.org/ac/ac-microservice/ac-ms-vpn-wireguard
|
|-- AndroidCast - MS - VPN - WireGuard | @ms-vpn-wireguard | VPN WG microservice | git://f0xx.org/ac/ac-microservice/ac-ms-vpn-wireguard
|
||||||
|
[AI]: package by directory inside console first: `src/RemoteAccess/Rssh/`, `src/RemoteAccess/WireGuard/`
|
||||||
|-- AndroidCast - MS - VCS | @ms-vcs | VCS microservice (gitea) | git://f0xx.org/ac/ac-microservice/ac-ms-vcs
|
|-- AndroidCast - MS - VCS | @ms-vcs | VCS microservice (gitea) | git://f0xx.org/ac/ac-microservice/ac-ms-vcs
|
||||||
|
[AI]: **Not a code repo** — Gitea is infra; keep **ac-deploy** scripts + docs for Gitea ops
|
||||||
|-- AndroidCast - MS - URL shortener | @ms-url-shortener | URL shortener microservice | git://f0xx.org/ac/ac-microservice/ac-ms-url-shortener
|
|-- AndroidCast - MS - URL shortener | @ms-url-shortener | URL shortener microservice | git://f0xx.org/ac/ac-microservice/ac-ms-url-shortener
|
||||||
|
[AI]: **P2** — flatten path to `git://f0xx.org/ac/ac-ms-url-shortener` (no nested ac-microservice parent)
|
||||||
|-- AndroidCast - MS - analytics | @ms-analytics | analytics microservice | git://f0xx.org/ac/ac-microservice/ac-ms-analytics
|
|-- AndroidCast - MS - analytics | @ms-analytics | analytics microservice | git://f0xx.org/ac/ac-microservice/ac-ms-analytics
|
||||||
|
[AI]: graphs ingest + GA config — module in console until read load warrants split
|
||||||
|-- AndroidCast - MS - orchestration | @ms-orchestration | orchestration microservice | git://f0xx.org/ac/ac-microservice/ac-ms-orchestration
|
|-- AndroidCast - MS - orchestration | @ms-orchestration | orchestration microservice | git://f0xx.org/ac/ac-microservice/ac-ms-orchestration
|
||||||
|
[AI]: today = **ac-deploy** + builder API; name collision with folder `orchestration/`
|
||||||
|-- AndroidCast - MS - tracker | @ms-tracker | issues & tickets tracker microservice | git://f0xx.org/ac/ac-microservice/ac-ms-tracker
|
|-- AndroidCast - MS - tracker | @ms-tracker | issues & tickets tracker microservice | git://f0xx.org/ac/ac-microservice/ac-ms-tracker
|
||||||
|-- AndroidCast - MS - RBAC | @ms-rbac | RBAC microservice | git://f0xx.org/ac/ac-microservice/ac-ms-rbac
|
|-- AndroidCast - MS - RBAC | @ms-rbac | RBAC microservice | git://f0xx.org/ac/ac-microservice/ac-ms-rbac
|
||||||
... TBD
|
... TBD
|
||||||
|
|
||||||
|
|
||||||
Sub-project of AndroidCast BE/MS, mostly Web UI stuff, but also should be OpenAPI-compatible and provide services to the mobile app (@ means suibmodule)
|
Sub-project of AndroidCast BE/MS, mostly Web UI stuff, but also should be OpenAPI-compatible and provide services to the mobile app (@ means suibmodule)
|
||||||
AndroidCast - BE - common <- AndroidCast - BE - MS | git://f0xx.org/ac/ac-backend
|
AndroidCast - BE - common <- AndroidCast - BE - MS | git://f0xx.org/ac/ac-backend
|
||||||
|
[AI]: prefer **per-app repos** (ac-be-console, ac-be-builder, …) over deep `ac-backend/*` tree — flatter Gitea permissions
|
||||||
|-- AndroidCast - BE - vcs (UI) | @be-vcs | VCS frontend, former gitea, backend UI | git://f0xx.org/ac/ac-backend/ac-be-vcs
|
|-- AndroidCast - BE - vcs (UI) | @be-vcs | VCS frontend, former gitea, backend UI | git://f0xx.org/ac/ac-backend/ac-be-vcs
|
||||||
|
[AI]: Gitea **is** the UI — link from hub only; optional thin PHP wrapper if needed
|
||||||
|-- AndroidCast - MS - vcs | @ms-vcs | VCS microservice (gitea) | git://f0xx.org/ac/ac-microservice/ac-ms-vcs
|
|-- AndroidCast - MS - vcs | @ms-vcs | VCS microservice (gitea) | git://f0xx.org/ac/ac-microservice/ac-ms-vcs
|
||||||
|-- AndroidCast - BE - console (UI) | @be-console | git://f0xx.org/ac/ac-backend/ac-be-console | primary console, ./examples/build_console ? check if it is
|
|-- AndroidCast - BE - console (UI) | @be-console | git://f0xx.org/ac/ac-backend/ac-be-console | primary console, ./examples/build_console ? check if it is
|
||||||
|
[AI]: **FIX:** primary console = **ac-be-console** ← `examples/crash_reporter/backend`. Builder = **ac-be-builder** ← `examples/build_console`.
|
||||||
|-- AndroidCast - MS - common | @ms-common | git://f0xx.org/ac/ac-microservice/ac-ms-common | microservice commons
|
|-- AndroidCast - MS - common | @ms-common | git://f0xx.org/ac/ac-microservice/ac-ms-common | microservice commons
|
||||||
|-- AndroidCast - BE - landing (UI) | @be-landing | git://f0xx.org/ac/ac-backend/ac-be-landing | landing page(s), now in ./examples/app_hub
|
|-- AndroidCast - BE - landing (UI) | @be-landing | git://f0xx.org/ac/ac-backend/ac-be-landing | landing page(s), now in ./examples/app_hub
|
||||||
|-- AndroidCast - MS - common | @ms-common | git://f0xx.org/ac/ac-microservice/ac-ms-common | microservice commons
|
|-- AndroidCast - MS - common | @ms-common | git://f0xx.org/ac/ac-microservice/ac-ms-common | microservice commons
|
||||||
|-- AndroidCast - BE - issues (UI) | @be-issues | git://f0xx.org/ac/ac-backend/ac-be-issues | issues tracker UI, now is in ./examples somewhere
|
|-- AndroidCast - BE - issues (UI) | @be-issues | git://f0xx.org/ac/ac-backend/ac-be-issues | issues tracker UI, now is in ./examples somewhere
|
||||||
|
[AI]: **same PHP app** as tickets — `?view=` routes in crash_reporter views, not separate deployable
|
||||||
|-- AndroidCast - MS - tracker | @ms-tracker | git://f0xx.org/ac/ac-microservice/ac-ms-tracker | issues tracker microservice shared base, now is in ./examples somewhere
|
|-- AndroidCast - MS - tracker | @ms-tracker | git://f0xx.org/ac/ac-microservice/ac-ms-tracker | issues tracker microservice shared base, now is in ./examples somewhere
|
||||||
|-- AndroidCast - BE - tickets (UI) | @be-tickets | git://f0xx.org/ac/ac-backend/ac-be-tickets | tickets UI, now is in ./examples somewhere
|
|-- AndroidCast - BE - tickets (UI) | @be-tickets | git://f0xx.org/ac/ac-backend/ac-be-tickets | tickets UI, now is in ./examples somewhere
|
||||||
|-- AndroidCast - MS - tracker | @ms-tracker | git://f0xx.org/ac/ac-microservice/ac-ms-tracker | issues tracker microservice shared base, now is in ./examples somewhere
|
|-- AndroidCast - MS - tracker | @ms-tracker | git://f0xx.org/ac/ac-microservice/ac-ms-tracker | issues tracker microservice shared base, now is in ./examples somewhere
|
||||||
@@ -80,19 +212,345 @@ AndroidCast - BE - common <- AndroidCast - BE - MS | git://f0xx.org
|
|||||||
|-- AndroidCast - MS - analytics | @ms-analytics | git://f0xx.org/ac/ac-microservice/ac-ms-analytics | analytics base microservice
|
|-- AndroidCast - MS - analytics | @ms-analytics | git://f0xx.org/ac/ac-microservice/ac-ms-analytics | analytics base microservice
|
||||||
|-- AndroidCast - BE - analytics (UI) | @be-analytics | git://f0xx.org/ac/ac-backend/ac-be-analytics | analytics and statistics web UI
|
|-- AndroidCast - BE - analytics (UI) | @be-analytics | git://f0xx.org/ac/ac-backend/ac-be-analytics | analytics and statistics web UI
|
||||||
|-- AndroidCast - MS - analytics | @ms-analytics | git://f0xx.org/ac/ac-microservice/ac-ms-analytics | analytics base microservice
|
|-- AndroidCast - MS - analytics | @ms-analytics | git://f0xx.org/ac/ac-microservice/ac-ms-analytics | analytics base microservice
|
||||||
|
[AI]: graphs + analytics UI = views inside **ac-be-console** today (`GraphRepository`, `AnalyticsHead`)
|
||||||
|-- AndroidCast - BE - remote access (UI) | @be-remote-access | git://f0xx.org/ac/ac-backend/ac-be-remote-access | remote access (now for vpn, somewhere in ./examples now) UI
|
|-- AndroidCast - BE - remote access (UI) | @be-remote-access | git://f0xx.org/ac/ac-backend/ac-be-remote-access | remote access (now for vpn, somewhere in ./examples now) UI
|
||||||
|-- AndroidCast - MS - common | @ms-commmon | git://f0xx.org/ac/ac-microservice/ac-ms-common | microservice commons
|
|-- AndroidCast - MS - common | @ms-commmon | git://f0xx.org/ac/ac-microservice/ac-ms-common | microservice commons
|
||||||
|
[AI]: typo **commmon** → common; RA UI = admin views in console + mobile app
|
||||||
|-- AndroidCast - MS - VPN | @ms-vpn | git://f0xx.org/ac/ac-microservice/ac-ms-vpn | VPN MS as a dependency
|
|-- AndroidCast - MS - VPN | @ms-vpn | git://f0xx.org/ac/ac-microservice/ac-ms-vpn | VPN MS as a dependency
|
||||||
|-- AndroidCast - MS - RBAC | @ms-rbac | @ms-rbac | git://f0xx.org/ac/ac-microservice/ac-ms-rbac | RBAC MS as a dependency
|
|-- AndroidCast - MS - RBAC | @ms-rbac | @ms-rbac | git://f0xx.org/ac/ac-microservice/ac-ms-rbac | RBAC MS as a dependency
|
||||||
|-- AndroidCast - BE - URL shortener (UI) | @be-url-shortener | git://f0xx.org/ac/ac-backend/ac-be-url-shortener | URL shortener UI, now is in separate submodule
|
|-- AndroidCast - BE - URL shortener (UI) | @be-url-shortener | git://f0xx.org/ac/ac-backend/ac-be-url-shortener | URL shortener UI, now is in separate submodule
|
||||||
|
[AI]: short-link **admin UI** is in crash console (`short_links.js`, hub tokens); **API** is submodule
|
||||||
|-- AndroidCast - MS - URL shortener | @ms-url-shortener | git://f0xx.org/ac/ac-microservice/ac-ms-url-shortener | URL shortener microservice, now is in separate submodule
|
|-- AndroidCast - MS - URL shortener | @ms-url-shortener | git://f0xx.org/ac/ac-microservice/ac-ms-url-shortener | URL shortener microservice, now is in separate submodule
|
||||||
|-- AndroidCast - BE - builder (UI) | @be-builder | git://f0xx.org/ac/ac-backend/ac-be-builder | orchestration builder UI, now in ./orchestration
|
|-- AndroidCast - BE - builder (UI) | @be-builder | git://f0xx.org/ac/ac-backend/ac-be-builder | orchestration builder UI, now in ./orchestration
|
||||||
|
[AI]: **FIX:** builder UI = `examples/build_console`; **orchestration/** = docker/cluster0 (**ac-deploy**)
|
||||||
|-- AndroidCast - MS - orchestration | @ms-orchestration | git://f0xx.org/ac/ac-microservice/ac-ms-orchestration | orchestration microservice, now in ./orchestration
|
|-- AndroidCast - MS - orchestration | @ms-orchestration | git://f0xx.org/ac/ac-microservice/ac-ms-orchestration | orchestration microservice, now in ./orchestration
|
||||||
|-- AndroidCast - BE - RBAC (UI) | @be-rbac | git://f0xx.org/ac/ac-backend/ac-be-rbac | RBAC backend, now mixed within ./examples somewhere
|
|-- AndroidCast - BE - RBAC (UI) | @be-rbac | git://f0xx.org/ac/ac-backend/ac-be-rbac | RBAC backend, now mixed within ./examples somewhere
|
||||||
|-- AndroidCast - MS - common | @ms-common | git://f0xx.org/ac/ac-microservice/ac-ms-common | microservice commons
|
|-- AndroidCast - MS - common | @ms-common | git://f0xx.org/ac/ac-microservice/ac-ms-common | microservice commons
|
||||||
|-- AndroidCast - MS - RBAC | @ms-rbac | git://f0xx.org/ac/ac-microservice/ac-ms-rbac | RBAC microservice
|
|-- AndroidCast - MS - RBAC | @ms-rbac | git://f0xx.org/ac/ac-microservice/ac-ms-rbac | RBAC microservice
|
||||||
|-- AndroidCast - BE - OTA | @be-ota | git://f0xx.org/ac/ac-backend/ac-be-ota | OTA BE UI
|
|-- AndroidCast - BE - OTA | @be-ota | git://f0xx.org/ac/ac-backend/ac-be-ota | OTA BE UI
|
||||||
|-- AndroidCast - MS - OTA | @ms-ota | git://f0xx.org/ac/ac-microservice/ac-ms-ota | OTA microservice, now intermixed in examples/ota and other commons
|
|-- AndroidCast - MS - OTA | @ms-ota | git://f0xx.org/ac/ac-microservice/ac-ms-ota | OTA microservice, now intermixed in examples/ota and other commons
|
||||||
|
[AI]: OTA = static tree + builder publish + nginx location; extract API when `5.1 OTA` nginx stable
|
||||||
... TBD
|
... TBD
|
||||||
|
|
||||||
ms-common (microservices common) needs a sharp eye, it may share project sensitive parts, like common stuff such as shared web sessions, DB access (or should be separated to @be-db-service/@ac-db-service sort of)
|
ms-common (microservices common) needs a sharp eye, it may share project sensitive parts, like common stuff such as shared web sessions, DB access (or should be separated to @be-db-service/@ac-db-service sort of)
|
||||||
|
[AI]: **Agree.** Recommended split:
|
||||||
|
- **ac-platform-php** — session *code*, Auth, Rbac, PDO factory (reads DSN from env)
|
||||||
|
- **ac-platform-db** — SQL migrations + schema version table (shared by console, builder, shortener)
|
||||||
|
- **ac-deploy/secrets** — example only; prod DSN in vault / Alpine `config.php` outside git
|
||||||
|
- **No ac-db-service runtime** until you run DB proxy (PlanetScale, RDS, etc.) — app VMs use driver + secret
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
G. FULL REPO CATALOG — microservice end-state (Gitea org **ac**)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Canonical clone: `git://f0xx.org/ac/<repo>`. HTTPS UI: `https://apps.f0xx.org/app/androidcast_project/git/ac/<repo>.git` (path TBD with edge migration). Legacy `git://f0xx.org/android_cast` → read-only mirror until retired.
|
||||||
|
|
||||||
|
| ID | Repo | Git URL | Role | From (monolith) | VM / cloud slot |
|
||||||
|
|----|------|---------|------|-----------------|-----------------|
|
||||||
|
| **Workspace** |
|
||||||
|
| W0 | ac-workspace | `git://f0xx.org/ac/ac-workspace` | Optional submodule manifest; **no unified build** | `.gitmodules` | dev laptops |
|
||||||
|
| **Platform** |
|
||||||
|
| P0 | ac-platform-php | `git://f0xx.org/ac/ac-platform-php` | Composer: HTTP, auth client, PDO factory | Auth*, bootstrap | baked into PHP images |
|
||||||
|
| P1 | ac-platform-db | `git://f0xx.org/ac/ac-platform-db` | SQL migrations + schema version | sql/, migrations | be-data-1 job |
|
||||||
|
| P2 | ac-platform-web | `git://f0xx.org/ac/ac-platform-web` | Shared CSS/JS/theme | crashes/assets, hub links | CDN / edge static |
|
||||||
|
| P3 | ac-platform-edge | `git://f0xx.org/ac/ac-platform-edge` | nginx routes, cookie domain, proxy | fe/apps.conf | be-web-1 / FE |
|
||||||
|
| P4 | ac-scripts | `git://f0xx.org/ac/ac-scripts` | CI, OTA, native codec, pdf scripts | `scripts/` | CI runners |
|
||||||
|
| P5 | ac-docs | `git://f0xx.org/ac/ac-docs` | Documentation | `docs/` | — |
|
||||||
|
| **Deploy** |
|
||||||
|
| D0 | ac-deploy | `git://f0xx.org/ac/ac-deploy` | docker, cluster0, env templates; **uses ac-scripts** | `orchestration/` | all VMs |
|
||||||
|
| **Clients** |
|
||||||
|
| C0 | ac-mobile-android | `git://f0xx.org/ac/ac-mobile-android` | Android app + ndk + third-party | `app/`, `ndk/` | CI → OTA |
|
||||||
|
| C1 | ac-mobile-ios | `git://f0xx.org/ac/ac-mobile-ios` | Future iOS client | — | CI → OTA |
|
||||||
|
| C2 | ac-session-studio | `git://f0xx.org/ac/ac-session-studio` | Desktop analyzer | `desktop/session-studio/` | releases |
|
||||||
|
| **Microservices** |
|
||||||
|
| S0 | ac-ms-template | `git://f0xx.org/ac/ac-ms-template` | Cookiecutter for new PHP MS | url-shortener layout | — |
|
||||||
|
| S1 | ac-ms-identity | `git://f0xx.org/ac/ac-ms-identity` | Users, login, register, 2FA, sessions | Auth*, UserRepository | be-web-1 |
|
||||||
|
| S2 | ac-ms-rbac | `git://f0xx.org/ac/ac-ms-rbac` | Companies, memberships, privileges | Rbac*, RbacAdmin* | be-web-1 |
|
||||||
|
| S3 | ac-ms-devices | `git://f0xx.org/ac/ac-ms-devices` | Device registry | DeviceRepository | be-web-1 |
|
||||||
|
| S4 | ac-ms-issues | `git://f0xx.org/ac/ac-ms-issues` | Crash/report ingest + reports API | upload, Report*, Tag* | be-web-1 |
|
||||||
|
| S5 | ac-ms-tickets | `git://f0xx.org/ac/ac-ms-tickets` | Tickets workflow, comments, attachments | Ticket* | be-web-1 |
|
||||||
|
| S6 | ac-ms-graphs | `git://f0xx.org/ac/ac-ms-graphs` | Graph ingest + query | GraphRepository, graph_*.php | be-web-1 |
|
||||||
|
| S7 | ac-ms-remote-access | `git://f0xx.org/ac/ac-ms-remote-access` | WG + RSSH control API | RemoteAccess*, WireGuard*, Rssh* | be-ops-1 |
|
||||||
|
| S8 | ac-ms-url-shortener | `git://f0xx.org/ac/ac-ms-url-shortener` | Short link API | `backend/url-shortener/` | be-ops-1 |
|
||||||
|
| S9 | ac-ms-build | `git://f0xx.org/ac/ac-ms-build` | Build queue + worker API | BuildRunner, build_console APIs | be-build-1 |
|
||||||
|
| S10 | ac-ms-ota | `git://f0xx.org/ac/ac-ms-ota` | Channel manifests; multi-client SHAs | OTA scripts + v0 tree | be-web-1 / OBJ |
|
||||||
|
| S11 | ac-ms-notifications | `git://f0xx.org/ac/ac-ms-notifications` | SMTP / auth mail (post-alpha) | AuthMailer | be-ops-1 |
|
||||||
|
| **Backend UI** |
|
||||||
|
| B0 | ac-be-hub | `git://f0xx.org/ac/ac-be-hub` | Landing | `examples/app_hub/` | be-web-1 |
|
||||||
|
| B1 | ac-be-issues | `git://f0xx.org/ac/ac-be-issues` | Issues UI → S4 | views reports/* | be-web-1 |
|
||||||
|
| B2 | ac-be-tickets | `git://f0xx.org/ac/ac-be-tickets` | Tickets UI → S5 | views tickets/* | be-web-1 |
|
||||||
|
| B3 | ac-be-graphs | `git://f0xx.org/ac/ac-be-graphs` | Graphs dashboard UI | graphs views | be-web-1 |
|
||||||
|
| B4 | ac-be-remote-access | `git://f0xx.org/ac/ac-be-remote-access` | RA admin UI → S7 | view remote_access | be-web-1 |
|
||||||
|
| B5 | ac-be-access | `git://f0xx.org/ac/ac-be-access` | RBAC admin UI → S2 | view rbac | be-web-1 |
|
||||||
|
| B6 | ac-be-builder | `git://f0xx.org/ac/ac-be-builder` | Builder UI → S9 | `examples/build_console/` | be-build-1 |
|
||||||
|
| B7 | ac-be-auth | `git://f0xx.org/ac/ac-be-auth` | Login/register chrome → S1 | login.php shared | be-web-1 |
|
||||||
|
| **Third-party (submodules of C0 only)** |
|
||||||
|
| — | libvpx, opus, speex, wireguard-android | upstream URLs + Gitea mirrors under `ac/` | Native deps | `third-party/*` | mobile CI |
|
||||||
|
| **Future** |
|
||||||
|
| F1 | ac-ms-sfu-signaling | `git://f0xx.org/ac/ac-ms-sfu-signaling` | WebRTC signaling | examples/sfu | be-media-N |
|
||||||
|
| F2 | ac-ms-media-transcode | `git://f0xx.org/ac/ac-ms-media-transcode` | VOD/ffmpeg | V.2x draft | be-media-N |
|
||||||
|
|
||||||
|
**PO draft mapping fixes:**
|
||||||
|
- `ac-be-console` (monolith) → **dissolved** into B1–B5 + S4–S7
|
||||||
|
- `ac-ms-tracker` → **S4 + S5** (issues ≠ tickets — PO URL split confirms)
|
||||||
|
- `ac-ms-analytics` → **S6** (graphs) + later BI; GA config in **P2 platform-web**
|
||||||
|
- `ac-ms-orchestration` → **S9 + D0** (worker vs deploy)
|
||||||
|
- `ac-ms-vcs` → **not a repo**; document in **D0**
|
||||||
|
- `ac-be-url-shortener` UI → admin panel in **B5** or **B0** calling **S8** (tokens already in crashes console)
|
||||||
|
|
||||||
|
**ac-deploy ↔ ac-scripts:** D0 submodules P4 at pinned SHA; deploy-local scripts only for image entrypoints.
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
H. ac-workspace sketch (optional — PO §5)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
**Purpose:** “I only work on mobile” or “I only work on tickets UI” — clone workspace, init selected submodules. **Not** for building everything in one Gradle/PHP tree.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# ac-workspace — example partial checkout
|
||||||
|
[submodule "mobile-android"]
|
||||||
|
path = clients/android
|
||||||
|
url = git://f0xx.org/ac/ac-mobile-android
|
||||||
|
[submodule "ms-issues"]
|
||||||
|
path = services/issues
|
||||||
|
url = git://f0xx.org/ac/ac-ms-issues
|
||||||
|
[submodule "be-issues"]
|
||||||
|
path = ui/issues
|
||||||
|
url = git://f0xx.org/ac/ac-be-issues
|
||||||
|
[submodule "deploy"]
|
||||||
|
path = deploy
|
||||||
|
url = git://f0xx.org/ac/ac-deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
Developers without workspace clone individual repos directly.
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
I. PO decisions (2026-06-18) — recorded
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
| # | Question | Decision |
|
||||||
|
|---|----------|----------|
|
||||||
|
| 1 | Gitea org | **`ac`** |
|
||||||
|
| 2 | Architecture | **Full microservice** end-state (phased delivery) — §K |
|
||||||
|
| 3 | ac-scripts | **Separate repo**; **ac-deploy submodules/invokes it** |
|
||||||
|
| 4 | Public URLs | **Change desired** — path-per-service (§M); not frozen under `/crashes/?view=` |
|
||||||
|
| 5 | Meta repo | **`ac-workspace` optional** — selective clone only; **not** build-all-in-one; §N OTA |
|
||||||
|
| 6 | Cloud | **No K8s requirement** — start VM lift-and-shift (§O); K8s later optional |
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
J. Microservice vs modular monolith — comparison (PO requested)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
| Criterion | Full microservice (PO choice) | Modular monolith (single ac-be-console) |
|
||||||
|
|-----------|------------------------------|----------------------------------------|
|
||||||
|
| **Independent deploy** | Deploy tickets without touching issues | One PHP deploy for any UI fix |
|
||||||
|
| **Team scaling** | Different owners per repo/service | Single codebase, simpler grep |
|
||||||
|
| **Failure isolation** | Ticket MS down ≠ crash ingest | Shared FPM pool — blast radius larger |
|
||||||
|
| **CI speed** | Small repos, faster per-service tests | One `./gradlew`-scale PHP test suite |
|
||||||
|
| **Auth/session** | Needs **ac-ms-identity** + edge cookie early | Already works (`ac_crash_sess`) |
|
||||||
|
| **DB** | Shared MariaDB OK initially; schema ownership per MS | One migration stream today |
|
||||||
|
| **Operational cost** | More nginx routes, more images, more logs | One BE tree to sync ([INFRA.md](../INFRA.md)) |
|
||||||
|
| **PO microservice learning curve** | Higher — mitigated by **ac-ms-template**, **D0** | Lower short-term |
|
||||||
|
| **Cloud path** | Maps 1:1 to VM roles then containers | Lift whole monolith first |
|
||||||
|
| **URL clarity** | Natural `/issues/`, `/tickets/` per service | Stuck on `?view=` without refactor |
|
||||||
|
| **Rollback** | Revert one service SHA | Revert one monolith commit |
|
||||||
|
| **Risk for alpha** | **Higher** if done before identity split | Lower — current prod pattern |
|
||||||
|
|
||||||
|
[AI]: PO choice is valid **if P3 (identity + platform) is non-negotiable first**. Without it, full MS split repeats the build-console/bootstrap.php class of failures across N repos.
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
K. Service dependency graph (logical + structural + repo)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph clients [Clients]
|
||||||
|
AND[ac-mobile-android]
|
||||||
|
IOS[ac-mobile-ios future]
|
||||||
|
HUB[ac-be-hub]
|
||||||
|
SS[ac-session-studio]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph edge [Edge]
|
||||||
|
EDGE[ac-platform-edge]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph core [Core platform MS]
|
||||||
|
ID[ac-ms-identity]
|
||||||
|
RB[ac-ms-rbac]
|
||||||
|
DV[ac-ms-devices]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph domain [Domain MS]
|
||||||
|
IS[ac-ms-issues]
|
||||||
|
TK[ac-ms-tickets]
|
||||||
|
GR[ac-ms-graphs]
|
||||||
|
RA[ac-ms-remote-access]
|
||||||
|
UL[ac-ms-url-shortener]
|
||||||
|
BL[ac-ms-build]
|
||||||
|
OT[ac-ms-ota]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph ui [Backend UI]
|
||||||
|
UI_I[ac-be-issues]
|
||||||
|
UI_T[ac-be-tickets]
|
||||||
|
UI_G[ac-be-graphs]
|
||||||
|
UI_R[ac-be-remote-access]
|
||||||
|
UI_A[ac-be-access]
|
||||||
|
UI_B[ac-be-builder]
|
||||||
|
end
|
||||||
|
|
||||||
|
AND -->|upload API| EDGE
|
||||||
|
AND -->|RA API| EDGE
|
||||||
|
AND -->|graph upload| EDGE
|
||||||
|
HUB --> EDGE
|
||||||
|
UI_I & UI_T & UI_G & UI_R & UI_A & UI_B --> EDGE
|
||||||
|
EDGE --> ID & IS & TK & GR & RA & UL & BL & OT & RB
|
||||||
|
|
||||||
|
ID --> RB
|
||||||
|
RB --> DV
|
||||||
|
IS --> DV
|
||||||
|
IS --> RB
|
||||||
|
TK --> RB
|
||||||
|
TK -.->|optional link| IS
|
||||||
|
GR --> ID
|
||||||
|
RA --> RB
|
||||||
|
RA --> DV
|
||||||
|
RA -.->|issue deep links| IS
|
||||||
|
BL --> ID
|
||||||
|
BL -->|git clone| GITEA[(Gitea infra)]
|
||||||
|
OT --> BL
|
||||||
|
OT --> AND
|
||||||
|
OT --> IOS
|
||||||
|
UL --> RB
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dependency rules (enforce in DR/SPEC):**
|
||||||
|
|
||||||
|
| Service | Hard depends on | Soft / read depends on | Mobile / public API |
|
||||||
|
|---------|-----------------|------------------------|---------------------|
|
||||||
|
| ac-ms-identity | ac-platform-db (users schema) | ac-ms-notifications (mail) | register, login |
|
||||||
|
| ac-ms-rbac | identity (JWT/session validation) | — | — |
|
||||||
|
| ac-ms-devices | rbac (company scope) | identity | — |
|
||||||
|
| ac-ms-issues | devices, rbac | tags catalog | **upload.php** |
|
||||||
|
| ac-ms-tickets | rbac | issues (link ticket↔report) | — |
|
||||||
|
| ac-ms-graphs | identity | — | graph_upload |
|
||||||
|
| ac-ms-remote-access | rbac, devices | issues (device context URLs) | remote_access.php |
|
||||||
|
| ac-ms-url-shortener | rbac (API tokens) | — | s.f0xx.org |
|
||||||
|
| ac-ms-build | identity | gitea mirror | — |
|
||||||
|
| ac-ms-ota | build artifacts | mobile/ios SHAs | `/v0/ota/` |
|
||||||
|
| ac-be-hub | identity (session for nav) | **P2 platform-web** assets | — |
|
||||||
|
|
||||||
|
**Cross-cutting fixes vs PO draft:**
|
||||||
|
|
||||||
|
1. **Auth must be first MS** — every UI and API currently includes `bootstrap.php` → Auth.
|
||||||
|
2. **Devices before issues** — upload auto-registers device + company ([README](../examples/crash_reporter/backend/README.md) RBAC).
|
||||||
|
3. **Hub → crashes assets** — today `app_hub/index.php` loads `/crashes/assets/css/app.css`; extract **ac-platform-web** before splitting hub.
|
||||||
|
4. **Builder → same DB users** — build_console uses `androidcast_crashes.users`; cannot split S9 before S1.
|
||||||
|
5. **Short links admin** — UI in monolith, API in submodule; UI calls S8, not duplicate repo **ac-be-url-shortener**.
|
||||||
|
6. **Graphs** — already separate nginx location `/graphs/` ([apps.conf](../../examples/crash_reporter/backend/nginx/fe/apps.conf)) — good MS boundary.
|
||||||
|
|
||||||
|
**Suggested new intermediates (actors):**
|
||||||
|
|
||||||
|
| Service | Why |
|
||||||
|
|---------|-----|
|
||||||
|
| **ac-ms-identity** | Single login/register/2FA; avoids 8 copies of Auth.php |
|
||||||
|
| **ac-ms-devices** | Shared by issues, RA, graphs device keys |
|
||||||
|
| **ac-platform-edge** | One nginx config for PO’s URL map + SSO cookie |
|
||||||
|
| **ac-ms-ota** | PO multi-repo Android+iOS same version (§N) |
|
||||||
|
| **ac-ms-notifications** | Decouple SMTP from identity (task 2.x mail frozen) |
|
||||||
|
| **ac-ms-attachments** (later) | Ticket files → object store when > local disk |
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
L. Public URL migration map (PO §4)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Base prefix (discussion): keep **`/app/androidcast_project/`** for alpha compatibility, or shorten to **`/app/ac/`** later. Below uses PO’s intent under current prefix.
|
||||||
|
|
||||||
|
| Today | Target path | MS / UI | Notes |
|
||||||
|
|-------|-------------|---------|-------|
|
||||||
|
| `/crashes/?view=reports` | **`/issues/`** | B1 → S4 | “Issue tracker” / crash reports list |
|
||||||
|
| `/crashes/?view=report&id=N` | **`/issues/N`** or `/issues/report/N` | B1 | Detail |
|
||||||
|
| `/crashes/?view=tickets` | **`/tickets/`** | B2 → S5 | Tickets catalog |
|
||||||
|
| `/crashes/?view=ticket&id=N` | **`/tickets/N`** | B2 | Detail |
|
||||||
|
| `/crashes/?view=rbac` | **`/access/`** | B5 → S2 | RBAC admin |
|
||||||
|
| `/crashes/?view=remote_access` | **`/remote-access/`** | B4 → S7 | RA admin |
|
||||||
|
| `/crashes/?view=short_links` | **`/short-links/`** or hub panel | B0/B5 → S8 | API stays on s.f0xx.org |
|
||||||
|
| `/crashes/?view=home` | **`/`** hub or `/console/` | B0 | Retire crashes “home” |
|
||||||
|
| `/crashes/api/upload.php` | **`/issues/api/upload`** or `/api/v1/issues/upload` | S4 | Update `BackendEndpoints.java`, `CrashSettings.java` |
|
||||||
|
| `/crashes/api/*` (tickets) | **`/tickets/api/*`** | S5 | |
|
||||||
|
| `/graphs/` | **`/graphs/`** (keep) | B3 → S6 | Already separate vhost block |
|
||||||
|
| `/build/` | **`/build/`** (keep) | B6 → S9 | |
|
||||||
|
| `/` (hub) | **`/`** (keep) | B0 | |
|
||||||
|
| `/git/` | **`/git/`** (keep) | Gitea | Not a MS |
|
||||||
|
| `/v0/ota/` | **`/v0/ota/`** (keep) | S10 | |
|
||||||
|
|
||||||
|
**Migration mechanics:** ac-platform-edge 301 redirects old URLs ≥ 1 release; mobile app legacy URL rewrite already in `BackendEndpoints.normalizeUploadUrl()`.
|
||||||
|
|
||||||
|
**Naming note:** Today “reports” in code = **issues/crashes** in product language — align UI strings when moving to `/issues/`.
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
M. ac-workspace + OTA multi-repo versioning (PO §5)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
**ac-workspace is NOT:**
|
||||||
|
- A single Gradle/Composer build root
|
||||||
|
- Required for CI (each repo has own pipeline)
|
||||||
|
|
||||||
|
**ac-workspace IS:**
|
||||||
|
- Optional manifest: “which repos at which SHA for a release train”
|
||||||
|
- Convenience for developers who want `clients/android` + `services/issues` side by side
|
||||||
|
|
||||||
|
**OTA same version, different client SHAs (Android + iOS):**
|
||||||
|
|
||||||
|
`ac-ms-ota` channel manifest example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channel": "staging",
|
||||||
|
"version": "00.02.00.00",
|
||||||
|
"artifacts": {
|
||||||
|
"android": { "repo": "ac-mobile-android", "git_sha": "abc123", "apk_url": "..." },
|
||||||
|
"ios": { "repo": "ac-mobile-ios", "git_sha": "def456", "ipa_url": "..." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Builder (S9) triggers per-repo builds; **S10** assembles manifest. Mobile apps read **`version`** for UX; **`git_sha`** per platform for support.
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
N. Cloud & VM strategy (PO §6 — no K8s experience)
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
**Recommendation: Phase 1 = VM lift-and-shift** (matches cluster0 lab, scaling SPEC §6.3, PO skill set).
|
||||||
|
|
||||||
|
| Stage | Where | Pattern |
|
||||||
|
|-------|-------|---------|
|
||||||
|
| **Now** | FE + BE Alpine | nginx + PHP-FPM + MariaDB ([INFRA.md](../INFRA.md)) |
|
||||||
|
| **Lab** | cast01–03 | GTID MariaDB, NFS config only, apps local ([cluster0 ARCHITECTURE](../../orchestration/sim/cluster0/ARCHITECTURE.md)) |
|
||||||
|
| **Cloud phase A** | 4–6 VMs (be-web, be-data, be-ops, be-build) | Same as scaling table; **docker only on build** |
|
||||||
|
| **Cloud phase B** | Managed DB (RDS-class) | Apps use DSN secret; **ac-platform-db** migrations as job |
|
||||||
|
| **Cloud phase C** (optional) | K8s / compose swarm | **Not required** — introduce when MS count > ~8 and PO wants auto-scale |
|
||||||
|
|
||||||
|
**ac-deploy deliverables for cloud:** Terraform optional; minimum **Ansible + docker-compose fragments per VM role** + env templates. Each MS = one PHP-FPM pool or one container behind edge nginx.
|
||||||
|
|
||||||
|
**Inter-service calls (PO poor on microservices):** Start with **HTTP on localhost/private VLAN** (same BE host), not service mesh. Edge nginx routes public paths; internal `127.0.0.1:900x` or docker network. OpenAPI spec in **ac-docs/openapi/** is the contract.
|
||||||
|
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
O. DR readiness checklist
|
||||||
|
──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
- [x] Map monolith → full MS catalog (§G)
|
||||||
|
- [x] PO decisions recorded (§I)
|
||||||
|
- [x] Comparison table (§J)
|
||||||
|
- [x] Dependency graph + fixes (§K)
|
||||||
|
- [x] URL migration map (§L)
|
||||||
|
- [x] Workspace / OTA model (§M)
|
||||||
|
- [x] Cloud recommendation (§N)
|
||||||
|
- [ ] PO confirms URL prefix (`/app/androidcast_project/` vs `/app/ac/`) — **locked: full prefix**
|
||||||
|
- [ ] PO confirms **ac-ms-identity** as P3 gate — **approved**
|
||||||
|
- [x] Write `docs/DRs/20260618_repos_reorganizing.md` + PDF
|
||||||
|
- [ ] Update gitea migrate scripts for org **`ac`**
|
||||||
|
- [ ] Migration runbook P0–P7
|
||||||
|
|
||||||
|
[AI]: **Create Gitea repos from §G table in waves:** Wave 1 = P4,P5,D0,C2,S8; Wave 2 = S1,S2,S3; Wave 3 = S4,S5 + B1,B2; then edge + URL migration.
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
51 0 obj
|
51 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220502+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220502+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (URL shortener service \204 specification) /Trapped /False
|
/Subject (\(unspecified\)) /Title (URL shortener service \204 specification) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -647,7 +647,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<db651936781475f5cdf0f7bf03422a2e><db651936781475f5cdf0f7bf03422a2e>]
|
[<70460d5cbd1dab2ea3f548e883e3e39b><70460d5cbd1dab2ea3f548e883e3e39b>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 51 0 R
|
/Info 51 0 R
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ endobj
|
|||||||
endobj
|
endobj
|
||||||
67 0 obj
|
67 0 obj
|
||||||
<<
|
<<
|
||||||
/Author (Android Cast project) /CreationDate (D:20260617220503+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260617220503+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
/Author (Android Cast project) /CreationDate (D:20260618174834+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260618174834+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
/Subject (\(unspecified\)) /Title (Platform scaling and multi-environment architecture \204 specification) /Trapped /False
|
/Subject (\(unspecified\)) /Title (Platform scaling and multi-environment architecture \204 specification) /Trapped /False
|
||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
@@ -862,7 +862,7 @@ xref
|
|||||||
trailer
|
trailer
|
||||||
<<
|
<<
|
||||||
/ID
|
/ID
|
||||||
[<f0fbbf50d8dfd95b3b0f2c0df4aae3a2><f0fbbf50d8dfd95b3b0f2c0df4aae3a2>]
|
[<09178fbdf1e2842e537b65a31d547b84><09178fbdf1e2842e537b65a31d547b84>]
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
/Info 67 0 R
|
/Info 67 0 R
|
||||||
|
|||||||
@@ -100,3 +100,19 @@ find_library(log-lib log)
|
|||||||
find_library(android-lib android)
|
find_library(android-lib android)
|
||||||
find_library(dl-lib dl)
|
find_library(dl-lib dl)
|
||||||
target_link_libraries(androidcast_codecs ${log-lib} ${android-lib} ${dl-lib})
|
target_link_libraries(androidcast_codecs ${log-lib} ${android-lib} ${dl-lib})
|
||||||
|
|
||||||
|
# Per-account obfuscated entitlement flags (separate .so; safe to omit on load failure).
|
||||||
|
set(OBFUSCATE_ROOT ${CMAKE_SOURCE_DIR}/obfuscate)
|
||||||
|
|
||||||
|
add_library(androidcast_entitlements SHARED
|
||||||
|
${NDK_BRIDGE}/jni/entitlements_jni.c
|
||||||
|
${OBFUSCATE_ROOT}/obfuscate.c
|
||||||
|
${OBFUSCATE_ROOT}/obfuscate_file.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(androidcast_entitlements PRIVATE
|
||||||
|
${OBFUSCATE_ROOT}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_options(androidcast_entitlements PRIVATE -std=c11 -Wall -Wextra)
|
||||||
|
target_link_libraries(androidcast_entitlements ${log-lib})
|
||||||
|
|||||||
103
ndk/jni/entitlements_jni.c
Normal file
103
ndk/jni/entitlements_jni.c
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#include <jni.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "obfuscate.h"
|
||||||
|
#include "obfuscate_file.h"
|
||||||
|
|
||||||
|
static jint throw_illegal_state(JNIEnv *env, const char *msg)
|
||||||
|
{
|
||||||
|
jclass cls = (*env)->FindClass(env, "java/lang/IllegalStateException");
|
||||||
|
if (cls != NULL)
|
||||||
|
(*env)->ThrowNew(env, cls, msg);
|
||||||
|
return OBF_ERR_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL
|
||||||
|
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeReadFrom(
|
||||||
|
JNIEnv *env, jclass clazz, jstring path, jlong key, jlongArray out_val)
|
||||||
|
{
|
||||||
|
const char *cpath;
|
||||||
|
uint64_t decoded = 0;
|
||||||
|
int32_t rv;
|
||||||
|
jlong slot;
|
||||||
|
|
||||||
|
(void)clazz;
|
||||||
|
if (path == NULL || out_val == NULL)
|
||||||
|
return throw_illegal_state(env, "path or out_val is null");
|
||||||
|
|
||||||
|
cpath = (*env)->GetStringUTFChars(env, path, NULL);
|
||||||
|
if (cpath == NULL)
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
|
||||||
|
rv = read_from(cpath, (uint64_t)key, &decoded);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, path, cpath);
|
||||||
|
if (rv != OBF_OK)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
slot = (jlong)decoded;
|
||||||
|
(*env)->SetLongArrayRegion(env, out_val, 0, 1, &slot);
|
||||||
|
return OBF_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL
|
||||||
|
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeWriteTo(
|
||||||
|
JNIEnv *env, jclass clazz, jstring path, jlong val, jlong key)
|
||||||
|
{
|
||||||
|
const char *cpath;
|
||||||
|
int32_t rv;
|
||||||
|
|
||||||
|
(void)clazz;
|
||||||
|
if (path == NULL)
|
||||||
|
return throw_illegal_state(env, "path is null");
|
||||||
|
|
||||||
|
cpath = (*env)->GetStringUTFChars(env, path, NULL);
|
||||||
|
if (cpath == NULL)
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
|
||||||
|
rv = write_to(cpath, (uint64_t)val, (uint64_t)key);
|
||||||
|
(*env)->ReleaseStringUTFChars(env, path, cpath);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL
|
||||||
|
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeDecode(
|
||||||
|
JNIEnv *env, jclass clazz, jlong encoded_val, jlong key, jlongArray out_val)
|
||||||
|
{
|
||||||
|
uint64_t decoded = 0;
|
||||||
|
int32_t rv;
|
||||||
|
jlong slot;
|
||||||
|
|
||||||
|
(void)clazz;
|
||||||
|
if (out_val == NULL)
|
||||||
|
return throw_illegal_state(env, "out_val is null");
|
||||||
|
|
||||||
|
rv = decode((uint64_t)encoded_val, (uint64_t)key, &decoded);
|
||||||
|
if (rv != OBF_OK)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
slot = (jlong)decoded;
|
||||||
|
(*env)->SetLongArrayRegion(env, out_val, 0, 1, &slot);
|
||||||
|
return OBF_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL
|
||||||
|
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeEncode(
|
||||||
|
JNIEnv *env, jclass clazz, jlong val, jlong key, jlongArray out_encoded)
|
||||||
|
{
|
||||||
|
uint64_t encoded = 0;
|
||||||
|
int32_t rv;
|
||||||
|
jlong slot;
|
||||||
|
|
||||||
|
(void)clazz;
|
||||||
|
if (out_encoded == NULL)
|
||||||
|
return throw_illegal_state(env, "out_encoded is null");
|
||||||
|
|
||||||
|
rv = encode((uint64_t)val, (uint64_t)key, &encoded);
|
||||||
|
if (rv != OBF_OK)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
slot = (jlong)encoded;
|
||||||
|
(*env)->SetLongArrayRegion(env, out_encoded, 0, 1, &slot);
|
||||||
|
return OBF_OK;
|
||||||
|
}
|
||||||
8
ndk/obfuscate/README.md
Normal file
8
ndk/obfuscate/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Entitlements obfuscation (C)
|
||||||
|
|
||||||
|
Shared by `libandroidcast_entitlements.so` and the archived spike under `reference-design/obfuscate_entitlements/`.
|
||||||
|
|
||||||
|
- `encode` / `decode` — 40-bit payload + 24-bit verification tag, keyed by per-account `uint64_t` **key** (not wall clock).
|
||||||
|
- `write_to` / `read_from` — file layout: 32-bit dummy header, N×uint64_t payloads, 32-bit dummy tail.
|
||||||
|
|
||||||
|
Reference demo: `tmp/obfuscate_test/` (standalone; not wired into the main app).
|
||||||
122
ndk/obfuscate/obfuscate.c
Normal file
122
ndk/obfuscate/obfuscate.c
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#include "obfuscate.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
static uint16_t rotl16(uint16_t x, unsigned int n)
|
||||||
|
{
|
||||||
|
n &= 15u;
|
||||||
|
return (uint16_t)((x << n) | (x >> (16u - n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t rotl40(uint64_t x, unsigned int n)
|
||||||
|
{
|
||||||
|
n %= OBF_PAYLOAD_BITS;
|
||||||
|
x &= OBF_PAYLOAD_MASK;
|
||||||
|
if (n == 0u)
|
||||||
|
return x;
|
||||||
|
return ((x << n) | (x >> (OBF_PAYLOAD_BITS - n))) & OBF_PAYLOAD_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t rotr40(uint64_t x, unsigned int n)
|
||||||
|
{
|
||||||
|
n %= OBF_PAYLOAD_BITS;
|
||||||
|
x &= OBF_PAYLOAD_MASK;
|
||||||
|
if (n == 0u)
|
||||||
|
return x;
|
||||||
|
return ((x >> n) | (x << (OBF_PAYLOAD_BITS - n))) & OBF_PAYLOAD_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lightweight seed mixer — same output for same seed on any platform. */
|
||||||
|
static uint64_t derive_key(uint64_t seed)
|
||||||
|
{
|
||||||
|
uint64_t x = seed ^ 0xA5A5A5A5A5A5A5A5ULL;
|
||||||
|
|
||||||
|
x ^= x >> 33;
|
||||||
|
x *= 0xFF51AFD7ED558CCDULL;
|
||||||
|
x ^= x >> 33;
|
||||||
|
x *= 0xC4CEB9FE1A85EC53ULL;
|
||||||
|
x ^= x >> 33;
|
||||||
|
return x & OBF_PAYLOAD_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int derive_rotation(uint64_t seed)
|
||||||
|
{
|
||||||
|
uint64_t mix = seed * 0x9E3779B97F4A7C15ULL;
|
||||||
|
|
||||||
|
return (unsigned int)((mix >> 59) + 1u); /* 1..8 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 24-bit FEC-style tag: four 16-bit shards XOR-mixed with key and seed.
|
||||||
|
* Detects single-bit flips and wrong seed with high probability.
|
||||||
|
*/
|
||||||
|
static uint32_t compute_tag(uint64_t val, uint64_t seed)
|
||||||
|
{
|
||||||
|
uint64_t k = derive_key(seed);
|
||||||
|
uint64_t a = (val & 0xFFFFULL) ^ (k & 0xFFFFULL);
|
||||||
|
uint64_t b = ((val >> 16) & 0xFFFFULL) ^ ((k >> 16) & 0xFFFFULL);
|
||||||
|
uint64_t c = ((val >> 32) & 0xFFFFULL) ^ ((k >> 32) & 0xFFFFULL);
|
||||||
|
uint64_t d = ((val >> 48) & 0xFFFFULL) ^ ((k >> 48) & 0xFFFFULL);
|
||||||
|
uint64_t p1 = a ^ rotl16((uint16_t)b, 3) ^ c;
|
||||||
|
uint64_t p2 = b ^ rotl16((uint16_t)c, 7) ^ d;
|
||||||
|
uint64_t p3 = c ^ rotl16((uint16_t)d, 11) ^ a;
|
||||||
|
uint64_t mix = (p1 * 0x01000193ULL) ^ (p2 << 16) ^ (p3 << 8);
|
||||||
|
|
||||||
|
mix ^= seed;
|
||||||
|
mix *= 0x85EBCA77C2B2AE63ULL;
|
||||||
|
mix ^= mix >> 33;
|
||||||
|
return (uint32_t)(mix & 0xFFFFFFULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t obfuscate(uint64_t val, uint64_t seed)
|
||||||
|
{
|
||||||
|
uint64_t key = derive_key(seed);
|
||||||
|
unsigned int rot = derive_rotation(seed);
|
||||||
|
|
||||||
|
return rotl40(val & OBF_PAYLOAD_MASK, rot) ^ key;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t deobfuscate(uint64_t obscured, uint64_t seed)
|
||||||
|
{
|
||||||
|
uint64_t key = derive_key(seed);
|
||||||
|
unsigned int rot = derive_rotation(seed);
|
||||||
|
|
||||||
|
return rotr40(obscured ^ key, rot);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t encode(uint64_t val, uint64_t seed, uint64_t *out)
|
||||||
|
{
|
||||||
|
uint64_t packed;
|
||||||
|
uint32_t tag;
|
||||||
|
|
||||||
|
if (out == NULL)
|
||||||
|
return OBF_ERR_NULL;
|
||||||
|
if (val > OBF_PAYLOAD_MASK)
|
||||||
|
return OBF_ERR_RANGE;
|
||||||
|
|
||||||
|
tag = compute_tag(val, seed);
|
||||||
|
packed = ((uint64_t)tag << OBF_PAYLOAD_BITS) | obfuscate(val, seed);
|
||||||
|
*out = packed;
|
||||||
|
return OBF_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t decode(uint64_t encoded_val, uint64_t seed, uint64_t *out)
|
||||||
|
{
|
||||||
|
uint32_t stored_tag;
|
||||||
|
uint32_t expected_tag;
|
||||||
|
uint64_t obscured;
|
||||||
|
uint64_t val;
|
||||||
|
|
||||||
|
if (out == NULL)
|
||||||
|
return OBF_ERR_NULL;
|
||||||
|
|
||||||
|
stored_tag = (uint32_t)(encoded_val >> OBF_PAYLOAD_BITS);
|
||||||
|
obscured = encoded_val & OBF_PAYLOAD_MASK;
|
||||||
|
val = deobfuscate(obscured, seed);
|
||||||
|
expected_tag = compute_tag(val, seed);
|
||||||
|
|
||||||
|
if (stored_tag != expected_tag)
|
||||||
|
return OBF_ERR_VERIFY;
|
||||||
|
|
||||||
|
*out = val;
|
||||||
|
return OBF_OK;
|
||||||
|
}
|
||||||
30
ndk/obfuscate/obfuscate.h
Normal file
30
ndk/obfuscate/obfuscate.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef OBFUSCATE_H
|
||||||
|
#define OBFUSCATE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Payload uses lower 40 bits; upper 24 bits hold verification tag. */
|
||||||
|
#define OBF_PAYLOAD_BITS 40u
|
||||||
|
#define OBF_PAYLOAD_MASK ((1ULL << OBF_PAYLOAD_BITS) - 1ULL)
|
||||||
|
|
||||||
|
/* Return codes */
|
||||||
|
#define OBF_OK 0
|
||||||
|
#define OBF_ERR_NULL -1 /* out pointer is NULL */
|
||||||
|
#define OBF_ERR_RANGE -2 /* val exceeds encodable payload width */
|
||||||
|
#define OBF_ERR_VERIFY -3 /* tag mismatch (wrong seed or corrupted data) */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Encode uint64_t payload (e.g. flag bits) using seed-derived keystream.
|
||||||
|
* seed is typically unix time in whole seconds shared by encoder and decoder.
|
||||||
|
*
|
||||||
|
* On success writes single uint64_t to *out and returns OBF_OK.
|
||||||
|
*/
|
||||||
|
int32_t encode(uint64_t val, uint64_t seed, uint64_t *out);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decode and verify. seed must match the value used at encode time.
|
||||||
|
* On success writes recovered payload to *out and returns OBF_OK.
|
||||||
|
*/
|
||||||
|
int32_t decode(uint64_t encoded_val, uint64_t seed, uint64_t *out);
|
||||||
|
|
||||||
|
#endif /* OBFUSCATE_H */
|
||||||
215
ndk/obfuscate/obfuscate_file.c
Normal file
215
ndk/obfuscate/obfuscate_file.c
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
#include "obfuscate_file.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static uint32_t dummy_u32(uint64_t seed, uint32_t salt)
|
||||||
|
{
|
||||||
|
uint64_t x = seed ^ ((uint64_t)salt * 0x9E3779B97F4A7C15ULL);
|
||||||
|
|
||||||
|
x ^= x >> 33;
|
||||||
|
x *= 0xFF51AFD7ED558CCDULL;
|
||||||
|
x ^= x >> 33;
|
||||||
|
return (uint32_t)(x ^ (x >> 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_u32_le(uint8_t *dst, uint32_t v)
|
||||||
|
{
|
||||||
|
dst[0] = (uint8_t)(v);
|
||||||
|
dst[1] = (uint8_t)(v >> 8);
|
||||||
|
dst[2] = (uint8_t)(v >> 16);
|
||||||
|
dst[3] = (uint8_t)(v >> 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_u64_le(uint8_t *dst, uint64_t v)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < 8u; i++)
|
||||||
|
dst[i] = (uint8_t)(v >> (8u * i));
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t get_u32_le(const uint8_t *src)
|
||||||
|
{
|
||||||
|
return (uint32_t)src[0] | ((uint32_t)src[1] << 8) | ((uint32_t)src[2] << 16) |
|
||||||
|
((uint32_t)src[3] << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t get_u64_le(const uint8_t *src)
|
||||||
|
{
|
||||||
|
uint64_t v = 0;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < 8u; i++)
|
||||||
|
v |= (uint64_t)src[i] << (8u * i);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t obf_file_payload_count(long file_size, size_t *count_out)
|
||||||
|
{
|
||||||
|
long payload_bytes;
|
||||||
|
|
||||||
|
if (file_size < 0)
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
if ((unsigned long)file_size < OBF_FILE_OVERHEAD + sizeof(uint64_t))
|
||||||
|
return OBF_ERR_FORMAT;
|
||||||
|
|
||||||
|
payload_bytes = file_size - (long)OBF_FILE_OVERHEAD;
|
||||||
|
if (payload_bytes % (long)sizeof(uint64_t) != 0)
|
||||||
|
return OBF_ERR_FORMAT;
|
||||||
|
|
||||||
|
*count_out = (size_t)payload_bytes / sizeof(uint64_t);
|
||||||
|
return OBF_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t write_encoded_file(const char *path, const uint64_t *encoded, size_t count,
|
||||||
|
uint64_t seed)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
uint8_t hdr[OBF_FILE_HDR_SIZE];
|
||||||
|
uint8_t tail[OBF_FILE_TAIL_SIZE];
|
||||||
|
uint8_t block[sizeof(uint64_t)];
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (path == NULL || encoded == NULL || count == 0)
|
||||||
|
return OBF_ERR_NULL;
|
||||||
|
|
||||||
|
fp = fopen(path, "wb");
|
||||||
|
if (fp == NULL)
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
|
||||||
|
put_u32_le(hdr, dummy_u32(seed, 0xA11CEu));
|
||||||
|
if (fwrite(hdr, 1, sizeof(hdr), fp) != sizeof(hdr)) {
|
||||||
|
fclose(fp);
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
put_u64_le(block, encoded[i]);
|
||||||
|
if (fwrite(block, 1, sizeof(block), fp) != sizeof(block)) {
|
||||||
|
fclose(fp);
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
put_u32_le(tail, dummy_u32(seed, 0x7A1Lu));
|
||||||
|
if (fwrite(tail, 1, sizeof(tail), fp) != sizeof(tail)) {
|
||||||
|
fclose(fp);
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fclose(fp) != 0)
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
return OBF_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t write_many_to(const char *path, const uint64_t *vals, size_t count, uint64_t seed)
|
||||||
|
{
|
||||||
|
uint64_t *encoded;
|
||||||
|
size_t i;
|
||||||
|
int32_t rv;
|
||||||
|
|
||||||
|
if (path == NULL || vals == NULL || count == 0)
|
||||||
|
return OBF_ERR_NULL;
|
||||||
|
|
||||||
|
encoded = (uint64_t *)malloc(count * sizeof(uint64_t));
|
||||||
|
if (encoded == NULL)
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
rv = encode(vals[i], seed, &encoded[i]);
|
||||||
|
if (rv != OBF_OK) {
|
||||||
|
free(encoded);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = write_encoded_file(path, encoded, count, seed);
|
||||||
|
free(encoded);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t write_to(const char *path, uint64_t val, uint64_t seed)
|
||||||
|
{
|
||||||
|
return write_many_to(path, &val, 1, seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t read_many_from(const char *path, uint64_t seed, uint64_t *out_vals, size_t out_cap,
|
||||||
|
size_t *out_count)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
long file_size;
|
||||||
|
size_t count;
|
||||||
|
size_t i;
|
||||||
|
uint8_t hdr[OBF_FILE_HDR_SIZE];
|
||||||
|
uint8_t tail[OBF_FILE_TAIL_SIZE];
|
||||||
|
uint8_t block[sizeof(uint64_t)];
|
||||||
|
uint64_t encoded;
|
||||||
|
uint64_t decoded;
|
||||||
|
int32_t rv;
|
||||||
|
|
||||||
|
if (path == NULL || out_vals == NULL || out_count == NULL)
|
||||||
|
return OBF_ERR_NULL;
|
||||||
|
|
||||||
|
fp = fopen(path, "rb");
|
||||||
|
if (fp == NULL)
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
|
||||||
|
if (fseek(fp, 0, SEEK_END) != 0) {
|
||||||
|
fclose(fp);
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
}
|
||||||
|
file_size = ftell(fp);
|
||||||
|
if (file_size < 0 || fseek(fp, 0, SEEK_SET) != 0) {
|
||||||
|
fclose(fp);
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = obf_file_payload_count(file_size, &count);
|
||||||
|
if (rv != OBF_OK) {
|
||||||
|
fclose(fp);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
if (count > out_cap) {
|
||||||
|
fclose(fp);
|
||||||
|
return OBF_ERR_OVERFLOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fread(hdr, 1, sizeof(hdr), fp) != sizeof(hdr)) {
|
||||||
|
fclose(fp);
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
}
|
||||||
|
(void)get_u32_le(hdr);
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
if (fread(block, 1, sizeof(block), fp) != sizeof(block)) {
|
||||||
|
fclose(fp);
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
}
|
||||||
|
encoded = get_u64_le(block);
|
||||||
|
rv = decode(encoded, seed, &decoded);
|
||||||
|
if (rv != OBF_OK) {
|
||||||
|
fclose(fp);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
out_vals[i] = decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fread(tail, 1, sizeof(tail), fp) != sizeof(tail)) {
|
||||||
|
fclose(fp);
|
||||||
|
return OBF_ERR_IO;
|
||||||
|
}
|
||||||
|
(void)get_u32_le(tail);
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
*out_count = count;
|
||||||
|
return OBF_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t read_from(const char *path, uint64_t seed, uint64_t *out)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
|
return read_many_from(path, seed, out, 1, &count);
|
||||||
|
}
|
||||||
28
ndk/obfuscate/obfuscate_file.h
Normal file
28
ndk/obfuscate/obfuscate_file.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef OBFUSCATE_FILE_H
|
||||||
|
#define OBFUSCATE_FILE_H
|
||||||
|
|
||||||
|
#include "obfuscate.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* 32-bit dummy header/tail — skipped by decode; only uint64_t payloads are processed. */
|
||||||
|
#define OBF_FILE_HDR_SIZE 4u
|
||||||
|
#define OBF_FILE_TAIL_SIZE 4u
|
||||||
|
#define OBF_FILE_OVERHEAD (OBF_FILE_HDR_SIZE + OBF_FILE_TAIL_SIZE)
|
||||||
|
|
||||||
|
#define OBF_ERR_IO -4
|
||||||
|
#define OBF_ERR_FORMAT -5
|
||||||
|
#define OBF_ERR_OVERFLOW -6
|
||||||
|
|
||||||
|
int32_t write_to(const char *path, uint64_t val, uint64_t seed);
|
||||||
|
int32_t read_from(const char *path, uint64_t seed, uint64_t *out);
|
||||||
|
|
||||||
|
int32_t write_many_to(const char *path, const uint64_t *vals, size_t count, uint64_t seed);
|
||||||
|
|
||||||
|
int32_t read_many_from(const char *path, uint64_t seed, uint64_t *out_vals, size_t out_cap,
|
||||||
|
size_t *out_count);
|
||||||
|
|
||||||
|
/* How many uint64_t payloads are stored (excludes 32-bit head/tail). */
|
||||||
|
int32_t obf_file_payload_count(long file_size, size_t *count_out);
|
||||||
|
|
||||||
|
#endif /* OBFUSCATE_FILE_H */
|
||||||
Reference in New Issue
Block a user