From 7ccb920ad2596ea09ebd6bee22f1dabc1fcacffc Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 29 Sep 2026 15:00:30 -0400 Subject: [PATCH] [Feat] (Plugin, P11 B1): the broker presents the PairBind pair at hand-off (PairBindFrames, the encoder's bytes) and answers v2 with that flag and the apply core its supervisor announced; the helper passes MOBILEGL_IPC_FD_PAIRED=1 on, and Java no longer re-derives the reserved core --- .../mobilegl/plugin/ExternalClientBroker.java | 48 +++++++++++---- .../mobilegl/plugin/ExternalClientHelper.java | 16 +++-- .../plugin/MobileGLServerService.java | 57 +++++++----------- .../top/mobilegl/plugin/PairBindFrames.java | 58 +++++++++++++++++++ .../mobilegl/plugin/ServerEnvironment.java | 41 ++++++------- tools/trace_replay/PairBindFramesTest.java | 47 +++++++++++++++ tools/trace_replay/ServerEnvironmentTest.java | 35 ++++++----- tools/trace_replay/test_android_lifecycle.py | 3 + 8 files changed, 214 insertions(+), 91 deletions(-) create mode 100644 android-plugin/app/src/main/java/top/mobilegl/plugin/PairBindFrames.java create mode 100644 tools/trace_replay/PairBindFramesTest.java diff --git a/android-plugin/app/src/main/java/top/mobilegl/plugin/ExternalClientBroker.java b/android-plugin/app/src/main/java/top/mobilegl/plugin/ExternalClientBroker.java index efee6d41..8edf5e7a 100644 --- a/android-plugin/app/src/main/java/top/mobilegl/plugin/ExternalClientBroker.java +++ b/android-plugin/app/src/main/java/top/mobilegl/plugin/ExternalClientBroker.java @@ -25,8 +25,11 @@ import java.util.concurrent.atomic.AtomicBoolean; * receiver checks the token against the running server's own ({@code MOBILEGL_IPC_TOKEN}), connects * TWICE to the server's unix endpoint - or, when the server listens on TCP only, to the private * abstract listener {@link MobileGLServerService} runs beside it for this - and hands both client - * ends back as {@link ParcelFileDescriptor}s. It writes nothing on them: the client presents its own - * PairBind pair ({@code MOBILEGL_IPC_CONTROL=fd:,}). + * ends back as {@link ParcelFileDescriptor}s. Ruling ID-P11-12: whoever opens the connections presents + * the pair's identity, so it writes one {@code PairBind} pair on them at once ({@link PairBindFrames}: + * a fresh nonce, control then aux) and says so in its answer; the helper tells the program + * ({@code MOBILEGL_IPC_FD_PAIRED=1}) not to present a second one. The server pairs the connections + * now, not when the program - which may spend many seconds loading first - reaches its first EGL call. * *

EVERY request is answered on the callback - the two descriptors, or a refusal with its name - * so a helper never has to time out to learn it was refused. The refusals: {@code bad-request}, @@ -47,13 +50,17 @@ public final class ExternalClientBroker extends BroadcastReceiver { public static final String KEY_CALLBACK = "callback"; public static final String KEY_TOKEN = "token"; public static final String KEY_VERSION = "version"; - public static final int PROTOCOL_VERSION = 1; + /** 2: the answer carries the paired-at-hand-off flag (a v1 helper would add a second PairBind pair). */ + public static final int PROTOCOL_VERSION = 2; /** - * The broker's answer: int version, the control and aux descriptors, then long applyCore - the cpu - * mask the server's `auto` policy reserves for this session's apply thread (0 = none). + * The broker's answer: int version, the control and aux descriptors, long applyCore - the cpu mask + * the running supervisor announced it reserves for this session's apply thread (0 = none) - and + * int flags ({@link #FLAG_PAIRED_AT_HANDOFF}). */ public static final int REPLY_FDS = IBinder.FIRST_CALL_TRANSACTION; + /** The broker already wrote the PairBind pair on the two connections. */ + public static final int FLAG_PAIRED_AT_HANDOFF = 1; /** The broker's refusal: String code, String message. */ public static final int REPLY_REFUSED = IBinder.FIRST_CALL_TRANSACTION + 1; @@ -160,21 +167,42 @@ public final class ExternalClientBroker extends BroadcastReceiver { + ": " + failure[0]); return; } + // ID-P11-12: the pair's identity, presented by whoever opened the connections - here, now. The + // server's PairAcceptor pairs them within its 2000 ms budget however long the program the helper + // execs takes to reach its Hello (the paired session then waits for that Hello until EOF). + try { + byte[] nonce = new byte[PairBindFrames.NONCE_BYTES]; + new java.security.SecureRandom().nextBytes(nonce); + java.io.OutputStream controlOut = sockets[0].getOutputStream(); + controlOut.write(PairBindFrames.frame(nonce, false)); + controlOut.flush(); + java.io.OutputStream auxOut = sockets[1].getOutputStream(); + auxOut.write(PairBindFrames.frame(nonce, true)); + auxOut.flush(); + } catch (Exception error) { + closeQuietly(sockets[0]); + closeQuietly(sockets[1]); + refuse(callback, REFUSE_CONNECT_FAILED, "could not present the PairBind pair on " + target.endpoint + + ": " + error); + return; + } ParcelFileDescriptor control = null; ParcelFileDescriptor aux = null; Parcel data = Parcel.obtain(); + long applyCore = Math.max(0, target.applyCore.get()); // -1: not announced yet -> no mask try { control = ParcelFileDescriptor.dup(sockets[0].getFileDescriptor()); aux = ParcelFileDescriptor.dup(sockets[1].getFileDescriptor()); data.writeInt(PROTOCOL_VERSION); control.writeToParcel(data, 0); aux.writeToParcel(data, 0); - // The core the server reserves for this session's apply thread (0 = none): the helper keeps - // the client off it. Appended after the descriptors; a reader that stops before it is fine. - data.writeLong(target.applyCore); + // The core the running supervisor announced it reserves for this session's apply thread + // (0 = none): the helper keeps the client off it. + data.writeLong(applyCore); + data.writeInt(FLAG_PAIRED_AT_HANDOFF); boolean delivered = callback.transact(REPLY_FDS, data, null, IBinder.FLAG_ONEWAY); - Log.i(TAG, "brokered a connection pair to " + target.endpoint + " for a client (delivered=" - + delivered + ", apply core 0x" + Long.toHexString(target.applyCore) + ")"); + Log.i(TAG, "brokered a connection pair to " + target.endpoint + " for a client, paired at hand-off " + + "(delivered=" + delivered + ", apply core 0x" + Long.toHexString(applyCore) + ")"); } catch (Exception error) { Log.e(TAG, "could not hand the connection pair to the client", error); } finally { diff --git a/android-plugin/app/src/main/java/top/mobilegl/plugin/ExternalClientHelper.java b/android-plugin/app/src/main/java/top/mobilegl/plugin/ExternalClientHelper.java index a3ddd0c9..2d83cea1 100644 --- a/android-plugin/app/src/main/java/top/mobilegl/plugin/ExternalClientHelper.java +++ b/android-plugin/app/src/main/java/top/mobilegl/plugin/ExternalClientHelper.java @@ -43,7 +43,9 @@ import java.util.Map; * that enforces it there breaks this route; CONTRACT-P11 B1), and waits for the broker's answer. * Two connected sockets: it clears their close-on-exec flag and {@code execve}s the program with * {@code MOBILEGL_TRANSPORT=spawn}, {@code MOBILEGL_IPC_CONTROL=fd:,}, - * {@code MOBILEGL_IPC_DATA=shm} and {@code MOBILEGL_IPC_TOKEN} (the session's Hello carries it too). + * {@code MOBILEGL_IPC_DATA=shm} and {@code MOBILEGL_IPC_TOKEN} (the session's Hello carries it too), plus + * {@code MOBILEGL_IPC_FD_PAIRED=1} when the broker already presented the PairBind pair at hand-off + * (ID-P11-12) - the program may then take as long as it likes to reach its first EGL call. * When the broker names a cpu core the server reserves for the session's apply thread, the program is * exec'd through {@code taskset} on every other core (see {@link #clientMask}; {@code --keep-affinity} * opts out). A refusal: it prints the broker's name for it and exits with that refusal's code. No @@ -147,15 +149,18 @@ public final class ExternalClientHelper { ParcelFileDescriptor control = ParcelFileDescriptor.CREATOR.createFromParcel(data); ParcelFileDescriptor aux = ParcelFileDescriptor.CREATOR.createFromParcel(data); long applyCore = data.dataAvail() >= 8 ? data.readLong() : 0; + int replyFlags = data.dataAvail() >= 4 ? data.readInt() : 0; + boolean paired = (replyFlags & ExternalClientBroker.FLAG_PAIRED_AT_HANDOFF) != 0; int controlFd = inheritable(control); int auxFd = inheritable(aux); long mask = keepAffinity ? 0 : clientMask(applyCore); say("broker v" + version + " handed over fd:" + controlFd + "," + auxFd + + (paired ? " paired at hand-off" : " unpaired") + (mask != 0 ? "; the server's apply core 0x" + Long.toHexString(applyCore) + " reserved, the program runs on cpu mask 0x" + Long.toHexString(mask) : "; no cpu mask applied") + "; exec " + programPath); - exec(programPath, argv, controlFd, auxFd, presented, mask); + exec(programPath, argv, controlFd, auxFd, presented, mask, paired); } catch (Throwable error) { say("could not exec " + programPath + ": " + error); finish(EXIT_EXEC_FAILED); @@ -267,8 +272,8 @@ public final class ExternalClientHelper { return pfd.detachFd(); } - private static void exec(String path, List argv, int control, int aux, String token, long cpuMask) - throws Exception { + private static void exec(String path, List argv, int control, int aux, String token, long cpuMask, + boolean pairedAtHandoff) throws Exception { Map env = new LinkedHashMap<>(); String[] inherited = Os.environ(); if (inherited != null) { @@ -283,6 +288,9 @@ public final class ExternalClientHelper { env.put("MOBILEGL_IPC_CONTROL", "fd:" + control + "," + aux); env.put("MOBILEGL_IPC_DATA", "shm"); env.put("MOBILEGL_IPC_TOKEN", token); + // ID-P11-12: the broker presented the PairBind pair; the program's fd: endpoint must not add one. + if (pairedAtHandoff) env.put("MOBILEGL_IPC_FD_PAIRED", "1"); + else env.remove("MOBILEGL_IPC_FD_PAIRED"); List envp = new ArrayList<>(); for (Map.Entry entry : env.entrySet()) envp.add(entry.getKey() + "=" + entry.getValue()); if (cpuMask != 0 && new File(TASKSET).canExecute()) { diff --git a/android-plugin/app/src/main/java/top/mobilegl/plugin/MobileGLServerService.java b/android-plugin/app/src/main/java/top/mobilegl/plugin/MobileGLServerService.java index 56e43b56..ba288f2c 100644 --- a/android-plugin/app/src/main/java/top/mobilegl/plugin/MobileGLServerService.java +++ b/android-plugin/app/src/main/java/top/mobilegl/plugin/MobileGLServerService.java @@ -17,6 +17,7 @@ import java.io.File; import java.io.InputStreamReader; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; /** Entry point for the offscreen TCP supervisor; EGL belongs to its session children. */ public final class MobileGLServerService extends Service { @@ -40,12 +41,12 @@ public final class MobileGLServerService extends Service { final String endpoint; final String token; final Process process; - // The core the server's `auto` policy gives a dialled-in shared-segment client's apply thread - // (ServerEnvironment.reservedApplyCore); the broker hands it to the helper, which keeps the - // client off it. 0 = none reserved. - final long applyCore; + // The core THIS supervisor reserves for a dialled-in shared-segment client's apply thread, as it + // announced on stdout at start-up (ServerEnvironment.parseReservedApplyCore); the broker hands + // it to the helper, which keeps the client off it. -1 until the line is read, 0 = none. + final AtomicLong applyCore; - BrokerTarget(String endpoint, String token, Process process, long applyCore) { + BrokerTarget(String endpoint, String token, Process process, AtomicLong applyCore) { this.endpoint = endpoint; this.token = token; this.process = process; @@ -53,21 +54,6 @@ public final class MobileGLServerService extends Service { } } - /** cpuinfo_max_freq of every cpu this process can count (0 where unreadable). */ - private static long[] cpuMaxFrequenciesKHz() { - int count = Math.min(64, Math.max(1, Runtime.getRuntime().availableProcessors())); - long[] frequencies = new long[count]; - for (int cpu = 0; cpu < count; cpu++) { - File file = new File("/sys/devices/system/cpu/cpu" + cpu + "/cpufreq/cpuinfo_max_freq"); - try (BufferedReader reader = new BufferedReader(new java.io.FileReader(file))) { - frequencies[cpu] = Long.parseLong(reader.readLine().trim()); - } catch (Exception unreadable) { - frequencies[cpu] = 0; - } - } - return frequencies; - } - // Set when a supervisor the broker can reach is running, cleared when it stops. The broker runs // in this same process (:mglsrv), so a broadcast that finds null here finds no server. private static volatile BrokerTarget brokerTarget; @@ -155,30 +141,26 @@ public final class MobileGLServerService extends Service { if (endpoint == null) endpoint = "tcp://127.0.0.1:40613"; final String envExtra = intent == null ? null : intent.getStringExtra("env"); final String token = intent == null ? null : intent.getStringExtra("token"); - java.util.Map requested = new java.util.HashMap<>(); - ServerEnvironment.apply(requested, envExtra); - final long applyCore = ServerEnvironment.reservedApplyCore(cpuMaxFrequenciesKHz(), - requested.get("MOBILEGL_IPC_SERVER_AFFINITY")); + final AtomicLong primaryCore = new AtomicLong(-1); try { - supervisor = startSupervisor(endpoint, envExtra, token, "mgl-supervisor-log", startId, true); + supervisor = startSupervisor(endpoint, envExtra, token, "mgl-supervisor-log", startId, true, primaryCore); // P11 B1: the broker's target. A unix endpoint is reachable by this app as it is (a // relative path resolves against filesDir, the supervisor's working directory); a TCP // one gets a private abstract listener of its own (BrokerTarget). if (endpoint.startsWith("tcp://")) { String privateName = "@" + getPackageName() + ".broker." + Long.toHexString( new java.security.SecureRandom().nextLong() & 0xffffffffL); + final AtomicLong brokerCore = new AtomicLong(-1); brokerSupervisor = startSupervisor(privateName, envExtra, token, "mgl-broker-supervisor-log", - startId, false); - brokerTarget = new BrokerTarget(privateName, token, brokerSupervisor, applyCore); + startId, false, brokerCore); + brokerTarget = new BrokerTarget(privateName, token, brokerSupervisor, brokerCore); Log.i(TAG, "same-device external clients: broker listener " + privateName - + " beside the TCP-only supervisor on " + endpoint + "; apply core reserved for them 0x" - + Long.toHexString(applyCore)); + + " beside the TCP-only supervisor on " + endpoint); } else { String unix = endpoint.startsWith("@") || endpoint.startsWith("/") ? endpoint : new File(getFilesDir(), endpoint).getAbsolutePath(); - brokerTarget = new BrokerTarget(unix, token, supervisor, applyCore); - Log.i(TAG, "same-device external clients: the broker connects to " + unix - + "; apply core reserved for them 0x" + Long.toHexString(applyCore)); + brokerTarget = new BrokerTarget(unix, token, supervisor, primaryCore); + Log.i(TAG, "same-device external clients: the broker connects to " + unix); } } catch (Exception error) { Log.e(TAG, "cannot start TCP supervisor", error); @@ -190,10 +172,11 @@ public final class MobileGLServerService extends Service { /** * Execs one {@code libMobileGLServer.so --serve} with the server role's environment and - * logs its output. When {@code primary}, its exit stops the service. + * logs its output. When {@code primary}, its exit stops the service. {@code reservedCore} receives the + * apply core the supervisor announces at start-up (P11 B1). */ private Process startSupervisor(String endpoint, String envExtra, String token, String logThreadName, - int startId, boolean primary) throws java.io.IOException { + int startId, boolean primary, AtomicLong reservedCore) throws java.io.IOException { File executable = new File(getApplicationInfo().nativeLibraryDir, "libMobileGLServer.so"); ProcessBuilder builder = new ProcessBuilder(executable.getAbsolutePath(), endpoint, "--serve"); builder.directory(getFilesDir()).redirectErrorStream(true); @@ -207,7 +190,11 @@ public final class MobileGLServerService extends Service { new Thread(() -> { try (BufferedReader output = new BufferedReader(new InputStreamReader(child.getInputStream()))) { String line; - while ((line = output.readLine()) != null) Log.i(TAG, line); + while ((line = output.readLine()) != null) { + Log.i(TAG, line); + long announced = ServerEnvironment.parseReservedApplyCore(line); + if (announced != -1) reservedCore.set(announced); // one core, so never all 64 bits + } Log.i(TAG, (primary ? "supervisor" : "broker supervisor") + " exited " + child.waitFor()); } catch (java.io.InterruptedIOException stopped) { // onDestroy's Process.destroy() closes this stream under the read: the service diff --git a/android-plugin/app/src/main/java/top/mobilegl/plugin/PairBindFrames.java b/android-plugin/app/src/main/java/top/mobilegl/plugin/PairBindFrames.java new file mode 100644 index 00000000..3a980808 --- /dev/null +++ b/android-plugin/app/src/main/java/top/mobilegl/plugin/PairBindFrames.java @@ -0,0 +1,58 @@ +package top.mobilegl.plugin; + +/** + * P11 B1 (CONTRACT-P11 B1, ruling ID-P11-12): the two framed {@code PairBind} control messages the + * broker writes at hand-off - {@code PairBind{nonce, aux=false}} first on the control connection and + * {@code PairBind{nonce, aux=true}} on the aux one - so the server's PairAcceptor pairs the two + * connections the moment the broker opens them, not when the program it opened them for reaches its + * first EGL call. + * + *

The bytes are the native encoder's own ({@code MG_Remote/Transport/PairBind.h EncodePairBind} + * framed by {@code Framing.h AppendFrame}): a flatbuffers {@code CtrlEnvelope} whose layout for a + * 16-byte nonce is fixed, with the nonce as its last 16 bytes. The two prefixes below are that + * encoder's output up to the nonce, and {@code ServerSpawnTest.TheBrokersPairBindTemplatesAreTheEncoders} + * reads them out of this file and compares against the encoder for random nonces - a schema change + * that moves a byte turns that test red, not a device. + * + *

No android.* import: tools/trace_replay/test_android_lifecycle.py compiles and runs it with javac + * (PairBindFramesTest). + */ +public final class PairBindFrames { + private PairBindFrames() {} + + public static final int NONCE_BYTES = 16; + /** Framing.h kFrameMagic, little-endian on the wire. */ + public static final int FRAME_MAGIC = 0x464C474D; + + // EncodePairBind(nonce, aux=false) up to the nonce (the nonce is the payload's last 16 bytes). + static final String CONTROL_PREFIX_HEX = + "100000004d474c4308000e0007000800080000000000000f0c0000000000060008000400060000000400000010000000"; + // EncodePairBind(nonce, aux=true) up to the nonce. + static final String AUX_PREFIX_HEX = + "100000004d474c4308000c0007000800080000000000000f0c00000008000c000800070008000000000000010400000010000000"; + + /** One framed PairBind: 8-byte header (magic, payload length; little-endian) + the payload. */ + public static byte[] frame(byte[] nonce, boolean aux) { + if (nonce == null || nonce.length != NONCE_BYTES) { + throw new IllegalArgumentException("a PairBind nonce is " + NONCE_BYTES + " bytes"); + } + byte[] prefix = hex(aux ? AUX_PREFIX_HEX : CONTROL_PREFIX_HEX); + int payload = prefix.length + NONCE_BYTES; + byte[] out = new byte[8 + payload]; + putLittleEndian(out, 0, FRAME_MAGIC); + putLittleEndian(out, 4, payload); + System.arraycopy(prefix, 0, out, 8, prefix.length); + System.arraycopy(nonce, 0, out, 8 + prefix.length, NONCE_BYTES); + return out; + } + + private static void putLittleEndian(byte[] out, int offset, int value) { + for (int i = 0; i < 4; i++) out[offset + i] = (byte) (value >>> (8 * i)); + } + + static byte[] hex(String text) { + byte[] out = new byte[text.length() / 2]; + for (int i = 0; i < out.length; i++) out[i] = (byte) Integer.parseInt(text.substring(2 * i, 2 * i + 2), 16); + return out; + } +} diff --git a/android-plugin/app/src/main/java/top/mobilegl/plugin/ServerEnvironment.java b/android-plugin/app/src/main/java/top/mobilegl/plugin/ServerEnvironment.java index cb0dc480..b5e4c23d 100644 --- a/android-plugin/app/src/main/java/top/mobilegl/plugin/ServerEnvironment.java +++ b/android-plugin/app/src/main/java/top/mobilegl/plugin/ServerEnvironment.java @@ -81,32 +81,25 @@ public final class ServerEnvironment { return edits; } + /** The supervisor's start-up announcement (MG_Remote/Server/ServerMain.cpp), up to the mask. */ + static final String RESERVED_CORE_ANNOUNCEMENT = + "MG_Remote server: apply core reserved for dialled-in shared-segment clients: 0x"; + /** - * P11 B1 (CONTRACT-P11 B1): the core the server's `auto` policy reserves for the apply thread of a - * dialled-in shared-segment client - MG_Remote/Server/ApplyThreadPolicy.h ReservedApplyCore over - * ServerLoop.cpp's DetectBigCoreMask, mirrored here so the broker can tell the helper which core to - * keep the client off. {@code maxFreqKHz[i]} is cpu i's cpuinfo_max_freq (0 = unreadable, which - * like the native probe gives no answer). A cpu is big when its ceiling is within 15% of the peak; - * the reserved core is the LOWEST big one, and only when the big set is a strict subset of at least - * two. 0 = none. {@code affinityOverride} is the server's MOBILEGL_IPC_SERVER_AFFINITY: anything - * but unset / empty / {@code auto} is an operator's own placement, and then nothing is reserved. + * P11 B1 (CONTRACT-P11 B1): the core the running supervisor reserves for a dialled-in shared-segment + * client's apply thread, read off the line it prints on stdout at start-up (the native policy, + * ApplyThreadPolicy.h ReservedApplyCoreForConfig, is the one source of it; nothing here re-derives + * it). The mask, or -1 when {@code line} is not that announcement. */ - public static long reservedApplyCore(long[] maxFreqKHz, String affinityOverride) { - if (affinityOverride != null && !affinityOverride.isEmpty() && !affinityOverride.equals("auto")) return 0; - int count = Math.min(64, maxFreqKHz.length); - long peak = 0; - for (int cpu = 0; cpu < count; cpu++) { - if (maxFreqKHz[cpu] <= 0) return 0; - peak = Math.max(peak, maxFreqKHz[cpu]); - } - if (count == 0 || peak == 0) return 0; - long big = 0; - for (int cpu = 0; cpu < count; cpu++) { - if (maxFreqKHz[cpu] >= peak * 85 / 100) big |= 1L << cpu; - } - long online = count >= 64 ? ~0L : (1L << count) - 1; - if ((big & online) == online || Long.bitCount(big) < 2) return 0; - return Long.lowestOneBit(big); + public static long parseReservedApplyCore(String line) { + if (line == null) return -1; + int at = line.indexOf(RESERVED_CORE_ANNOUNCEMENT); + if (at < 0) return -1; + int start = at + RESERVED_CORE_ANNOUNCEMENT.length(); + int end = start; + while (end < line.length() && Character.digit(line.charAt(end), 16) >= 0) end++; + if (end == start || end - start > 16) return -1; + return Long.parseUnsignedLong(line.substring(start, end), 16); } /** diff --git a/tools/trace_replay/PairBindFramesTest.java b/tools/trace_replay/PairBindFramesTest.java new file mode 100644 index 00000000..03cc577a --- /dev/null +++ b/tools/trace_replay/PairBindFramesTest.java @@ -0,0 +1,47 @@ +package top.mobilegl.plugin; + +/** + * Host-side checks for PairBindFrames, the broker's hand-off PairBinds (P11 B1): the frame header and + * the nonce's place. That the prefixes ARE the native encoder's bytes is ServerSpawnTest's + * TheBrokersPairBindTemplatesAreTheEncoders (it reads them out of PairBindFrames.java). Run by + * tools/trace_replay/test_android_lifecycle.py with plain javac; assert-free like its siblings. + */ +public final class PairBindFramesTest { + private static int failures = 0; + + private static void expect(String what, Object actual, Object expected) { + if (expected == null ? actual == null : expected.equals(actual)) return; + ++failures; + System.err.println(what + "\n got: [" + actual + "]\n expected: [" + expected + "]"); + } + + private static int littleEndian(byte[] b, int offset) { + return (b[offset] & 0xff) | (b[offset + 1] & 0xff) << 8 | (b[offset + 2] & 0xff) << 16 | (b[offset + 3] & 0xff) << 24; + } + + public static void main(String[] args) { + byte[] nonce = new byte[PairBindFrames.NONCE_BYTES]; + for (int i = 0; i < nonce.length; i++) nonce[i] = (byte) (0xA0 + i); + for (boolean aux : new boolean[]{false, true}) { + byte[] frame = PairBindFrames.frame(nonce, aux); + String role = aux ? "aux" : "control"; + expect(role + ": the frame magic is Framing.h's, little-endian", littleEndian(frame, 0), 0x464C474D); + expect(role + ": the length field is the payload's", littleEndian(frame, 4), frame.length - 8); + expect(role + ": the payload is the encoder's size (64 control, 68 aux)", frame.length - 8, aux ? 68 : 64); + expect(role + ": the envelope carries the MGLC identifier", new String(frame, 12, 4, java.nio.charset.StandardCharsets.US_ASCII), "MGLC"); + byte[] tail = java.util.Arrays.copyOfRange(frame, frame.length - 16, frame.length); + expect(role + ": the nonce is the last 16 bytes", java.util.Arrays.toString(tail), java.util.Arrays.toString(nonce)); + } + try { + PairBindFrames.frame(new byte[8], false); + ++failures; + System.err.println("a short nonce was framed"); + } catch (IllegalArgumentException expected) { + } + if (failures != 0) { + System.err.println("PairBindFramesTest: " + failures + " failure(s)"); + System.exit(1); + } + System.out.println("PairBindFramesTest: all checks passed"); + } +} diff --git a/tools/trace_replay/ServerEnvironmentTest.java b/tools/trace_replay/ServerEnvironmentTest.java index ea443b4f..bcd79358 100644 --- a/tools/trace_replay/ServerEnvironmentTest.java +++ b/tools/trace_replay/ServerEnvironmentTest.java @@ -94,24 +94,23 @@ public final class ServerEnvironmentTest { expect("an unmeasured view fills the view", java.util.Arrays.toString(ServerEnvironment.aspectFit(0, 0, 640, 480)), "[-1, -1]"); - // ---- P11 B1: the apply core the broker tells the helper to keep the client off ------------------ - // The Java mirror of MG_Remote/Server/ApplyThreadPolicy.h ReservedApplyCore over DetectBigCoreMask. - long[] redmi = {3532800, 3532800, 3532800, 3532800, 3532800, 3532800, 4320000, 4320000}; - expect("the Redmi (cpu6-7 prime): the LOWEST prime core, cpu6", - ServerEnvironment.reservedApplyCore(redmi, null), 0x40L); - expect("`auto` and an empty override are the policy's own", ServerEnvironment.reservedApplyCore(redmi, "auto"), - 0x40L); - expect("an operator's affinity (a mask or off) reserves nothing", - ServerEnvironment.reservedApplyCore(redmi, "0x80") + ServerEnvironment.reservedApplyCore(redmi, "off"), - 0L); - expect("a symmetric machine has no prime pair to split", - ServerEnvironment.reservedApplyCore(new long[]{2000000, 2000000, 2000000, 2000000}, ""), 0L); - expect("one prime core is not a pair", - ServerEnvironment.reservedApplyCore(new long[]{1800000, 1800000, 1800000, 3000000}, null), 0L); - expect("within 15% of the peak is big: cpu2 and cpu3 of {1.0, 1.0, 2.6, 3.0} GHz, the lowest is cpu2", - ServerEnvironment.reservedApplyCore(new long[]{1000000, 1000000, 2600000, 3000000}, null), 0x4L); - expect("an unreadable ceiling gives no answer, like the native probe", - ServerEnvironment.reservedApplyCore(new long[]{3532800, 0, 4320000, 4320000}, null), 0L); + // ---- P11 B1: the apply core the supervisor announces and the broker relays --------------------- + // The native policy is the one source; the service only reads the supervisor's start-up line. + expect("the announcement as the supervisor prints it", + ServerEnvironment.parseReservedApplyCore("MG_Remote server: apply core reserved for dialled-in " + + "shared-segment clients: 0x40 (auto: the lowest of the prime cores, the rest left to the " + + "dialled-in shared-segment client)"), 0x40L); + expect("none reserved is 0, not absent", + ServerEnvironment.parseReservedApplyCore("MG_Remote server: apply core reserved for dialled-in " + + "shared-segment clients: 0x0 (auto: unpinned - no asymmetric big/prime topology to split)"), 0L); + expect("a 64-bit mask", ServerEnvironment.parseReservedApplyCore( + "MG_Remote server: apply core reserved for dialled-in shared-segment clients: 0x8000000000000000 (x)"), + Long.MIN_VALUE); + expect("any other supervisor line is not the announcement", + ServerEnvironment.parseReservedApplyCore("MG_Remote server: pid=12 listening on @x"), -1L); + expect("a truncated announcement is not one", + ServerEnvironment.parseReservedApplyCore("MG_Remote server: apply core reserved for dialled-in " + + "shared-segment clients: 0x"), -1L); if (failures != 0) { System.err.println("ServerEnvironmentTest: " + failures + " failure(s)"); diff --git a/tools/trace_replay/test_android_lifecycle.py b/tools/trace_replay/test_android_lifecycle.py index f8ba52ce..bc3e338a 100644 --- a/tools/trace_replay/test_android_lifecycle.py +++ b/tools/trace_replay/test_android_lifecycle.py @@ -71,6 +71,9 @@ def main(): (trace, "top.mobilegl.plugin.trace", "TraceReplaySession", "TraceReplaySessionTest"), (trace, "top.mobilegl.plugin.trace", "SpawnServerPath", "SpawnServerPathTest"), (main_src, "top.mobilegl.plugin", "ServerEnvironment", "ServerEnvironmentTest"), + # P11 B1: the broker's hand-off PairBinds (their bytes are pinned to the native encoder by + # ServerSpawnTest.TheBrokersPairBindTemplatesAreTheEncoders). + (main_src, "top.mobilegl.plugin", "PairBindFrames", "PairBindFramesTest"), ] with tempfile.TemporaryDirectory(prefix="mobilegl-replay-lifecycle-") as output: sources = []