Repository navigation
[BOUNTY #2851] Reuse remote DroidGuard sessions across snapshots - #3841
woahwhattheheck wants to merge 24 commits into
Conversation
## Summary - Implement `guardWithRequest` on the bounded executor and close its handle on every completion path. - Reuse remote server state through the existing `init` / repeated `snapshot` / `close` lifecycle. - Centralize HTTP request encoding, timeouts, non-success status handling, and connection cleanup. ## Verification - `./gradlew.bat :play-services-droidguard-core:testDebugUnitTest --tests org.microg.gms.droidguard.core.RemoteDroidGuardHttpClientTest --tests org.microg.gms.droidguard.core.RemoteDroidGuardSessionTest --offline` - BUILD SUCCESSFUL; 4 focused tests pass, and the task compiles the module and AIDL. - `git diff --check` passes. This verifies local transport/session behavior. A real server/device Play Integrity flow remains unverified. Related issue: microg#2851.
|
To align the remote-session lifecycle with the intended Play Integrity call sequence: this change keeps one remote session alive across repeated Can you confirm whether the required multi-step sequence reuses one handle for multiple snapshots, or expects state to persist across separate one-shot |
|
Did you read the bounty note?
Can you show a video where it works? |
|
Thanks for clarifying the bounty's acceptance criterion. PR #3841 implements the remote session transport path and includes four focused local session/transport tests. It has not been exercised with Dott unlocking or riding against a real server/device, so I cannot provide a video demonstrating that outcome. The PR body records this limit; end-to-end Dott behavior remains unverified. |
… ci] Keep each initialization pending until session setup finishes, including when a synchronous snapshot overtakes one-way init dispatch. Bind snapshot metadata to the observed generation and close late sessions after handle shutdown. Validation: five maintained JVM tests passed. A loopback HTTP replay of the actual RemoteHandleImpl source with disposable platform stubs improved from 1/4 to 4/4 scenarios: pending begin, snapshot before init dispatch, close during begin, and completed reinitialization. No full Gradle, Binder, device, Play Integrity, or Dott scooter acceptance claim.
Retain the successful three-class, twelve-case Gradle run and VtmDefaultDebug APK receipt for product 7a648d5 plus the original lifecycle test patch. Keep the tested source separate from later PR revisions; no production file is changed.
Move result delivery inside the existing task's cleanup finally boundary. A slow remote close no longer delays the completed callback; fallback, rejection, logging and cleanup on callback failure are preserved. Record the focused production-class loopback replay and controlled callback-latency results, preserving the concurrent integrated Android report.
Add a DroidGuardClient that returns Task<DroidGuardHandle> and then uses snapshot, isOpened, and close against a begin/snapshot/close session backend. Loopback interop covers retained-handle reuse and rejects a single-attest server that has no session. This does not mint Play Integrity or device results. Co-authored-by: woahwhattheheck <woahwhattheheck@users.noreply.github.com>
Copy the public String map into a session map so handle.snapshot compiles and still reuses the retained remote session. Co-authored-by: woahwhattheheck <woahwhattheheck@users.noreply.github.com>
Keep begin, snapshot, and close ordered within one retained session while allowing other sessions to progress independently. Prevent close from retiring remote state during an in-flight snapshot. Direct production-class loopback execution reproduces both overlaps on the parent and removes both with this change. The maintained session class passes four focused JUnit checks. JVM evidence and Android/device limits are recorded in SESSION_SERIALIZATION.md. Preserve the latest retained handle client and snapshot map repair.
Return failed Tasks when the supplied executor rejects initial admission. Keep begin, snapshot, result publication and close in one accepted getResults worker, so a second dispatch cannot strand the result Task or the opened remote session. Use trySetException after task publication to avoid duplicate completion when a listener throws. Preserve result-before-close ordering and existing per-session serialization. Extend RemoteDroidGuardHandleInteropTest with rejected-admission and accept-once executor cases. The three existing interop cases remain unchanged. With the actual microG TaskImpl, full current client/session/HTTP classes and the existing loopback server, parent 04fbd91 passes 3/5 and fails both new cases (RejectedExecutionException and DuplicateTaskCompletionException); this change passes 5/5. The accepted-worker case observes result completion before close and exactly one executor submission. Focused JVM validation used retained OpenJDK 17, Kotlin 2.2.21 and JUnit 4.13.2. Executed source blob 2836391; test blob dc2b839. Android packaging and device acceptance remain separate.
Add a default-disabled provider and authenticated host adapter for the existing begin/snapshot/close protocol. Retain actual core handles, check readiness, preserve initialization metadata, and return native result bytes through an explicit response envelope. Runtime caller checks restrict the bridge to authorized local operators; the ordinary service broker is unchanged. Bound active and closing sessions, work queues, operation waits, and idle lifetime. Reject overlapping work for one session before occupying a global worker, so another session can progress. Close failed native setup and late initialization results. Compile the complete changed Kotlin production classes for JVM 1.8 and execute six focused scenarios with retained Robolectric API29 and a recording VM boundary: 6 passed. Exercise the production Python HTTP/subprocess adapter with ten requests and seven content-command fixtures. The setup guide records source blobs, outcomes, donor credit, and execution limits. No APK, installed-provider, Google VM, Play Integrity, or Dott ride result is asserted.
The native runtime can return encoded error bytes through a successful snapshot response. Clarify that an explicit provider error status becomes an HTTP error; Base64 validity alone does not establish attestation success.
Generate a test-only public API JAR from the active JDK module and resolve jdk.httpserver in unit-test JVMs. The ordinary module task passes all 17 maintained cases on validation tree 4866ca6 in run37234495146, without an init script, filters, or APK assembly. No production dependencies or API behavior change.
Preflight request-target bytes, form-body bytes, and adapter field counts before opening a connection. Preserve the existing response cap and cover accepted and rejected protocol boundaries.
|
/claim #2851 I claim the applicable allocation from the advertised $85 USD bounty for my contribution in this PR, payable to the original contributor @woahwhattheheck. The contribution and remaining acceptance requirements are documented in the existing PR; this claim preserves the original contributors' attribution. Please confirm eligibility and the award upon acceptance, payment method, and payout date. |
Summary
Reuse remote DroidGuard state across the existing
init/ repeatedsnapshot/closelifecycle, centralize the HTTP transport, and return completed callback results before potentially slow remote cleanup. The protocol now also has an operator-enabled Android provider and authenticated host adapter that retain actual Embedded core handles.Response transport source:
7a45ad14d102e89c1ba7082887908efa9246737b. Request-bound source:f826c8ed1540ac8969140c340f7c211ad83e68c2. The executed validation pins and their source scope are recorded below.guardWithRequestuses the existing bounded executor, returns its callback result, and closes its handle in afinallyboundary, including callback failure.Native server and public client
The operator setup guide documents the new default-disabled provider and Python host adapter. The provider checks DUMP permission and the actual local caller UID before decoding or changing Binder identity. It requires Embedded mode, checks native readiness, retains one core handle for repeated snapshots, and closes failed initialization and late results. The ordinary service broker is unchanged.
Session capacity includes pending initialization and cleanup. Operation deadlines, idle expiry, and bounded queues limit retained work. A duplicate operation for a busy session is rejected before occupying a global worker, preserving progress for another session. The adapter selects an explicit ADB device, authenticates the existing base-URL token query, and carries exact JSON envelopes instead of treating content-command diagnostics as result bytes.
The HTTP action/form protocol is adapted from GautamKumarOffical's PR #3575, source
9d9a8d37e1f4b70997ba1868d2c196261dfd7d0f; its Apache notice and source credit are retained.The public
RemoteDroidGuardHandleClientexposes the existing Task/handle lifecycle. At 02f6c0f0, initial executor rejection becomes a failed Task, andgetResultsuses one accepted worker for begin/snapshot/result/close. Per-session HTTP serialization from 04fbd911 remains in place.Executed validation and source pins
HTTP response boundary and current Android execution
At
7a45ad14,RemoteDroidGuardHttpClientreads responses through an 8,192-byte buffer with a 262,144-byte limit. Its maintained regression rejects an oversized response and still verifies connection cleanup.Run 37229223484 checked out product
2de4887ea7ffd27ec86256f3ccbd48e3e46a8baf/ treef23fccdf238d29c8a515381de40e06f586958565, then applied exactly the two retained response-bound postimages. The scope guard passed. The production HTTP blob is730ea3d34bb44f25d13070aa2a44fc59dde24ce0, and the maintained test blob is0e1887b51bc57cfe8224e3f19f5aa1c9ad476355; both are now exact readbacks from the existing PR at7a45ad14.The three selected classes produced 12 actual JUnit XML tests, 0 failures, errors or skips: initialization 5, session 4, HTTP client 3.
:play-services-droidguard-core:testDebugUnitTestandassembleVtmDefaultDebugcompleted with Gradle exit 0 in 186.811 seconds. The runner's own JDKjdk.httpservermodule supplied an evidence-scoped unit-test classpath JAR through a Gradle init script.The downloaded VtmDefaultDebug APK is
com.google.android.gms-252432000.apk, 107,332,090 bytes, SHA-256f4981a147e7b05d71669d48e2983c3673fd810e021768b20d71cfcdafa0cc1d6. These results and that APK belong to2de4887eplus the stated exact two-file response patch. Device Play Integrity and the Dott unlock-and-ride outcome remain pending.Request admission and adapter concurrency
At
f826c8ed, the client rejects an over-limit encoded request target, form body or field count before opening the HTTP connection. The bounds are 8,192 bytes for the request target, 32,768 bytes for the form body, 128 query fields and 256 form fields. The already-landed 262,144-byte response limit and connection cleanup remain in place.The actual Kotlin/JDK test replay completed 10/10 cases, including the exact 8 KiB target, 32 KiB body, 128/256 field boundaries and an accepted exact 256 KiB response. Controlled runs removing the target, field and response guards failed their intended cases. These are the focused production-client checks for those postimages.
At
0e8369b0, the Python adapter protocol test executes the maintained HTTP adapter and ContentBridge with a disposable subprocess at the Android content-command boundary. Python 3.12.10 completed its one concurrent acceptance scenario; a forced-sequential control failed the assertion that the independent B session progresses while A is blocked. Six content-command calls were retained. The adapter source is unchanged in that test forward.Native server at
911188d3The complete changed provider, store, and core handle compiled with Kotlin 2.2.21 for JVM 1.8. Six focused scenarios passed using retained JDK 17 / Robolectric 4.12.2 / Android API 29: failed native setup closes once without an ID; owner isolation and idle expiry; caller/input admission; independent-session progress under a held snapshot; one core handle across two snapshots; and cleanup of initialization completing after its deadline.
Those runs execute the actual core handle, HandleProxy, GuardCallback, and Android Binder/Bundle/JSON behavior with a recording VM/factory and controlled local preferences. The provider was manually attached using retained resources. Installed manifest/component behavior, Google VM/JNI, an Android device, and a new APK were not exercised.
The production Python HTTP/subprocess adapter completed ten requests and seven content-command fixtures covering lifecycle, byte preservation, authentication, invalid input, and provider errors. This verifies the transport implementation at an explicit fixture boundary, not a connected device. The setup guide records the exact source blobs and limits.
Session serialization and client dispatch
At
04fbd911, the actual Kotlin session/HTTP classes against a latched loopback server reproduced snapshot/snapshot and snapshot/close overlap before the change and prevented both afterward; a separate session completed while the first was held. Its maintained four-case session class passed. Source and execution scope.At
02f6c0f0, the complete public client/session/HTTP code ran with the actual retained microG TaskImpl and existing loopback server. The original three interop cases plus two rejection regressions passed 5/5; the preceding source failed the two new cases. One worker admission and result-before-close ordering were asserted. These focused results are separate from the earlier integrated APK below.Integrated Android run at
7a648d5fplus the retained test patchRun 37193312230, job
111409876131, succeeded after checking out product7a648d5fe5f073c61ef7b202aef4c74dd329f4be/ tree24896870026d0edb04989c9627b4a1dc0586670d.The checkout applied only the retained session lifecycle test patch v2 (SHA-256
497af7ad875d46645c85eff4f2d02711678dc0513fbf70f4359ccc7636b7fc24; test blobf300e7fad7a6a9649d6c591f36a701b6bf7ee80b). No production file changed in the execution checkout.The three selected classes produced 12 actual XML tests, 0 failures, errors, or skips:
RemoteDroidGuardInitializationTestRemoteDroidGuardSessionTestwith v2 patchRemoteDroidGuardHttpClientTest:play-services-droidguard-core:testDebugUnitTestandassembleVtmDefaultDebugcompleted with Gradle exit 0. The unit task executed. Toolchain: Gradle 8.13, OpenJDK 17.0.20.1, Android API 35 / build-tools 35.0.0.com.google.android.gms-252432000.apk, 107,313,927 bytes; SHA-256d40be530096efce543222918b062abfe2310e1508fddb0393c2603ed302e5749.That APK belongs to the
7a648d5fproduction source. The session test extension was transient validation input; the report records it separately.Later callback-order repair at
4fec1b39The callback-order report records execution of the complete original/repaired production service and the remote handle, initialization, session, HTTP, and fallback classes against a real loopback HTTP server with disposable Android/AIDL/context/preference collaborators.
Two ordering cases failed before the repair and passed afterward; the two existing callback-failure/rejected-executor controls remained passing. The ordering assertions use latches and still verify one callback, one close, and expected result bytes.
With a deliberately delayed 300 ms close response, the recorded callback-latency median across three samples changed from 391.153 ms to 42.407 ms. These are observations from that controlled loopback scenario. Cleanup still occupies the executor worker; this is callback ordering/latency evidence, not a sustained-throughput measurement.
The current callback change has that focused production-class replay. The integrated APK and 12-case Gradle result above are for its preceding source pin.
Earlier source results retained
The original
3f10ba2btransport/session change passed its four focused offline Gradle tests and compiled the module/AIDL. The7a648d5finitialization follow-up separately passed five maintained JVM checks and four stubbed production-handle loopback scenarios. Those earlier counts overlap with subsequent selections and are not added together.Device acceptance
Real remote-server/device Play Integrity behavior and an actual Dott unlock-and-ride video remain pending. The linked software checks do not contain that field result.
Refs #2851.
Original-author conditional bounty / compensation request
I, @woahwhattheheck (GitHub ID 293286387), submit and affirmatively claim the original DroidGuard transport, lifecycle, request-admission, Android provider and client integration work recorded in this PR for any applicable issue/bounty compensation. Please identify the corresponding eligible BountyHub or sponsor program, acceptance criteria, payable amount and payout registration route before treating this original contribution as uncompensated work. Third-party protocol/source credit to GautamKumarOffical PR #3575 is explicitly retained; this request covers my own integration and changes, not someone else's original implementation.
Neither BountyHub listing eligibility nor portal registration for #2851 is verified by this update. The attached unit/Gradle results retain their exact historical source pins; actual device/Play Integrity/Dott unlock-and-ride acceptance remains outstanding. This metadata update changes no source, tests, PR head or evidence.