From 406b7516b3f73b41da5eb6c405a96bc796a91a85 Mon Sep 17 00:00:00 2001 From: pjay Date: Mon, 8 Jun 2026 19:10:54 +0530 Subject: [PATCH] iOS simulator driver: Go-native companion-backed backend (#62) * perf(ios): use prebuilt XCTest runner to cut startup * chore(ioscompanion): add companion asset prepare script * feat(ioscompanion): embed and extract simulator companion bundle * test(ioscompanion): cover companion stub and embedded extraction * docs: add third party notices for vendored companion * chore: ignore vendored companion bundle artifact * build(proto): pin simulator companion proto v1.1.8 * build(proto): add dedicated buf module and gen template for pinned proto * build(proto): exclude pinned companion proto from root buf workspace * feat(ioscompanion): commit generated companion gRPC stubs * feat(ioscompanion): map flat companion describe dump to TreeNode JSON * test(ioscompanion): add hierarchy-map golden and unit tests * feat(ioscompanion): port screen-settle stability polling to Go * test(ioscompanion): cover settle transitional, hash, streak, and cap rules * feat(ioscompanion): add USB HID keymap module * test(ioscompanion): cover keymap branches and paste-chord constants * build: embed companion assets via withcompanion tag * feat(ioscompanion): add transport companion interface * feat(ioscompanion): add HID event wrapper and builders * feat(ioscompanion): wire gRPC companion client and Dial * test(ioscompanion): cover HID builders and unit conversions * test(ioscompanion): cover Dial, process-state mapping, and install archive * test(ioscompanion): add gated simulator integration smoke test * feat(ioscompanion): text input and gesture HID composition with pasteboard fallback * test(ioscompanion): cover input composers, paste dialog loop, and pure helpers * feat(ioscompanion): add Describe to companion transport * feat(ioscompanion): implement DeviceDriver with companion supervision * test(ioscompanion): unit tests with fake companion transport * test(ioscompanion): gated companion smoke test * feat(ios): add ResolveTarget for simulator vs physical-device routing * feat(testrun): route iOS simulators through the native companion driver * refactor(testrun): defer the java preflight check to the physical-device path * feat(cli): add --ios-app-path flag * feat(doctor): split iOS checks into simulator and physical-device paths * test(folio): add gate-analyzer fixtures for G1-G5 * feat(folio): add iOS conformance gate script * chore(folio): wire gates recipe, app path, and ignore gate output * style: gofmt struct alignment drift * fix(doctor): probe simctl via xcrun instead of PATH lookup * fix(ioscompanion): spawn companion under driver-lifetime context * test(ioscompanion): prove companion child outlives startup context * fix(ioscompanion): chunk install payload under companion message cap * test(ioscompanion): cover install payload chunking * fix(ioscompanion): reinstall via simctl and sanitize companion env * fix(ioscompanion): wait out unresolved accessibility values after launch * perf(ioscompanion): paste long text for atomic landing * test(ioscompanion): cover paste threshold, retry flow, and sentinel detection * fix(ioscompanion): treat unresolved bridge values as transitional, never as content * fix(ioscompanion): accept masked secure-field values as paste landing * test(ioscompanion): cover sentinel mapping and masked-field landing * fix(ioscompanion): atomic erase and single-send paste to prevent doubling * test(ioscompanion): cover atomic erase, single chord, unverifiable field * fix(ioscompanion): verify paste on a time budget that outlasts the bridge blackout * test(ioscompanion): cover bridge-blackout paste verification * fix(ioscompanion): drop unresolved-value settle gate that never let empty-field screens settle * refactor(ioscompanion): name the empty-editable-field sentinel for what it is * perf(ioscompanion): tighten settle streak for the fast companion transport * feat(ioscompanion): pre-grant pasteboard access so unicode input skips the OS prompt * refactor(ioscompanion): drop paste warm-up now that the grant suppresses the prompt * test(ioscompanion): cover pasteboard grant on launch, drop warm-up tests * fix(ioscompanion): retry describe past transient collapsed accessibility dumps * test(ioscompanion): cover collapsed-dump detection * perf(ioscompanion): split raw and retrying describe so settle does not double-wait collapses * perf(ioscompanion): tighten settle now that collapses are handled separately * fix(ioscompanion): replace field content on input so blackout-skipped erase cannot accumulate text * test(ioscompanion): cover replace-on-input and TextReplacer capability * refactor(ioscompanion): neutralize HID events behind the transport seam * feat(companion): add simulator runner project skeleton * feat(companion): serve accessibility snapshots over the wire protocol * feat(companion): synthesize timestamped touch gestures * feat(companion): type text with replace semantics * feat(companion): serve the wire protocol from a parked runner * feat(ioscompanion): add TextEditor capability and unavailable sentinel to the transport seam * feat(ioscompanion): route text input through a text-editing companion when available * fix(companion): bind listener by port and source screen size from snapshot * feat(ioscompanion): add runner companion JSON transport * test(ioscompanion): cover runner transport protocol mapping * fix(companion): synthesize gestures synchronously to avoid the async completion crash * fix(companion): type on the main thread and recover from focus assertions * fix(companion): keep serving after an automation failure * refactor(companion): tidy snapshot serialization * fix(companion): honor sequential tap gaps and survive synthesis exceptions * feat(ioscompanion): expose native typing with an explicit replace flag * chore(companion): add runner asset prepare script * feat(ioscompanion): embed and extract the runner test bundle * test(ioscompanion): cover runner asset extraction * build(ioscompanion): commit runner asset archive * feat(ioscompanion): pair the legacy companion with the in-simulator runner * test(ioscompanion): cover hybrid routing, paste-grant skip, and port binding * fix(ioscompanion): reconnect after interrupted runner calls instead of restarting * fix(ioscompanion): route hybrid lifecycle through the runner and harden restarts * feat(companion): launch and terminate apps through the automation session * build(ioscompanion): refresh runner asset with session lifecycle * fix(ioscompanion): classify connection deadline expiry as caller budget * fix(companion): capture snapshots on the main thread inside the catch bridge * build(ioscompanion): refresh runner asset with main-thread snapshots * perf(ioscompanion): count read spans toward settle and capture snapshots concurrently * feat(ioscompanion): make the hybrid simulator companion the default * test(folio): cover runner-session orphans in the gate harness * test(ioscompanion): pin the child-lifetime test to the legacy path * fix(ioscompanion): keep mappable text on one HID stream and verify unicode clears * fix(ioscompanion): pause the clear chord so selection applies before the delete * fix(companion): prune the keyboard subtree from snapshots * build(ioscompanion): refresh runner asset without keyboard elements * fix(ioscompanion): capture the screenshot transport before a recovery can reassign it * fix(companion): pin the runner listener to loopback * fix(companion): size the replace delete prefix to cover any focused field * build(ioscompanion): refresh runner asset with loopback bind and replace fix * fix(cli): cancel the run context on SIGINT so spawned children are reaped * fix(testrun): point the device java preflight hint at the ios-device doctor * fix(folio): word-bound the G2 ERROR scan and drop the dead objc allowlist glob * test(ioscompanion): cover stopProcess, restart, and failed bring-up supervision * chore: add test-companion target for the withcompanion-tagged suite * chore(ioscompanion): stop tracking the runner archive build artifact * build: produce the runner archive from source like the companion bundle * refactor(conformance): move the gate harness out of examples/folio * chore(folio): drop the gate harness wiring from the example app --- .gitignore | 12 + Makefile | 27 +- THIRD_PARTY_NOTICES.md | 36 + buf.yaml | 7 + cmd/sanderling/doctor.go | 38 +- cmd/sanderling/doctor_test.go | 22 +- cmd/sanderling/main.go | 28 +- cmd/sanderling/main_test.go | 25 + cmd/sanderling/test_run.go | 19 +- companion/Sources/AppLifecycle.swift | 51 + companion/Sources/ElementTypeName.swift | 93 + companion/Sources/Gesture.swift | 206 + .../PrivateAutomation/PrivateAutomation.h | 74 + companion/Sources/RunnerTestCase.swift | 14 + companion/Sources/Server.swift | 174 + companion/Sources/Snapshot.swift | 80 + companion/Sources/TextInput.swift | 67 + companion/prepare.sh | 74 + companion/project.yml | 36 + conformance/.gitignore | 1 + conformance/gates.sh | 405 + .../testdata/g1-nonzero-exit/exit_status | 1 + .../testdata/g1-nonzero-exit/output.log | 1 + .../testdata/g1-nonzero-exit/trace.jsonl | 4 + .../testdata/g2-benign-only/exit_status | 1 + .../testdata/g2-benign-only/output.log | 5 + .../testdata/g2-benign-only/trace.jsonl | 4 + .../testdata/g2-real-error/exit_status | 1 + conformance/testdata/g2-real-error/output.log | 5 + .../testdata/g2-real-error/trace.jsonl | 4 + .../testdata/g3-dirty-field/exit_status | 1 + .../testdata/g3-dirty-field/output.log | 1 + .../testdata/g3-dirty-field/trace.jsonl | 2 + .../testdata/g4-doubled-text/exit_status | 1 + .../testdata/g4-doubled-text/output.log | 1 + .../testdata/g4-doubled-text/trace.jsonl | 2 + conformance/testdata/g5-slow-p95/exit_status | 1 + conformance/testdata/g5-slow-p95/output.log | 1 + conformance/testdata/g5-slow-p95/trace.jsonl | 4 + conformance/testdata/generate.py | 121 + conformance/testdata/pass/exit_status | 1 + conformance/testdata/pass/output.log | 1 + conformance/testdata/pass/trace.jsonl | 4 + examples/folio/justfile | 4 +- internal/_oracle/main.go | 8 +- .../ioscompanion/companionassets/embed.go | 140 + .../companionassets/embed_real.go | 12 + .../companionassets/embed_stub.go | 10 + .../companionassets/embed_stub_test.go | 27 + .../embed_withcompanion_test.go | 146 + .../ioscompanion/companionassets/prepare.sh | 97 + .../driver/ioscompanion/companionpb/idb.pb.go | 10596 ++++++++++++++++ .../ioscompanion/companionpb/idb_grpc.pb.go | 1687 +++ internal/driver/ioscompanion/driver.go | 1118 ++ internal/driver/ioscompanion/driver_test.go | 853 ++ internal/driver/ioscompanion/hierarchymap.go | 171 + .../driver/ioscompanion/hierarchymap_test.go | 260 + internal/driver/ioscompanion/input.go | 434 + internal/driver/ioscompanion/input_test.go | 433 + internal/driver/ioscompanion/keymap.go | 129 + internal/driver/ioscompanion/keymap_test.go | 209 + .../driver/ioscompanion/runnerassets/embed.go | 140 + .../ioscompanion/runnerassets/embed_real.go | 12 + .../ioscompanion/runnerassets/embed_stub.go | 10 + .../runnerassets/embed_stub_test.go | 27 + .../runnerassets/embed_withcompanion_test.go | 143 + internal/driver/ioscompanion/runnerspawn.go | 89 + internal/driver/ioscompanion/settle.go | 198 + internal/driver/ioscompanion/settle_test.go | 199 + internal/driver/ioscompanion/smoke_test.go | 85 + .../driver/ioscompanion/supervision_test.go | 157 + .../testdata/accounts-describe.json | 1 + .../ioscompanion/testdata/login-describe.json | 1 + .../ioscompanion/testdata/paste-dialog.json | 7 + .../driver/ioscompanion/transport/grpc.go | 295 + .../ioscompanion/transport/grpc_test.go | 158 + internal/driver/ioscompanion/transport/hid.go | 63 + .../driver/ioscompanion/transport/hid_test.go | 137 + .../transport/integration_test.go | 105 + .../driver/ioscompanion/transport/runner.go | 342 + .../ioscompanion/transport/runner_test.go | 556 + .../ioscompanion/transport/transport.go | 116 + internal/ios/ios.go | 78 + internal/ios/ios_test.go | 119 + internal/replay/runs_decode.go | 6 +- internal/replay/server.go | 1 - internal/runner/runner.go | 1 - internal/testrun/driver.go | 29 +- internal/testrun/preflight.go | 24 +- internal/testrun/preflight_test.go | 45 +- internal/testrun/testrun.go | 49 +- internal/trace/writer_test.go | 26 +- internal/verifier/spec_integration_test.go | 12 +- proto/idbcompanion/buf.gen.yaml | 19 + proto/idbcompanion/buf.yaml | 15 + proto/idbcompanion/idb.proto | 930 ++ .../dev/sanderling/sidecar/DriverBackend.kt | 2 +- 97 files changed, 22104 insertions(+), 83 deletions(-) create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 companion/Sources/AppLifecycle.swift create mode 100644 companion/Sources/ElementTypeName.swift create mode 100644 companion/Sources/Gesture.swift create mode 100644 companion/Sources/PrivateAutomation/PrivateAutomation.h create mode 100644 companion/Sources/RunnerTestCase.swift create mode 100644 companion/Sources/Server.swift create mode 100644 companion/Sources/Snapshot.swift create mode 100644 companion/Sources/TextInput.swift create mode 100755 companion/prepare.sh create mode 100644 companion/project.yml create mode 100644 conformance/.gitignore create mode 100755 conformance/gates.sh create mode 100644 conformance/testdata/g1-nonzero-exit/exit_status create mode 100644 conformance/testdata/g1-nonzero-exit/output.log create mode 100644 conformance/testdata/g1-nonzero-exit/trace.jsonl create mode 100644 conformance/testdata/g2-benign-only/exit_status create mode 100644 conformance/testdata/g2-benign-only/output.log create mode 100644 conformance/testdata/g2-benign-only/trace.jsonl create mode 100644 conformance/testdata/g2-real-error/exit_status create mode 100644 conformance/testdata/g2-real-error/output.log create mode 100644 conformance/testdata/g2-real-error/trace.jsonl create mode 100644 conformance/testdata/g3-dirty-field/exit_status create mode 100644 conformance/testdata/g3-dirty-field/output.log create mode 100644 conformance/testdata/g3-dirty-field/trace.jsonl create mode 100644 conformance/testdata/g4-doubled-text/exit_status create mode 100644 conformance/testdata/g4-doubled-text/output.log create mode 100644 conformance/testdata/g4-doubled-text/trace.jsonl create mode 100644 conformance/testdata/g5-slow-p95/exit_status create mode 100644 conformance/testdata/g5-slow-p95/output.log create mode 100644 conformance/testdata/g5-slow-p95/trace.jsonl create mode 100644 conformance/testdata/generate.py create mode 100644 conformance/testdata/pass/exit_status create mode 100644 conformance/testdata/pass/output.log create mode 100644 conformance/testdata/pass/trace.jsonl create mode 100644 internal/driver/ioscompanion/companionassets/embed.go create mode 100644 internal/driver/ioscompanion/companionassets/embed_real.go create mode 100644 internal/driver/ioscompanion/companionassets/embed_stub.go create mode 100644 internal/driver/ioscompanion/companionassets/embed_stub_test.go create mode 100644 internal/driver/ioscompanion/companionassets/embed_withcompanion_test.go create mode 100755 internal/driver/ioscompanion/companionassets/prepare.sh create mode 100644 internal/driver/ioscompanion/companionpb/idb.pb.go create mode 100644 internal/driver/ioscompanion/companionpb/idb_grpc.pb.go create mode 100644 internal/driver/ioscompanion/driver.go create mode 100644 internal/driver/ioscompanion/driver_test.go create mode 100644 internal/driver/ioscompanion/hierarchymap.go create mode 100644 internal/driver/ioscompanion/hierarchymap_test.go create mode 100644 internal/driver/ioscompanion/input.go create mode 100644 internal/driver/ioscompanion/input_test.go create mode 100644 internal/driver/ioscompanion/keymap.go create mode 100644 internal/driver/ioscompanion/keymap_test.go create mode 100644 internal/driver/ioscompanion/runnerassets/embed.go create mode 100644 internal/driver/ioscompanion/runnerassets/embed_real.go create mode 100644 internal/driver/ioscompanion/runnerassets/embed_stub.go create mode 100644 internal/driver/ioscompanion/runnerassets/embed_stub_test.go create mode 100644 internal/driver/ioscompanion/runnerassets/embed_withcompanion_test.go create mode 100644 internal/driver/ioscompanion/runnerspawn.go create mode 100644 internal/driver/ioscompanion/settle.go create mode 100644 internal/driver/ioscompanion/settle_test.go create mode 100644 internal/driver/ioscompanion/smoke_test.go create mode 100644 internal/driver/ioscompanion/supervision_test.go create mode 100644 internal/driver/ioscompanion/testdata/accounts-describe.json create mode 100644 internal/driver/ioscompanion/testdata/login-describe.json create mode 100644 internal/driver/ioscompanion/testdata/paste-dialog.json create mode 100644 internal/driver/ioscompanion/transport/grpc.go create mode 100644 internal/driver/ioscompanion/transport/grpc_test.go create mode 100644 internal/driver/ioscompanion/transport/hid.go create mode 100644 internal/driver/ioscompanion/transport/hid_test.go create mode 100644 internal/driver/ioscompanion/transport/integration_test.go create mode 100644 internal/driver/ioscompanion/transport/runner.go create mode 100644 internal/driver/ioscompanion/transport/runner_test.go create mode 100644 internal/driver/ioscompanion/transport/transport.go create mode 100644 proto/idbcompanion/buf.gen.yaml create mode 100644 proto/idbcompanion/buf.yaml create mode 100644 proto/idbcompanion/idb.proto diff --git a/.gitignore b/.gitignore index 5bcbfd2..a7d7fea 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,14 @@ node_modules/ # `go build -tags withsidecar`. Never commit: it's ~130 MB. internal/sidecarassets/assets/sidecar-all.jar +# Simulator companion bundle. Build artifact produced by prepare.sh before +# `go build -tags withcompanion`. Never commit: it's ~16 MB. +internal/driver/ioscompanion/companionassets/assets/companion-*.tar.gz + +# In-simulator runner bundle. Build artifact produced by companion/prepare.sh +# before `go build -tags withcompanion`. Never commit: it's ~5 MB. +internal/driver/ioscompanion/runnerassets/assets/runner-*.tar.gz + # spec-api compiled output pkg/spec/dist/ @@ -48,6 +56,10 @@ replay-ui/node_modules/ replay-ui/dist/ replay-ui/.vite/ +# Simulator runner generated project and build output (xcodegen artifacts) +companion/CompanionRunner.xcodeproj/ +companion/build/ + # folio-web frontend examples/folio-web/node_modules/ examples/folio-web/dist/ diff --git a/Makefile b/Makefile index df0bee4..c61f0e2 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,11 @@ BUF := buf GO_PACKAGES := ./... SIDECAR_JAR := sidecar/build/libs/sidecar-all.jar SIDECAR_EMBED := internal/sidecarassets/assets/sidecar-all.jar +COMPANION_EMBED := internal/driver/ioscompanion/companionassets/assets/companion-1.1.8.tar.gz +COMPANION_PREPARE := internal/driver/ioscompanion/companionassets/prepare.sh +RUNNER_EMBED := internal/driver/ioscompanion/runnerassets/assets/runner-1.0.0.tar.gz +RUNNER_PREPARE := companion/prepare.sh +RUNNER_SRC := $(shell find companion/Sources -type f -name '*.swift' 2>/dev/null) companion/project.yml SIDECAR_SRC := $(shell find sidecar/src -type f \( -name '*.kt' -o -name '*.kts' \) 2>/dev/null) build.gradle.kts settings.gradle.kts SANDERLING_BIN := bin/sanderling @@ -22,7 +27,7 @@ DOCS_TEMPLATE := docs/_template/page.html REPLAY_DIST := internal/replay/dist WEB_DIST := replay-ui/dist -.PHONY: bootstrap proto sidecar sanderling install test test-go test-browser test-kotlin test-spec-api web-test web-typecheck web-build web-dev replay-dev docs clean release-cli release-npm-dry +.PHONY: bootstrap proto sidecar sanderling install test test-go test-browser test-companion test-kotlin test-spec-api web-test web-typecheck web-build web-dev replay-dev docs clean release-cli release-npm-dry bootstrap: $(GO) mod download @@ -37,14 +42,14 @@ sidecar: $(SIDECAR_JAR) sanderling: $(SANDERLING_BIN) -$(SANDERLING_BIN): $(SIDECAR_EMBED) web-build +$(SANDERLING_BIN): $(SIDECAR_EMBED) $(COMPANION_EMBED) $(RUNNER_EMBED) web-build mkdir -p bin - $(GO) build -tags withsidecar -o $(SANDERLING_BIN) ./cmd/sanderling + $(GO) build -tags "withsidecar withcompanion" -o $(SANDERLING_BIN) ./cmd/sanderling # Installs `sanderling` into $GOBIN (or $GOPATH/bin) so it's directly on PATH for # anyone with a standard Go toolchain setup. -install: $(SIDECAR_EMBED) web-build - $(GO) install -tags withsidecar ./cmd/sanderling +install: $(SIDECAR_EMBED) $(COMPANION_EMBED) $(RUNNER_EMBED) web-build + $(GO) install -tags "withsidecar withcompanion" ./cmd/sanderling @dest="$$($(GO) env GOBIN)"; [ -n "$$dest" ] || dest="$$($(GO) env GOPATH)/bin"; echo "installed sanderling to $$dest" web-build: @@ -69,6 +74,12 @@ $(SIDECAR_EMBED): $(SIDECAR_JAR) mkdir -p $(dir $@) cp $< $@ +$(COMPANION_EMBED): $(COMPANION_PREPARE) + $(COMPANION_PREPARE) + +$(RUNNER_EMBED): $(RUNNER_SRC) $(RUNNER_PREPARE) + $(RUNNER_PREPARE) + test: test-go test-spec-api web-typecheck web-test test-go: @@ -82,6 +93,12 @@ web-test: test-browser: $(GO) test -tags browser ./test/browser/... ./internal/driver/chrome/... +# Runs the withcompanion-tagged tests (asset embedding, extraction, checksum +# reuse) against the real companion and runner bundles. Kept out of `test` +# because preparing the companion bundle needs the darwin toolchain. +test-companion: $(COMPANION_EMBED) $(RUNNER_EMBED) + $(GO) test -tags withcompanion ./internal/driver/ioscompanion/... + test-kotlin: ANDROID_HOME=$(ANDROID_HOME) $(GRADLE) :sidecar:test diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..b8de086 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,36 @@ +# Third Party Notices + +This project distributes third party software. The components below are +included under their respective licenses. + +## idb-companion + +- Project: idb (https://github.com/facebook/idb) +- Version: 1.1.8 +- Copyright: Copyright (c) Meta Platforms, Inc. and affiliates. +- License: MIT + +The simulator companion is vendored as a prebuilt binary artifact (the +`idb_companion` executable together with its runtime frameworks). It is +embedded into the sanderling binary at build time and extracted to a local +directory at runtime. + +### MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/buf.yaml b/buf.yaml index bc92f10..c58a455 100644 --- a/buf.yaml +++ b/buf.yaml @@ -1,6 +1,13 @@ version: v2 +# The pinned simulator companion proto lives under proto/idbcompanion and is +# deliberately excluded here. It is its own standalone buf module (see +# proto/idbcompanion/buf.yaml) so the STANDARD lint rules below never apply to +# it and a normal `make proto` never generates from it. Its stubs are committed +# and regenerated only via proto/idbcompanion/buf.gen.yaml. modules: - path: proto + excludes: + - proto/idbcompanion lint: use: - STANDARD diff --git a/cmd/sanderling/doctor.go b/cmd/sanderling/doctor.go index dafe612..40072ce 100644 --- a/cmd/sanderling/doctor.go +++ b/cmd/sanderling/doctor.go @@ -33,6 +33,8 @@ func doctorChecksFor(platform string) []doctorCheck { return androidChecks() case "ios": return iosChecks() + case "ios-device": + return append(iosChecks(), iosDeviceChecks()...) case "all": return allChecks() default: @@ -55,19 +57,30 @@ func androidChecks() []doctorCheck { } } +// iosChecks covers the simulator path, which the native companion drives with +// no JVM. A simulator host with no Java still passes. Physical-device runs +// additionally need java and the sidecar JAR, covered by iosDeviceChecks and +// surfaced through the "all" union. func iosChecks() []doctorCheck { return []doctorCheck{ - {Name: "xcrun on PATH", Run: checkExecutableOnPath("xcrun")}, - {Name: "simctl on PATH", Run: checkExecutableOnPath("simctl")}, - {Name: "java 17+ on PATH", Run: checkJavaVersion}, - {Name: "sidecar JAR is real (not placeholder)", Run: checkSidecarJAR}, + {Name: "xcrun on PATH (ios simulator)", Run: checkExecutableOnPath("xcrun")}, + {Name: "simctl available (ios simulator)", Run: checkSimctl}, + } +} + +// iosDeviceChecks covers the extra prerequisites a physical iOS device needs: +// the JVM and a real sidecar JAR for the sidecar driver path. +func iosDeviceChecks() []doctorCheck { + return []doctorCheck{ + {Name: "java 17+ on PATH (ios physical device)", Run: checkJavaVersion}, + {Name: "sidecar JAR is real (ios physical device)", Run: checkSidecarJAR}, } } func allChecks() []doctorCheck { seen := map[string]bool{} var combined []doctorCheck - for _, group := range [][]doctorCheck{webChecks(), androidChecks(), iosChecks()} { + for _, group := range [][]doctorCheck{webChecks(), androidChecks(), iosChecks(), iosDeviceChecks()} { for _, c := range group { if seen[c.Name] { continue @@ -98,6 +111,15 @@ func checkChromiumLaunch(ctx context.Context) error { return nil } +// checkSimctl exercises `xcrun simctl help`: simctl is an xcrun subcommand, +// not a standalone binary, so a PATH lookup can never find it. +func checkSimctl(ctx context.Context) error { + if err := exec.CommandContext(ctx, "xcrun", "simctl", "help").Run(); err != nil { + return fmt.Errorf("xcrun simctl help: %w", err) + } + return nil +} + func checkSidecarJAR(_ context.Context) error { if sidecarassets.IsPlaceholder() { return fmt.Errorf("placeholder JAR embedded; run `make sidecar && make sanderling` to embed the real fat JAR") @@ -116,15 +138,15 @@ func parseDoctorArgs(args []string, stderr io.Writer) (doctorOptions, error) { flagSet := flag.NewFlagSet("doctor", flag.ContinueOnError) flagSet.SetOutput(stderr) var options doctorOptions - flagSet.StringVar(&options.platform, "platform", "all", "target platform: web, android, ios, all") + flagSet.StringVar(&options.platform, "platform", "all", "target platform: web, android, ios, ios-device, all") if err := flagSet.Parse(args); err != nil { return doctorOptions{}, err } switch options.platform { - case "web", "android", "ios", "all": + case "web", "android", "ios", "ios-device", "all": return options, nil default: - return doctorOptions{}, fmt.Errorf("unsupported platform: %q (web, android, ios, all)", options.platform) + return doctorOptions{}, fmt.Errorf("unsupported platform: %q (web, android, ios, ios-device, all)", options.platform) } } diff --git a/cmd/sanderling/doctor_test.go b/cmd/sanderling/doctor_test.go index d33a34c..d6c464d 100644 --- a/cmd/sanderling/doctor_test.go +++ b/cmd/sanderling/doctor_test.go @@ -127,13 +127,33 @@ func TestDoctorChecksFor_All_IsUnion(t *testing.T) { for _, c := range all { names[c.Name]++ } - for _, name := range []string{"adb on PATH", "xcrun on PATH", "headless chromium can launch"} { + for _, name := range []string{"adb on PATH", "xcrun on PATH (ios simulator)", "headless chromium can launch"} { if names[name] != 1 { t.Errorf("expected %q in 'all' exactly once, got %d", name, names[name]) } } } +func TestDoctorChecksFor_iOSSimulator_OmitsJava(t *testing.T) { + for _, c := range doctorChecksFor("ios") { + if strings.Contains(c.Name, "java") || strings.Contains(c.Name, "sidecar") { + t.Errorf("ios simulator checks must omit %q; simulator runs need no JVM", c.Name) + } + } +} + +func TestDoctorChecksFor_iOSDevice_IncludesJava(t *testing.T) { + found := false + for _, c := range doctorChecksFor("ios-device") { + if strings.Contains(c.Name, "java") { + found = true + } + } + if !found { + t.Error("ios-device checks must include java for the sidecar path") + } +} + func TestDoctorChecksFor_UnknownPlatform(t *testing.T) { if got := doctorChecksFor("fuchsia"); got != nil { t.Errorf("expected nil for unknown platform, got %+v", got) diff --git a/cmd/sanderling/main.go b/cmd/sanderling/main.go index 8273656..16b0088 100644 --- a/cmd/sanderling/main.go +++ b/cmd/sanderling/main.go @@ -8,6 +8,8 @@ import ( "fmt" "io" "os" + "os/signal" + "syscall" "time" ) @@ -16,15 +18,16 @@ import ( var Version = "dev" type testOptions struct { - spec string - bundleID string - platform string - avd string - iosDevice string - duration time.Duration - seed int64 - output string - clearData bool + spec string + bundleID string + platform string + avd string + iosDevice string + iosAppPath string + duration time.Duration + seed int64 + output string + clearData bool } const topUsage = `sanderling is a property-based UI fuzzer for mobile apps. @@ -50,6 +53,7 @@ func parseTestArgs(args []string, stderr io.Writer) (testOptions, error) { flagSet.StringVar(&options.platform, "platform", "android", "target platform: android, ios, web") flagSet.StringVar(&options.avd, "avd", "", "Android AVD name to boot if no device is connected") flagSet.StringVar(&options.iosDevice, "ios-device", "", "iOS simulator name or UDID to boot if none is running") + flagSet.StringVar(&options.iosAppPath, "ios-app-path", "", "path to the .app bundle for iOS simulator clear-state reinstall") flagSet.DurationVar(&options.duration, "duration", 5*time.Minute, "total test duration") flagSet.Int64Var(&options.seed, "seed", 0, "RNG seed (0 = random)") flagSet.StringVar(&options.output, "output", "./runs", "output directory for traces") @@ -72,7 +76,11 @@ func parseTestArgs(args []string, stderr io.Writer) (testOptions, error) { } func runTest(options testOptions, stdout io.Writer) error { - ctx, cancel := context.WithCancel(context.Background()) + // A signal-aware root context: on Ctrl-C the cancellation propagates into + // the drivers' process contexts, so spawned children (the iOS companion, + // the xcodebuild runner session) get their SIGTERM instead of outliving + // the run as orphans. + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer cancel() return runTestPipeline(ctx, options, stdout) } diff --git a/cmd/sanderling/main_test.go b/cmd/sanderling/main_test.go index 2cb99a5..fa9ebc0 100644 --- a/cmd/sanderling/main_test.go +++ b/cmd/sanderling/main_test.go @@ -181,6 +181,31 @@ func TestParseTestArgs_IosDeviceFlag(t *testing.T) { } } +func TestParseTestArgs_IosAppPathFlag(t *testing.T) { + options, err := parseTestArgs([]string{ + "--spec", "s.ts", + "--bundle-id", "com.example.app", + "--platform", "ios", + "--ios-app-path", "/tmp/build/iosApp.app", + }, io.Discard) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if options.iosAppPath != "/tmp/build/iosApp.app" { + t.Errorf("expected iosAppPath=/tmp/build/iosApp.app, got %q", options.iosAppPath) + } +} + +func TestParseTestArgs_IosAppPathOptional(t *testing.T) { + options, err := parseTestArgs([]string{"--spec", "s.ts", "--bundle-id", "com.example.app"}, io.Discard) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if options.iosAppPath != "" { + t.Errorf("iosAppPath default: got %q, want empty", options.iosAppPath) + } +} + func TestRun_TestSubcommand_PipelineErrors(t *testing.T) { // Web skips host-dependent device boot and bundles first, so a missing spec // deterministically surfaces a bundle-resolution error. This proves the diff --git a/cmd/sanderling/test_run.go b/cmd/sanderling/test_run.go index 96d32e1..f8407d1 100644 --- a/cmd/sanderling/test_run.go +++ b/cmd/sanderling/test_run.go @@ -9,14 +9,15 @@ import ( func runTestPipeline(ctx context.Context, options testOptions, stdout io.Writer) error { return testrun.Execute(ctx, testrun.Options{ - Spec: options.spec, - BundleID: options.bundleID, - Platform: options.platform, - AVD: options.avd, - IosDevice: options.iosDevice, - Duration: options.duration, - Seed: options.seed, - Output: options.output, - ClearData: options.clearData, + Spec: options.spec, + BundleID: options.bundleID, + Platform: options.platform, + AVD: options.avd, + IosDevice: options.iosDevice, + IosAppPath: options.iosAppPath, + Duration: options.duration, + Seed: options.seed, + Output: options.output, + ClearData: options.clearData, }, stdout) } diff --git a/companion/Sources/AppLifecycle.swift b/companion/Sources/AppLifecycle.swift new file mode 100644 index 0000000..d7f8770 --- /dev/null +++ b/companion/Sources/AppLifecycle.swift @@ -0,0 +1,51 @@ +import XCTest + +// Launches and terminates apps through the automation session. Lifecycle +// performed outside the session leaves its cached app proxies bound to dead +// processes, after which snapshots hang and typing asserts. +enum AppLifecycle { + enum LifecycleError: Error { + case failed(String) + } + + static func launch(bundleIdentifier: String, foregroundIfRunning: Bool) throws { + try onMainCatching { + let application = XCUIApplication(bundleIdentifier: bundleIdentifier) + if foregroundIfRunning { + // activate starts the app when needed and foregrounds it when + // already running, without a forced relaunch. + application.activate() + } else { + application.launch() + } + } + } + + static func terminate(bundleIdentifier: String) throws { + try onMainCatching { + let application = XCUIApplication(bundleIdentifier: bundleIdentifier) + if application.state == .notRunning { + return + } + application.terminate() + } + } + + // onMainCatching runs automation work on the main thread and converts a + // framework assertion into a thrown error so the server survives it. + private static func onMainCatching(_ work: @escaping () -> Void) throws { + var caughtError: NSError? + var completed = false + let run = { + completed = CompanionRunCatching(work, &caughtError) + } + if Thread.isMainThread { + run() + } else { + DispatchQueue.main.sync(execute: run) + } + if !completed { + throw LifecycleError.failed(caughtError?.localizedDescription ?? "unknown") + } + } +} diff --git a/companion/Sources/ElementTypeName.swift b/companion/Sources/ElementTypeName.swift new file mode 100644 index 0000000..d5f517d --- /dev/null +++ b/companion/Sources/ElementTypeName.swift @@ -0,0 +1,93 @@ +import XCTest + +// Maps XCUIElement.ElementType to the capitalized conventional type names the +// Go mapper consumes. textView is deliberately "TextArea": the Go side treats +// "TextArea" and "TextField" as the editable types. +func elementTypeName(_ elementType: XCUIElement.ElementType) -> String { + switch elementType { + case .application: return "Application" + case .group: return "Group" + case .window: return "Window" + case .sheet: return "Sheet" + case .drawer: return "Drawer" + case .alert: return "Alert" + case .dialog: return "Dialog" + case .button: return "Button" + case .radioButton: return "RadioButton" + case .radioGroup: return "RadioGroup" + case .checkBox: return "CheckBox" + case .disclosureTriangle: return "DisclosureTriangle" + case .popUpButton: return "PopUpButton" + case .comboBox: return "ComboBox" + case .menuButton: return "MenuButton" + case .toolbarButton: return "ToolbarButton" + case .popover: return "Popover" + case .keyboard: return "Keyboard" + case .key: return "Key" + case .navigationBar: return "NavigationBar" + case .tabBar: return "TabBar" + case .tabGroup: return "TabGroup" + case .toolbar: return "Toolbar" + case .statusBar: return "StatusBar" + case .table: return "Table" + case .tableRow: return "TableRow" + case .tableColumn: return "TableColumn" + case .outline: return "Outline" + case .outlineRow: return "OutlineRow" + case .browser: return "Browser" + case .collectionView: return "CollectionView" + case .slider: return "Slider" + case .pageIndicator: return "PageIndicator" + case .progressIndicator: return "ProgressIndicator" + case .activityIndicator: return "ActivityIndicator" + case .segmentedControl: return "SegmentedControl" + case .picker: return "Picker" + case .pickerWheel: return "PickerWheel" + case .switch: return "Switch" + case .toggle: return "Toggle" + case .link: return "Link" + case .image: return "Image" + case .icon: return "Icon" + case .searchField: return "SearchField" + case .scrollView: return "ScrollView" + case .scrollBar: return "ScrollBar" + case .staticText: return "StaticText" + case .textField: return "TextField" + case .secureTextField: return "SecureTextField" + case .datePicker: return "DatePicker" + case .textView: return "TextArea" + case .menu: return "Menu" + case .menuItem: return "MenuItem" + case .menuBar: return "MenuBar" + case .menuBarItem: return "MenuBarItem" + case .map: return "Map" + case .webView: return "WebView" + case .incrementArrow: return "IncrementArrow" + case .decrementArrow: return "DecrementArrow" + case .timeline: return "Timeline" + case .ratingIndicator: return "RatingIndicator" + case .valueIndicator: return "ValueIndicator" + case .splitGroup: return "SplitGroup" + case .splitter: return "Splitter" + case .relevanceIndicator: return "RelevanceIndicator" + case .colorWell: return "ColorWell" + case .helpTag: return "HelpTag" + case .matte: return "Matte" + case .dockItem: return "DockItem" + case .ruler: return "Ruler" + case .rulerMarker: return "RulerMarker" + case .grid: return "Grid" + case .levelIndicator: return "LevelIndicator" + case .cell: return "Cell" + case .layoutArea: return "LayoutArea" + case .layoutItem: return "LayoutItem" + case .handle: return "Handle" + case .stepper: return "Stepper" + case .tab: return "Tab" + case .touchBar: return "TouchBar" + case .statusItem: return "StatusItem" + case .any: return "Any" + case .other: return "Other" + @unknown default: return "Other" + } +} diff --git a/companion/Sources/Gesture.swift b/companion/Sources/Gesture.swift new file mode 100644 index 0000000..59148f2 --- /dev/null +++ b/companion/Sources/Gesture.swift @@ -0,0 +1,206 @@ +import XCTest +import CoreGraphics + +// Synthesizes one timestamped event record from a list of primitive events. +// +// The synthesizer constrains how precise timing can be expressed: +// - Within one pointer path, event offsets are honored to the millisecond, +// but nothing after the path's first lift is delivered: a second press is +// silently dropped, whether added through the press helper or as a raw +// pointer event. +// - Across paths, every event is delivered, but each path's timeline is +// normalized to its own first event, so a later start offset collapses. +// +// Sequential taps therefore become one path per tap, each anchored by a raw +// zero-offset move event at the tap point: the anchor pins the path's +// timeline origin to the record's, turning the tap's absolute press offset +// into an in-path delta, which is honored. This preserves the inter-tap gap +// to the millisecond. +enum Gesture { + enum GestureError: Error { + case missingField(String) + case unknownKind(String) + case touchUpWithoutTouchDown + case touchDownWhileTouchActive + case synthesisFailed(String) + } + + // minimumHoldSeconds keeps a tap's press observable when its down and up + // arrive at the same offset; far below any long-press threshold. + private static let minimumHoldSeconds = 0.03 + + // pathGapSeconds orders a press strictly after the previous lift when a + // requested gap is shorter than the minimum hold. + private static let pathGapSeconds = 0.005 + + // The raw shape of a touch move, read once from a probe path built with + // the path helpers so no private enum values are hardcoded. + private struct MoveEventShape { + let type: Int + let button: Int + let clicks: UInt + } + + private static let moveShape: MoveEventShape = { + let probe = XCPointerEventPath(forTouchAt: CGPoint(x: 1, y: 1), offset: 0) + probe.move(to: CGPoint(x: 2, y: 2), atOffset: minimumHoldSeconds) + probe.liftUp(atOffset: minimumHoldSeconds * 2) + let events = probe.pointerEvents + let move = events.count > 1 ? events[1] : nil + return MoveEventShape( + type: move?.eventType ?? 2, + button: move?.buttonType ?? 0, + clicks: move.map { UInt($0.clickCount) } ?? 0 + ) + }() + + // probe reports the raw events the path helpers produce, for diagnostics. + static func probe() -> [String: Any] { + let path = XCPointerEventPath(forTouchAt: CGPoint(x: 1, y: 2), offset: 0) + path.move(to: CGPoint(x: 3, y: 4), atOffset: 0.05) + path.liftUp(atOffset: 0.1) + return [ + "descriptions": path.pointerEvents.map { $0.description }, + "types": path.pointerEvents.map { $0.eventType }, + "buttons": path.pointerEvents.map { $0.buttonType }, + "offsets": path.pointerEvents.map { $0.offset }, + ] + } + + // touchSegment is one press-to-lift span at absolute record offsets. + private struct touchSegment { + var downPoint: CGPoint + var downOffset: Double + var upPoint: CGPoint + var upOffset: Double + var movePoint: CGPoint? + var moveOffset: Double = 0 + } + + static func perform(events: [[String: Any]]) throws { + var offset = 0.0 + var lastLiftOffset = -1.0 + var segments: [touchSegment] = [] + var active: touchSegment? + + func point(_ event: [String: Any], _ xKey: String, _ yKey: String) throws -> CGPoint { + guard let x = event[xKey] as? Double, let y = event[yKey] as? Double else { + throw GestureError.missingField("\(xKey)/\(yKey)") + } + return CGPoint(x: x, y: y) + } + + for event in events { + guard let kind = event["kind"] as? String else { + throw GestureError.missingField("kind") + } + switch kind { + case "touchDown": + guard active == nil else { + throw GestureError.touchDownWhileTouchActive + } + let location = try point(event, "x", "y") + let downOffset = max(offset, lastLiftOffset + Gesture.pathGapSeconds) + active = touchSegment( + downPoint: location, downOffset: downOffset, + upPoint: location, upOffset: downOffset + Gesture.minimumHoldSeconds) + case "touchUp": + guard var segment = active else { + throw GestureError.touchUpWithoutTouchDown + } + segment.upPoint = try point(event, "x", "y") + segment.upOffset = max(offset, segment.downOffset + Gesture.minimumHoldSeconds) + lastLiftOffset = segment.upOffset + segments.append(segment) + active = nil + case "delay": + guard let milliseconds = event["milliseconds"] as? Double else { + throw GestureError.missingField("milliseconds") + } + offset += milliseconds / 1000.0 + case "swipe": + guard active == nil else { + throw GestureError.touchDownWhileTouchActive + } + let from = try point(event, "fromX", "fromY") + let to = try point(event, "toX", "toY") + guard let seconds = event["seconds"] as? Double else { + throw GestureError.missingField("seconds") + } + let downOffset = max(offset, lastLiftOffset + Gesture.pathGapSeconds) + segments.append(touchSegment( + downPoint: from, downOffset: downOffset, + upPoint: to, upOffset: downOffset + seconds, + movePoint: to, moveOffset: downOffset + seconds)) + lastLiftOffset = downOffset + seconds + offset = downOffset + seconds + default: + throw GestureError.unknownKind(kind) + } + } + if let segment = active { + // A down without an up would leave a stuck touch on screen. + segments.append(segment) + } + guard !segments.isEmpty else { + throw GestureError.missingField("events") + } + + // The synthesis stack raises ObjC exceptions for invalid paths; bridge + // them into a thrown error so the server survives a bad record. + var caughtException: NSError? + var synthesisError: Error? + let completed = CompanionRunCatching({ + do { + try Gesture.synthesize(segments: segments) + } catch { + synthesisError = error + } + }, &caughtException) + if !completed { + throw GestureError.synthesisFailed(caughtException?.localizedDescription ?? "unknown exception") + } + if let synthesisError = synthesisError { + throw synthesisError + } + } + + private static func synthesize(segments: [touchSegment]) throws { + let record = XCSynthesizedEventRecord(name: "companion", interfaceOrientation: 0) + for (index, segment) in segments.enumerated() { + // Paths play back one after another with their start offsets + // normalized away, so each path carries its events relative to its + // own press. A trailing hover move then stretches the path out to + // the next press's absolute offset, which preserves the requested + // inter-tap gap under sequential playback. + let base = segment.downOffset + let path = XCPointerEventPath(forTouchAt: segment.downPoint, offset: 0) + // Distinct pointer identities: without this, sequential taps at + // the same point merge into one multi-tap gesture and the app + // sees a single click no matter the gap. + path.index = UInt64(index) + if let movePoint = segment.movePoint { + path.move(to: movePoint, atOffset: segment.moveOffset - base) + } + let upDelta = segment.upOffset - base + path.liftUp(atOffset: upDelta) + if index + 1 < segments.count { + let tailDelta = segments[index + 1].downOffset - base + if tailDelta > upDelta { + path._addPointerEvent(XCPointerEvent( + type: Gesture.moveShape.type, + buttonType: Gesture.moveShape.button, + coordinate: segment.upPoint, + offset: tailDelta, + clickCount: Gesture.moveShape.clicks)) + } + } + record.add(path) + } + + // Synchronous delivery: returns whether the event was synthesized and + // populates the error out-pointer on failure. This avoids the async + // completion-block path. + try record.synthesize() + } +} diff --git a/companion/Sources/PrivateAutomation/PrivateAutomation.h b/companion/Sources/PrivateAutomation/PrivateAutomation.h new file mode 100644 index 0000000..ac66745 --- /dev/null +++ b/companion/Sources/PrivateAutomation/PrivateAutomation.h @@ -0,0 +1,74 @@ +#import +#import + +// Private XCTest event-synthesis interfaces. These ship inside the testing +// frameworks bundled with Xcode and are the only path to timestamped touch +// events with millisecond-precision offsets. The selectors below were verified +// present in Xcode 26's XCUIAutomation framework binary. + +NS_ASSUME_NONNULL_BEGIN + +// One raw pointer event inside a path: type, screen coordinate, and offset in +// seconds. Constructed directly so a single path can carry several +// press-and-lift pairs at exact offsets; the press-and-lift helper methods on +// the path silently drop a second press after a lift. +@interface XCPointerEvent : NSObject ++ (instancetype)eventWithType:(NSInteger)eventType + buttonType:(NSInteger)buttonType + coordinate:(CGPoint)coordinate + offset:(double)offset + clickCount:(NSUInteger)clickCount; +@property (nonatomic, readonly) NSInteger eventType; +@property (nonatomic, readonly) NSInteger buttonType; +@property (nonatomic, readonly) NSUInteger clickCount; +@property (nonatomic, readonly) CGPoint coordinate; +@property (nonatomic, readonly) double offset; +@end + +// One pointer's path through a synthesized event. Offsets are seconds from the +// start of the event record. +@interface XCPointerEventPath : NSObject +- (instancetype)initForTouchAtPoint:(CGPoint)point offset:(double)offset; +- (void)moveToPoint:(CGPoint)point atOffset:(double)offset; +- (void)pressDownAtOffset:(double)offset; +- (void)liftUpAtOffset:(double)offset; +- (void)_addPointerEvent:(XCPointerEvent *)pointerEvent; +@property (nonatomic, readonly) NSArray *pointerEvents; +@property (nonatomic) unsigned long long index; +@end + +// A complete synthesized event composed of one or more pointer paths. +// synthesizeWithError: delivers the event synchronously and returns whether it +// succeeded, which avoids the asynchronous completion-block path. +@interface XCSynthesizedEventRecord : NSObject +- (instancetype)initWithName:(NSString *)name + interfaceOrientation:(NSInteger)interfaceOrientation; +- (void)addPointerEventPath:(XCPointerEventPath *)pointerEventPath; +- (BOOL)synthesizeWithError:(NSError *_Nullable *_Nullable)error; +@end + +// The runner-side session that delivers synthesized events to the system under +// test. +@interface XCTRunnerDaemonSession : NSObject ++ (instancetype)sharedSession; +- (void)synthesizeEvent:(XCSynthesizedEventRecord *)eventRecord + completion:(void (^)(NSError *_Nullable error))completion; +@end + +// Runs the block and converts any raised NSException into an NSError so a +// testing-framework assertion does not terminate the runner process. +NS_INLINE BOOL CompanionRunCatching(void (^block)(void), NSError *_Nullable *_Nullable error) { + @try { + block(); + return YES; + } @catch (NSException *exception) { + if (error) { + *error = [NSError errorWithDomain:@"dev.sanderling.companion" + code:1 + userInfo:@{NSLocalizedDescriptionKey: exception.reason ?: exception.name}]; + } + return NO; + } +} + +NS_ASSUME_NONNULL_END diff --git a/companion/Sources/RunnerTestCase.swift b/companion/Sources/RunnerTestCase.swift new file mode 100644 index 0000000..43af2e5 --- /dev/null +++ b/companion/Sources/RunnerTestCase.swift @@ -0,0 +1,14 @@ +import XCTest + +// The single long-lived test method: it starts the server and parks the runner +// in a run loop so the simulator process stays alive serving the protocol. +final class RunnerTestCase: XCTestCase { + func testServeForever() throws { + // A failed automation call must never end the test: the server has to + // keep serving so the host can recover and retry. + continueAfterFailure = true + let server = try Server() + server.start() + RunLoop.current.run() + } +} diff --git a/companion/Sources/Server.swift b/companion/Sources/Server.swift new file mode 100644 index 0000000..1d67f94 --- /dev/null +++ b/companion/Sources/Server.swift @@ -0,0 +1,174 @@ +import Foundation +import Network +import UIKit +import XCTest + +// Newline-delimited JSON-over-TCP server. Listens on 127.0.0.1 on the port from +// the COMPANION_PORT environment variable (default 27753). Each line is one +// request; each reply is one line. +final class Server { + private let port: NWEndpoint.Port + private let listener: NWListener + private let queue = DispatchQueue(label: "dev.sanderling.companion.server") + // Requests are handled off the network queue so blocking automation work + // (snapshot, gesture synthesis) never stalls accept and receive. + private let work = DispatchQueue(label: "dev.sanderling.companion.work") + + // The bundle identifier of the most recently snapshotted app, used as the + // default target for typeText when no explicit bundleId is supplied. + private var currentBundleIdentifier = "com.apple.springboard" + + init() throws { + let resolvedPort = ProcessInfo.processInfo.environment["COMPANION_PORT"] + .flatMap { UInt16($0) } ?? 27753 + self.port = NWEndpoint.Port(rawValue: resolvedPort)! + let parameters = NWParameters.tcp + parameters.allowLocalEndpointReuse = true + // The simulator shares the host network stack, so an unbound listener + // would be reachable from the host's LAN interfaces. Pin it to + // loopback so only local processes can drive the automation surface. + parameters.requiredLocalEndpoint = NWEndpoint.hostPort(host: "127.0.0.1", port: self.port) + self.listener = try NWListener(using: parameters) + } + + func start() { + listener.newConnectionHandler = { [weak self] connection in + self?.accept(connection) + } + listener.start(queue: queue) + } + + private func accept(_ connection: NWConnection) { + connection.start(queue: queue) + receive(on: connection, buffer: Data()) + } + + private func receive(on connection: NWConnection, buffer: Data) { + connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] data, _, isComplete, error in + guard let self = self else { return } + var working = buffer + if let data = data { + working.append(data) + } + while let newlineIndex = working.firstIndex(of: 0x0A) { + let lineData = working.subdata(in: working.startIndex.. [String: Any] { + guard let request = try JSONSerialization.jsonObject(with: lineData) as? [String: Any] else { + throw ServerError.malformedRequest + } + let requestId = request["id"] as? Int ?? 0 + guard let method = request["method"] as? String else { + throw ServerError.malformedRequest + } + let params = request["params"] as? [String: Any] ?? [:] + let result = try handle(method: method, params: params) + return ["id": requestId, "result": result] + } + + private func handle(method: String, params: [String: Any]) throws -> [String: Any] { + switch method { + case "health": + return ["ok": true] + case "describe": + return describeScreen() + case "snapshot": + let bundleIdentifier = params["bundleId"] as? String ?? currentBundleIdentifier + currentBundleIdentifier = bundleIdentifier + return ["elements": Snapshot.elements(bundleIdentifier: bundleIdentifier)] + case "gesture": + let events = params["events"] as? [[String: Any]] ?? [] + try Gesture.perform(events: events) + return ["ok": true] + case "gestureProbe": + return Gesture.probe() + case "launch": + let bundleIdentifier = params["bundleId"] as? String ?? currentBundleIdentifier + let foregroundIfRunning = params["foregroundIfRunning"] as? Bool ?? false + try AppLifecycle.launch(bundleIdentifier: bundleIdentifier, foregroundIfRunning: foregroundIfRunning) + return ["ok": true] + case "terminate": + let bundleIdentifier = params["bundleId"] as? String ?? currentBundleIdentifier + try AppLifecycle.terminate(bundleIdentifier: bundleIdentifier) + return ["ok": true] + case "typeText": + let text = params["text"] as? String ?? "" + let replace = params["replace"] as? Bool ?? false + let bundleIdentifier = params["bundleId"] as? String ?? currentBundleIdentifier + try TextInput.type(text: text, replace: replace, bundleIdentifier: bundleIdentifier) + return ["ok": true] + case "screenshot": + return try screenshot() + default: + throw ServerError.unknownMethod(method) + } + } + + private func describeScreen() -> [String: Any] { + // The runner process has no foreground scene, so UIScreen.main.bounds + // returns a fallback size. The springboard application snapshot frame is + // the device size in points; the screen scale comes from UIScreen. + var scale: CGFloat = 1 + let collect = { scale = UIScreen.main.scale } + if Thread.isMainThread { + collect() + } else { + DispatchQueue.main.sync(execute: collect) + } + var width = 0 + var height = 0 + if let root = Snapshot.elements(bundleIdentifier: "com.apple.springboard").first, + let frame = root["frame"] as? [String: Any] { + width = Int(frame["width"] as? Double ?? 0) + height = Int(frame["height"] as? Double ?? 0) + } + return [ + "widthPoints": width, + "heightPoints": height, + "scale": Double(scale), + ] + } + + private func screenshot() throws -> [String: Any] { + let image = XCUIScreen.main.screenshot() + return ["pngBase64": image.pngRepresentation.base64EncodedString()] + } + + private func send(_ object: [String: Any], on connection: NWConnection) { + guard var data = try? JSONSerialization.data(withJSONObject: object) else { + return + } + data.append(0x0A) + connection.send(content: data, completion: .contentProcessed { _ in }) + } + + enum ServerError: Error { + case malformedRequest + case unknownMethod(String) + } +} diff --git a/companion/Sources/Snapshot.swift b/companion/Sources/Snapshot.swift new file mode 100644 index 0000000..63783ce --- /dev/null +++ b/companion/Sources/Snapshot.swift @@ -0,0 +1,80 @@ +import XCTest + +// Flattens an accessibility snapshot tree into the legacy flat element array. +enum Snapshot { + // Produces the flat element array for the given bundle identifier. Falls back + // to springboard if the requested app is not running or its snapshot throws. + // Snapshot resolution can demand the test's execution context, which lives + // on the main thread, and a framework assertion raises an exception that + // would kill the server; so the capture runs on main inside the catch + // bridge, and an assertion yields an empty dump, which upstream treats as + // collapsed and retries. + static func elements(bundleIdentifier: String) -> [[String: Any]] { + var result: [[String: Any]] = [] + var caughtError: NSError? + let work = { + _ = CompanionRunCatching({ + if let captured = try? capture(bundleIdentifier: bundleIdentifier) { + result = captured + } else if let fallback = try? capture(bundleIdentifier: "com.apple.springboard") { + result = fallback + } + }, &caughtError) + } + if Thread.isMainThread { + work() + } else { + DispatchQueue.main.sync(execute: work) + } + return result + } + + private static func capture(bundleIdentifier: String) throws -> [[String: Any]] { + let application = XCUIApplication(bundleIdentifier: bundleIdentifier) + let root = try application.snapshot() + var result: [[String: Any]] = [] + walk(root, into: &result) + return result + } + + private static func walk(_ node: XCUIElementSnapshot, into result: inout [[String: Any]]) { + // The keyboard subtree is pruned: the legacy accessibility bridge + // never exposed it, its key frames are unreliable as tap targets, and + // its shift-state churn destabilizes settle hashing. + if node.elementType == .keyboard { + return + } + result.append(serialize(node)) + for child in node.children { + walk(child, into: &result) + } + } + + private static func serialize(_ node: XCUIElementSnapshot) -> [String: Any] { + let frame = node.frame + return [ + "type": elementTypeName(node.elementType), + "frame": [ + "x": Double(frame.origin.x), + "y": Double(frame.origin.y), + "width": Double(frame.size.width), + "height": Double(frame.size.height), + ], + "enabled": node.isEnabled, + "AXLabel": nullableString(node.label), + "AXValue": nullableString(stringifyValue(node.value)), + "AXUniqueId": nullableString(node.identifier), + ] + } + + private static func nullableString(_ value: String?) -> Any { + guard let value = value, !value.isEmpty else { return NSNull() } + return value + } + + private static func stringifyValue(_ value: Any?) -> String? { + guard let value = value else { return nil } + if let text = value as? String { return text } + return String(describing: value) + } +} diff --git a/companion/Sources/TextInput.swift b/companion/Sources/TextInput.swift new file mode 100644 index 0000000..0efcf3f --- /dev/null +++ b/companion/Sources/TextInput.swift @@ -0,0 +1,67 @@ +import XCTest + +// Types unicode text into the focused element through the public typeText API, +// avoiding the pasteboard entirely. With replace enabled, the current value +// length is consumed first by prepending that many delete keys so the whole +// replacement happens in one typeText call. +enum TextInput { + enum TextInputError: Error { + case typingFailed(String) + } + + static func type(text: String, replace: Bool, bundleIdentifier: String) throws { + let application = XCUIApplication(bundleIdentifier: bundleIdentifier) + + var payload = text + if replace { + let deleteCount = deletePrefixLength(bundleIdentifier: bundleIdentifier) + if deleteCount > 0 { + let deletes = String(repeating: XCUIKeyboardKey.delete.rawValue, count: deleteCount) + payload = deletes + text + } + } + + // Type at the application level so it lands in whatever holds keyboard + // focus, without resolving a specific element. Compose Multiplatform + // text fields do not reliably expose hasKeyboardFocus to the query layer. + // typeText must run on the main thread. + var caughtError: NSError? + var completed = false + runOnMain { + completed = CompanionRunCatching({ + application.typeText(payload) + }, &caughtError) + } + if !completed { + throw TextInputError.typingFailed(caughtError?.localizedDescription ?? "unknown") + } + } + + private static func runOnMain(_ work: () -> Void) { + if Thread.isMainThread { + work() + } else { + DispatchQueue.main.sync(execute: work) + } + } + + // Sizes the delete prefix for replace. The snapshot does not say which + // editable field holds keyboard focus (Compose fields never expose + // hasKeyboardFocus), so the prefix covers the longest editable value on + // screen: deletes beyond the focused field's content are no-ops at the + // start of the field, while undersizing would leave residue and silently + // turn replace into append. + private static func deletePrefixLength(bundleIdentifier: String) -> Int { + let elements = Snapshot.elements(bundleIdentifier: bundleIdentifier) + var longest = 0 + for element in elements { + guard let type = element["type"] as? String, + type == "TextArea" || type == "TextField", + let value = element["AXValue"] as? String else { + continue + } + longest = max(longest, value.count) + } + return longest + } +} diff --git a/companion/prepare.sh b/companion/prepare.sh new file mode 100755 index 0000000..b1d8b8e --- /dev/null +++ b/companion/prepare.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# +# Builds the embeddable simulator runner tarball. +# +# Regenerates the Xcode project, builds the UI test bundle for a generic +# simulator destination (so no booted simulator is needed), then stages a +# relocatable test root: the runner app alongside its xctestrun. A port +# placeholder is injected into the xctestrun so the driver can substitute the +# real port before launching. The staged tree is packaged as a tarball. +# +# Output: ../internal/driver/ioscompanion/runnerassets/assets/runner-1.0.0.tar.gz + +set -euo pipefail + +# Keep the archiver from emitting AppleDouble (._name) sidecar entries that +# would otherwise duplicate extended attributes into the payload. +export COPYFILE_DISABLE=1 + +VERSION="1.0.0" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ASSETS_DIR="${SCRIPT_DIR}/../internal/driver/ioscompanion/runnerassets/assets" +OUTPUT="${ASSETS_DIR}/runner-${VERSION}.tar.gz" + +cd "${SCRIPT_DIR}" + +# Regenerate the project from its spec. The .xcodeproj is gitignored, so a +# fresh checkout must produce it before building. +/opt/homebrew/bin/xcodegen --spec project.yml + +# Build the UI test bundle for a generic simulator destination so no booted +# simulator is required; products land under build/Build/Products/. +xcodebuild build-for-testing \ + -project CompanionRunner.xcodeproj \ + -scheme CompanionRunner \ + -destination 'generic/platform=iOS Simulator' \ + -derivedDataPath build \ + CODE_SIGNING_ALLOWED=NO + +PRODUCTS="${SCRIPT_DIR}/build/Build/Products" + +STAGE="$(mktemp -d)" +trap 'rm -rf "${STAGE}"' EXIT + +# The xctestrun name embeds the simulator SDK version, so resolve it rather +# than hardcoding it. +XCTESTRUN="$(find "${PRODUCTS}" -maxdepth 1 -name '*.xctestrun' -print -quit)" +if [ -z "${XCTESTRUN}" ]; then + echo "no xctestrun under ${PRODUCTS}" >&2 + exit 1 +fi +cp "${XCTESTRUN}" "${STAGE}/runner.xctestrun" + +# Copy the runner app into the staged products directory. The .swiftmodule +# sibling is build-time only and is not copied. +mkdir -p "${STAGE}/Debug-iphonesimulator" +cp -R \ + "${PRODUCTS}/Debug-iphonesimulator/CompanionRunnerUITests-Runner.app" \ + "${STAGE}/Debug-iphonesimulator/CompanionRunnerUITests-Runner.app" + +# Inject the port placeholder the driver substitutes at launch time. +/usr/libexec/PlistBuddy \ + -c "Add :CompanionRunnerUITests:EnvironmentVariables:COMPANION_PORT string __COMPANION_PORT__" \ + "${STAGE}/runner.xctestrun" + +STAGED_BYTES="$(find "${STAGE}" -type f -exec stat -f%z {} + | awk '{sum += $1} END {print sum}')" + +mkdir -p "${ASSETS_DIR}" +tar -czf "${OUTPUT}" -C "${STAGE}" . + +SHA="$(shasum -a 256 "${OUTPUT}" | awk '{print $1}')" + +echo "wrote ${OUTPUT}" +echo "staged uncompressed size: ${STAGED_BYTES} bytes" +echo "sha256: ${SHA}" diff --git a/companion/project.yml b/companion/project.yml new file mode 100644 index 0000000..4ebd6a8 --- /dev/null +++ b/companion/project.yml @@ -0,0 +1,36 @@ +name: CompanionRunner +options: + bundleIdPrefix: dev.sanderling + deploymentTarget: + iOS: "16.0" + createIntermediateGroups: true + +settings: + base: + CODE_SIGNING_ALLOWED: "NO" + CODE_SIGNING_REQUIRED: "NO" + SWIFT_VERSION: "5.0" + +targets: + CompanionRunnerUITests: + type: bundle.ui-testing + platform: iOS + productName: CompanionRunnerUITests + sources: + - Sources + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: dev.sanderling.companion + SWIFT_OBJC_BRIDGING_HEADER: Sources/PrivateAutomation/PrivateAutomation.h + OTHER_LDFLAGS: + - "-framework" + - "XCTest" + +schemes: + CompanionRunner: + build: + targets: + CompanionRunnerUITests: all + test: + targets: + - CompanionRunnerUITests diff --git a/conformance/.gitignore b/conformance/.gitignore new file mode 100644 index 0000000..a1e0396 --- /dev/null +++ b/conformance/.gitignore @@ -0,0 +1 @@ +runs/ diff --git a/conformance/gates.sh b/conformance/gates.sh new file mode 100755 index 0000000..81d580b --- /dev/null +++ b/conformance/gates.sh @@ -0,0 +1,405 @@ +#!/usr/bin/env bash +# Scripted conformance gate for the iOS simulator driver. Runs five serial, +# non-overlapping 3-minute fuzz runs against the folio example app +# (examples/folio) and scores five gates (G1..G5) over the captured traces and +# output. Exits non-zero if any gate fails. +# +# Backends: +# BACKEND=simulator (default) drive the booted iOS simulator +# BACKEND=device drive an attached physical iPhone via the +# native sidecar; select it with +# IOS_DEVICE="" (passed as --ios-device) +# +# Usage: +# ./gates.sh run the simulator gates +# BACKEND=device IOS_DEVICE="iPhone" ./gates.sh +# ./gates.sh --self-test run the offline analyzer tests only +# +# Tunables (environment): +# RUNS=5 number of serial runs +# DURATION=3m per-run fuzz duration +# SEED=0 fuzz seed +# P95_LIMIT_MS=2500 G5 p95 step-latency ceiling in milliseconds +# SANDERLING=sanderling binary to invoke + +set -euo pipefail + +script_directory="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +folio_directory="$(cd "${script_directory}/../examples/folio" && pwd)" + +BACKEND="${BACKEND:-simulator}" +RUNS="${RUNS:-5}" +DURATION="${DURATION:-3m}" +SEED="${SEED:-0}" +P95_LIMIT_MS="${P95_LIMIT_MS:-2500}" +SANDERLING="${SANDERLING:-sanderling}" +IOS_DEVICE="${IOS_DEVICE:-iPhone 17 Pro}" + +bundle_id="app.folio" +spec_path="${folio_directory}/sanderling/spec.ts" +ios_app="${folio_directory}/app/iosApp/build/Build/Products/Debug-iphonesimulator/iosApp.app" + +# The companion binary, embedded for simulator runs. Referenced by file name +# only for the orphan-process check; prose elsewhere says "the companion". +companion_process_name="idb_companion" + +# Known-benign stderr lines the companion always prints. These are matched as +# fixed substrings and excluded from the G2 ERROR scan. Keep this list tight: +# only lines that are provably harmless and emitted on every healthy run. +benign_stderr_substrings=( + # The dynamic linker reports the same Objective-C class registered by two + # loaded images. The companion runs fine; this is cosmetic. The real line + # reads "objc[]: Class ...", so the fixed substring is "]: Class". + "]: Class" + "is implemented in both" + "One of the two will be used. Which one is undefined." + # gRPC and absl emit informational banner lines on startup. + "WARNING: All log messages before absl::InitializeLog()" +) + +# ---- gate analyzers (pure, operate on a single run directory) -------------- + +# G2 helper: strip benign companion noise, then report sanderling ERROR lines. +# sanderling's progress logger renders error-level records as lines beginning +# "error:" (see internal/testrun/progress.go). We also catch the upper-case +# ERROR token (word-bounded, so ERRORS/ERRORLESS and path fragments do not +# trip the gate) for safety against future handlers. +error_lines_in() { + local output_file="$1" + local filtered + filtered="$(cat "$output_file")" + local pattern + for pattern in "${benign_stderr_substrings[@]}"; do + filtered="$(printf '%s\n' "$filtered" | grep -vF "$pattern" || true)" + done + printf '%s\n' "$filtered" | grep -E '(^error:|\bERROR\b)' || true +} + +# G1: process exit status recorded by the runner loop. +gate_exit_zero() { + local run_directory="$1" + [[ "$(cat "${run_directory}/exit_status")" == "0" ]] +} + +# G2: no sanderling ERROR lines after filtering benign companion noise. +gate_no_error_lines() { + local run_directory="$1" + local found + found="$(error_lines_in "${run_directory}/output.log")" + [[ -z "$found" ]] +} + +# G3: the first hierarchy snapshot shows the login screen with empty email and +# password fields (clear-state proof). Reads the first trace line carrying a +# hierarchy and asserts LoginEmail/LoginPassword carry no text. +gate_clear_state() { + local run_directory="$1" + local trace_file="${run_directory}/trace.jsonl" + [[ -f "$trace_file" ]] || return 1 + local verdict + verdict="$(jq -s -r ' + [ .[] | select(.hierarchy != null) ] as $withHierarchy + | if ($withHierarchy | length) == 0 then "fail:no-hierarchy" + else ($withHierarchy[0].hierarchy.elements // []) as $elements + | ($elements | map(select(.resourceId == "LoginEmail")) | first) as $email + | ($elements | map(select(.resourceId == "LoginPassword")) | first) as $password + | if $email == null or $password == null then "fail:no-login-fields" + elif (($email.attrs.text // $email.text // "") != "") then "fail:email-not-empty" + elif (($password.attrs.text // $password.text // "") != "") then "fail:password-not-empty" + else "pass" end + end + ' "$trace_file")" + [[ "$verdict" == "pass" ]] +} + +# G4: no doubled text after InputText. For each InputText action targeting a +# field, the field's value in the NEXT hierarchy must not contain the input +# concatenated with itself (catches append-vs-replace and double-paste bugs). +# The action chosen at step N is applied before step N+1 is observed, so the +# effect lands in the following snapshot. +gate_no_doubled_text() { + local run_directory="$1" + local trace_file="${run_directory}/trace.jsonl" + [[ -f "$trace_file" ]] || return 1 + local verdict + verdict="$(jq -s -r ' + # Map a selector like "testTag:LoginScreen > testTag:LoginEmail" to its + # target field id: the token after the final ":" of the last segment. + def target_field(selector): + (selector | split(">") | last | gsub("^\\s+|\\s+$";"")) as $last + | ($last | split(":") | last); + + [ .[] | select(.hierarchy != null) ] as $steps + | reduce range(0; ($steps | length)) as $i ([]; + ($steps[$i]) as $current + | (if $i + 1 < ($steps | length) then $steps[$i + 1] else null end) as $next + | if ($current.next_action.kind == "InputText") and ($next != null) + then + (target_field($current.next_action.selector // "")) as $field + | ($current.next_action.text // "") as $typed + | (($next.hierarchy.elements // []) + | map(select(.resourceId == $field)) | first) as $element + | if $element != null and $typed != "" + then + (($element.attrs.text // $element.text // "")) as $value + | if ($value | contains($typed + $typed)) + then . + [{field: $field, typed: $typed, value: $value}] + else . end + else . end + else . end) + | if length == 0 then "pass" + else "fail:" + (.[0].field) + ":" + (.[0].value) end + ' "$trace_file")" + [[ "$verdict" == "pass" ]] +} + +# Emit one step-latency sample per consecutive trace-step pair, in +# milliseconds, on stdout. The trace records a wall-clock timestamp per step; +# the latency of a step is the gap to the next step's observation. Used by G5, +# which aggregates samples across all runs before computing the p95. Timestamps +# are RFC3339 with fractional seconds and a numeric offset, so they are parsed +# with python3 rather than jq's UTC-only fromdateiso8601. +emit_step_latencies() { + local trace_file="$1" + [[ -f "$trace_file" ]] || return 0 + jq -r 'select(.timestamp != null) | .timestamp' "$trace_file" \ + | python3 -c ' +import sys +from datetime import datetime +stamps = [datetime.fromisoformat(line.strip()) for line in sys.stdin if line.strip()] +for earlier, later in zip(stamps, stamps[1:]): + print(int((later - earlier).total_seconds() * 1000)) +' +} + +# Compute the p95 (nearest-rank) of the latency samples on stdin, in ms. +p95_of() { + python3 -c ' +import sys, math +samples = sorted(int(float(line)) for line in sys.stdin if line.strip()) +if not samples: + print(0) + sys.exit(0) +rank = max(1, math.ceil(0.95 * len(samples))) +print(samples[rank - 1]) +' +} + +# G5 orphan check: report any lingering companion, runner session (the hybrid +# simulator driver hosts an in-simulator runner), and, on the device backend, +# the XCTest runner java sidecar. Empty output means clean. +orphan_processes() { + local found="" + if pgrep -f "$companion_process_name" >/dev/null 2>&1; then + found+="companion " + fi + if pgrep -f "sanderling-runner.*xctestrun" >/dev/null 2>&1; then + found+="runner-session " + fi + if pgrep -f "CompanionRunnerUITests-Runner" >/dev/null 2>&1; then + found+="runner-app " + fi + if [[ "$BACKEND" == "device" ]]; then + # The native sidecar that drives the XCTest runner for physical devices. + if pgrep -f "sanderling.*sidecar.jar" >/dev/null 2>&1; then + found+="sidecar " + fi + fi + printf '%s' "$found" +} + +# ---- run orchestration ----------------------------------------------------- + +invoke_sanderling() { + local output_directory="$1" + local output_log="$2" + local exit_status_file="$3" + + local target_flags=() + if [[ "$BACKEND" == "device" ]]; then + target_flags=(--ios-device "$IOS_DEVICE") + else + # Simulator: build + install the current app so each run starts from the + # current build, matching the test-ios recipe. clear-state reinstall uses + # --ios-app-path. just ios boots IOS_DEVICE if nothing is booted. + target_flags=(--ios-device "$IOS_DEVICE" --ios-app-path "$ios_app") + fi + + local status=0 + "$SANDERLING" test \ + --platform ios \ + --spec "$spec_path" \ + --bundle-id "$bundle_id" \ + "${target_flags[@]}" \ + --duration "$DURATION" \ + --seed "$SEED" \ + --output "$output_directory" \ + >"$output_log" 2>&1 || status=$? + printf '%s' "$status" >"$exit_status_file" +} + +# Locate the run directory sanderling created under output_directory (it nests +# a timestamped subdirectory) and normalise its artifacts up one level. +collect_run_artifacts() { + local output_directory="$1" + [[ -f "${output_directory}/trace.jsonl" ]] && return 0 + local produced + produced="$(find "$output_directory" -mindepth 2 -name 'trace.jsonl' 2>/dev/null | head -1 || true)" + if [[ -n "$produced" ]]; then + cp "$produced" "${output_directory}/trace.jsonl" 2>/dev/null || true + fi +} + +run_gates() { + if [[ "$BACKEND" == "simulator" ]]; then + echo "preparing folio build for the simulator backend" + ( cd "$folio_directory" && just ios >/dev/null ) + fi + + local timestamp + timestamp="$(date +%Y%m%d-%H%M%S)" + local gate_root="${script_directory}/runs/${timestamp}" + mkdir -p "$gate_root" + echo "gate root: ${gate_root}" + echo "backend=${BACKEND} runs=${RUNS} duration=${DURATION} seed=${SEED} p95_limit_ms=${P95_LIMIT_MS}" + + local all_latencies="${gate_root}/all-latencies.txt" + : >"$all_latencies" + + local -a g1 g2 g3 g4 g5 + local run_index + for ((run_index = 1; run_index <= RUNS; run_index++)); do + local run_directory="${gate_root}/run-${run_index}" + mkdir -p "$run_directory" + echo "run ${run_index}/${RUNS} -> ${run_directory}" + + invoke_sanderling "$run_directory" "${run_directory}/output.log" "${run_directory}/exit_status" + collect_run_artifacts "$run_directory" + + g1[run_index]=$(gate_exit_zero "$run_directory" && echo PASS || echo FAIL) + g2[run_index]=$(gate_no_error_lines "$run_directory" && echo PASS || echo FAIL) + g3[run_index]=$(gate_clear_state "$run_directory" && echo PASS || echo FAIL) + g4[run_index]=$(gate_no_doubled_text "$run_directory" && echo PASS || echo FAIL) + + emit_step_latencies "${run_directory}/trace.jsonl" >>"$all_latencies" + + local orphans + orphans="$(orphan_processes)" + if [[ -n "$orphans" ]]; then + g5[run_index]="FAIL" + echo " orphaned processes after run ${run_index}: ${orphans}" + else + g5[run_index]="PENDING" + fi + done + + local p95 + p95="$(p95_of <"$all_latencies")" + local final_orphans + final_orphans="$(orphan_processes)" + + # G5 is global: p95 is computed over every run's samples and an orphan after + # any single run fails the whole gate. Decide once, then stamp every row. + local g5_global="PASS" + if [[ "$p95" -ge "$P95_LIMIT_MS" ]]; then + g5_global="FAIL" + fi + if [[ -n "$final_orphans" ]]; then + g5_global="FAIL" + echo "orphaned processes at end: ${final_orphans}" + fi + for ((run_index = 1; run_index <= RUNS; run_index++)); do + if [[ "${g5[run_index]}" == "FAIL" ]]; then + g5_global="FAIL" + fi + done + for ((run_index = 1; run_index <= RUNS; run_index++)); do + g5[run_index]="$g5_global" + done + + echo + printf 'run G1 G2 G3 G4 G5\n' + local verdict="PASS" + for ((run_index = 1; run_index <= RUNS; run_index++)); do + printf '%-4s %-5s %-5s %-5s %-5s %-5s\n' \ + "$run_index" "${g1[run_index]}" "${g2[run_index]}" "${g3[run_index]}" "${g4[run_index]}" "${g5[run_index]}" + for cell in "${g1[run_index]}" "${g2[run_index]}" "${g3[run_index]}" "${g4[run_index]}" "${g5[run_index]}"; do + [[ "$cell" == "PASS" ]] || verdict="FAIL" + done + done + echo + echo "p95 step latency: ${p95}ms (limit ${P95_LIMIT_MS}ms)" + if [[ "$verdict" == "PASS" ]]; then + echo "GATES PASS" + return 0 + fi + echo "GATES FAIL" + return 1 +} + +# ---- offline self-test ----------------------------------------------------- +# Exercises every analyzer against canned passing/failing run directories under +# testdata/ so the parsing logic can be checked without a device. + +self_test() { + local testdata="${script_directory}/testdata" + local failures=0 + + assert() { + local label="$1" expected="$2" actual="$3" + if [[ "$expected" == "$actual" ]]; then + printf 'ok %s\n' "$label" + else + printf 'FAIL %s (expected %s, got %s)\n' "$label" "$expected" "$actual" + failures=$((failures + 1)) + fi + } + + assert "G1 pass run exits zero" PASS \ + "$(gate_exit_zero "${testdata}/pass" && echo PASS || echo FAIL)" + assert "G1 fail run nonzero exit" FAIL \ + "$(gate_exit_zero "${testdata}/g1-nonzero-exit" && echo PASS || echo FAIL)" + + assert "G2 pass run no errors" PASS \ + "$(gate_no_error_lines "${testdata}/pass" && echo PASS || echo FAIL)" + assert "G2 benign noise tolerated" PASS \ + "$(gate_no_error_lines "${testdata}/g2-benign-only" && echo PASS || echo FAIL)" + assert "G2 real error caught" FAIL \ + "$(gate_no_error_lines "${testdata}/g2-real-error" && echo PASS || echo FAIL)" + + assert "G3 pass clear state" PASS \ + "$(gate_clear_state "${testdata}/pass" && echo PASS || echo FAIL)" + assert "G3 dirty fields caught" FAIL \ + "$(gate_clear_state "${testdata}/g3-dirty-field" && echo PASS || echo FAIL)" + + assert "G4 pass no doubling" PASS \ + "$(gate_no_doubled_text "${testdata}/pass" && echo PASS || echo FAIL)" + assert "G4 doubled text caught" FAIL \ + "$(gate_no_doubled_text "${testdata}/g4-doubled-text" && echo PASS || echo FAIL)" + + local pass_p95 slow_p95 + pass_p95="$(emit_step_latencies "${testdata}/pass/trace.jsonl" | p95_of)" + slow_p95="$(emit_step_latencies "${testdata}/g5-slow-p95/trace.jsonl" | p95_of)" + assert "G5 pass p95 under limit" PASS \ + "$([[ "$pass_p95" -lt "$P95_LIMIT_MS" ]] && echo PASS || echo FAIL)" + assert "G5 slow p95 over limit" FAIL \ + "$([[ "$slow_p95" -lt "$P95_LIMIT_MS" ]] && echo PASS || echo FAIL)" + + echo + if [[ "$failures" -eq 0 ]]; then + echo "SELF-TEST PASS" + return 0 + fi + echo "SELF-TEST FAIL (${failures} failed)" + return 1 +} + +main() { + case "${1:-}" in + --self-test) self_test ;; + "") run_gates ;; + *) echo "usage: $0 [--self-test]" >&2; exit 2 ;; + esac +} + +main "$@" diff --git a/conformance/testdata/g1-nonzero-exit/exit_status b/conformance/testdata/g1-nonzero-exit/exit_status new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/conformance/testdata/g1-nonzero-exit/exit_status @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/conformance/testdata/g1-nonzero-exit/output.log b/conformance/testdata/g1-nonzero-exit/output.log new file mode 100644 index 0000000..ca891a9 --- /dev/null +++ b/conformance/testdata/g1-nonzero-exit/output.log @@ -0,0 +1 @@ +run complete diff --git a/conformance/testdata/g1-nonzero-exit/trace.jsonl b/conformance/testdata/g1-nonzero-exit/trace.jsonl new file mode 100644 index 0000000..f40d3b2 --- /dev/null +++ b/conformance/testdata/g1-nonzero-exit/trace.jsonl @@ -0,0 +1,4 @@ +{"step": 1, "timestamp": "2026-06-06T12:00:00.600000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "demo@folio.app", "selector": "testTag:LoginScreen > testTag:LoginEmail"}} +{"step": 2, "timestamp": "2026-06-06T12:00:01.200000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "ledger123", "selector": "testTag:LoginScreen > testTag:LoginPassword"}} +{"step": 3, "timestamp": "2026-06-06T12:00:01.800000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "ledger123"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} +{"step": 4, "timestamp": "2026-06-06T12:00:02.400000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "ledger123"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} diff --git a/conformance/testdata/g2-benign-only/exit_status b/conformance/testdata/g2-benign-only/exit_status new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/conformance/testdata/g2-benign-only/exit_status @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/conformance/testdata/g2-benign-only/output.log b/conformance/testdata/g2-benign-only/output.log new file mode 100644 index 0000000..9403538 --- /dev/null +++ b/conformance/testdata/g2-benign-only/output.log @@ -0,0 +1,5 @@ +companion pid=4242 listening on 127.0.0.1:51000 +objc[4242]: Class FBSDKError is implemented in both /a (0x1) and /b (0x2). One of the two will be used. Which one is undefined. +WARNING: All log messages before absl::InitializeLog() is called are written to STDERR +archived previous runs under runs/ERRORS_archive +run complete diff --git a/conformance/testdata/g2-benign-only/trace.jsonl b/conformance/testdata/g2-benign-only/trace.jsonl new file mode 100644 index 0000000..f40d3b2 --- /dev/null +++ b/conformance/testdata/g2-benign-only/trace.jsonl @@ -0,0 +1,4 @@ +{"step": 1, "timestamp": "2026-06-06T12:00:00.600000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "demo@folio.app", "selector": "testTag:LoginScreen > testTag:LoginEmail"}} +{"step": 2, "timestamp": "2026-06-06T12:00:01.200000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "ledger123", "selector": "testTag:LoginScreen > testTag:LoginPassword"}} +{"step": 3, "timestamp": "2026-06-06T12:00:01.800000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "ledger123"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} +{"step": 4, "timestamp": "2026-06-06T12:00:02.400000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "ledger123"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} diff --git a/conformance/testdata/g2-real-error/exit_status b/conformance/testdata/g2-real-error/exit_status new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/conformance/testdata/g2-real-error/exit_status @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/conformance/testdata/g2-real-error/output.log b/conformance/testdata/g2-real-error/output.log new file mode 100644 index 0000000..6fb91f1 --- /dev/null +++ b/conformance/testdata/g2-real-error/output.log @@ -0,0 +1,5 @@ +companion pid=4242 listening on 127.0.0.1:51000 +objc[4242]: Class FBSDKError is implemented in both /a (0x1) and /b (0x2). One of the two will be used. Which one is undefined. +WARNING: All log messages before absl::InitializeLog() is called are written to STDERR +archived previous runs under runs/ERRORS_archive +error: hierarchy fetch failed: companion connection lost diff --git a/conformance/testdata/g2-real-error/trace.jsonl b/conformance/testdata/g2-real-error/trace.jsonl new file mode 100644 index 0000000..f40d3b2 --- /dev/null +++ b/conformance/testdata/g2-real-error/trace.jsonl @@ -0,0 +1,4 @@ +{"step": 1, "timestamp": "2026-06-06T12:00:00.600000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "demo@folio.app", "selector": "testTag:LoginScreen > testTag:LoginEmail"}} +{"step": 2, "timestamp": "2026-06-06T12:00:01.200000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "ledger123", "selector": "testTag:LoginScreen > testTag:LoginPassword"}} +{"step": 3, "timestamp": "2026-06-06T12:00:01.800000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "ledger123"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} +{"step": 4, "timestamp": "2026-06-06T12:00:02.400000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "ledger123"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} diff --git a/conformance/testdata/g3-dirty-field/exit_status b/conformance/testdata/g3-dirty-field/exit_status new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/conformance/testdata/g3-dirty-field/exit_status @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/conformance/testdata/g3-dirty-field/output.log b/conformance/testdata/g3-dirty-field/output.log new file mode 100644 index 0000000..ca891a9 --- /dev/null +++ b/conformance/testdata/g3-dirty-field/output.log @@ -0,0 +1 @@ +run complete diff --git a/conformance/testdata/g3-dirty-field/trace.jsonl b/conformance/testdata/g3-dirty-field/trace.jsonl new file mode 100644 index 0000000..5319d91 --- /dev/null +++ b/conformance/testdata/g3-dirty-field/trace.jsonl @@ -0,0 +1,2 @@ +{"step": 1, "timestamp": "2026-06-06T12:00:00.600000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "stale@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "leftover"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "demo@folio.app", "selector": "testTag:LoginScreen > testTag:LoginEmail"}} +{"step": 2, "timestamp": "2026-06-06T12:00:01.200000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "leftover"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} diff --git a/conformance/testdata/g4-doubled-text/exit_status b/conformance/testdata/g4-doubled-text/exit_status new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/conformance/testdata/g4-doubled-text/exit_status @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/conformance/testdata/g4-doubled-text/output.log b/conformance/testdata/g4-doubled-text/output.log new file mode 100644 index 0000000..ca891a9 --- /dev/null +++ b/conformance/testdata/g4-doubled-text/output.log @@ -0,0 +1 @@ +run complete diff --git a/conformance/testdata/g4-doubled-text/trace.jsonl b/conformance/testdata/g4-doubled-text/trace.jsonl new file mode 100644 index 0000000..7108273 --- /dev/null +++ b/conformance/testdata/g4-doubled-text/trace.jsonl @@ -0,0 +1,2 @@ +{"step": 1, "timestamp": "2026-06-06T12:00:00.600000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "demo@folio.app", "selector": "testTag:LoginScreen > testTag:LoginEmail"}} +{"step": 2, "timestamp": "2026-06-06T12:00:01.200000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.appdemo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} diff --git a/conformance/testdata/g5-slow-p95/exit_status b/conformance/testdata/g5-slow-p95/exit_status new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/conformance/testdata/g5-slow-p95/exit_status @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/conformance/testdata/g5-slow-p95/output.log b/conformance/testdata/g5-slow-p95/output.log new file mode 100644 index 0000000..ca891a9 --- /dev/null +++ b/conformance/testdata/g5-slow-p95/output.log @@ -0,0 +1 @@ +run complete diff --git a/conformance/testdata/g5-slow-p95/trace.jsonl b/conformance/testdata/g5-slow-p95/trace.jsonl new file mode 100644 index 0000000..ae4d996 --- /dev/null +++ b/conformance/testdata/g5-slow-p95/trace.jsonl @@ -0,0 +1,4 @@ +{"step": 1, "timestamp": "2026-06-06T12:00:04+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "demo@folio.app", "selector": "testTag:LoginScreen > testTag:LoginEmail"}} +{"step": 2, "timestamp": "2026-06-06T12:00:08+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "ledger123", "selector": "testTag:LoginScreen > testTag:LoginPassword"}} +{"step": 3, "timestamp": "2026-06-06T12:00:12+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "ledger123"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} +{"step": 4, "timestamp": "2026-06-06T12:00:16+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "ledger123"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} diff --git a/conformance/testdata/generate.py b/conformance/testdata/generate.py new file mode 100644 index 0000000..2132ce3 --- /dev/null +++ b/conformance/testdata/generate.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# Regenerates the canned gate-analyzer fixtures. Each fixture is a minimal but +# format-accurate run directory (trace.jsonl + output.log + exit_status) that +# isolates one gate's pass or fail condition. Run from this directory: +# python3 generate.py +import json +import os +from datetime import datetime, timedelta, timezone + +HERE = os.path.dirname(os.path.abspath(__file__)) +BASE = datetime(2026, 6, 6, 12, 0, 0, tzinfo=timezone(timedelta(hours=5, minutes=30))) + + +def element(resource_id, text="", element_class="android.widget.EditText"): + return { + "resourceId": resource_id, + "class": element_class, + "editable": True, + "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, + "attrs": {"resource-id": resource_id, "class": element_class, "text": text}, + } + + +def login_screen(email_text="", password_text=""): + return { + "elements": [ + element("LoginScreen", element_class="android.view.View"), + element("LoginEmail", email_text), + element("LoginPassword", password_text), + element("LoginSubmit", element_class="android.view.View"), + ] + } + + +def input_action(field, text): + return { + "kind": "InputText", + "text": text, + "selector": f"testTag:LoginScreen > testTag:{field}", + } + + +def step(index, gap_ms, hierarchy, next_action=None): + timestamp = (BASE + timedelta(milliseconds=index * gap_ms)).isoformat() + record = {"step": index, "timestamp": timestamp, "hierarchy": hierarchy} + if next_action is not None: + record["next_action"] = next_action + return record + + +def write_run(name, steps, exit_status="0", output_lines=None): + directory = os.path.join(HERE, name) + os.makedirs(directory, exist_ok=True) + with open(os.path.join(directory, "trace.jsonl"), "w") as handle: + for record in steps: + handle.write(json.dumps(record) + "\n") + with open(os.path.join(directory, "exit_status"), "w") as handle: + handle.write(exit_status) + lines = output_lines if output_lines is not None else ["run complete\n"] + with open(os.path.join(directory, "output.log"), "w") as handle: + handle.writelines(lines) + + +# A clean run: empty login fields first, single (not doubled) typed values in +# the following snapshots, sub-second step gaps. +def healthy_steps(gap_ms=600): + return [ + step(1, gap_ms, login_screen("", ""), input_action("LoginEmail", "demo@folio.app")), + step(2, gap_ms, login_screen("demo@folio.app", ""), input_action("LoginPassword", "ledger123")), + step(3, gap_ms, login_screen("demo@folio.app", "ledger123")), + step(4, gap_ms, login_screen("demo@folio.app", "ledger123")), + ] + + +BENIGN_NOISE = [ + "companion pid=4242 listening on 127.0.0.1:51000\n", + "objc[4242]: Class FBSDKError is implemented in both /a (0x1) and /b (0x2). " + "One of the two will be used. Which one is undefined.\n", + "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n", + # An incidental ERROR substring inside a longer word: the G2 scan is + # word-bounded, so this must not trip the gate. + "archived previous runs under runs/ERRORS_archive\n", +] + +write_run("pass", healthy_steps()) + +write_run("g1-nonzero-exit", healthy_steps(), exit_status="1") + +write_run("g2-benign-only", healthy_steps(), output_lines=BENIGN_NOISE + ["run complete\n"]) + +write_run( + "g2-real-error", + healthy_steps(), + output_lines=BENIGN_NOISE + + ["error: hierarchy fetch failed: companion connection lost\n"], +) + +# G3: first snapshot already carries text in the login fields (clear-state +# failed to wipe the previous session). +write_run( + "g3-dirty-field", + [ + step(1, 600, login_screen("stale@folio.app", "leftover"), input_action("LoginEmail", "demo@folio.app")), + step(2, 600, login_screen("demo@folio.app", "leftover")), + ], +) + +# G4: after typing into LoginEmail the next snapshot shows the value appended to +# itself (append-vs-replace / double-paste regression). +write_run( + "g4-doubled-text", + [ + step(1, 600, login_screen("", ""), input_action("LoginEmail", "demo@folio.app")), + step(2, 600, login_screen("demo@folio.appdemo@folio.app", "")), + ], +) + +# G5: step gaps far exceed the 2.5s ceiling, driving p95 over the limit. +write_run("g5-slow-p95", healthy_steps(gap_ms=4000)) + +print("fixtures written") diff --git a/conformance/testdata/pass/exit_status b/conformance/testdata/pass/exit_status new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/conformance/testdata/pass/exit_status @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/conformance/testdata/pass/output.log b/conformance/testdata/pass/output.log new file mode 100644 index 0000000..ca891a9 --- /dev/null +++ b/conformance/testdata/pass/output.log @@ -0,0 +1 @@ +run complete diff --git a/conformance/testdata/pass/trace.jsonl b/conformance/testdata/pass/trace.jsonl new file mode 100644 index 0000000..f40d3b2 --- /dev/null +++ b/conformance/testdata/pass/trace.jsonl @@ -0,0 +1,4 @@ +{"step": 1, "timestamp": "2026-06-06T12:00:00.600000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "demo@folio.app", "selector": "testTag:LoginScreen > testTag:LoginEmail"}} +{"step": 2, "timestamp": "2026-06-06T12:00:01.200000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": ""}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}, "next_action": {"kind": "InputText", "text": "ledger123", "selector": "testTag:LoginScreen > testTag:LoginPassword"}} +{"step": 3, "timestamp": "2026-06-06T12:00:01.800000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "ledger123"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} +{"step": 4, "timestamp": "2026-06-06T12:00:02.400000+05:30", "hierarchy": {"elements": [{"resourceId": "LoginScreen", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginScreen", "class": "android.view.View", "text": ""}}, {"resourceId": "LoginEmail", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginEmail", "class": "android.widget.EditText", "text": "demo@folio.app"}}, {"resourceId": "LoginPassword", "class": "android.widget.EditText", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginPassword", "class": "android.widget.EditText", "text": "ledger123"}}, {"resourceId": "LoginSubmit", "class": "android.view.View", "editable": true, "bounds": {"left": 34, "top": 87, "right": 286, "bottom": 135}, "attrs": {"resource-id": "LoginSubmit", "class": "android.view.View", "text": ""}}]}} diff --git a/examples/folio/justfile b/examples/folio/justfile index 2bc82f7..1faf7ef 100644 --- a/examples/folio/justfile +++ b/examples/folio/justfile @@ -7,6 +7,7 @@ duration := env_var_or_default("DURATION", "1m") seed := env_var_or_default("SEED", "0") output := env_var_or_default("OUTPUT", justfile_directory() / "sanderling" / "runs") ios_device := env_var_or_default("IOS_DEVICE", "iPhone 17 Pro") +ios_app := justfile_directory() / "app" / "iosApp" / "build" / "Build" / "Products" / "Debug-iphonesimulator" / "iosApp.app" default: @just --list @@ -118,7 +119,7 @@ ios: -destination 'platform=iOS Simulator,name={{ios_device}}' \ -derivedDataPath app/iosApp/build \ build | tail -5 - xcrun simctl install booted app/iosApp/build/Build/Products/Debug-iphonesimulator/iosApp.app + xcrun simctl install booted "{{ios_app}}" xcrun simctl launch booted app.folio # Run 'sanderling test' against the folio app. Uses a connected device if one is @@ -167,6 +168,7 @@ test-ios: --platform ios \ --spec "{{justfile_directory()}}/sanderling/spec.ts" \ --bundle-id app.folio \ + --ios-app-path "{{ios_app}}" \ "${ios_device_flag[@]}" \ --duration "{{duration}}" \ --seed "{{seed}}" \ diff --git a/internal/_oracle/main.go b/internal/_oracle/main.go index dafd728..f9f593e 100644 --- a/internal/_oracle/main.go +++ b/internal/_oracle/main.go @@ -26,10 +26,10 @@ type seed struct { } type caseEntry struct { - Seed seed `json:"seed"` - Uint64 []string `json:"uint64"` - Float64 []float64 `json:"float64"` - IntN map[string][]int `json:"intN"` + Seed seed `json:"seed"` + Uint64 []string `json:"uint64"` + Float64 []float64 `json:"float64"` + IntN map[string][]int `json:"intN"` } const drawCount = 40 diff --git a/internal/driver/ioscompanion/companionassets/embed.go b/internal/driver/ioscompanion/companionassets/embed.go new file mode 100644 index 0000000..4438eb7 --- /dev/null +++ b/internal/driver/ioscompanion/companionassets/embed.go @@ -0,0 +1,140 @@ +// Package companionassets embeds the simulator companion binary and its +// frameworks, and extracts the bundle to disk at runtime. +package companionassets + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +// companionBinaryName is the file name inside bin/ fixed by the vendored +// layout. The binary resolves its frameworks through @rpath relative to this +// path, so it must not be renamed. +const companionBinaryName = "idb_companion" + +// EmbeddedSize returns the size in bytes of the embedded companion archive. +func EmbeddedSize() int { return len(embeddedArchive) } + +// EmbeddedSHA256 returns the hex-encoded SHA-256 of the embedded archive. +func EmbeddedSHA256() string { + sum := sha256.Sum256(embeddedArchive) + return hex.EncodeToString(sum[:]) +} + +// Extract unpacks the embedded companion archive into dir, preserving the +// bin/ and Frameworks/ layout and any symlinks. A .sha256 marker next to the +// extracted tree gates re-extraction: if it already matches, no rewrite +// happens. Returns the absolute path to the companion binary. +func Extract(dir string) (string, error) { + if len(embeddedArchive) == 0 { + return "", errors.New("companion: binary built without -tags withcompanion; rebuild with `make sanderling`") + } + if err := os.MkdirAll(dir, 0o755); err != nil { + return "", fmt.Errorf("mkdir %s: %w", dir, err) + } + + binaryPath, err := filepath.Abs(filepath.Join(dir, "bin", companionBinaryName)) + if err != nil { + return "", err + } + checksumPath := filepath.Join(dir, "companion.sha256") + checksum := EmbeddedSHA256() + + if existing, err := os.ReadFile(checksumPath); err == nil && string(existing) == checksum { + if _, err := os.Stat(binaryPath); err == nil { + return binaryPath, nil + } + } + + if err := unpack(dir); err != nil { + return "", err + } + if err := os.Chmod(binaryPath, 0o755); err != nil { + return "", fmt.Errorf("chmod companion binary: %w", err) + } + if err := os.WriteFile(checksumPath, []byte(checksum), 0o644); err != nil { + return "", fmt.Errorf("write checksum: %w", err) + } + return binaryPath, nil +} + +func unpack(dir string) error { + gzipReader, err := gzip.NewReader(bytes.NewReader(embeddedArchive)) + if err != nil { + return fmt.Errorf("open companion archive: %w", err) + } + defer gzipReader.Close() + + tarReader := tar.NewReader(gzipReader) + for { + header, err := tarReader.Next() + if err == io.EOF { + return nil + } + if err != nil { + return fmt.Errorf("read companion archive: %w", err) + } + + if strings.HasPrefix(filepath.Base(header.Name), "._") { + continue + } + + target, err := safeJoin(dir, header.Name) + if err != nil { + return err + } + + switch header.Typeflag { + case tar.TypeDir: + if err := os.MkdirAll(target, 0o755); err != nil { + return err + } + case tar.TypeSymlink: + if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { + return err + } + os.Remove(target) + if err := os.Symlink(header.Linkname, target); err != nil { + return err + } + case tar.TypeReg: + if err := writeFile(target, tarReader, os.FileMode(header.Mode)); err != nil { + return err + } + } + } +} + +func writeFile(path string, src io.Reader, mode os.FileMode) error { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode) + if err != nil { + return err + } + if _, err := io.Copy(file, src); err != nil { + file.Close() + return err + } + return file.Close() +} + +// safeJoin rejects archive entries that would escape dir. +func safeJoin(dir, name string) (string, error) { + target := filepath.Join(dir, name) + relative, err := filepath.Rel(dir, target) + if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(os.PathSeparator)) { + return "", fmt.Errorf("archive entry escapes destination: %s", name) + } + return target, nil +} diff --git a/internal/driver/ioscompanion/companionassets/embed_real.go b/internal/driver/ioscompanion/companionassets/embed_real.go new file mode 100644 index 0000000..9d6d66a --- /dev/null +++ b/internal/driver/ioscompanion/companionassets/embed_real.go @@ -0,0 +1,12 @@ +//go:build withcompanion + +package companionassets + +import _ "embed" + +//go:embed assets/companion-1.1.8.tar.gz +var embeddedArchive []byte + +// IsPlaceholder reports whether the binary was built without the real +// companion archive embedded. -tags withcompanion builds always return false. +func IsPlaceholder() bool { return false } diff --git a/internal/driver/ioscompanion/companionassets/embed_stub.go b/internal/driver/ioscompanion/companionassets/embed_stub.go new file mode 100644 index 0000000..415fa8b --- /dev/null +++ b/internal/driver/ioscompanion/companionassets/embed_stub.go @@ -0,0 +1,10 @@ +//go:build !withcompanion + +package companionassets + +var embeddedArchive []byte + +// IsPlaceholder reports whether the binary was built without the real +// companion archive embedded. Build with `make sanderling` (which passes +// -tags withcompanion) to embed the real companion bundle. +func IsPlaceholder() bool { return true } diff --git a/internal/driver/ioscompanion/companionassets/embed_stub_test.go b/internal/driver/ioscompanion/companionassets/embed_stub_test.go new file mode 100644 index 0000000..b0319f7 --- /dev/null +++ b/internal/driver/ioscompanion/companionassets/embed_stub_test.go @@ -0,0 +1,27 @@ +//go:build !withcompanion + +package companionassets + +import ( + "strings" + "testing" +) + +func TestStubBuild_IsPlaceholder(t *testing.T) { + if !IsPlaceholder() { + t.Error("default build (no -tags withcompanion) must report a placeholder") + } + if EmbeddedSize() != 0 { + t.Errorf("placeholder build must embed no archive, got %d bytes", EmbeddedSize()) + } +} + +func TestStubBuild_ExtractErrors(t *testing.T) { + _, err := Extract(t.TempDir()) + if err == nil { + t.Fatal("Extract must fail when no archive is embedded") + } + if !strings.Contains(err.Error(), "withcompanion") { + t.Errorf("error should tell the user to rebuild with -tags withcompanion, got %v", err) + } +} diff --git a/internal/driver/ioscompanion/companionassets/embed_withcompanion_test.go b/internal/driver/ioscompanion/companionassets/embed_withcompanion_test.go new file mode 100644 index 0000000..51aa9b3 --- /dev/null +++ b/internal/driver/ioscompanion/companionassets/embed_withcompanion_test.go @@ -0,0 +1,146 @@ +//go:build withcompanion + +package companionassets + +import ( + "crypto/sha256" + "encoding/hex" + "os" + "path/filepath" + "testing" +) + +func TestEmbeddedNonZero(t *testing.T) { + if EmbeddedSize() == 0 { + t.Errorf("expected embedded companion archive to be non-empty") + } + if IsPlaceholder() { + t.Errorf("withcompanion build should not be a placeholder") + } +} + +func TestEmbeddedSHA256Matches(t *testing.T) { + sum := sha256.Sum256(embeddedArchive) + if hex.EncodeToString(sum[:]) != EmbeddedSHA256() { + t.Errorf("EmbeddedSHA256 does not match a fresh hash of the archive") + } +} + +func TestExtract_WritesBinaryAndChecksum(t *testing.T) { + directory := t.TempDir() + path, err := Extract(directory) + if err != nil { + t.Fatal(err) + } + expected, err := filepath.Abs(filepath.Join(directory, "bin", companionBinaryName)) + if err != nil { + t.Fatal(err) + } + if path != expected { + t.Errorf("unexpected binary path: got %s want %s", path, expected) + } + checksum, err := os.ReadFile(filepath.Join(directory, "companion.sha256")) + if err != nil { + t.Fatal(err) + } + if string(checksum) != EmbeddedSHA256() { + t.Errorf("checksum file content wrong: %q", checksum) + } +} + +func TestExtract_LayoutAndSymlinks(t *testing.T) { + directory := t.TempDir() + binaryPath, err := Extract(directory) + if err != nil { + t.Fatal(err) + } + + info, err := os.Stat(binaryPath) + if err != nil { + t.Fatalf("companion binary missing: %v", err) + } + if info.Mode().Perm()&0o111 == 0 { + t.Errorf("companion binary is not executable, mode=%v", info.Mode()) + } + + frameworksDir := filepath.Join(directory, "Frameworks") + if stat, err := os.Stat(frameworksDir); err != nil || !stat.IsDir() { + t.Fatalf("Frameworks directory missing: %v", err) + } + + expectedFrameworks := []string{ + "FBControlCore.framework", + "FBDeviceControl.framework", + "FBSimulatorControl.framework", + "IDBCompanionUtilities.framework", + "IDBGRPCSwift.framework", + "XCTestBootstrap.framework", + } + for _, name := range expectedFrameworks { + if stat, err := os.Stat(filepath.Join(frameworksDir, name)); err != nil || !stat.IsDir() { + t.Errorf("expected framework %s missing: %v", name, err) + } + } + + // The Versions/Current symlink must resolve to a real directory, proving + // symlinks survived extraction. + current := filepath.Join(frameworksDir, "FBControlCore.framework", "Versions", "Current") + linkInfo, err := os.Lstat(current) + if err != nil { + t.Fatalf("Versions/Current missing: %v", err) + } + if linkInfo.Mode()&os.ModeSymlink == 0 { + t.Errorf("Versions/Current is not a symlink") + } + resolved, err := os.Stat(current) + if err != nil || !resolved.IsDir() { + t.Errorf("Versions/Current does not resolve to a directory: %v", err) + } + + // A runtime dylib that must be preserved inside FBControlCore Resources. + dylib := filepath.Join(frameworksDir, "FBControlCore.framework", "Versions", "A", "Resources", "libMaculator.dylib") + if _, err := os.Stat(dylib); err != nil { + t.Errorf("expected runtime dylib preserved: %v", err) + } +} + +func TestExtract_ReusesWhenChecksumMatches(t *testing.T) { + directory := t.TempDir() + binaryPath, err := Extract(directory) + if err != nil { + t.Fatal(err) + } + sentinel := []byte("SENTINEL-do-not-rewrite") + if err := os.WriteFile(binaryPath, sentinel, 0o755); err != nil { + t.Fatal(err) + } + + if _, err := Extract(directory); err != nil { + t.Fatal(err) + } + + after, err := os.ReadFile(binaryPath) + if err != nil { + t.Fatal(err) + } + if string(after) != string(sentinel) { + t.Errorf("second extract rewrote the binary; reuse branch should have skipped extraction") + } +} + +func TestExtract_RewritesIfChecksumMissing(t *testing.T) { + directory := t.TempDir() + if _, err := Extract(directory); err != nil { + t.Fatal(err) + } + checksumPath := filepath.Join(directory, "companion.sha256") + if err := os.Remove(checksumPath); err != nil { + t.Fatal(err) + } + if _, err := Extract(directory); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(checksumPath); err != nil { + t.Errorf("checksum should have been rewritten: %v", err) + } +} diff --git a/internal/driver/ioscompanion/companionassets/prepare.sh b/internal/driver/ioscompanion/companionassets/prepare.sh new file mode 100755 index 0000000..d69c27d --- /dev/null +++ b/internal/driver/ioscompanion/companionassets/prepare.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# +# Builds the embeddable simulator companion tarball. +# +# Copies the runtime files from a local install, preserving the bin/ and +# Frameworks/ layout (the binary resolves frameworks through @rpath, so the +# two directories must stay siblings). Build-time metadata that is never +# needed at runtime is stripped to keep the embedded payload small. The +# stripped layout is re-signed ad-hoc and proven to execute before it is +# packaged. +# +# Output: assets/companion-1.1.8.tar.gz next to this script. + +set -euo pipefail + +# Keep the archiver from emitting AppleDouble (._name) sidecar entries that +# would otherwise duplicate extended attributes into the payload. +export COPYFILE_DISABLE=1 + +VERSION="1.1.8" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ASSETS_DIR="${SCRIPT_DIR}/assets" +OUTPUT="${ASSETS_DIR}/companion-${VERSION}.tar.gz" + +# Default source is the local formula install. Resolve the symlink so the +# rsync below copies real files rather than a dangling link. +SOURCE="${COMPANION_SOURCE:-/opt/homebrew/opt/idb-companion}" +if [ ! -e "${SOURCE}" ]; then + echo "source not found: ${SOURCE}" >&2 + echo "set COMPANION_SOURCE to the install prefix" >&2 + exit 1 +fi +SOURCE="$(cd "${SOURCE}" && pwd -P)" + +if [ ! -x "${SOURCE}/bin/idb_companion" ]; then + echo "no companion binary at ${SOURCE}/bin/idb_companion" >&2 + exit 1 +fi + +STAGE="$(mktemp -d)" +PROOF="$(mktemp -d)" +trap 'rm -rf "${STAGE}" "${PROOF}"' EXIT + +echo "staging from ${SOURCE}" + +# Copy the runtime layout. -a preserves symlinks (the macOS framework +# Versions/Current links must survive intact). +mkdir -p "${STAGE}/bin" +cp -a "${SOURCE}/bin/idb_companion" "${STAGE}/bin/idb_companion" +cp -aR "${SOURCE}/Frameworks" "${STAGE}/Frameworks" + +# Strip build-time metadata from every framework. The framework binary, its +# Info.plist, the code signature, and any runtime dylibs are kept; headers and +# the Swift module/source-info artifacts are not loaded at runtime. +for framework in "${STAGE}"/Frameworks/*.framework; do + version_dir="${framework}/Versions/A" + [ -d "${version_dir}" ] || continue + + rm -rf "${version_dir}/Headers" "${version_dir}/PrivateHeaders" + rm -rf "${version_dir}/Modules" + rm -f "${framework}/Headers" "${framework}/PrivateHeaders" "${framework}/Modules" + + find "${version_dir}" -name '*.swiftmodule' -prune -exec rm -rf {} + 2>/dev/null || true + find "${version_dir}" \( -name '*.swiftdoc' -o -name '*.swiftsourceinfo' \) -delete 2>/dev/null || true +done + +# Re-sign each framework and the main binary ad-hoc. Removing files breaks the +# existing signature seal, which the loader rejects on arm64 macOS. +for framework in "${STAGE}"/Frameworks/*.framework; do + codesign --force --sign - --timestamp=none "${framework}" >/dev/null 2>&1 +done +codesign --force --sign - --timestamp=none "${STAGE}/bin/idb_companion" >/dev/null 2>&1 + +# Prove the stripped, re-signed layout still loads and runs. tar stores +# symlinks as symlinks so the extracted copy mirrors the embedded payload. +# --version dynamically links every framework and exits zero on success, +# which fails if a strip or re-sign broke a load command or the seal. +STRIPPED_BYTES="$(find "${STAGE}" -type f -exec stat -f%z {} + | awk '{sum += $1} END {print sum}')" + +PROOF_TAR="${PROOF}/payload.tar.gz" +tar -czf "${PROOF_TAR}" -C "${STAGE}" . +tar -xzf "${PROOF_TAR}" -C "${PROOF}" + +if ! "${PROOF}/bin/idb_companion" --version >/dev/null 2>&1; then + echo "stripped companion failed to execute" >&2 + exit 1 +fi +echo "stripped layout executes" + +mkdir -p "${ASSETS_DIR}" +tar -czf "${OUTPUT}" -C "${STAGE}" . + +SHA="$(shasum -a 256 "${OUTPUT}" | awk '{print $1}')" + +echo "wrote ${OUTPUT}" +echo "stripped uncompressed size: ${STRIPPED_BYTES} bytes" +echo "sha256: ${SHA}" diff --git a/internal/driver/ioscompanion/companionpb/idb.pb.go b/internal/driver/ioscompanion/companionpb/idb.pb.go new file mode 100644 index 0000000..6073d47 --- /dev/null +++ b/internal/driver/ioscompanion/companionpb/idb.pb.go @@ -0,0 +1,10596 @@ +// +// Copyright (c) Meta Platforms, Inc. and affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +// Pinned to the simulator companion v1.1.8 and matches the embedded binary. +// Do NOT regenerate this file from upstream HEAD: HEAD has drifted from the +// pinned binary, so regenerating would produce stubs that no longer match the +// companion this driver speaks to. The only local change versus the upstream +// source is the go_package option below, which targets the committed stub +// package internal/driver/ioscompanion/companionpb. Regenerate the stubs from +// the repository root with: +// buf generate proto/idbcompanion --template proto/idbcompanion/buf.gen.yaml + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: idb.proto + +package companionpb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Setting int32 + +const ( + Setting_LOCALE Setting = 0 + Setting_ANY Setting = 1 // any kind of app or global setting +) + +// Enum value maps for Setting. +var ( + Setting_name = map[int32]string{ + 0: "LOCALE", + 1: "ANY", + } + Setting_value = map[string]int32{ + "LOCALE": 0, + "ANY": 1, + } +) + +func (x Setting) Enum() *Setting { + p := new(Setting) + *p = x + return p +} + +func (x Setting) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Setting) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[0].Descriptor() +} + +func (Setting) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[0] +} + +func (x Setting) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Setting.Descriptor instead. +func (Setting) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{0} +} + +type Payload_Compression int32 + +const ( + Payload_GZIP Payload_Compression = 0 + Payload_ZSTD Payload_Compression = 1 +) + +// Enum value maps for Payload_Compression. +var ( + Payload_Compression_name = map[int32]string{ + 0: "GZIP", + 1: "ZSTD", + } + Payload_Compression_value = map[string]int32{ + "GZIP": 0, + "ZSTD": 1, + } +) + +func (x Payload_Compression) Enum() *Payload_Compression { + p := new(Payload_Compression) + *p = x + return p +} + +func (x Payload_Compression) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Payload_Compression) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[1].Descriptor() +} + +func (Payload_Compression) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[1] +} + +func (x Payload_Compression) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Payload_Compression.Descriptor instead. +func (Payload_Compression) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{0, 0} +} + +type ProcessOutput_Interface int32 + +const ( + ProcessOutput_STDOUT ProcessOutput_Interface = 0 + ProcessOutput_STDERR ProcessOutput_Interface = 1 +) + +// Enum value maps for ProcessOutput_Interface. +var ( + ProcessOutput_Interface_name = map[int32]string{ + 0: "STDOUT", + 1: "STDERR", + } + ProcessOutput_Interface_value = map[string]int32{ + "STDOUT": 0, + "STDERR": 1, + } +) + +func (x ProcessOutput_Interface) Enum() *ProcessOutput_Interface { + p := new(ProcessOutput_Interface) + *p = x + return p +} + +func (x ProcessOutput_Interface) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ProcessOutput_Interface) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[2].Descriptor() +} + +func (ProcessOutput_Interface) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[2] +} + +func (x ProcessOutput_Interface) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ProcessOutput_Interface.Descriptor instead. +func (ProcessOutput_Interface) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{1, 0} +} + +type InstalledAppInfo_AppProcessState int32 + +const ( + InstalledAppInfo_UNKNOWN InstalledAppInfo_AppProcessState = 0 + InstalledAppInfo_NOT_RUNNING InstalledAppInfo_AppProcessState = 1 + InstalledAppInfo_RUNNING InstalledAppInfo_AppProcessState = 2 +) + +// Enum value maps for InstalledAppInfo_AppProcessState. +var ( + InstalledAppInfo_AppProcessState_name = map[int32]string{ + 0: "UNKNOWN", + 1: "NOT_RUNNING", + 2: "RUNNING", + } + InstalledAppInfo_AppProcessState_value = map[string]int32{ + "UNKNOWN": 0, + "NOT_RUNNING": 1, + "RUNNING": 2, + } +) + +func (x InstalledAppInfo_AppProcessState) Enum() *InstalledAppInfo_AppProcessState { + p := new(InstalledAppInfo_AppProcessState) + *p = x + return p +} + +func (x InstalledAppInfo_AppProcessState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (InstalledAppInfo_AppProcessState) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[3].Descriptor() +} + +func (InstalledAppInfo_AppProcessState) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[3] +} + +func (x InstalledAppInfo_AppProcessState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use InstalledAppInfo_AppProcessState.Descriptor instead. +func (InstalledAppInfo_AppProcessState) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{11, 0} +} + +type InstallRequest_Destination int32 + +const ( + InstallRequest_APP InstallRequest_Destination = 0 + InstallRequest_XCTEST InstallRequest_Destination = 1 + InstallRequest_DYLIB InstallRequest_Destination = 2 + InstallRequest_DSYM InstallRequest_Destination = 3 + InstallRequest_FRAMEWORK InstallRequest_Destination = 4 +) + +// Enum value maps for InstallRequest_Destination. +var ( + InstallRequest_Destination_name = map[int32]string{ + 0: "APP", + 1: "XCTEST", + 2: "DYLIB", + 3: "DSYM", + 4: "FRAMEWORK", + } + InstallRequest_Destination_value = map[string]int32{ + "APP": 0, + "XCTEST": 1, + "DYLIB": 2, + "DSYM": 3, + "FRAMEWORK": 4, + } +) + +func (x InstallRequest_Destination) Enum() *InstallRequest_Destination { + p := new(InstallRequest_Destination) + *p = x + return p +} + +func (x InstallRequest_Destination) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (InstallRequest_Destination) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[4].Descriptor() +} + +func (InstallRequest_Destination) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[4] +} + +func (x InstallRequest_Destination) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use InstallRequest_Destination.Descriptor instead. +func (InstallRequest_Destination) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{12, 0} +} + +type InstallRequest_LinkDsymToBundle_BundleType int32 + +const ( + InstallRequest_LinkDsymToBundle_APP InstallRequest_LinkDsymToBundle_BundleType = 0 + InstallRequest_LinkDsymToBundle_XCTEST InstallRequest_LinkDsymToBundle_BundleType = 1 +) + +// Enum value maps for InstallRequest_LinkDsymToBundle_BundleType. +var ( + InstallRequest_LinkDsymToBundle_BundleType_name = map[int32]string{ + 0: "APP", + 1: "XCTEST", + } + InstallRequest_LinkDsymToBundle_BundleType_value = map[string]int32{ + "APP": 0, + "XCTEST": 1, + } +) + +func (x InstallRequest_LinkDsymToBundle_BundleType) Enum() *InstallRequest_LinkDsymToBundle_BundleType { + p := new(InstallRequest_LinkDsymToBundle_BundleType) + *p = x + return p +} + +func (x InstallRequest_LinkDsymToBundle_BundleType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (InstallRequest_LinkDsymToBundle_BundleType) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[5].Descriptor() +} + +func (InstallRequest_LinkDsymToBundle_BundleType) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[5] +} + +func (x InstallRequest_LinkDsymToBundle_BundleType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use InstallRequest_LinkDsymToBundle_BundleType.Descriptor instead. +func (InstallRequest_LinkDsymToBundle_BundleType) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{12, 0, 0} +} + +type AccessibilityInfoRequest_Format int32 + +const ( + AccessibilityInfoRequest_LEGACY AccessibilityInfoRequest_Format = 0 + AccessibilityInfoRequest_NESTED AccessibilityInfoRequest_Format = 1 +) + +// Enum value maps for AccessibilityInfoRequest_Format. +var ( + AccessibilityInfoRequest_Format_name = map[int32]string{ + 0: "LEGACY", + 1: "NESTED", + } + AccessibilityInfoRequest_Format_value = map[string]int32{ + "LEGACY": 0, + "NESTED": 1, + } +) + +func (x AccessibilityInfoRequest_Format) Enum() *AccessibilityInfoRequest_Format { + p := new(AccessibilityInfoRequest_Format) + *p = x + return p +} + +func (x AccessibilityInfoRequest_Format) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AccessibilityInfoRequest_Format) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[6].Descriptor() +} + +func (AccessibilityInfoRequest_Format) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[6] +} + +func (x AccessibilityInfoRequest_Format) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AccessibilityInfoRequest_Format.Descriptor instead. +func (AccessibilityInfoRequest_Format) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{19, 0} +} + +type ApproveRequest_Permission int32 + +const ( + ApproveRequest_PHOTOS ApproveRequest_Permission = 0 + ApproveRequest_CAMERA ApproveRequest_Permission = 1 + ApproveRequest_CONTACTS ApproveRequest_Permission = 2 + ApproveRequest_URL ApproveRequest_Permission = 3 + ApproveRequest_LOCATION ApproveRequest_Permission = 4 + ApproveRequest_NOTIFICATION ApproveRequest_Permission = 5 + ApproveRequest_MICROPHONE ApproveRequest_Permission = 6 +) + +// Enum value maps for ApproveRequest_Permission. +var ( + ApproveRequest_Permission_name = map[int32]string{ + 0: "PHOTOS", + 1: "CAMERA", + 2: "CONTACTS", + 3: "URL", + 4: "LOCATION", + 5: "NOTIFICATION", + 6: "MICROPHONE", + } + ApproveRequest_Permission_value = map[string]int32{ + "PHOTOS": 0, + "CAMERA": 1, + "CONTACTS": 2, + "URL": 3, + "LOCATION": 4, + "NOTIFICATION": 5, + "MICROPHONE": 6, + } +) + +func (x ApproveRequest_Permission) Enum() *ApproveRequest_Permission { + p := new(ApproveRequest_Permission) + *p = x + return p +} + +func (x ApproveRequest_Permission) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ApproveRequest_Permission) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[7].Descriptor() +} + +func (ApproveRequest_Permission) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[7] +} + +func (x ApproveRequest_Permission) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ApproveRequest_Permission.Descriptor instead. +func (ApproveRequest_Permission) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{21, 0} +} + +type RevokeRequest_Permission int32 + +const ( + RevokeRequest_PHOTOS RevokeRequest_Permission = 0 + RevokeRequest_CAMERA RevokeRequest_Permission = 1 + RevokeRequest_CONTACTS RevokeRequest_Permission = 2 + RevokeRequest_URL RevokeRequest_Permission = 3 + RevokeRequest_LOCATION RevokeRequest_Permission = 4 + RevokeRequest_NOTIFICATION RevokeRequest_Permission = 5 + RevokeRequest_MICROPHONE RevokeRequest_Permission = 6 +) + +// Enum value maps for RevokeRequest_Permission. +var ( + RevokeRequest_Permission_name = map[int32]string{ + 0: "PHOTOS", + 1: "CAMERA", + 2: "CONTACTS", + 3: "URL", + 4: "LOCATION", + 5: "NOTIFICATION", + 6: "MICROPHONE", + } + RevokeRequest_Permission_value = map[string]int32{ + "PHOTOS": 0, + "CAMERA": 1, + "CONTACTS": 2, + "URL": 3, + "LOCATION": 4, + "NOTIFICATION": 5, + "MICROPHONE": 6, + } +) + +func (x RevokeRequest_Permission) Enum() *RevokeRequest_Permission { + p := new(RevokeRequest_Permission) + *p = x + return p +} + +func (x RevokeRequest_Permission) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RevokeRequest_Permission) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[8].Descriptor() +} + +func (RevokeRequest_Permission) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[8] +} + +func (x RevokeRequest_Permission) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use RevokeRequest_Permission.Descriptor instead. +func (RevokeRequest_Permission) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{23, 0} +} + +type HIDEvent_HIDDirection int32 + +const ( + HIDEvent_DOWN HIDEvent_HIDDirection = 0 + HIDEvent_UP HIDEvent_HIDDirection = 1 +) + +// Enum value maps for HIDEvent_HIDDirection. +var ( + HIDEvent_HIDDirection_name = map[int32]string{ + 0: "DOWN", + 1: "UP", + } + HIDEvent_HIDDirection_value = map[string]int32{ + "DOWN": 0, + "UP": 1, + } +) + +func (x HIDEvent_HIDDirection) Enum() *HIDEvent_HIDDirection { + p := new(HIDEvent_HIDDirection) + *p = x + return p +} + +func (x HIDEvent_HIDDirection) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (HIDEvent_HIDDirection) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[9].Descriptor() +} + +func (HIDEvent_HIDDirection) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[9] +} + +func (x HIDEvent_HIDDirection) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use HIDEvent_HIDDirection.Descriptor instead. +func (HIDEvent_HIDDirection) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{40, 0} +} + +type HIDEvent_HIDButtonType int32 + +const ( + HIDEvent_APPLE_PAY HIDEvent_HIDButtonType = 0 + HIDEvent_HOME HIDEvent_HIDButtonType = 1 + HIDEvent_LOCK HIDEvent_HIDButtonType = 2 + HIDEvent_SIDE_BUTTON HIDEvent_HIDButtonType = 3 + HIDEvent_SIRI HIDEvent_HIDButtonType = 4 +) + +// Enum value maps for HIDEvent_HIDButtonType. +var ( + HIDEvent_HIDButtonType_name = map[int32]string{ + 0: "APPLE_PAY", + 1: "HOME", + 2: "LOCK", + 3: "SIDE_BUTTON", + 4: "SIRI", + } + HIDEvent_HIDButtonType_value = map[string]int32{ + "APPLE_PAY": 0, + "HOME": 1, + "LOCK": 2, + "SIDE_BUTTON": 3, + "SIRI": 4, + } +) + +func (x HIDEvent_HIDButtonType) Enum() *HIDEvent_HIDButtonType { + p := new(HIDEvent_HIDButtonType) + *p = x + return p +} + +func (x HIDEvent_HIDButtonType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (HIDEvent_HIDButtonType) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[10].Descriptor() +} + +func (HIDEvent_HIDButtonType) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[10] +} + +func (x HIDEvent_HIDButtonType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use HIDEvent_HIDButtonType.Descriptor instead. +func (HIDEvent_HIDButtonType) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{40, 1} +} + +type LogRequest_Source int32 + +const ( + LogRequest_TARGET LogRequest_Source = 0 + LogRequest_COMPANION LogRequest_Source = 1 +) + +// Enum value maps for LogRequest_Source. +var ( + LogRequest_Source_name = map[int32]string{ + 0: "TARGET", + 1: "COMPANION", + } + LogRequest_Source_value = map[string]int32{ + "TARGET": 0, + "COMPANION": 1, + } +) + +func (x LogRequest_Source) Enum() *LogRequest_Source { + p := new(LogRequest_Source) + *p = x + return p +} + +func (x LogRequest_Source) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (LogRequest_Source) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[11].Descriptor() +} + +func (LogRequest_Source) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[11] +} + +func (x LogRequest_Source) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use LogRequest_Source.Descriptor instead. +func (LogRequest_Source) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{46, 0} +} + +type VideoStreamRequest_Format int32 + +const ( + VideoStreamRequest_H264 VideoStreamRequest_Format = 0 + VideoStreamRequest_RBGA VideoStreamRequest_Format = 1 + VideoStreamRequest_MJPEG VideoStreamRequest_Format = 2 + VideoStreamRequest_MINICAP VideoStreamRequest_Format = 3 + VideoStreamRequest_I420 VideoStreamRequest_Format = 4 +) + +// Enum value maps for VideoStreamRequest_Format. +var ( + VideoStreamRequest_Format_name = map[int32]string{ + 0: "H264", + 1: "RBGA", + 2: "MJPEG", + 3: "MINICAP", + 4: "I420", + } + VideoStreamRequest_Format_value = map[string]int32{ + "H264": 0, + "RBGA": 1, + "MJPEG": 2, + "MINICAP": 3, + "I420": 4, + } +) + +func (x VideoStreamRequest_Format) Enum() *VideoStreamRequest_Format { + p := new(VideoStreamRequest_Format) + *p = x + return p +} + +func (x VideoStreamRequest_Format) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (VideoStreamRequest_Format) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[12].Descriptor() +} + +func (VideoStreamRequest_Format) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[12] +} + +func (x VideoStreamRequest_Format) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use VideoStreamRequest_Format.Descriptor instead. +func (VideoStreamRequest_Format) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{50, 0} +} + +type InstrumentsRunResponse_State int32 + +const ( + InstrumentsRunResponse_UNKNOWN InstrumentsRunResponse_State = 0 + InstrumentsRunResponse_RUNNING_INSTRUMENTS InstrumentsRunResponse_State = 1 + InstrumentsRunResponse_POST_PROCESSING InstrumentsRunResponse_State = 2 +) + +// Enum value maps for InstrumentsRunResponse_State. +var ( + InstrumentsRunResponse_State_name = map[int32]string{ + 0: "UNKNOWN", + 1: "RUNNING_INSTRUMENTS", + 2: "POST_PROCESSING", + } + InstrumentsRunResponse_State_value = map[string]int32{ + "UNKNOWN": 0, + "RUNNING_INSTRUMENTS": 1, + "POST_PROCESSING": 2, + } +) + +func (x InstrumentsRunResponse_State) Enum() *InstrumentsRunResponse_State { + p := new(InstrumentsRunResponse_State) + *p = x + return p +} + +func (x InstrumentsRunResponse_State) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (InstrumentsRunResponse_State) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[13].Descriptor() +} + +func (InstrumentsRunResponse_State) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[13] +} + +func (x InstrumentsRunResponse_State) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use InstrumentsRunResponse_State.Descriptor instead. +func (InstrumentsRunResponse_State) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{57, 0} +} + +type XctraceRecordResponse_State int32 + +const ( + XctraceRecordResponse_UNKNOWN XctraceRecordResponse_State = 0 + XctraceRecordResponse_RUNNING XctraceRecordResponse_State = 1 + XctraceRecordResponse_PROCESSING XctraceRecordResponse_State = 2 +) + +// Enum value maps for XctraceRecordResponse_State. +var ( + XctraceRecordResponse_State_name = map[int32]string{ + 0: "UNKNOWN", + 1: "RUNNING", + 2: "PROCESSING", + } + XctraceRecordResponse_State_value = map[string]int32{ + "UNKNOWN": 0, + "RUNNING": 1, + "PROCESSING": 2, + } +) + +func (x XctraceRecordResponse_State) Enum() *XctraceRecordResponse_State { + p := new(XctraceRecordResponse_State) + *p = x + return p +} + +func (x XctraceRecordResponse_State) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (XctraceRecordResponse_State) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[14].Descriptor() +} + +func (XctraceRecordResponse_State) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[14] +} + +func (x XctraceRecordResponse_State) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use XctraceRecordResponse_State.Descriptor instead. +func (XctraceRecordResponse_State) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{59, 0} +} + +type XctestRunRequest_CodeCoverage_Format int32 + +const ( + XctestRunRequest_CodeCoverage_EXPORTED XctestRunRequest_CodeCoverage_Format = 0 + XctestRunRequest_CodeCoverage_RAW XctestRunRequest_CodeCoverage_Format = 1 +) + +// Enum value maps for XctestRunRequest_CodeCoverage_Format. +var ( + XctestRunRequest_CodeCoverage_Format_name = map[int32]string{ + 0: "EXPORTED", + 1: "RAW", + } + XctestRunRequest_CodeCoverage_Format_value = map[string]int32{ + "EXPORTED": 0, + "RAW": 1, + } +) + +func (x XctestRunRequest_CodeCoverage_Format) Enum() *XctestRunRequest_CodeCoverage_Format { + p := new(XctestRunRequest_CodeCoverage_Format) + *p = x + return p +} + +func (x XctestRunRequest_CodeCoverage_Format) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (XctestRunRequest_CodeCoverage_Format) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[15].Descriptor() +} + +func (XctestRunRequest_CodeCoverage_Format) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[15] +} + +func (x XctestRunRequest_CodeCoverage_Format) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use XctestRunRequest_CodeCoverage_Format.Descriptor instead. +func (XctestRunRequest_CodeCoverage_Format) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{71, 4, 0} +} + +type XctestRunResponse_Status int32 + +const ( + XctestRunResponse_RUNNING XctestRunResponse_Status = 0 + XctestRunResponse_TERMINATED_NORMALLY XctestRunResponse_Status = 1 + XctestRunResponse_TERMINATED_ABNORMALLY XctestRunResponse_Status = 2 +) + +// Enum value maps for XctestRunResponse_Status. +var ( + XctestRunResponse_Status_name = map[int32]string{ + 0: "RUNNING", + 1: "TERMINATED_NORMALLY", + 2: "TERMINATED_ABNORMALLY", + } + XctestRunResponse_Status_value = map[string]int32{ + "RUNNING": 0, + "TERMINATED_NORMALLY": 1, + "TERMINATED_ABNORMALLY": 2, + } +) + +func (x XctestRunResponse_Status) Enum() *XctestRunResponse_Status { + p := new(XctestRunResponse_Status) + *p = x + return p +} + +func (x XctestRunResponse_Status) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (XctestRunResponse_Status) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[16].Descriptor() +} + +func (XctestRunResponse_Status) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[16] +} + +func (x XctestRunResponse_Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use XctestRunResponse_Status.Descriptor instead. +func (XctestRunResponse_Status) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{72, 0} +} + +type XctestRunResponse_TestRunInfo_Status int32 + +const ( + XctestRunResponse_TestRunInfo_PASSED XctestRunResponse_TestRunInfo_Status = 0 + XctestRunResponse_TestRunInfo_FAILED XctestRunResponse_TestRunInfo_Status = 1 + XctestRunResponse_TestRunInfo_CRASHED XctestRunResponse_TestRunInfo_Status = 2 +) + +// Enum value maps for XctestRunResponse_TestRunInfo_Status. +var ( + XctestRunResponse_TestRunInfo_Status_name = map[int32]string{ + 0: "PASSED", + 1: "FAILED", + 2: "CRASHED", + } + XctestRunResponse_TestRunInfo_Status_value = map[string]int32{ + "PASSED": 0, + "FAILED": 1, + "CRASHED": 2, + } +) + +func (x XctestRunResponse_TestRunInfo_Status) Enum() *XctestRunResponse_TestRunInfo_Status { + p := new(XctestRunResponse_TestRunInfo_Status) + *p = x + return p +} + +func (x XctestRunResponse_TestRunInfo_Status) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (XctestRunResponse_TestRunInfo_Status) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[17].Descriptor() +} + +func (XctestRunResponse_TestRunInfo_Status) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[17] +} + +func (x XctestRunResponse_TestRunInfo_Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use XctestRunResponse_TestRunInfo_Status.Descriptor instead. +func (XctestRunResponse_TestRunInfo_Status) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{72, 0, 0} +} + +type FileContainer_Kind int32 + +const ( + FileContainer_NONE FileContainer_Kind = 0 + FileContainer_APPLICATION FileContainer_Kind = 1 + FileContainer_ROOT FileContainer_Kind = 2 + FileContainer_MEDIA FileContainer_Kind = 3 + FileContainer_CRASHES FileContainer_Kind = 4 + FileContainer_PROVISIONING_PROFILES FileContainer_Kind = 5 + FileContainer_MDM_PROFILES FileContainer_Kind = 6 + FileContainer_SPRINGBOARD_ICONS FileContainer_Kind = 7 + FileContainer_WALLPAPER FileContainer_Kind = 8 + FileContainer_DISK_IMAGES FileContainer_Kind = 9 + FileContainer_GROUP_CONTAINER FileContainer_Kind = 10 + FileContainer_APPLICATION_CONTAINER FileContainer_Kind = 11 + FileContainer_AUXILLARY FileContainer_Kind = 12 + FileContainer_XCTEST FileContainer_Kind = 13 + FileContainer_DYLIB FileContainer_Kind = 14 + FileContainer_DSYM FileContainer_Kind = 15 + FileContainer_FRAMEWORK FileContainer_Kind = 16 + FileContainer_SYMBOLS FileContainer_Kind = 17 +) + +// Enum value maps for FileContainer_Kind. +var ( + FileContainer_Kind_name = map[int32]string{ + 0: "NONE", + 1: "APPLICATION", + 2: "ROOT", + 3: "MEDIA", + 4: "CRASHES", + 5: "PROVISIONING_PROFILES", + 6: "MDM_PROFILES", + 7: "SPRINGBOARD_ICONS", + 8: "WALLPAPER", + 9: "DISK_IMAGES", + 10: "GROUP_CONTAINER", + 11: "APPLICATION_CONTAINER", + 12: "AUXILLARY", + 13: "XCTEST", + 14: "DYLIB", + 15: "DSYM", + 16: "FRAMEWORK", + 17: "SYMBOLS", + } + FileContainer_Kind_value = map[string]int32{ + "NONE": 0, + "APPLICATION": 1, + "ROOT": 2, + "MEDIA": 3, + "CRASHES": 4, + "PROVISIONING_PROFILES": 5, + "MDM_PROFILES": 6, + "SPRINGBOARD_ICONS": 7, + "WALLPAPER": 8, + "DISK_IMAGES": 9, + "GROUP_CONTAINER": 10, + "APPLICATION_CONTAINER": 11, + "AUXILLARY": 12, + "XCTEST": 13, + "DYLIB": 14, + "DSYM": 15, + "FRAMEWORK": 16, + "SYMBOLS": 17, + } +) + +func (x FileContainer_Kind) Enum() *FileContainer_Kind { + p := new(FileContainer_Kind) + *p = x + return p +} + +func (x FileContainer_Kind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (FileContainer_Kind) Descriptor() protoreflect.EnumDescriptor { + return file_idb_proto_enumTypes[18].Descriptor() +} + +func (FileContainer_Kind) Type() protoreflect.EnumType { + return &file_idb_proto_enumTypes[18] +} + +func (x FileContainer_Kind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use FileContainer_Kind.Descriptor instead. +func (FileContainer_Kind) EnumDescriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{73, 0} +} + +type Payload struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Source: + // + // *Payload_FilePath + // *Payload_Data + // *Payload_Url + // *Payload_Compression_ + Source isPayload_Source `protobuf_oneof:"source"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Payload) Reset() { + *x = Payload{} + mi := &file_idb_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Payload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Payload) ProtoMessage() {} + +func (x *Payload) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Payload.ProtoReflect.Descriptor instead. +func (*Payload) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{0} +} + +func (x *Payload) GetSource() isPayload_Source { + if x != nil { + return x.Source + } + return nil +} + +func (x *Payload) GetFilePath() string { + if x != nil { + if x, ok := x.Source.(*Payload_FilePath); ok { + return x.FilePath + } + } + return "" +} + +func (x *Payload) GetData() []byte { + if x != nil { + if x, ok := x.Source.(*Payload_Data); ok { + return x.Data + } + } + return nil +} + +func (x *Payload) GetUrl() string { + if x != nil { + if x, ok := x.Source.(*Payload_Url); ok { + return x.Url + } + } + return "" +} + +func (x *Payload) GetCompression() Payload_Compression { + if x != nil { + if x, ok := x.Source.(*Payload_Compression_); ok { + return x.Compression + } + } + return Payload_GZIP +} + +type isPayload_Source interface { + isPayload_Source() +} + +type Payload_FilePath struct { + FilePath string `protobuf:"bytes,1,opt,name=file_path,json=filePath,proto3,oneof"` +} + +type Payload_Data struct { + Data []byte `protobuf:"bytes,2,opt,name=data,proto3,oneof"` +} + +type Payload_Url struct { + Url string `protobuf:"bytes,3,opt,name=url,proto3,oneof"` +} + +type Payload_Compression_ struct { + Compression Payload_Compression `protobuf:"varint,4,opt,name=compression,proto3,enum=idb.Payload_Compression,oneof"` +} + +func (*Payload_FilePath) isPayload_Source() {} + +func (*Payload_Data) isPayload_Source() {} + +func (*Payload_Url) isPayload_Source() {} + +func (*Payload_Compression_) isPayload_Source() {} + +type ProcessOutput struct { + state protoimpl.MessageState `protogen:"open.v1"` + Interface ProcessOutput_Interface `protobuf:"varint,1,opt,name=interface,proto3,enum=idb.ProcessOutput_Interface" json:"interface,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProcessOutput) Reset() { + *x = ProcessOutput{} + mi := &file_idb_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProcessOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProcessOutput) ProtoMessage() {} + +func (x *ProcessOutput) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProcessOutput.ProtoReflect.Descriptor instead. +func (*ProcessOutput) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{1} +} + +func (x *ProcessOutput) GetInterface() ProcessOutput_Interface { + if x != nil { + return x.Interface + } + return ProcessOutput_STDOUT +} + +func (x *ProcessOutput) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type CompanionInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Udid string `protobuf:"bytes,1,opt,name=udid,proto3" json:"udid,omitempty"` + IsLocal bool `protobuf:"varint,4,opt,name=is_local,json=isLocal,proto3" json:"is_local,omitempty"` + Metadata []byte `protobuf:"bytes,6,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CompanionInfo) Reset() { + *x = CompanionInfo{} + mi := &file_idb_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CompanionInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CompanionInfo) ProtoMessage() {} + +func (x *CompanionInfo) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CompanionInfo.ProtoReflect.Descriptor instead. +func (*CompanionInfo) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{2} +} + +func (x *CompanionInfo) GetUdid() string { + if x != nil { + return x.Udid + } + return "" +} + +func (x *CompanionInfo) GetIsLocal() bool { + if x != nil { + return x.IsLocal + } + return false +} + +func (x *CompanionInfo) GetMetadata() []byte { + if x != nil { + return x.Metadata + } + return nil +} + +type SettingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Setting: + // + // *SettingRequest_HardwareKeyboard_ + // *SettingRequest_StringSetting_ + Setting isSettingRequest_Setting `protobuf_oneof:"setting"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SettingRequest) Reset() { + *x = SettingRequest{} + mi := &file_idb_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SettingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SettingRequest) ProtoMessage() {} + +func (x *SettingRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SettingRequest.ProtoReflect.Descriptor instead. +func (*SettingRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{3} +} + +func (x *SettingRequest) GetSetting() isSettingRequest_Setting { + if x != nil { + return x.Setting + } + return nil +} + +func (x *SettingRequest) GetHardwareKeyboard() *SettingRequest_HardwareKeyboard { + if x != nil { + if x, ok := x.Setting.(*SettingRequest_HardwareKeyboard_); ok { + return x.HardwareKeyboard + } + } + return nil +} + +func (x *SettingRequest) GetStringSetting() *SettingRequest_StringSetting { + if x != nil { + if x, ok := x.Setting.(*SettingRequest_StringSetting_); ok { + return x.StringSetting + } + } + return nil +} + +type isSettingRequest_Setting interface { + isSettingRequest_Setting() +} + +type SettingRequest_HardwareKeyboard_ struct { + HardwareKeyboard *SettingRequest_HardwareKeyboard `protobuf:"bytes,1,opt,name=hardwareKeyboard,proto3,oneof"` +} + +type SettingRequest_StringSetting_ struct { + StringSetting *SettingRequest_StringSetting `protobuf:"bytes,2,opt,name=stringSetting,proto3,oneof"` +} + +func (*SettingRequest_HardwareKeyboard_) isSettingRequest_Setting() {} + +func (*SettingRequest_StringSetting_) isSettingRequest_Setting() {} + +type SettingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SettingResponse) Reset() { + *x = SettingResponse{} + mi := &file_idb_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SettingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SettingResponse) ProtoMessage() {} + +func (x *SettingResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SettingResponse.ProtoReflect.Descriptor instead. +func (*SettingResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{4} +} + +type GetSettingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Setting Setting `protobuf:"varint,1,opt,name=setting,proto3,enum=idb.Setting" json:"setting,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // iff setting == ANY + Domain string `protobuf:"bytes,3,opt,name=domain,proto3" json:"domain,omitempty"` // assumed "Apple Global Domain" if empty + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSettingRequest) Reset() { + *x = GetSettingRequest{} + mi := &file_idb_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSettingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSettingRequest) ProtoMessage() {} + +func (x *GetSettingRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSettingRequest.ProtoReflect.Descriptor instead. +func (*GetSettingRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{5} +} + +func (x *GetSettingRequest) GetSetting() Setting { + if x != nil { + return x.Setting + } + return Setting_LOCALE +} + +func (x *GetSettingRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetSettingRequest) GetDomain() string { + if x != nil { + return x.Domain + } + return "" +} + +type GetSettingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSettingResponse) Reset() { + *x = GetSettingResponse{} + mi := &file_idb_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSettingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSettingResponse) ProtoMessage() {} + +func (x *GetSettingResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSettingResponse.ProtoReflect.Descriptor instead. +func (*GetSettingResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{6} +} + +func (x *GetSettingResponse) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type ListSettingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Setting Setting `protobuf:"varint,1,opt,name=setting,proto3,enum=idb.Setting" json:"setting,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSettingRequest) Reset() { + *x = ListSettingRequest{} + mi := &file_idb_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSettingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSettingRequest) ProtoMessage() {} + +func (x *ListSettingRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSettingRequest.ProtoReflect.Descriptor instead. +func (*ListSettingRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{7} +} + +func (x *ListSettingRequest) GetSetting() Setting { + if x != nil { + return x.Setting + } + return Setting_LOCALE +} + +type ListSettingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSettingResponse) Reset() { + *x = ListSettingResponse{} + mi := &file_idb_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSettingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSettingResponse) ProtoMessage() {} + +func (x *ListSettingResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSettingResponse.ProtoReflect.Descriptor instead. +func (*ListSettingResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{8} +} + +func (x *ListSettingResponse) GetValues() []string { + if x != nil { + return x.Values + } + return nil +} + +type ListAppsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SuppressProcessState bool `protobuf:"varint,1,opt,name=suppress_process_state,json=suppressProcessState,proto3" json:"suppress_process_state,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAppsRequest) Reset() { + *x = ListAppsRequest{} + mi := &file_idb_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAppsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAppsRequest) ProtoMessage() {} + +func (x *ListAppsRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAppsRequest.ProtoReflect.Descriptor instead. +func (*ListAppsRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{9} +} + +func (x *ListAppsRequest) GetSuppressProcessState() bool { + if x != nil { + return x.SuppressProcessState + } + return false +} + +type ListAppsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Apps []*InstalledAppInfo `protobuf:"bytes,1,rep,name=apps,proto3" json:"apps,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAppsResponse) Reset() { + *x = ListAppsResponse{} + mi := &file_idb_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAppsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAppsResponse) ProtoMessage() {} + +func (x *ListAppsResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAppsResponse.ProtoReflect.Descriptor instead. +func (*ListAppsResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{10} +} + +func (x *ListAppsResponse) GetApps() []*InstalledAppInfo { + if x != nil { + return x.Apps + } + return nil +} + +type InstalledAppInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + BundleId string `protobuf:"bytes,1,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Architectures []string `protobuf:"bytes,3,rep,name=architectures,proto3" json:"architectures,omitempty"` + InstallType string `protobuf:"bytes,4,opt,name=install_type,json=installType,proto3" json:"install_type,omitempty"` + ProcessState InstalledAppInfo_AppProcessState `protobuf:"varint,5,opt,name=process_state,json=processState,proto3,enum=idb.InstalledAppInfo_AppProcessState" json:"process_state,omitempty"` + Debuggable bool `protobuf:"varint,6,opt,name=debuggable,proto3" json:"debuggable,omitempty"` + ProcessIdentifier uint64 `protobuf:"varint,7,opt,name=process_identifier,json=processIdentifier,proto3" json:"process_identifier,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstalledAppInfo) Reset() { + *x = InstalledAppInfo{} + mi := &file_idb_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstalledAppInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstalledAppInfo) ProtoMessage() {} + +func (x *InstalledAppInfo) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InstalledAppInfo.ProtoReflect.Descriptor instead. +func (*InstalledAppInfo) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{11} +} + +func (x *InstalledAppInfo) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +func (x *InstalledAppInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *InstalledAppInfo) GetArchitectures() []string { + if x != nil { + return x.Architectures + } + return nil +} + +func (x *InstalledAppInfo) GetInstallType() string { + if x != nil { + return x.InstallType + } + return "" +} + +func (x *InstalledAppInfo) GetProcessState() InstalledAppInfo_AppProcessState { + if x != nil { + return x.ProcessState + } + return InstalledAppInfo_UNKNOWN +} + +func (x *InstalledAppInfo) GetDebuggable() bool { + if x != nil { + return x.Debuggable + } + return false +} + +func (x *InstalledAppInfo) GetProcessIdentifier() uint64 { + if x != nil { + return x.ProcessIdentifier + } + return 0 +} + +type InstallRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Value: + // + // *InstallRequest_Destination_ + // *InstallRequest_Payload + // *InstallRequest_NameHint + // *InstallRequest_MakeDebuggable + // *InstallRequest_BundleId + // *InstallRequest_LinkDsymToBundle_ + // *InstallRequest_OverrideModificationTime + Value isInstallRequest_Value `protobuf_oneof:"value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstallRequest) Reset() { + *x = InstallRequest{} + mi := &file_idb_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstallRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstallRequest) ProtoMessage() {} + +func (x *InstallRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InstallRequest.ProtoReflect.Descriptor instead. +func (*InstallRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{12} +} + +func (x *InstallRequest) GetValue() isInstallRequest_Value { + if x != nil { + return x.Value + } + return nil +} + +func (x *InstallRequest) GetDestination() InstallRequest_Destination { + if x != nil { + if x, ok := x.Value.(*InstallRequest_Destination_); ok { + return x.Destination + } + } + return InstallRequest_APP +} + +func (x *InstallRequest) GetPayload() *Payload { + if x != nil { + if x, ok := x.Value.(*InstallRequest_Payload); ok { + return x.Payload + } + } + return nil +} + +func (x *InstallRequest) GetNameHint() string { + if x != nil { + if x, ok := x.Value.(*InstallRequest_NameHint); ok { + return x.NameHint + } + } + return "" +} + +func (x *InstallRequest) GetMakeDebuggable() bool { + if x != nil { + if x, ok := x.Value.(*InstallRequest_MakeDebuggable); ok { + return x.MakeDebuggable + } + } + return false +} + +func (x *InstallRequest) GetBundleId() string { + if x != nil { + if x, ok := x.Value.(*InstallRequest_BundleId); ok { + return x.BundleId + } + } + return "" +} + +func (x *InstallRequest) GetLinkDsymToBundle() *InstallRequest_LinkDsymToBundle { + if x != nil { + if x, ok := x.Value.(*InstallRequest_LinkDsymToBundle_); ok { + return x.LinkDsymToBundle + } + } + return nil +} + +func (x *InstallRequest) GetOverrideModificationTime() bool { + if x != nil { + if x, ok := x.Value.(*InstallRequest_OverrideModificationTime); ok { + return x.OverrideModificationTime + } + } + return false +} + +type isInstallRequest_Value interface { + isInstallRequest_Value() +} + +type InstallRequest_Destination_ struct { + Destination InstallRequest_Destination `protobuf:"varint,1,opt,name=destination,proto3,enum=idb.InstallRequest_Destination,oneof"` +} + +type InstallRequest_Payload struct { + Payload *Payload `protobuf:"bytes,2,opt,name=payload,proto3,oneof"` +} + +type InstallRequest_NameHint struct { + NameHint string `protobuf:"bytes,3,opt,name=name_hint,json=nameHint,proto3,oneof"` +} + +type InstallRequest_MakeDebuggable struct { + MakeDebuggable bool `protobuf:"varint,4,opt,name=make_debuggable,json=makeDebuggable,proto3,oneof"` +} + +type InstallRequest_BundleId struct { + BundleId string `protobuf:"bytes,5,opt,name=bundle_id,json=bundleId,proto3,oneof"` //(2022-03-02) REMOVE, For back compatibility only. +} + +type InstallRequest_LinkDsymToBundle_ struct { + // Link dSYM to app bundle_id + LinkDsymToBundle *InstallRequest_LinkDsymToBundle `protobuf:"bytes,6,opt,name=link_dsym_to_bundle,json=linkDsymToBundle,proto3,oneof"` +} + +type InstallRequest_OverrideModificationTime struct { + OverrideModificationTime bool `protobuf:"varint,7,opt,name=override_modification_time,json=overrideModificationTime,proto3,oneof"` +} + +func (*InstallRequest_Destination_) isInstallRequest_Value() {} + +func (*InstallRequest_Payload) isInstallRequest_Value() {} + +func (*InstallRequest_NameHint) isInstallRequest_Value() {} + +func (*InstallRequest_MakeDebuggable) isInstallRequest_Value() {} + +func (*InstallRequest_BundleId) isInstallRequest_Value() {} + +func (*InstallRequest_LinkDsymToBundle_) isInstallRequest_Value() {} + +func (*InstallRequest_OverrideModificationTime) isInstallRequest_Value() {} + +type InstallResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Uuid string `protobuf:"bytes,2,opt,name=uuid,proto3" json:"uuid,omitempty"` + Progress float64 `protobuf:"fixed64,3,opt,name=progress,proto3" json:"progress,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstallResponse) Reset() { + *x = InstallResponse{} + mi := &file_idb_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstallResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstallResponse) ProtoMessage() {} + +func (x *InstallResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InstallResponse.ProtoReflect.Descriptor instead. +func (*InstallResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{13} +} + +func (x *InstallResponse) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *InstallResponse) GetUuid() string { + if x != nil { + return x.Uuid + } + return "" +} + +func (x *InstallResponse) GetProgress() float64 { + if x != nil { + return x.Progress + } + return 0 +} + +type ScreenshotRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ScreenshotRequest) Reset() { + *x = ScreenshotRequest{} + mi := &file_idb_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ScreenshotRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScreenshotRequest) ProtoMessage() {} + +func (x *ScreenshotRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScreenshotRequest.ProtoReflect.Descriptor instead. +func (*ScreenshotRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{14} +} + +type ScreenshotResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ImageData []byte `protobuf:"bytes,1,opt,name=image_data,json=imageData,proto3" json:"image_data,omitempty"` + ImageFormat string `protobuf:"bytes,2,opt,name=image_format,json=imageFormat,proto3" json:"image_format,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ScreenshotResponse) Reset() { + *x = ScreenshotResponse{} + mi := &file_idb_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ScreenshotResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScreenshotResponse) ProtoMessage() {} + +func (x *ScreenshotResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScreenshotResponse.ProtoReflect.Descriptor instead. +func (*ScreenshotResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{15} +} + +func (x *ScreenshotResponse) GetImageData() []byte { + if x != nil { + return x.ImageData + } + return nil +} + +func (x *ScreenshotResponse) GetImageFormat() string { + if x != nil { + return x.ImageFormat + } + return "" +} + +type FocusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FocusRequest) Reset() { + *x = FocusRequest{} + mi := &file_idb_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FocusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FocusRequest) ProtoMessage() {} + +func (x *FocusRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FocusRequest.ProtoReflect.Descriptor instead. +func (*FocusRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{16} +} + +type FocusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FocusResponse) Reset() { + *x = FocusResponse{} + mi := &file_idb_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FocusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FocusResponse) ProtoMessage() {} + +func (x *FocusResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FocusResponse.ProtoReflect.Descriptor instead. +func (*FocusResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{17} +} + +type Point struct { + state protoimpl.MessageState `protogen:"open.v1"` + X float64 `protobuf:"fixed64,1,opt,name=x,proto3" json:"x,omitempty"` + Y float64 `protobuf:"fixed64,2,opt,name=y,proto3" json:"y,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Point) Reset() { + *x = Point{} + mi := &file_idb_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Point) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Point) ProtoMessage() {} + +func (x *Point) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Point.ProtoReflect.Descriptor instead. +func (*Point) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{18} +} + +func (x *Point) GetX() float64 { + if x != nil { + return x.X + } + return 0 +} + +func (x *Point) GetY() float64 { + if x != nil { + return x.Y + } + return 0 +} + +type AccessibilityInfoRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Point *Point `protobuf:"bytes,2,opt,name=point,proto3" json:"point,omitempty"` + Format AccessibilityInfoRequest_Format `protobuf:"varint,3,opt,name=format,proto3,enum=idb.AccessibilityInfoRequest_Format" json:"format,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AccessibilityInfoRequest) Reset() { + *x = AccessibilityInfoRequest{} + mi := &file_idb_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AccessibilityInfoRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AccessibilityInfoRequest) ProtoMessage() {} + +func (x *AccessibilityInfoRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AccessibilityInfoRequest.ProtoReflect.Descriptor instead. +func (*AccessibilityInfoRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{19} +} + +func (x *AccessibilityInfoRequest) GetPoint() *Point { + if x != nil { + return x.Point + } + return nil +} + +func (x *AccessibilityInfoRequest) GetFormat() AccessibilityInfoRequest_Format { + if x != nil { + return x.Format + } + return AccessibilityInfoRequest_LEGACY +} + +type AccessibilityInfoResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Json string `protobuf:"bytes,1,opt,name=json,proto3" json:"json,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AccessibilityInfoResponse) Reset() { + *x = AccessibilityInfoResponse{} + mi := &file_idb_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AccessibilityInfoResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AccessibilityInfoResponse) ProtoMessage() {} + +func (x *AccessibilityInfoResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AccessibilityInfoResponse.ProtoReflect.Descriptor instead. +func (*AccessibilityInfoResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{20} +} + +func (x *AccessibilityInfoResponse) GetJson() string { + if x != nil { + return x.Json + } + return "" +} + +type ApproveRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + BundleId string `protobuf:"bytes,1,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + Permissions []ApproveRequest_Permission `protobuf:"varint,2,rep,packed,name=permissions,proto3,enum=idb.ApproveRequest_Permission" json:"permissions,omitempty"` + Scheme string `protobuf:"bytes,3,opt,name=scheme,proto3" json:"scheme,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApproveRequest) Reset() { + *x = ApproveRequest{} + mi := &file_idb_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApproveRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApproveRequest) ProtoMessage() {} + +func (x *ApproveRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApproveRequest.ProtoReflect.Descriptor instead. +func (*ApproveRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{21} +} + +func (x *ApproveRequest) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +func (x *ApproveRequest) GetPermissions() []ApproveRequest_Permission { + if x != nil { + return x.Permissions + } + return nil +} + +func (x *ApproveRequest) GetScheme() string { + if x != nil { + return x.Scheme + } + return "" +} + +type ApproveResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApproveResponse) Reset() { + *x = ApproveResponse{} + mi := &file_idb_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApproveResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApproveResponse) ProtoMessage() {} + +func (x *ApproveResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApproveResponse.ProtoReflect.Descriptor instead. +func (*ApproveResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{22} +} + +type RevokeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + BundleId string `protobuf:"bytes,1,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + Permissions []RevokeRequest_Permission `protobuf:"varint,2,rep,packed,name=permissions,proto3,enum=idb.RevokeRequest_Permission" json:"permissions,omitempty"` + Scheme string `protobuf:"bytes,3,opt,name=scheme,proto3" json:"scheme,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RevokeRequest) Reset() { + *x = RevokeRequest{} + mi := &file_idb_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeRequest) ProtoMessage() {} + +func (x *RevokeRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeRequest.ProtoReflect.Descriptor instead. +func (*RevokeRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{23} +} + +func (x *RevokeRequest) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +func (x *RevokeRequest) GetPermissions() []RevokeRequest_Permission { + if x != nil { + return x.Permissions + } + return nil +} + +func (x *RevokeRequest) GetScheme() string { + if x != nil { + return x.Scheme + } + return "" +} + +type RevokeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RevokeResponse) Reset() { + *x = RevokeResponse{} + mi := &file_idb_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeResponse) ProtoMessage() {} + +func (x *RevokeResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeResponse.ProtoReflect.Descriptor instead. +func (*RevokeResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{24} +} + +type ClearKeychainRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClearKeychainRequest) Reset() { + *x = ClearKeychainRequest{} + mi := &file_idb_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClearKeychainRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClearKeychainRequest) ProtoMessage() {} + +func (x *ClearKeychainRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClearKeychainRequest.ProtoReflect.Descriptor instead. +func (*ClearKeychainRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{25} +} + +type ClearKeychainResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClearKeychainResponse) Reset() { + *x = ClearKeychainResponse{} + mi := &file_idb_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClearKeychainResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClearKeychainResponse) ProtoMessage() {} + +func (x *ClearKeychainResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClearKeychainResponse.ProtoReflect.Descriptor instead. +func (*ClearKeychainResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{26} +} + +type SetLocationRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Location *Location `protobuf:"bytes,1,opt,name=location,proto3" json:"location,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetLocationRequest) Reset() { + *x = SetLocationRequest{} + mi := &file_idb_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetLocationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetLocationRequest) ProtoMessage() {} + +func (x *SetLocationRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetLocationRequest.ProtoReflect.Descriptor instead. +func (*SetLocationRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{27} +} + +func (x *SetLocationRequest) GetLocation() *Location { + if x != nil { + return x.Location + } + return nil +} + +type Location struct { + state protoimpl.MessageState `protogen:"open.v1"` + Latitude float64 `protobuf:"fixed64,1,opt,name=latitude,proto3" json:"latitude,omitempty"` + Longitude float64 `protobuf:"fixed64,2,opt,name=longitude,proto3" json:"longitude,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Location) Reset() { + *x = Location{} + mi := &file_idb_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Location) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Location) ProtoMessage() {} + +func (x *Location) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Location.ProtoReflect.Descriptor instead. +func (*Location) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{28} +} + +func (x *Location) GetLatitude() float64 { + if x != nil { + return x.Latitude + } + return 0 +} + +func (x *Location) GetLongitude() float64 { + if x != nil { + return x.Longitude + } + return 0 +} + +type SetLocationResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetLocationResponse) Reset() { + *x = SetLocationResponse{} + mi := &file_idb_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetLocationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetLocationResponse) ProtoMessage() {} + +func (x *SetLocationResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetLocationResponse.ProtoReflect.Descriptor instead. +func (*SetLocationResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{29} +} + +type UninstallRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + BundleId string `protobuf:"bytes,1,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UninstallRequest) Reset() { + *x = UninstallRequest{} + mi := &file_idb_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UninstallRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UninstallRequest) ProtoMessage() {} + +func (x *UninstallRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UninstallRequest.ProtoReflect.Descriptor instead. +func (*UninstallRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{30} +} + +func (x *UninstallRequest) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +type UninstallResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UninstallResponse) Reset() { + *x = UninstallResponse{} + mi := &file_idb_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UninstallResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UninstallResponse) ProtoMessage() {} + +func (x *UninstallResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UninstallResponse.ProtoReflect.Descriptor instead. +func (*UninstallResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{31} +} + +type TerminateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + BundleId string `protobuf:"bytes,1,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TerminateRequest) Reset() { + *x = TerminateRequest{} + mi := &file_idb_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TerminateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TerminateRequest) ProtoMessage() {} + +func (x *TerminateRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TerminateRequest.ProtoReflect.Descriptor instead. +func (*TerminateRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{32} +} + +func (x *TerminateRequest) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +type TerminateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TerminateResponse) Reset() { + *x = TerminateResponse{} + mi := &file_idb_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TerminateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TerminateResponse) ProtoMessage() {} + +func (x *TerminateResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[33] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TerminateResponse.ProtoReflect.Descriptor instead. +func (*TerminateResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{33} +} + +type OpenUrlRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OpenUrlRequest) Reset() { + *x = OpenUrlRequest{} + mi := &file_idb_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OpenUrlRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OpenUrlRequest) ProtoMessage() {} + +func (x *OpenUrlRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[34] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OpenUrlRequest.ProtoReflect.Descriptor instead. +func (*OpenUrlRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{34} +} + +func (x *OpenUrlRequest) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type OpenUrlResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OpenUrlResponse) Reset() { + *x = OpenUrlResponse{} + mi := &file_idb_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OpenUrlResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OpenUrlResponse) ProtoMessage() {} + +func (x *OpenUrlResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[35] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OpenUrlResponse.ProtoReflect.Descriptor instead. +func (*OpenUrlResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{35} +} + +type ContactsUpdateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Payload *Payload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ContactsUpdateRequest) Reset() { + *x = ContactsUpdateRequest{} + mi := &file_idb_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ContactsUpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContactsUpdateRequest) ProtoMessage() {} + +func (x *ContactsUpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContactsUpdateRequest.ProtoReflect.Descriptor instead. +func (*ContactsUpdateRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{36} +} + +func (x *ContactsUpdateRequest) GetPayload() *Payload { + if x != nil { + return x.Payload + } + return nil +} + +type ContactsUpdateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ContactsUpdateResponse) Reset() { + *x = ContactsUpdateResponse{} + mi := &file_idb_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ContactsUpdateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContactsUpdateResponse) ProtoMessage() {} + +func (x *ContactsUpdateResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[37] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContactsUpdateResponse.ProtoReflect.Descriptor instead. +func (*ContactsUpdateResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{37} +} + +type TargetDescriptionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + FetchDiagnostics bool `protobuf:"varint,1,opt,name=fetch_diagnostics,json=fetchDiagnostics,proto3" json:"fetch_diagnostics,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TargetDescriptionRequest) Reset() { + *x = TargetDescriptionRequest{} + mi := &file_idb_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TargetDescriptionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TargetDescriptionRequest) ProtoMessage() {} + +func (x *TargetDescriptionRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[38] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TargetDescriptionRequest.ProtoReflect.Descriptor instead. +func (*TargetDescriptionRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{38} +} + +func (x *TargetDescriptionRequest) GetFetchDiagnostics() bool { + if x != nil { + return x.FetchDiagnostics + } + return false +} + +type TargetDescriptionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + TargetDescription *TargetDescription `protobuf:"bytes,1,opt,name=target_description,json=targetDescription,proto3" json:"target_description,omitempty"` + Companion *CompanionInfo `protobuf:"bytes,2,opt,name=companion,proto3" json:"companion,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TargetDescriptionResponse) Reset() { + *x = TargetDescriptionResponse{} + mi := &file_idb_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TargetDescriptionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TargetDescriptionResponse) ProtoMessage() {} + +func (x *TargetDescriptionResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[39] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TargetDescriptionResponse.ProtoReflect.Descriptor instead. +func (*TargetDescriptionResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{39} +} + +func (x *TargetDescriptionResponse) GetTargetDescription() *TargetDescription { + if x != nil { + return x.TargetDescription + } + return nil +} + +func (x *TargetDescriptionResponse) GetCompanion() *CompanionInfo { + if x != nil { + return x.Companion + } + return nil +} + +type HIDEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Event: + // + // *HIDEvent_Press + // *HIDEvent_Swipe + // *HIDEvent_Delay + Event isHIDEvent_Event `protobuf_oneof:"event"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HIDEvent) Reset() { + *x = HIDEvent{} + mi := &file_idb_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HIDEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HIDEvent) ProtoMessage() {} + +func (x *HIDEvent) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[40] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HIDEvent.ProtoReflect.Descriptor instead. +func (*HIDEvent) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{40} +} + +func (x *HIDEvent) GetEvent() isHIDEvent_Event { + if x != nil { + return x.Event + } + return nil +} + +func (x *HIDEvent) GetPress() *HIDEvent_HIDPress { + if x != nil { + if x, ok := x.Event.(*HIDEvent_Press); ok { + return x.Press + } + } + return nil +} + +func (x *HIDEvent) GetSwipe() *HIDEvent_HIDSwipe { + if x != nil { + if x, ok := x.Event.(*HIDEvent_Swipe); ok { + return x.Swipe + } + } + return nil +} + +func (x *HIDEvent) GetDelay() *HIDEvent_HIDDelay { + if x != nil { + if x, ok := x.Event.(*HIDEvent_Delay); ok { + return x.Delay + } + } + return nil +} + +type isHIDEvent_Event interface { + isHIDEvent_Event() +} + +type HIDEvent_Press struct { + Press *HIDEvent_HIDPress `protobuf:"bytes,1,opt,name=press,proto3,oneof"` +} + +type HIDEvent_Swipe struct { + Swipe *HIDEvent_HIDSwipe `protobuf:"bytes,2,opt,name=swipe,proto3,oneof"` +} + +type HIDEvent_Delay struct { + Delay *HIDEvent_HIDDelay `protobuf:"bytes,3,opt,name=delay,proto3,oneof"` +} + +func (*HIDEvent_Press) isHIDEvent_Event() {} + +func (*HIDEvent_Swipe) isHIDEvent_Event() {} + +func (*HIDEvent_Delay) isHIDEvent_Event() {} + +type HIDResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HIDResponse) Reset() { + *x = HIDResponse{} + mi := &file_idb_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HIDResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HIDResponse) ProtoMessage() {} + +func (x *HIDResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[41] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HIDResponse.ProtoReflect.Descriptor instead. +func (*HIDResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{41} +} + +type ConnectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Metadata map[string]string `protobuf:"bytes,1,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + LocalFilePath string `protobuf:"bytes,4,opt,name=local_file_path,json=localFilePath,proto3" json:"local_file_path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectRequest) Reset() { + *x = ConnectRequest{} + mi := &file_idb_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectRequest) ProtoMessage() {} + +func (x *ConnectRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[42] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectRequest.ProtoReflect.Descriptor instead. +func (*ConnectRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{42} +} + +func (x *ConnectRequest) GetMetadata() map[string]string { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *ConnectRequest) GetLocalFilePath() string { + if x != nil { + return x.LocalFilePath + } + return "" +} + +type ConnectResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Companion *CompanionInfo `protobuf:"bytes,1,opt,name=companion,proto3" json:"companion,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectResponse) Reset() { + *x = ConnectResponse{} + mi := &file_idb_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectResponse) ProtoMessage() {} + +func (x *ConnectResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[43] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectResponse.ProtoReflect.Descriptor instead. +func (*ConnectResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{43} +} + +func (x *ConnectResponse) GetCompanion() *CompanionInfo { + if x != nil { + return x.Companion + } + return nil +} + +type ScreenDimensions struct { + state protoimpl.MessageState `protogen:"open.v1"` + Width uint64 `protobuf:"varint,1,opt,name=width,proto3" json:"width,omitempty"` + Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` + Density float64 `protobuf:"fixed64,3,opt,name=density,proto3" json:"density,omitempty"` + WidthPoints uint64 `protobuf:"varint,4,opt,name=width_points,json=widthPoints,proto3" json:"width_points,omitempty"` + HeightPoints uint64 `protobuf:"varint,5,opt,name=height_points,json=heightPoints,proto3" json:"height_points,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ScreenDimensions) Reset() { + *x = ScreenDimensions{} + mi := &file_idb_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ScreenDimensions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScreenDimensions) ProtoMessage() {} + +func (x *ScreenDimensions) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[44] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScreenDimensions.ProtoReflect.Descriptor instead. +func (*ScreenDimensions) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{44} +} + +func (x *ScreenDimensions) GetWidth() uint64 { + if x != nil { + return x.Width + } + return 0 +} + +func (x *ScreenDimensions) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *ScreenDimensions) GetDensity() float64 { + if x != nil { + return x.Density + } + return 0 +} + +func (x *ScreenDimensions) GetWidthPoints() uint64 { + if x != nil { + return x.WidthPoints + } + return 0 +} + +func (x *ScreenDimensions) GetHeightPoints() uint64 { + if x != nil { + return x.HeightPoints + } + return 0 +} + +type TargetDescription struct { + state protoimpl.MessageState `protogen:"open.v1"` + Udid string `protobuf:"bytes,1,opt,name=udid,proto3" json:"udid,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + ScreenDimensions *ScreenDimensions `protobuf:"bytes,3,opt,name=screen_dimensions,json=screenDimensions,proto3" json:"screen_dimensions,omitempty"` + State string `protobuf:"bytes,4,opt,name=state,proto3" json:"state,omitempty"` + TargetType string `protobuf:"bytes,5,opt,name=target_type,json=targetType,proto3" json:"target_type,omitempty"` + OsVersion string `protobuf:"bytes,6,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"` + Architecture string `protobuf:"bytes,7,opt,name=architecture,proto3" json:"architecture,omitempty"` + Extended []byte `protobuf:"bytes,9,opt,name=extended,proto3" json:"extended,omitempty"` + Diagnostics []byte `protobuf:"bytes,10,opt,name=diagnostics,proto3" json:"diagnostics,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TargetDescription) Reset() { + *x = TargetDescription{} + mi := &file_idb_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TargetDescription) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TargetDescription) ProtoMessage() {} + +func (x *TargetDescription) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[45] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TargetDescription.ProtoReflect.Descriptor instead. +func (*TargetDescription) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{45} +} + +func (x *TargetDescription) GetUdid() string { + if x != nil { + return x.Udid + } + return "" +} + +func (x *TargetDescription) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TargetDescription) GetScreenDimensions() *ScreenDimensions { + if x != nil { + return x.ScreenDimensions + } + return nil +} + +func (x *TargetDescription) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *TargetDescription) GetTargetType() string { + if x != nil { + return x.TargetType + } + return "" +} + +func (x *TargetDescription) GetOsVersion() string { + if x != nil { + return x.OsVersion + } + return "" +} + +func (x *TargetDescription) GetArchitecture() string { + if x != nil { + return x.Architecture + } + return "" +} + +func (x *TargetDescription) GetExtended() []byte { + if x != nil { + return x.Extended + } + return nil +} + +func (x *TargetDescription) GetDiagnostics() []byte { + if x != nil { + return x.Diagnostics + } + return nil +} + +type LogRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Arguments []string `protobuf:"bytes,1,rep,name=arguments,proto3" json:"arguments,omitempty"` + Source LogRequest_Source `protobuf:"varint,2,opt,name=source,proto3,enum=idb.LogRequest_Source" json:"source,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LogRequest) Reset() { + *x = LogRequest{} + mi := &file_idb_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogRequest) ProtoMessage() {} + +func (x *LogRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[46] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogRequest.ProtoReflect.Descriptor instead. +func (*LogRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{46} +} + +func (x *LogRequest) GetArguments() []string { + if x != nil { + return x.Arguments + } + return nil +} + +func (x *LogRequest) GetSource() LogRequest_Source { + if x != nil { + return x.Source + } + return LogRequest_TARGET +} + +type LogResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Output []byte `protobuf:"bytes,1,opt,name=output,proto3" json:"output,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LogResponse) Reset() { + *x = LogResponse{} + mi := &file_idb_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogResponse) ProtoMessage() {} + +func (x *LogResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[47] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogResponse.ProtoReflect.Descriptor instead. +func (*LogResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{47} +} + +func (x *LogResponse) GetOutput() []byte { + if x != nil { + return x.Output + } + return nil +} + +type RecordRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Control: + // + // *RecordRequest_Start_ + // *RecordRequest_Stop_ + Control isRecordRequest_Control `protobuf_oneof:"control"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RecordRequest) Reset() { + *x = RecordRequest{} + mi := &file_idb_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RecordRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecordRequest) ProtoMessage() {} + +func (x *RecordRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[48] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RecordRequest.ProtoReflect.Descriptor instead. +func (*RecordRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{48} +} + +func (x *RecordRequest) GetControl() isRecordRequest_Control { + if x != nil { + return x.Control + } + return nil +} + +func (x *RecordRequest) GetStart() *RecordRequest_Start { + if x != nil { + if x, ok := x.Control.(*RecordRequest_Start_); ok { + return x.Start + } + } + return nil +} + +func (x *RecordRequest) GetStop() *RecordRequest_Stop { + if x != nil { + if x, ok := x.Control.(*RecordRequest_Stop_); ok { + return x.Stop + } + } + return nil +} + +type isRecordRequest_Control interface { + isRecordRequest_Control() +} + +type RecordRequest_Start_ struct { + Start *RecordRequest_Start `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type RecordRequest_Stop_ struct { + Stop *RecordRequest_Stop `protobuf:"bytes,2,opt,name=stop,proto3,oneof"` +} + +func (*RecordRequest_Start_) isRecordRequest_Control() {} + +func (*RecordRequest_Stop_) isRecordRequest_Control() {} + +type RecordResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Output: + // + // *RecordResponse_LogOutput + // *RecordResponse_Payload + Output isRecordResponse_Output `protobuf_oneof:"output"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RecordResponse) Reset() { + *x = RecordResponse{} + mi := &file_idb_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RecordResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecordResponse) ProtoMessage() {} + +func (x *RecordResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[49] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RecordResponse.ProtoReflect.Descriptor instead. +func (*RecordResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{49} +} + +func (x *RecordResponse) GetOutput() isRecordResponse_Output { + if x != nil { + return x.Output + } + return nil +} + +func (x *RecordResponse) GetLogOutput() []byte { + if x != nil { + if x, ok := x.Output.(*RecordResponse_LogOutput); ok { + return x.LogOutput + } + } + return nil +} + +func (x *RecordResponse) GetPayload() *Payload { + if x != nil { + if x, ok := x.Output.(*RecordResponse_Payload); ok { + return x.Payload + } + } + return nil +} + +type isRecordResponse_Output interface { + isRecordResponse_Output() +} + +type RecordResponse_LogOutput struct { + LogOutput []byte `protobuf:"bytes,1,opt,name=log_output,json=logOutput,proto3,oneof"` +} + +type RecordResponse_Payload struct { + Payload *Payload `protobuf:"bytes,2,opt,name=payload,proto3,oneof"` +} + +func (*RecordResponse_LogOutput) isRecordResponse_Output() {} + +func (*RecordResponse_Payload) isRecordResponse_Output() {} + +type VideoStreamRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Control: + // + // *VideoStreamRequest_Start_ + // *VideoStreamRequest_Stop_ + Control isVideoStreamRequest_Control `protobuf_oneof:"control"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VideoStreamRequest) Reset() { + *x = VideoStreamRequest{} + mi := &file_idb_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VideoStreamRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VideoStreamRequest) ProtoMessage() {} + +func (x *VideoStreamRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[50] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VideoStreamRequest.ProtoReflect.Descriptor instead. +func (*VideoStreamRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{50} +} + +func (x *VideoStreamRequest) GetControl() isVideoStreamRequest_Control { + if x != nil { + return x.Control + } + return nil +} + +func (x *VideoStreamRequest) GetStart() *VideoStreamRequest_Start { + if x != nil { + if x, ok := x.Control.(*VideoStreamRequest_Start_); ok { + return x.Start + } + } + return nil +} + +func (x *VideoStreamRequest) GetStop() *VideoStreamRequest_Stop { + if x != nil { + if x, ok := x.Control.(*VideoStreamRequest_Stop_); ok { + return x.Stop + } + } + return nil +} + +type isVideoStreamRequest_Control interface { + isVideoStreamRequest_Control() +} + +type VideoStreamRequest_Start_ struct { + Start *VideoStreamRequest_Start `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type VideoStreamRequest_Stop_ struct { + Stop *VideoStreamRequest_Stop `protobuf:"bytes,2,opt,name=stop,proto3,oneof"` +} + +func (*VideoStreamRequest_Start_) isVideoStreamRequest_Control() {} + +func (*VideoStreamRequest_Stop_) isVideoStreamRequest_Control() {} + +type VideoStreamResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Output: + // + // *VideoStreamResponse_LogOutput + // *VideoStreamResponse_Payload + Output isVideoStreamResponse_Output `protobuf_oneof:"output"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VideoStreamResponse) Reset() { + *x = VideoStreamResponse{} + mi := &file_idb_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VideoStreamResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VideoStreamResponse) ProtoMessage() {} + +func (x *VideoStreamResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[51] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VideoStreamResponse.ProtoReflect.Descriptor instead. +func (*VideoStreamResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{51} +} + +func (x *VideoStreamResponse) GetOutput() isVideoStreamResponse_Output { + if x != nil { + return x.Output + } + return nil +} + +func (x *VideoStreamResponse) GetLogOutput() []byte { + if x != nil { + if x, ok := x.Output.(*VideoStreamResponse_LogOutput); ok { + return x.LogOutput + } + } + return nil +} + +func (x *VideoStreamResponse) GetPayload() *Payload { + if x != nil { + if x, ok := x.Output.(*VideoStreamResponse_Payload); ok { + return x.Payload + } + } + return nil +} + +type isVideoStreamResponse_Output interface { + isVideoStreamResponse_Output() +} + +type VideoStreamResponse_LogOutput struct { + LogOutput []byte `protobuf:"bytes,1,opt,name=log_output,json=logOutput,proto3,oneof"` +} + +type VideoStreamResponse_Payload struct { + Payload *Payload `protobuf:"bytes,2,opt,name=payload,proto3,oneof"` +} + +func (*VideoStreamResponse_LogOutput) isVideoStreamResponse_Output() {} + +func (*VideoStreamResponse_Payload) isVideoStreamResponse_Output() {} + +type LaunchRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Control: + // + // *LaunchRequest_Start_ + // *LaunchRequest_Stop_ + Control isLaunchRequest_Control `protobuf_oneof:"control"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LaunchRequest) Reset() { + *x = LaunchRequest{} + mi := &file_idb_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LaunchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LaunchRequest) ProtoMessage() {} + +func (x *LaunchRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[52] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LaunchRequest.ProtoReflect.Descriptor instead. +func (*LaunchRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{52} +} + +func (x *LaunchRequest) GetControl() isLaunchRequest_Control { + if x != nil { + return x.Control + } + return nil +} + +func (x *LaunchRequest) GetStart() *LaunchRequest_Start { + if x != nil { + if x, ok := x.Control.(*LaunchRequest_Start_); ok { + return x.Start + } + } + return nil +} + +func (x *LaunchRequest) GetStop() *LaunchRequest_Stop { + if x != nil { + if x, ok := x.Control.(*LaunchRequest_Stop_); ok { + return x.Stop + } + } + return nil +} + +type isLaunchRequest_Control interface { + isLaunchRequest_Control() +} + +type LaunchRequest_Start_ struct { + Start *LaunchRequest_Start `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type LaunchRequest_Stop_ struct { + Stop *LaunchRequest_Stop `protobuf:"bytes,2,opt,name=stop,proto3,oneof"` +} + +func (*LaunchRequest_Start_) isLaunchRequest_Control() {} + +func (*LaunchRequest_Stop_) isLaunchRequest_Control() {} + +type LaunchResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Output *ProcessOutput `protobuf:"bytes,3,opt,name=output,proto3" json:"output,omitempty"` + Debugger *DebuggerInfo `protobuf:"bytes,4,opt,name=debugger,proto3" json:"debugger,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LaunchResponse) Reset() { + *x = LaunchResponse{} + mi := &file_idb_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LaunchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LaunchResponse) ProtoMessage() {} + +func (x *LaunchResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[53] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LaunchResponse.ProtoReflect.Descriptor instead. +func (*LaunchResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{53} +} + +func (x *LaunchResponse) GetOutput() *ProcessOutput { + if x != nil { + return x.Output + } + return nil +} + +func (x *LaunchResponse) GetDebugger() *DebuggerInfo { + if x != nil { + return x.Debugger + } + return nil +} + +type AddMediaRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Payload *Payload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddMediaRequest) Reset() { + *x = AddMediaRequest{} + mi := &file_idb_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddMediaRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddMediaRequest) ProtoMessage() {} + +func (x *AddMediaRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[54] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddMediaRequest.ProtoReflect.Descriptor instead. +func (*AddMediaRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{54} +} + +func (x *AddMediaRequest) GetPayload() *Payload { + if x != nil { + return x.Payload + } + return nil +} + +type AddMediaResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddMediaResponse) Reset() { + *x = AddMediaResponse{} + mi := &file_idb_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddMediaResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddMediaResponse) ProtoMessage() {} + +func (x *AddMediaResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[55] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddMediaResponse.ProtoReflect.Descriptor instead. +func (*AddMediaResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{55} +} + +type InstrumentsRunRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Control: + // + // *InstrumentsRunRequest_Start_ + // *InstrumentsRunRequest_Stop_ + Control isInstrumentsRunRequest_Control `protobuf_oneof:"control"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstrumentsRunRequest) Reset() { + *x = InstrumentsRunRequest{} + mi := &file_idb_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstrumentsRunRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstrumentsRunRequest) ProtoMessage() {} + +func (x *InstrumentsRunRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[56] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InstrumentsRunRequest.ProtoReflect.Descriptor instead. +func (*InstrumentsRunRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{56} +} + +func (x *InstrumentsRunRequest) GetControl() isInstrumentsRunRequest_Control { + if x != nil { + return x.Control + } + return nil +} + +func (x *InstrumentsRunRequest) GetStart() *InstrumentsRunRequest_Start { + if x != nil { + if x, ok := x.Control.(*InstrumentsRunRequest_Start_); ok { + return x.Start + } + } + return nil +} + +func (x *InstrumentsRunRequest) GetStop() *InstrumentsRunRequest_Stop { + if x != nil { + if x, ok := x.Control.(*InstrumentsRunRequest_Stop_); ok { + return x.Stop + } + } + return nil +} + +type isInstrumentsRunRequest_Control interface { + isInstrumentsRunRequest_Control() +} + +type InstrumentsRunRequest_Start_ struct { + Start *InstrumentsRunRequest_Start `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type InstrumentsRunRequest_Stop_ struct { + Stop *InstrumentsRunRequest_Stop `protobuf:"bytes,2,opt,name=stop,proto3,oneof"` +} + +func (*InstrumentsRunRequest_Start_) isInstrumentsRunRequest_Control() {} + +func (*InstrumentsRunRequest_Stop_) isInstrumentsRunRequest_Control() {} + +type InstrumentsRunResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Output: + // + // *InstrumentsRunResponse_LogOutput + // *InstrumentsRunResponse_Payload + // *InstrumentsRunResponse_State_ + Output isInstrumentsRunResponse_Output `protobuf_oneof:"output"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstrumentsRunResponse) Reset() { + *x = InstrumentsRunResponse{} + mi := &file_idb_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstrumentsRunResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstrumentsRunResponse) ProtoMessage() {} + +func (x *InstrumentsRunResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[57] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InstrumentsRunResponse.ProtoReflect.Descriptor instead. +func (*InstrumentsRunResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{57} +} + +func (x *InstrumentsRunResponse) GetOutput() isInstrumentsRunResponse_Output { + if x != nil { + return x.Output + } + return nil +} + +func (x *InstrumentsRunResponse) GetLogOutput() []byte { + if x != nil { + if x, ok := x.Output.(*InstrumentsRunResponse_LogOutput); ok { + return x.LogOutput + } + } + return nil +} + +func (x *InstrumentsRunResponse) GetPayload() *Payload { + if x != nil { + if x, ok := x.Output.(*InstrumentsRunResponse_Payload); ok { + return x.Payload + } + } + return nil +} + +func (x *InstrumentsRunResponse) GetState() InstrumentsRunResponse_State { + if x != nil { + if x, ok := x.Output.(*InstrumentsRunResponse_State_); ok { + return x.State + } + } + return InstrumentsRunResponse_UNKNOWN +} + +type isInstrumentsRunResponse_Output interface { + isInstrumentsRunResponse_Output() +} + +type InstrumentsRunResponse_LogOutput struct { + LogOutput []byte `protobuf:"bytes,1,opt,name=log_output,json=logOutput,proto3,oneof"` +} + +type InstrumentsRunResponse_Payload struct { + Payload *Payload `protobuf:"bytes,2,opt,name=payload,proto3,oneof"` +} + +type InstrumentsRunResponse_State_ struct { + State InstrumentsRunResponse_State `protobuf:"varint,3,opt,name=state,proto3,enum=idb.InstrumentsRunResponse_State,oneof"` +} + +func (*InstrumentsRunResponse_LogOutput) isInstrumentsRunResponse_Output() {} + +func (*InstrumentsRunResponse_Payload) isInstrumentsRunResponse_Output() {} + +func (*InstrumentsRunResponse_State_) isInstrumentsRunResponse_Output() {} + +type XctraceRecordRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Control: + // + // *XctraceRecordRequest_Start_ + // *XctraceRecordRequest_Stop_ + Control isXctraceRecordRequest_Control `protobuf_oneof:"control"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctraceRecordRequest) Reset() { + *x = XctraceRecordRequest{} + mi := &file_idb_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctraceRecordRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctraceRecordRequest) ProtoMessage() {} + +func (x *XctraceRecordRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[58] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctraceRecordRequest.ProtoReflect.Descriptor instead. +func (*XctraceRecordRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{58} +} + +func (x *XctraceRecordRequest) GetControl() isXctraceRecordRequest_Control { + if x != nil { + return x.Control + } + return nil +} + +func (x *XctraceRecordRequest) GetStart() *XctraceRecordRequest_Start { + if x != nil { + if x, ok := x.Control.(*XctraceRecordRequest_Start_); ok { + return x.Start + } + } + return nil +} + +func (x *XctraceRecordRequest) GetStop() *XctraceRecordRequest_Stop { + if x != nil { + if x, ok := x.Control.(*XctraceRecordRequest_Stop_); ok { + return x.Stop + } + } + return nil +} + +type isXctraceRecordRequest_Control interface { + isXctraceRecordRequest_Control() +} + +type XctraceRecordRequest_Start_ struct { + Start *XctraceRecordRequest_Start `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type XctraceRecordRequest_Stop_ struct { + Stop *XctraceRecordRequest_Stop `protobuf:"bytes,2,opt,name=stop,proto3,oneof"` +} + +func (*XctraceRecordRequest_Start_) isXctraceRecordRequest_Control() {} + +func (*XctraceRecordRequest_Stop_) isXctraceRecordRequest_Control() {} + +type XctraceRecordResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Output: + // + // *XctraceRecordResponse_Log + // *XctraceRecordResponse_Payload + // *XctraceRecordResponse_State_ + Output isXctraceRecordResponse_Output `protobuf_oneof:"output"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctraceRecordResponse) Reset() { + *x = XctraceRecordResponse{} + mi := &file_idb_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctraceRecordResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctraceRecordResponse) ProtoMessage() {} + +func (x *XctraceRecordResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[59] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctraceRecordResponse.ProtoReflect.Descriptor instead. +func (*XctraceRecordResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{59} +} + +func (x *XctraceRecordResponse) GetOutput() isXctraceRecordResponse_Output { + if x != nil { + return x.Output + } + return nil +} + +func (x *XctraceRecordResponse) GetLog() []byte { + if x != nil { + if x, ok := x.Output.(*XctraceRecordResponse_Log); ok { + return x.Log + } + } + return nil +} + +func (x *XctraceRecordResponse) GetPayload() *Payload { + if x != nil { + if x, ok := x.Output.(*XctraceRecordResponse_Payload); ok { + return x.Payload + } + } + return nil +} + +func (x *XctraceRecordResponse) GetState() XctraceRecordResponse_State { + if x != nil { + if x, ok := x.Output.(*XctraceRecordResponse_State_); ok { + return x.State + } + } + return XctraceRecordResponse_UNKNOWN +} + +type isXctraceRecordResponse_Output interface { + isXctraceRecordResponse_Output() +} + +type XctraceRecordResponse_Log struct { + Log []byte `protobuf:"bytes,1,opt,name=log,proto3,oneof"` +} + +type XctraceRecordResponse_Payload struct { + Payload *Payload `protobuf:"bytes,2,opt,name=payload,proto3,oneof"` +} + +type XctraceRecordResponse_State_ struct { + State XctraceRecordResponse_State `protobuf:"varint,3,opt,name=state,proto3,enum=idb.XctraceRecordResponse_State,oneof"` +} + +func (*XctraceRecordResponse_Log) isXctraceRecordResponse_Output() {} + +func (*XctraceRecordResponse_Payload) isXctraceRecordResponse_Output() {} + +func (*XctraceRecordResponse_State_) isXctraceRecordResponse_Output() {} + +type DebugServerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Control: + // + // *DebugServerRequest_Start_ + // *DebugServerRequest_Stop_ + // *DebugServerRequest_Status_ + // *DebugServerRequest_Pipe_ + Control isDebugServerRequest_Control `protobuf_oneof:"control"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebugServerRequest) Reset() { + *x = DebugServerRequest{} + mi := &file_idb_proto_msgTypes[60] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebugServerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebugServerRequest) ProtoMessage() {} + +func (x *DebugServerRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[60] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebugServerRequest.ProtoReflect.Descriptor instead. +func (*DebugServerRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{60} +} + +func (x *DebugServerRequest) GetControl() isDebugServerRequest_Control { + if x != nil { + return x.Control + } + return nil +} + +func (x *DebugServerRequest) GetStart() *DebugServerRequest_Start { + if x != nil { + if x, ok := x.Control.(*DebugServerRequest_Start_); ok { + return x.Start + } + } + return nil +} + +func (x *DebugServerRequest) GetStop() *DebugServerRequest_Stop { + if x != nil { + if x, ok := x.Control.(*DebugServerRequest_Stop_); ok { + return x.Stop + } + } + return nil +} + +func (x *DebugServerRequest) GetStatus() *DebugServerRequest_Status { + if x != nil { + if x, ok := x.Control.(*DebugServerRequest_Status_); ok { + return x.Status + } + } + return nil +} + +func (x *DebugServerRequest) GetPipe() *DebugServerRequest_Pipe { + if x != nil { + if x, ok := x.Control.(*DebugServerRequest_Pipe_); ok { + return x.Pipe + } + } + return nil +} + +type isDebugServerRequest_Control interface { + isDebugServerRequest_Control() +} + +type DebugServerRequest_Start_ struct { + Start *DebugServerRequest_Start `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type DebugServerRequest_Stop_ struct { + Stop *DebugServerRequest_Stop `protobuf:"bytes,2,opt,name=stop,proto3,oneof"` +} + +type DebugServerRequest_Status_ struct { + Status *DebugServerRequest_Status `protobuf:"bytes,3,opt,name=status,proto3,oneof"` +} + +type DebugServerRequest_Pipe_ struct { + Pipe *DebugServerRequest_Pipe `protobuf:"bytes,4,opt,name=pipe,proto3,oneof"` +} + +func (*DebugServerRequest_Start_) isDebugServerRequest_Control() {} + +func (*DebugServerRequest_Stop_) isDebugServerRequest_Control() {} + +func (*DebugServerRequest_Status_) isDebugServerRequest_Control() {} + +func (*DebugServerRequest_Pipe_) isDebugServerRequest_Control() {} + +type DebugServerResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Control: + // + // *DebugServerResponse_Status_ + // *DebugServerResponse_Pipe_ + Control isDebugServerResponse_Control `protobuf_oneof:"control"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebugServerResponse) Reset() { + *x = DebugServerResponse{} + mi := &file_idb_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebugServerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebugServerResponse) ProtoMessage() {} + +func (x *DebugServerResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[61] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebugServerResponse.ProtoReflect.Descriptor instead. +func (*DebugServerResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{61} +} + +func (x *DebugServerResponse) GetControl() isDebugServerResponse_Control { + if x != nil { + return x.Control + } + return nil +} + +func (x *DebugServerResponse) GetStatus() *DebugServerResponse_Status { + if x != nil { + if x, ok := x.Control.(*DebugServerResponse_Status_); ok { + return x.Status + } + } + return nil +} + +func (x *DebugServerResponse) GetPipe() *DebugServerResponse_Pipe { + if x != nil { + if x, ok := x.Control.(*DebugServerResponse_Pipe_); ok { + return x.Pipe + } + } + return nil +} + +type isDebugServerResponse_Control interface { + isDebugServerResponse_Control() +} + +type DebugServerResponse_Status_ struct { + Status *DebugServerResponse_Status `protobuf:"bytes,1,opt,name=status,proto3,oneof"` +} + +type DebugServerResponse_Pipe_ struct { + Pipe *DebugServerResponse_Pipe `protobuf:"bytes,2,opt,name=pipe,proto3,oneof"` +} + +func (*DebugServerResponse_Status_) isDebugServerResponse_Control() {} + +func (*DebugServerResponse_Pipe_) isDebugServerResponse_Control() {} + +type CrashShowRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CrashShowRequest) Reset() { + *x = CrashShowRequest{} + mi := &file_idb_proto_msgTypes[62] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CrashShowRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CrashShowRequest) ProtoMessage() {} + +func (x *CrashShowRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[62] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CrashShowRequest.ProtoReflect.Descriptor instead. +func (*CrashShowRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{62} +} + +func (x *CrashShowRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type CrashLogResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + List []*CrashLogInfo `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CrashLogResponse) Reset() { + *x = CrashLogResponse{} + mi := &file_idb_proto_msgTypes[63] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CrashLogResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CrashLogResponse) ProtoMessage() {} + +func (x *CrashLogResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[63] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CrashLogResponse.ProtoReflect.Descriptor instead. +func (*CrashLogResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{63} +} + +func (x *CrashLogResponse) GetList() []*CrashLogInfo { + if x != nil { + return x.List + } + return nil +} + +type CrashLogInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + ProcessName string `protobuf:"bytes,3,opt,name=process_name,json=processName,proto3" json:"process_name,omitempty"` + ParentProcessName string `protobuf:"bytes,4,opt,name=parent_process_name,json=parentProcessName,proto3" json:"parent_process_name,omitempty"` + ProcessIdentifier uint64 `protobuf:"varint,5,opt,name=process_identifier,json=processIdentifier,proto3" json:"process_identifier,omitempty"` + ParentProcessIdentifier uint64 `protobuf:"varint,6,opt,name=parent_process_identifier,json=parentProcessIdentifier,proto3" json:"parent_process_identifier,omitempty"` + Timestamp uint64 `protobuf:"varint,7,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CrashLogInfo) Reset() { + *x = CrashLogInfo{} + mi := &file_idb_proto_msgTypes[64] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CrashLogInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CrashLogInfo) ProtoMessage() {} + +func (x *CrashLogInfo) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[64] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CrashLogInfo.ProtoReflect.Descriptor instead. +func (*CrashLogInfo) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{64} +} + +func (x *CrashLogInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CrashLogInfo) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +func (x *CrashLogInfo) GetProcessName() string { + if x != nil { + return x.ProcessName + } + return "" +} + +func (x *CrashLogInfo) GetParentProcessName() string { + if x != nil { + return x.ParentProcessName + } + return "" +} + +func (x *CrashLogInfo) GetProcessIdentifier() uint64 { + if x != nil { + return x.ProcessIdentifier + } + return 0 +} + +func (x *CrashLogInfo) GetParentProcessIdentifier() uint64 { + if x != nil { + return x.ParentProcessIdentifier + } + return 0 +} + +func (x *CrashLogInfo) GetTimestamp() uint64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +type CrashShowResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Info *CrashLogInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"` + Contents string `protobuf:"bytes,2,opt,name=contents,proto3" json:"contents,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CrashShowResponse) Reset() { + *x = CrashShowResponse{} + mi := &file_idb_proto_msgTypes[65] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CrashShowResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CrashShowResponse) ProtoMessage() {} + +func (x *CrashShowResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[65] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CrashShowResponse.ProtoReflect.Descriptor instead. +func (*CrashShowResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{65} +} + +func (x *CrashShowResponse) GetInfo() *CrashLogInfo { + if x != nil { + return x.Info + } + return nil +} + +func (x *CrashShowResponse) GetContents() string { + if x != nil { + return x.Contents + } + return "" +} + +type CrashLogQuery struct { + state protoimpl.MessageState `protogen:"open.v1"` + Since uint64 `protobuf:"varint,1,opt,name=since,proto3" json:"since,omitempty"` + Before uint64 `protobuf:"varint,2,opt,name=before,proto3" json:"before,omitempty"` + BundleId string `protobuf:"bytes,3,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CrashLogQuery) Reset() { + *x = CrashLogQuery{} + mi := &file_idb_proto_msgTypes[66] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CrashLogQuery) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CrashLogQuery) ProtoMessage() {} + +func (x *CrashLogQuery) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[66] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CrashLogQuery.ProtoReflect.Descriptor instead. +func (*CrashLogQuery) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{66} +} + +func (x *CrashLogQuery) GetSince() uint64 { + if x != nil { + return x.Since + } + return 0 +} + +func (x *CrashLogQuery) GetBefore() uint64 { + if x != nil { + return x.Before + } + return 0 +} + +func (x *CrashLogQuery) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +func (x *CrashLogQuery) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type XctestListBundlesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestListBundlesRequest) Reset() { + *x = XctestListBundlesRequest{} + mi := &file_idb_proto_msgTypes[67] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestListBundlesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestListBundlesRequest) ProtoMessage() {} + +func (x *XctestListBundlesRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[67] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestListBundlesRequest.ProtoReflect.Descriptor instead. +func (*XctestListBundlesRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{67} +} + +type XctestListBundlesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Bundles []*XctestListBundlesResponse_Bundles `protobuf:"bytes,1,rep,name=bundles,proto3" json:"bundles,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestListBundlesResponse) Reset() { + *x = XctestListBundlesResponse{} + mi := &file_idb_proto_msgTypes[68] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestListBundlesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestListBundlesResponse) ProtoMessage() {} + +func (x *XctestListBundlesResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[68] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestListBundlesResponse.ProtoReflect.Descriptor instead. +func (*XctestListBundlesResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{68} +} + +func (x *XctestListBundlesResponse) GetBundles() []*XctestListBundlesResponse_Bundles { + if x != nil { + return x.Bundles + } + return nil +} + +type XctestListTestsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + BundleName string `protobuf:"bytes,1,opt,name=bundle_name,json=bundleName,proto3" json:"bundle_name,omitempty"` + AppPath string `protobuf:"bytes,2,opt,name=app_path,json=appPath,proto3" json:"app_path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestListTestsRequest) Reset() { + *x = XctestListTestsRequest{} + mi := &file_idb_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestListTestsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestListTestsRequest) ProtoMessage() {} + +func (x *XctestListTestsRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[69] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestListTestsRequest.ProtoReflect.Descriptor instead. +func (*XctestListTestsRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{69} +} + +func (x *XctestListTestsRequest) GetBundleName() string { + if x != nil { + return x.BundleName + } + return "" +} + +func (x *XctestListTestsRequest) GetAppPath() string { + if x != nil { + return x.AppPath + } + return "" +} + +type XctestListTestsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestListTestsResponse) Reset() { + *x = XctestListTestsResponse{} + mi := &file_idb_proto_msgTypes[70] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestListTestsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestListTestsResponse) ProtoMessage() {} + +func (x *XctestListTestsResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[70] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestListTestsResponse.ProtoReflect.Descriptor instead. +func (*XctestListTestsResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{70} +} + +func (x *XctestListTestsResponse) GetNames() []string { + if x != nil { + return x.Names + } + return nil +} + +type XctestRunRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mode *XctestRunRequest_Mode `protobuf:"bytes,1,opt,name=mode,proto3" json:"mode,omitempty"` + TestBundleId string `protobuf:"bytes,2,opt,name=test_bundle_id,json=testBundleId,proto3" json:"test_bundle_id,omitempty"` + TestsToRun []string `protobuf:"bytes,3,rep,name=tests_to_run,json=testsToRun,proto3" json:"tests_to_run,omitempty"` + TestsToSkip []string `protobuf:"bytes,4,rep,name=tests_to_skip,json=testsToSkip,proto3" json:"tests_to_skip,omitempty"` + Arguments []string `protobuf:"bytes,5,rep,name=arguments,proto3" json:"arguments,omitempty"` + Environment map[string]string `protobuf:"bytes,6,rep,name=environment,proto3" json:"environment,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Timeout uint64 `protobuf:"varint,7,opt,name=timeout,proto3" json:"timeout,omitempty"` + ReportActivities bool `protobuf:"varint,8,opt,name=report_activities,json=reportActivities,proto3" json:"report_activities,omitempty"` + CollectCoverage bool `protobuf:"varint,9,opt,name=collect_coverage,json=collectCoverage,proto3" json:"collect_coverage,omitempty"` + ReportAttachments bool `protobuf:"varint,10,opt,name=report_attachments,json=reportAttachments,proto3" json:"report_attachments,omitempty"` + CollectLogs bool `protobuf:"varint,11,opt,name=collect_logs,json=collectLogs,proto3" json:"collect_logs,omitempty"` + WaitForDebugger bool `protobuf:"varint,12,opt,name=wait_for_debugger,json=waitForDebugger,proto3" json:"wait_for_debugger,omitempty"` + CodeCoverage *XctestRunRequest_CodeCoverage `protobuf:"bytes,13,opt,name=code_coverage,json=codeCoverage,proto3" json:"code_coverage,omitempty"` + CollectResultBundle bool `protobuf:"varint,14,opt,name=collect_result_bundle,json=collectResultBundle,proto3" json:"collect_result_bundle,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestRunRequest) Reset() { + *x = XctestRunRequest{} + mi := &file_idb_proto_msgTypes[71] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestRunRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestRunRequest) ProtoMessage() {} + +func (x *XctestRunRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[71] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestRunRequest.ProtoReflect.Descriptor instead. +func (*XctestRunRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{71} +} + +func (x *XctestRunRequest) GetMode() *XctestRunRequest_Mode { + if x != nil { + return x.Mode + } + return nil +} + +func (x *XctestRunRequest) GetTestBundleId() string { + if x != nil { + return x.TestBundleId + } + return "" +} + +func (x *XctestRunRequest) GetTestsToRun() []string { + if x != nil { + return x.TestsToRun + } + return nil +} + +func (x *XctestRunRequest) GetTestsToSkip() []string { + if x != nil { + return x.TestsToSkip + } + return nil +} + +func (x *XctestRunRequest) GetArguments() []string { + if x != nil { + return x.Arguments + } + return nil +} + +func (x *XctestRunRequest) GetEnvironment() map[string]string { + if x != nil { + return x.Environment + } + return nil +} + +func (x *XctestRunRequest) GetTimeout() uint64 { + if x != nil { + return x.Timeout + } + return 0 +} + +func (x *XctestRunRequest) GetReportActivities() bool { + if x != nil { + return x.ReportActivities + } + return false +} + +func (x *XctestRunRequest) GetCollectCoverage() bool { + if x != nil { + return x.CollectCoverage + } + return false +} + +func (x *XctestRunRequest) GetReportAttachments() bool { + if x != nil { + return x.ReportAttachments + } + return false +} + +func (x *XctestRunRequest) GetCollectLogs() bool { + if x != nil { + return x.CollectLogs + } + return false +} + +func (x *XctestRunRequest) GetWaitForDebugger() bool { + if x != nil { + return x.WaitForDebugger + } + return false +} + +func (x *XctestRunRequest) GetCodeCoverage() *XctestRunRequest_CodeCoverage { + if x != nil { + return x.CodeCoverage + } + return nil +} + +func (x *XctestRunRequest) GetCollectResultBundle() bool { + if x != nil { + return x.CollectResultBundle + } + return false +} + +type XctestRunResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status XctestRunResponse_Status `protobuf:"varint,1,opt,name=status,proto3,enum=idb.XctestRunResponse_Status" json:"status,omitempty"` + Results []*XctestRunResponse_TestRunInfo `protobuf:"bytes,2,rep,name=results,proto3" json:"results,omitempty"` + LogOutput []string `protobuf:"bytes,3,rep,name=log_output,json=logOutput,proto3" json:"log_output,omitempty"` + ResultBundle *Payload `protobuf:"bytes,4,opt,name=result_bundle,json=resultBundle,proto3" json:"result_bundle,omitempty"` + CoverageJson string `protobuf:"bytes,5,opt,name=coverage_json,json=coverageJson,proto3" json:"coverage_json,omitempty"` + LogDirectory *Payload `protobuf:"bytes,6,opt,name=log_directory,json=logDirectory,proto3" json:"log_directory,omitempty"` + Debugger *DebuggerInfo `protobuf:"bytes,7,opt,name=debugger,proto3" json:"debugger,omitempty"` + CodeCoverageData *Payload `protobuf:"bytes,8,opt,name=code_coverage_data,json=codeCoverageData,proto3" json:"code_coverage_data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestRunResponse) Reset() { + *x = XctestRunResponse{} + mi := &file_idb_proto_msgTypes[72] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestRunResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestRunResponse) ProtoMessage() {} + +func (x *XctestRunResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[72] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestRunResponse.ProtoReflect.Descriptor instead. +func (*XctestRunResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{72} +} + +func (x *XctestRunResponse) GetStatus() XctestRunResponse_Status { + if x != nil { + return x.Status + } + return XctestRunResponse_RUNNING +} + +func (x *XctestRunResponse) GetResults() []*XctestRunResponse_TestRunInfo { + if x != nil { + return x.Results + } + return nil +} + +func (x *XctestRunResponse) GetLogOutput() []string { + if x != nil { + return x.LogOutput + } + return nil +} + +func (x *XctestRunResponse) GetResultBundle() *Payload { + if x != nil { + return x.ResultBundle + } + return nil +} + +func (x *XctestRunResponse) GetCoverageJson() string { + if x != nil { + return x.CoverageJson + } + return "" +} + +func (x *XctestRunResponse) GetLogDirectory() *Payload { + if x != nil { + return x.LogDirectory + } + return nil +} + +func (x *XctestRunResponse) GetDebugger() *DebuggerInfo { + if x != nil { + return x.Debugger + } + return nil +} + +func (x *XctestRunResponse) GetCodeCoverageData() *Payload { + if x != nil { + return x.CodeCoverageData + } + return nil +} + +type FileContainer struct { + state protoimpl.MessageState `protogen:"open.v1"` + Kind FileContainer_Kind `protobuf:"varint,1,opt,name=kind,proto3,enum=idb.FileContainer_Kind" json:"kind,omitempty"` + BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FileContainer) Reset() { + *x = FileContainer{} + mi := &file_idb_proto_msgTypes[73] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FileContainer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FileContainer) ProtoMessage() {} + +func (x *FileContainer) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[73] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FileContainer.ProtoReflect.Descriptor instead. +func (*FileContainer) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{73} +} + +func (x *FileContainer) GetKind() FileContainer_Kind { + if x != nil { + return x.Kind + } + return FileContainer_NONE +} + +func (x *FileContainer) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +type FileInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FileInfo) Reset() { + *x = FileInfo{} + mi := &file_idb_proto_msgTypes[74] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FileInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FileInfo) ProtoMessage() {} + +func (x *FileInfo) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[74] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FileInfo.ProtoReflect.Descriptor instead. +func (*FileInfo) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{74} +} + +func (x *FileInfo) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +type FileListing struct { + state protoimpl.MessageState `protogen:"open.v1"` + Parent *FileInfo `protobuf:"bytes,1,opt,name=parent,proto3" json:"parent,omitempty"` + Files []*FileInfo `protobuf:"bytes,2,rep,name=files,proto3" json:"files,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FileListing) Reset() { + *x = FileListing{} + mi := &file_idb_proto_msgTypes[75] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FileListing) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FileListing) ProtoMessage() {} + +func (x *FileListing) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[75] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FileListing.ProtoReflect.Descriptor instead. +func (*FileListing) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{75} +} + +func (x *FileListing) GetParent() *FileInfo { + if x != nil { + return x.Parent + } + return nil +} + +func (x *FileListing) GetFiles() []*FileInfo { + if x != nil { + return x.Files + } + return nil +} + +type LsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Files []*FileInfo `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"` + Listings []*FileListing `protobuf:"bytes,2,rep,name=listings,proto3" json:"listings,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LsResponse) Reset() { + *x = LsResponse{} + mi := &file_idb_proto_msgTypes[76] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LsResponse) ProtoMessage() {} + +func (x *LsResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[76] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LsResponse.ProtoReflect.Descriptor instead. +func (*LsResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{76} +} + +func (x *LsResponse) GetFiles() []*FileInfo { + if x != nil { + return x.Files + } + return nil +} + +func (x *LsResponse) GetListings() []*FileListing { + if x != nil { + return x.Listings + } + return nil +} + +type LsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Container *FileContainer `protobuf:"bytes,3,opt,name=container,proto3" json:"container,omitempty"` + Paths []string `protobuf:"bytes,4,rep,name=paths,proto3" json:"paths,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LsRequest) Reset() { + *x = LsRequest{} + mi := &file_idb_proto_msgTypes[77] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LsRequest) ProtoMessage() {} + +func (x *LsRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[77] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LsRequest.ProtoReflect.Descriptor instead. +func (*LsRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{77} +} + +func (x *LsRequest) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *LsRequest) GetContainer() *FileContainer { + if x != nil { + return x.Container + } + return nil +} + +func (x *LsRequest) GetPaths() []string { + if x != nil { + return x.Paths + } + return nil +} + +type MkdirRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Container *FileContainer `protobuf:"bytes,3,opt,name=container,proto3" json:"container,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MkdirRequest) Reset() { + *x = MkdirRequest{} + mi := &file_idb_proto_msgTypes[78] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MkdirRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MkdirRequest) ProtoMessage() {} + +func (x *MkdirRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[78] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MkdirRequest.ProtoReflect.Descriptor instead. +func (*MkdirRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{78} +} + +func (x *MkdirRequest) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *MkdirRequest) GetContainer() *FileContainer { + if x != nil { + return x.Container + } + return nil +} + +type MkdirResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MkdirResponse) Reset() { + *x = MkdirResponse{} + mi := &file_idb_proto_msgTypes[79] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MkdirResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MkdirResponse) ProtoMessage() {} + +func (x *MkdirResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[79] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MkdirResponse.ProtoReflect.Descriptor instead. +func (*MkdirResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{79} +} + +type MvRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SrcPaths []string `protobuf:"bytes,2,rep,name=src_paths,json=srcPaths,proto3" json:"src_paths,omitempty"` + DstPath string `protobuf:"bytes,3,opt,name=dst_path,json=dstPath,proto3" json:"dst_path,omitempty"` + Container *FileContainer `protobuf:"bytes,4,opt,name=container,proto3" json:"container,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MvRequest) Reset() { + *x = MvRequest{} + mi := &file_idb_proto_msgTypes[80] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MvRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MvRequest) ProtoMessage() {} + +func (x *MvRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[80] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MvRequest.ProtoReflect.Descriptor instead. +func (*MvRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{80} +} + +func (x *MvRequest) GetSrcPaths() []string { + if x != nil { + return x.SrcPaths + } + return nil +} + +func (x *MvRequest) GetDstPath() string { + if x != nil { + return x.DstPath + } + return "" +} + +func (x *MvRequest) GetContainer() *FileContainer { + if x != nil { + return x.Container + } + return nil +} + +type MvResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MvResponse) Reset() { + *x = MvResponse{} + mi := &file_idb_proto_msgTypes[81] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MvResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MvResponse) ProtoMessage() {} + +func (x *MvResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[81] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MvResponse.ProtoReflect.Descriptor instead. +func (*MvResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{81} +} + +type RmRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Paths []string `protobuf:"bytes,2,rep,name=paths,proto3" json:"paths,omitempty"` + Container *FileContainer `protobuf:"bytes,3,opt,name=container,proto3" json:"container,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RmRequest) Reset() { + *x = RmRequest{} + mi := &file_idb_proto_msgTypes[82] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RmRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RmRequest) ProtoMessage() {} + +func (x *RmRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[82] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RmRequest.ProtoReflect.Descriptor instead. +func (*RmRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{82} +} + +func (x *RmRequest) GetPaths() []string { + if x != nil { + return x.Paths + } + return nil +} + +func (x *RmRequest) GetContainer() *FileContainer { + if x != nil { + return x.Container + } + return nil +} + +type RmResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RmResponse) Reset() { + *x = RmResponse{} + mi := &file_idb_proto_msgTypes[83] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RmResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RmResponse) ProtoMessage() {} + +func (x *RmResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[83] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RmResponse.ProtoReflect.Descriptor instead. +func (*RmResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{83} +} + +type PushRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Value: + // + // *PushRequest_Payload + // *PushRequest_Inner_ + Value isPushRequest_Value `protobuf_oneof:"value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PushRequest) Reset() { + *x = PushRequest{} + mi := &file_idb_proto_msgTypes[84] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PushRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushRequest) ProtoMessage() {} + +func (x *PushRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[84] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushRequest.ProtoReflect.Descriptor instead. +func (*PushRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{84} +} + +func (x *PushRequest) GetValue() isPushRequest_Value { + if x != nil { + return x.Value + } + return nil +} + +func (x *PushRequest) GetPayload() *Payload { + if x != nil { + if x, ok := x.Value.(*PushRequest_Payload); ok { + return x.Payload + } + } + return nil +} + +func (x *PushRequest) GetInner() *PushRequest_Inner { + if x != nil { + if x, ok := x.Value.(*PushRequest_Inner_); ok { + return x.Inner + } + } + return nil +} + +type isPushRequest_Value interface { + isPushRequest_Value() +} + +type PushRequest_Payload struct { + Payload *Payload `protobuf:"bytes,1,opt,name=payload,proto3,oneof"` +} + +type PushRequest_Inner_ struct { + Inner *PushRequest_Inner `protobuf:"bytes,2,opt,name=inner,proto3,oneof"` +} + +func (*PushRequest_Payload) isPushRequest_Value() {} + +func (*PushRequest_Inner_) isPushRequest_Value() {} + +type PushResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PushResponse) Reset() { + *x = PushResponse{} + mi := &file_idb_proto_msgTypes[85] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PushResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushResponse) ProtoMessage() {} + +func (x *PushResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[85] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushResponse.ProtoReflect.Descriptor instead. +func (*PushResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{85} +} + +type PullRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SrcPath string `protobuf:"bytes,2,opt,name=src_path,json=srcPath,proto3" json:"src_path,omitempty"` + DstPath string `protobuf:"bytes,3,opt,name=dst_path,json=dstPath,proto3" json:"dst_path,omitempty"` + Container *FileContainer `protobuf:"bytes,4,opt,name=container,proto3" json:"container,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PullRequest) Reset() { + *x = PullRequest{} + mi := &file_idb_proto_msgTypes[86] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PullRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PullRequest) ProtoMessage() {} + +func (x *PullRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[86] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PullRequest.ProtoReflect.Descriptor instead. +func (*PullRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{86} +} + +func (x *PullRequest) GetSrcPath() string { + if x != nil { + return x.SrcPath + } + return "" +} + +func (x *PullRequest) GetDstPath() string { + if x != nil { + return x.DstPath + } + return "" +} + +func (x *PullRequest) GetContainer() *FileContainer { + if x != nil { + return x.Container + } + return nil +} + +type PullResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Payload *Payload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PullResponse) Reset() { + *x = PullResponse{} + mi := &file_idb_proto_msgTypes[87] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PullResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PullResponse) ProtoMessage() {} + +func (x *PullResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[87] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PullResponse.ProtoReflect.Descriptor instead. +func (*PullResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{87} +} + +func (x *PullResponse) GetPayload() *Payload { + if x != nil { + return x.Payload + } + return nil +} + +type TailRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Control: + // + // *TailRequest_Start_ + // *TailRequest_Stop_ + Control isTailRequest_Control `protobuf_oneof:"control"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailRequest) Reset() { + *x = TailRequest{} + mi := &file_idb_proto_msgTypes[88] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailRequest) ProtoMessage() {} + +func (x *TailRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[88] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailRequest.ProtoReflect.Descriptor instead. +func (*TailRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{88} +} + +func (x *TailRequest) GetControl() isTailRequest_Control { + if x != nil { + return x.Control + } + return nil +} + +func (x *TailRequest) GetStart() *TailRequest_Start { + if x != nil { + if x, ok := x.Control.(*TailRequest_Start_); ok { + return x.Start + } + } + return nil +} + +func (x *TailRequest) GetStop() *TailRequest_Stop { + if x != nil { + if x, ok := x.Control.(*TailRequest_Stop_); ok { + return x.Stop + } + } + return nil +} + +type isTailRequest_Control interface { + isTailRequest_Control() +} + +type TailRequest_Start_ struct { + Start *TailRequest_Start `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type TailRequest_Stop_ struct { + Stop *TailRequest_Stop `protobuf:"bytes,2,opt,name=stop,proto3,oneof"` +} + +func (*TailRequest_Start_) isTailRequest_Control() {} + +func (*TailRequest_Stop_) isTailRequest_Control() {} + +type TailResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailResponse) Reset() { + *x = TailResponse{} + mi := &file_idb_proto_msgTypes[89] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailResponse) ProtoMessage() {} + +func (x *TailResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[89] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailResponse.ProtoReflect.Descriptor instead. +func (*TailResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{89} +} + +func (x *TailResponse) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type DebuggerInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Pid uint64 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"` + Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` + Port uint64 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebuggerInfo) Reset() { + *x = DebuggerInfo{} + mi := &file_idb_proto_msgTypes[90] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebuggerInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebuggerInfo) ProtoMessage() {} + +func (x *DebuggerInfo) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[90] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebuggerInfo.ProtoReflect.Descriptor instead. +func (*DebuggerInfo) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{90} +} + +func (x *DebuggerInfo) GetPid() uint64 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *DebuggerInfo) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *DebuggerInfo) GetPort() uint64 { + if x != nil { + return x.Port + } + return 0 +} + +type SendNotificationRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + BundleId string `protobuf:"bytes,1,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + JsonPayload string `protobuf:"bytes,2,opt,name=json_payload,json=jsonPayload,proto3" json:"json_payload,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendNotificationRequest) Reset() { + *x = SendNotificationRequest{} + mi := &file_idb_proto_msgTypes[91] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendNotificationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendNotificationRequest) ProtoMessage() {} + +func (x *SendNotificationRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[91] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendNotificationRequest.ProtoReflect.Descriptor instead. +func (*SendNotificationRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{91} +} + +func (x *SendNotificationRequest) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +func (x *SendNotificationRequest) GetJsonPayload() string { + if x != nil { + return x.JsonPayload + } + return "" +} + +type SendNotificationResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendNotificationResponse) Reset() { + *x = SendNotificationResponse{} + mi := &file_idb_proto_msgTypes[92] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendNotificationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendNotificationResponse) ProtoMessage() {} + +func (x *SendNotificationResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[92] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendNotificationResponse.ProtoReflect.Descriptor instead. +func (*SendNotificationResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{92} +} + +type DapRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Control: + // + // *DapRequest_Start_ + // *DapRequest_Pipe_ + // *DapRequest_Stop_ + Control isDapRequest_Control `protobuf_oneof:"control"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DapRequest) Reset() { + *x = DapRequest{} + mi := &file_idb_proto_msgTypes[93] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DapRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DapRequest) ProtoMessage() {} + +func (x *DapRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[93] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DapRequest.ProtoReflect.Descriptor instead. +func (*DapRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{93} +} + +func (x *DapRequest) GetControl() isDapRequest_Control { + if x != nil { + return x.Control + } + return nil +} + +func (x *DapRequest) GetStart() *DapRequest_Start { + if x != nil { + if x, ok := x.Control.(*DapRequest_Start_); ok { + return x.Start + } + } + return nil +} + +func (x *DapRequest) GetPipe() *DapRequest_Pipe { + if x != nil { + if x, ok := x.Control.(*DapRequest_Pipe_); ok { + return x.Pipe + } + } + return nil +} + +func (x *DapRequest) GetStop() *DapRequest_Stop { + if x != nil { + if x, ok := x.Control.(*DapRequest_Stop_); ok { + return x.Stop + } + } + return nil +} + +type isDapRequest_Control interface { + isDapRequest_Control() +} + +type DapRequest_Start_ struct { + Start *DapRequest_Start `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type DapRequest_Pipe_ struct { + Pipe *DapRequest_Pipe `protobuf:"bytes,2,opt,name=pipe,proto3,oneof"` +} + +type DapRequest_Stop_ struct { + Stop *DapRequest_Stop `protobuf:"bytes,3,opt,name=stop,proto3,oneof"` +} + +func (*DapRequest_Start_) isDapRequest_Control() {} + +func (*DapRequest_Pipe_) isDapRequest_Control() {} + +func (*DapRequest_Stop_) isDapRequest_Control() {} + +type DapResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Event: + // + // *DapResponse_Started + // *DapResponse_Stdout + // *DapResponse_Stopped + Event isDapResponse_Event `protobuf_oneof:"event"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DapResponse) Reset() { + *x = DapResponse{} + mi := &file_idb_proto_msgTypes[94] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DapResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DapResponse) ProtoMessage() {} + +func (x *DapResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[94] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DapResponse.ProtoReflect.Descriptor instead. +func (*DapResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{94} +} + +func (x *DapResponse) GetEvent() isDapResponse_Event { + if x != nil { + return x.Event + } + return nil +} + +func (x *DapResponse) GetStarted() *DapResponse_Event { + if x != nil { + if x, ok := x.Event.(*DapResponse_Started); ok { + return x.Started + } + } + return nil +} + +func (x *DapResponse) GetStdout() *DapResponse_Pipe { + if x != nil { + if x, ok := x.Event.(*DapResponse_Stdout); ok { + return x.Stdout + } + } + return nil +} + +func (x *DapResponse) GetStopped() *DapResponse_Event { + if x != nil { + if x, ok := x.Event.(*DapResponse_Stopped); ok { + return x.Stopped + } + } + return nil +} + +type isDapResponse_Event interface { + isDapResponse_Event() +} + +type DapResponse_Started struct { + Started *DapResponse_Event `protobuf:"bytes,1,opt,name=started,proto3,oneof"` +} + +type DapResponse_Stdout struct { + Stdout *DapResponse_Pipe `protobuf:"bytes,2,opt,name=stdout,proto3,oneof"` +} + +type DapResponse_Stopped struct { + Stopped *DapResponse_Event `protobuf:"bytes,3,opt,name=stopped,proto3,oneof"` +} + +func (*DapResponse_Started) isDapResponse_Event() {} + +func (*DapResponse_Stdout) isDapResponse_Event() {} + +func (*DapResponse_Stopped) isDapResponse_Event() {} + +type SimulateMemoryWarningRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SimulateMemoryWarningRequest) Reset() { + *x = SimulateMemoryWarningRequest{} + mi := &file_idb_proto_msgTypes[95] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SimulateMemoryWarningRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SimulateMemoryWarningRequest) ProtoMessage() {} + +func (x *SimulateMemoryWarningRequest) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[95] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SimulateMemoryWarningRequest.ProtoReflect.Descriptor instead. +func (*SimulateMemoryWarningRequest) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{95} +} + +type SimulateMemoryWarningResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SimulateMemoryWarningResponse) Reset() { + *x = SimulateMemoryWarningResponse{} + mi := &file_idb_proto_msgTypes[96] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SimulateMemoryWarningResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SimulateMemoryWarningResponse) ProtoMessage() {} + +func (x *SimulateMemoryWarningResponse) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[96] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SimulateMemoryWarningResponse.ProtoReflect.Descriptor instead. +func (*SimulateMemoryWarningResponse) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{96} +} + +type SettingRequest_HardwareKeyboard struct { + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SettingRequest_HardwareKeyboard) Reset() { + *x = SettingRequest_HardwareKeyboard{} + mi := &file_idb_proto_msgTypes[97] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SettingRequest_HardwareKeyboard) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SettingRequest_HardwareKeyboard) ProtoMessage() {} + +func (x *SettingRequest_HardwareKeyboard) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[97] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SettingRequest_HardwareKeyboard.ProtoReflect.Descriptor instead. +func (*SettingRequest_HardwareKeyboard) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *SettingRequest_HardwareKeyboard) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type SettingRequest_StringSetting struct { + state protoimpl.MessageState `protogen:"open.v1"` + Setting Setting `protobuf:"varint,1,opt,name=setting,proto3,enum=idb.Setting" json:"setting,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` // iff setting == ANY + Domain string `protobuf:"bytes,4,opt,name=domain,proto3" json:"domain,omitempty"` // assumed "Apple Global Domain" if empty + // Currently supported value_types and corresponding values are: + // + // string + // data + // int[eger] + // float + // bool[ean] (true | false | yes | no) + // date + // array ... + // array-add ... + // dict ... + // dict-add ... + // + // Check defaults set help for more details. + ValueType string `protobuf:"bytes,5,opt,name=value_type,json=valueType,proto3" json:"value_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SettingRequest_StringSetting) Reset() { + *x = SettingRequest_StringSetting{} + mi := &file_idb_proto_msgTypes[98] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SettingRequest_StringSetting) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SettingRequest_StringSetting) ProtoMessage() {} + +func (x *SettingRequest_StringSetting) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[98] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SettingRequest_StringSetting.ProtoReflect.Descriptor instead. +func (*SettingRequest_StringSetting) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{3, 1} +} + +func (x *SettingRequest_StringSetting) GetSetting() Setting { + if x != nil { + return x.Setting + } + return Setting_LOCALE +} + +func (x *SettingRequest_StringSetting) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +func (x *SettingRequest_StringSetting) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SettingRequest_StringSetting) GetDomain() string { + if x != nil { + return x.Domain + } + return "" +} + +func (x *SettingRequest_StringSetting) GetValueType() string { + if x != nil { + return x.ValueType + } + return "" +} + +type InstallRequest_LinkDsymToBundle struct { + state protoimpl.MessageState `protogen:"open.v1"` + BundleType InstallRequest_LinkDsymToBundle_BundleType `protobuf:"varint,1,opt,name=bundle_type,json=bundleType,proto3,enum=idb.InstallRequest_LinkDsymToBundle_BundleType" json:"bundle_type,omitempty"` + BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstallRequest_LinkDsymToBundle) Reset() { + *x = InstallRequest_LinkDsymToBundle{} + mi := &file_idb_proto_msgTypes[99] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstallRequest_LinkDsymToBundle) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstallRequest_LinkDsymToBundle) ProtoMessage() {} + +func (x *InstallRequest_LinkDsymToBundle) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[99] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InstallRequest_LinkDsymToBundle.ProtoReflect.Descriptor instead. +func (*InstallRequest_LinkDsymToBundle) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{12, 0} +} + +func (x *InstallRequest_LinkDsymToBundle) GetBundleType() InstallRequest_LinkDsymToBundle_BundleType { + if x != nil { + return x.BundleType + } + return InstallRequest_LinkDsymToBundle_APP +} + +func (x *InstallRequest_LinkDsymToBundle) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +type HIDEvent_HIDTouch struct { + state protoimpl.MessageState `protogen:"open.v1"` + Point *Point `protobuf:"bytes,1,opt,name=point,proto3" json:"point,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HIDEvent_HIDTouch) Reset() { + *x = HIDEvent_HIDTouch{} + mi := &file_idb_proto_msgTypes[100] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HIDEvent_HIDTouch) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HIDEvent_HIDTouch) ProtoMessage() {} + +func (x *HIDEvent_HIDTouch) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[100] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HIDEvent_HIDTouch.ProtoReflect.Descriptor instead. +func (*HIDEvent_HIDTouch) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{40, 0} +} + +func (x *HIDEvent_HIDTouch) GetPoint() *Point { + if x != nil { + return x.Point + } + return nil +} + +type HIDEvent_HIDButton struct { + state protoimpl.MessageState `protogen:"open.v1"` + Button HIDEvent_HIDButtonType `protobuf:"varint,1,opt,name=button,proto3,enum=idb.HIDEvent_HIDButtonType" json:"button,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HIDEvent_HIDButton) Reset() { + *x = HIDEvent_HIDButton{} + mi := &file_idb_proto_msgTypes[101] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HIDEvent_HIDButton) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HIDEvent_HIDButton) ProtoMessage() {} + +func (x *HIDEvent_HIDButton) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[101] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HIDEvent_HIDButton.ProtoReflect.Descriptor instead. +func (*HIDEvent_HIDButton) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{40, 1} +} + +func (x *HIDEvent_HIDButton) GetButton() HIDEvent_HIDButtonType { + if x != nil { + return x.Button + } + return HIDEvent_APPLE_PAY +} + +type HIDEvent_HIDKey struct { + state protoimpl.MessageState `protogen:"open.v1"` + Keycode uint64 `protobuf:"varint,1,opt,name=keycode,proto3" json:"keycode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HIDEvent_HIDKey) Reset() { + *x = HIDEvent_HIDKey{} + mi := &file_idb_proto_msgTypes[102] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HIDEvent_HIDKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HIDEvent_HIDKey) ProtoMessage() {} + +func (x *HIDEvent_HIDKey) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[102] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HIDEvent_HIDKey.ProtoReflect.Descriptor instead. +func (*HIDEvent_HIDKey) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{40, 2} +} + +func (x *HIDEvent_HIDKey) GetKeycode() uint64 { + if x != nil { + return x.Keycode + } + return 0 +} + +type HIDEvent_HIDPressAction struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Action: + // + // *HIDEvent_HIDPressAction_Touch + // *HIDEvent_HIDPressAction_Button + // *HIDEvent_HIDPressAction_Key + Action isHIDEvent_HIDPressAction_Action `protobuf_oneof:"action"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HIDEvent_HIDPressAction) Reset() { + *x = HIDEvent_HIDPressAction{} + mi := &file_idb_proto_msgTypes[103] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HIDEvent_HIDPressAction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HIDEvent_HIDPressAction) ProtoMessage() {} + +func (x *HIDEvent_HIDPressAction) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[103] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HIDEvent_HIDPressAction.ProtoReflect.Descriptor instead. +func (*HIDEvent_HIDPressAction) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{40, 3} +} + +func (x *HIDEvent_HIDPressAction) GetAction() isHIDEvent_HIDPressAction_Action { + if x != nil { + return x.Action + } + return nil +} + +func (x *HIDEvent_HIDPressAction) GetTouch() *HIDEvent_HIDTouch { + if x != nil { + if x, ok := x.Action.(*HIDEvent_HIDPressAction_Touch); ok { + return x.Touch + } + } + return nil +} + +func (x *HIDEvent_HIDPressAction) GetButton() *HIDEvent_HIDButton { + if x != nil { + if x, ok := x.Action.(*HIDEvent_HIDPressAction_Button); ok { + return x.Button + } + } + return nil +} + +func (x *HIDEvent_HIDPressAction) GetKey() *HIDEvent_HIDKey { + if x != nil { + if x, ok := x.Action.(*HIDEvent_HIDPressAction_Key); ok { + return x.Key + } + } + return nil +} + +type isHIDEvent_HIDPressAction_Action interface { + isHIDEvent_HIDPressAction_Action() +} + +type HIDEvent_HIDPressAction_Touch struct { + Touch *HIDEvent_HIDTouch `protobuf:"bytes,1,opt,name=touch,proto3,oneof"` +} + +type HIDEvent_HIDPressAction_Button struct { + Button *HIDEvent_HIDButton `protobuf:"bytes,2,opt,name=button,proto3,oneof"` +} + +type HIDEvent_HIDPressAction_Key struct { + Key *HIDEvent_HIDKey `protobuf:"bytes,3,opt,name=key,proto3,oneof"` +} + +func (*HIDEvent_HIDPressAction_Touch) isHIDEvent_HIDPressAction_Action() {} + +func (*HIDEvent_HIDPressAction_Button) isHIDEvent_HIDPressAction_Action() {} + +func (*HIDEvent_HIDPressAction_Key) isHIDEvent_HIDPressAction_Action() {} + +type HIDEvent_HIDPress struct { + state protoimpl.MessageState `protogen:"open.v1"` + Action *HIDEvent_HIDPressAction `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` + Direction HIDEvent_HIDDirection `protobuf:"varint,2,opt,name=direction,proto3,enum=idb.HIDEvent_HIDDirection" json:"direction,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HIDEvent_HIDPress) Reset() { + *x = HIDEvent_HIDPress{} + mi := &file_idb_proto_msgTypes[104] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HIDEvent_HIDPress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HIDEvent_HIDPress) ProtoMessage() {} + +func (x *HIDEvent_HIDPress) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[104] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HIDEvent_HIDPress.ProtoReflect.Descriptor instead. +func (*HIDEvent_HIDPress) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{40, 4} +} + +func (x *HIDEvent_HIDPress) GetAction() *HIDEvent_HIDPressAction { + if x != nil { + return x.Action + } + return nil +} + +func (x *HIDEvent_HIDPress) GetDirection() HIDEvent_HIDDirection { + if x != nil { + return x.Direction + } + return HIDEvent_DOWN +} + +type HIDEvent_HIDSwipe struct { + state protoimpl.MessageState `protogen:"open.v1"` + Start *Point `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"` + End *Point `protobuf:"bytes,2,opt,name=end,proto3" json:"end,omitempty"` + Delta float64 `protobuf:"fixed64,5,opt,name=delta,proto3" json:"delta,omitempty"` + Duration float64 `protobuf:"fixed64,6,opt,name=duration,proto3" json:"duration,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HIDEvent_HIDSwipe) Reset() { + *x = HIDEvent_HIDSwipe{} + mi := &file_idb_proto_msgTypes[105] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HIDEvent_HIDSwipe) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HIDEvent_HIDSwipe) ProtoMessage() {} + +func (x *HIDEvent_HIDSwipe) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[105] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HIDEvent_HIDSwipe.ProtoReflect.Descriptor instead. +func (*HIDEvent_HIDSwipe) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{40, 5} +} + +func (x *HIDEvent_HIDSwipe) GetStart() *Point { + if x != nil { + return x.Start + } + return nil +} + +func (x *HIDEvent_HIDSwipe) GetEnd() *Point { + if x != nil { + return x.End + } + return nil +} + +func (x *HIDEvent_HIDSwipe) GetDelta() float64 { + if x != nil { + return x.Delta + } + return 0 +} + +func (x *HIDEvent_HIDSwipe) GetDuration() float64 { + if x != nil { + return x.Duration + } + return 0 +} + +type HIDEvent_HIDDelay struct { + state protoimpl.MessageState `protogen:"open.v1"` + Duration float64 `protobuf:"fixed64,1,opt,name=duration,proto3" json:"duration,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HIDEvent_HIDDelay) Reset() { + *x = HIDEvent_HIDDelay{} + mi := &file_idb_proto_msgTypes[106] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HIDEvent_HIDDelay) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HIDEvent_HIDDelay) ProtoMessage() {} + +func (x *HIDEvent_HIDDelay) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[106] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HIDEvent_HIDDelay.ProtoReflect.Descriptor instead. +func (*HIDEvent_HIDDelay) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{40, 6} +} + +func (x *HIDEvent_HIDDelay) GetDuration() float64 { + if x != nil { + return x.Duration + } + return 0 +} + +type RecordRequest_Start struct { + state protoimpl.MessageState `protogen:"open.v1"` + FilePath string `protobuf:"bytes,1,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RecordRequest_Start) Reset() { + *x = RecordRequest_Start{} + mi := &file_idb_proto_msgTypes[108] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RecordRequest_Start) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecordRequest_Start) ProtoMessage() {} + +func (x *RecordRequest_Start) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[108] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RecordRequest_Start.ProtoReflect.Descriptor instead. +func (*RecordRequest_Start) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{48, 0} +} + +func (x *RecordRequest_Start) GetFilePath() string { + if x != nil { + return x.FilePath + } + return "" +} + +type RecordRequest_Stop struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RecordRequest_Stop) Reset() { + *x = RecordRequest_Stop{} + mi := &file_idb_proto_msgTypes[109] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RecordRequest_Stop) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecordRequest_Stop) ProtoMessage() {} + +func (x *RecordRequest_Stop) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[109] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RecordRequest_Stop.ProtoReflect.Descriptor instead. +func (*RecordRequest_Stop) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{48, 1} +} + +type VideoStreamRequest_Start struct { + state protoimpl.MessageState `protogen:"open.v1"` + FilePath string `protobuf:"bytes,1,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + Fps uint64 `protobuf:"varint,2,opt,name=fps,proto3" json:"fps,omitempty"` + Format VideoStreamRequest_Format `protobuf:"varint,3,opt,name=format,proto3,enum=idb.VideoStreamRequest_Format" json:"format,omitempty"` + CompressionQuality float64 `protobuf:"fixed64,4,opt,name=compression_quality,json=compressionQuality,proto3" json:"compression_quality,omitempty"` + ScaleFactor float64 `protobuf:"fixed64,5,opt,name=scale_factor,json=scaleFactor,proto3" json:"scale_factor,omitempty"` + AvgBitrate float64 `protobuf:"fixed64,6,opt,name=avg_bitrate,json=avgBitrate,proto3" json:"avg_bitrate,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VideoStreamRequest_Start) Reset() { + *x = VideoStreamRequest_Start{} + mi := &file_idb_proto_msgTypes[110] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VideoStreamRequest_Start) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VideoStreamRequest_Start) ProtoMessage() {} + +func (x *VideoStreamRequest_Start) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[110] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VideoStreamRequest_Start.ProtoReflect.Descriptor instead. +func (*VideoStreamRequest_Start) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{50, 0} +} + +func (x *VideoStreamRequest_Start) GetFilePath() string { + if x != nil { + return x.FilePath + } + return "" +} + +func (x *VideoStreamRequest_Start) GetFps() uint64 { + if x != nil { + return x.Fps + } + return 0 +} + +func (x *VideoStreamRequest_Start) GetFormat() VideoStreamRequest_Format { + if x != nil { + return x.Format + } + return VideoStreamRequest_H264 +} + +func (x *VideoStreamRequest_Start) GetCompressionQuality() float64 { + if x != nil { + return x.CompressionQuality + } + return 0 +} + +func (x *VideoStreamRequest_Start) GetScaleFactor() float64 { + if x != nil { + return x.ScaleFactor + } + return 0 +} + +func (x *VideoStreamRequest_Start) GetAvgBitrate() float64 { + if x != nil { + return x.AvgBitrate + } + return 0 +} + +type VideoStreamRequest_Stop struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VideoStreamRequest_Stop) Reset() { + *x = VideoStreamRequest_Stop{} + mi := &file_idb_proto_msgTypes[111] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VideoStreamRequest_Stop) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VideoStreamRequest_Stop) ProtoMessage() {} + +func (x *VideoStreamRequest_Stop) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[111] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VideoStreamRequest_Stop.ProtoReflect.Descriptor instead. +func (*VideoStreamRequest_Stop) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{50, 1} +} + +type LaunchRequest_Start struct { + state protoimpl.MessageState `protogen:"open.v1"` + BundleId string `protobuf:"bytes,1,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + Env map[string]string `protobuf:"bytes,2,rep,name=env,proto3" json:"env,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + AppArgs []string `protobuf:"bytes,3,rep,name=app_args,json=appArgs,proto3" json:"app_args,omitempty"` + ForegroundIfRunning bool `protobuf:"varint,4,opt,name=foreground_if_running,json=foregroundIfRunning,proto3" json:"foreground_if_running,omitempty"` + WaitFor bool `protobuf:"varint,5,opt,name=wait_for,json=waitFor,proto3" json:"wait_for,omitempty"` + WaitForDebugger bool `protobuf:"varint,6,opt,name=wait_for_debugger,json=waitForDebugger,proto3" json:"wait_for_debugger,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LaunchRequest_Start) Reset() { + *x = LaunchRequest_Start{} + mi := &file_idb_proto_msgTypes[112] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LaunchRequest_Start) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LaunchRequest_Start) ProtoMessage() {} + +func (x *LaunchRequest_Start) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[112] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LaunchRequest_Start.ProtoReflect.Descriptor instead. +func (*LaunchRequest_Start) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{52, 0} +} + +func (x *LaunchRequest_Start) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +func (x *LaunchRequest_Start) GetEnv() map[string]string { + if x != nil { + return x.Env + } + return nil +} + +func (x *LaunchRequest_Start) GetAppArgs() []string { + if x != nil { + return x.AppArgs + } + return nil +} + +func (x *LaunchRequest_Start) GetForegroundIfRunning() bool { + if x != nil { + return x.ForegroundIfRunning + } + return false +} + +func (x *LaunchRequest_Start) GetWaitFor() bool { + if x != nil { + return x.WaitFor + } + return false +} + +func (x *LaunchRequest_Start) GetWaitForDebugger() bool { + if x != nil { + return x.WaitForDebugger + } + return false +} + +type LaunchRequest_Stop struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LaunchRequest_Stop) Reset() { + *x = LaunchRequest_Stop{} + mi := &file_idb_proto_msgTypes[113] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LaunchRequest_Stop) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LaunchRequest_Stop) ProtoMessage() {} + +func (x *LaunchRequest_Stop) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[113] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LaunchRequest_Stop.ProtoReflect.Descriptor instead. +func (*LaunchRequest_Stop) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{52, 1} +} + +type InstrumentsRunRequest_InstrumentsTimings struct { + state protoimpl.MessageState `protogen:"open.v1"` + TerminateTimeout float64 `protobuf:"fixed64,1,opt,name=terminate_timeout,json=terminateTimeout,proto3" json:"terminate_timeout,omitempty"` + LaunchRetryTimeout float64 `protobuf:"fixed64,2,opt,name=launch_retry_timeout,json=launchRetryTimeout,proto3" json:"launch_retry_timeout,omitempty"` + LaunchErrorTimeout float64 `protobuf:"fixed64,3,opt,name=launch_error_timeout,json=launchErrorTimeout,proto3" json:"launch_error_timeout,omitempty"` + OperationDuration float64 `protobuf:"fixed64,4,opt,name=operation_duration,json=operationDuration,proto3" json:"operation_duration,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstrumentsRunRequest_InstrumentsTimings) Reset() { + *x = InstrumentsRunRequest_InstrumentsTimings{} + mi := &file_idb_proto_msgTypes[115] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstrumentsRunRequest_InstrumentsTimings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstrumentsRunRequest_InstrumentsTimings) ProtoMessage() {} + +func (x *InstrumentsRunRequest_InstrumentsTimings) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[115] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InstrumentsRunRequest_InstrumentsTimings.ProtoReflect.Descriptor instead. +func (*InstrumentsRunRequest_InstrumentsTimings) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{56, 0} +} + +func (x *InstrumentsRunRequest_InstrumentsTimings) GetTerminateTimeout() float64 { + if x != nil { + return x.TerminateTimeout + } + return 0 +} + +func (x *InstrumentsRunRequest_InstrumentsTimings) GetLaunchRetryTimeout() float64 { + if x != nil { + return x.LaunchRetryTimeout + } + return 0 +} + +func (x *InstrumentsRunRequest_InstrumentsTimings) GetLaunchErrorTimeout() float64 { + if x != nil { + return x.LaunchErrorTimeout + } + return 0 +} + +func (x *InstrumentsRunRequest_InstrumentsTimings) GetOperationDuration() float64 { + if x != nil { + return x.OperationDuration + } + return 0 +} + +type InstrumentsRunRequest_Start struct { + state protoimpl.MessageState `protogen:"open.v1"` + TemplateName string `protobuf:"bytes,2,opt,name=template_name,json=templateName,proto3" json:"template_name,omitempty"` + AppBundleId string `protobuf:"bytes,3,opt,name=app_bundle_id,json=appBundleId,proto3" json:"app_bundle_id,omitempty"` + Environment map[string]string `protobuf:"bytes,4,rep,name=environment,proto3" json:"environment,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Arguments []string `protobuf:"bytes,5,rep,name=arguments,proto3" json:"arguments,omitempty"` + Timings *InstrumentsRunRequest_InstrumentsTimings `protobuf:"bytes,6,opt,name=timings,proto3" json:"timings,omitempty"` + ToolArguments []string `protobuf:"bytes,7,rep,name=tool_arguments,json=toolArguments,proto3" json:"tool_arguments,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstrumentsRunRequest_Start) Reset() { + *x = InstrumentsRunRequest_Start{} + mi := &file_idb_proto_msgTypes[116] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstrumentsRunRequest_Start) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstrumentsRunRequest_Start) ProtoMessage() {} + +func (x *InstrumentsRunRequest_Start) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[116] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InstrumentsRunRequest_Start.ProtoReflect.Descriptor instead. +func (*InstrumentsRunRequest_Start) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{56, 1} +} + +func (x *InstrumentsRunRequest_Start) GetTemplateName() string { + if x != nil { + return x.TemplateName + } + return "" +} + +func (x *InstrumentsRunRequest_Start) GetAppBundleId() string { + if x != nil { + return x.AppBundleId + } + return "" +} + +func (x *InstrumentsRunRequest_Start) GetEnvironment() map[string]string { + if x != nil { + return x.Environment + } + return nil +} + +func (x *InstrumentsRunRequest_Start) GetArguments() []string { + if x != nil { + return x.Arguments + } + return nil +} + +func (x *InstrumentsRunRequest_Start) GetTimings() *InstrumentsRunRequest_InstrumentsTimings { + if x != nil { + return x.Timings + } + return nil +} + +func (x *InstrumentsRunRequest_Start) GetToolArguments() []string { + if x != nil { + return x.ToolArguments + } + return nil +} + +type InstrumentsRunRequest_Stop struct { + state protoimpl.MessageState `protogen:"open.v1"` + PostProcessArguments []string `protobuf:"bytes,1,rep,name=post_process_arguments,json=postProcessArguments,proto3" json:"post_process_arguments,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InstrumentsRunRequest_Stop) Reset() { + *x = InstrumentsRunRequest_Stop{} + mi := &file_idb_proto_msgTypes[117] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InstrumentsRunRequest_Stop) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InstrumentsRunRequest_Stop) ProtoMessage() {} + +func (x *InstrumentsRunRequest_Stop) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[117] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InstrumentsRunRequest_Stop.ProtoReflect.Descriptor instead. +func (*InstrumentsRunRequest_Stop) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{56, 2} +} + +func (x *InstrumentsRunRequest_Stop) GetPostProcessArguments() []string { + if x != nil { + return x.PostProcessArguments + } + return nil +} + +type XctraceRecordRequest_LauchProcess struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProcessToLaunch string `protobuf:"bytes,1,opt,name=process_to_launch,json=processToLaunch,proto3" json:"process_to_launch,omitempty"` + LaunchArgs []string `protobuf:"bytes,2,rep,name=launch_args,json=launchArgs,proto3" json:"launch_args,omitempty"` + TargetStdin string `protobuf:"bytes,3,opt,name=target_stdin,json=targetStdin,proto3" json:"target_stdin,omitempty"` + TargetStdout string `protobuf:"bytes,4,opt,name=target_stdout,json=targetStdout,proto3" json:"target_stdout,omitempty"` + ProcessEnv map[string]string `protobuf:"bytes,5,rep,name=process_env,json=processEnv,proto3" json:"process_env,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctraceRecordRequest_LauchProcess) Reset() { + *x = XctraceRecordRequest_LauchProcess{} + mi := &file_idb_proto_msgTypes[119] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctraceRecordRequest_LauchProcess) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctraceRecordRequest_LauchProcess) ProtoMessage() {} + +func (x *XctraceRecordRequest_LauchProcess) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[119] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctraceRecordRequest_LauchProcess.ProtoReflect.Descriptor instead. +func (*XctraceRecordRequest_LauchProcess) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{58, 0} +} + +func (x *XctraceRecordRequest_LauchProcess) GetProcessToLaunch() string { + if x != nil { + return x.ProcessToLaunch + } + return "" +} + +func (x *XctraceRecordRequest_LauchProcess) GetLaunchArgs() []string { + if x != nil { + return x.LaunchArgs + } + return nil +} + +func (x *XctraceRecordRequest_LauchProcess) GetTargetStdin() string { + if x != nil { + return x.TargetStdin + } + return "" +} + +func (x *XctraceRecordRequest_LauchProcess) GetTargetStdout() string { + if x != nil { + return x.TargetStdout + } + return "" +} + +func (x *XctraceRecordRequest_LauchProcess) GetProcessEnv() map[string]string { + if x != nil { + return x.ProcessEnv + } + return nil +} + +type XctraceRecordRequest_Target struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Target: + // + // *XctraceRecordRequest_Target_AllProcesses + // *XctraceRecordRequest_Target_ProcessToAttach + // *XctraceRecordRequest_Target_LaunchProcess + Target isXctraceRecordRequest_Target_Target `protobuf_oneof:"target"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctraceRecordRequest_Target) Reset() { + *x = XctraceRecordRequest_Target{} + mi := &file_idb_proto_msgTypes[120] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctraceRecordRequest_Target) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctraceRecordRequest_Target) ProtoMessage() {} + +func (x *XctraceRecordRequest_Target) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[120] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctraceRecordRequest_Target.ProtoReflect.Descriptor instead. +func (*XctraceRecordRequest_Target) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{58, 1} +} + +func (x *XctraceRecordRequest_Target) GetTarget() isXctraceRecordRequest_Target_Target { + if x != nil { + return x.Target + } + return nil +} + +func (x *XctraceRecordRequest_Target) GetAllProcesses() bool { + if x != nil { + if x, ok := x.Target.(*XctraceRecordRequest_Target_AllProcesses); ok { + return x.AllProcesses + } + } + return false +} + +func (x *XctraceRecordRequest_Target) GetProcessToAttach() string { + if x != nil { + if x, ok := x.Target.(*XctraceRecordRequest_Target_ProcessToAttach); ok { + return x.ProcessToAttach + } + } + return "" +} + +func (x *XctraceRecordRequest_Target) GetLaunchProcess() *XctraceRecordRequest_LauchProcess { + if x != nil { + if x, ok := x.Target.(*XctraceRecordRequest_Target_LaunchProcess); ok { + return x.LaunchProcess + } + } + return nil +} + +type isXctraceRecordRequest_Target_Target interface { + isXctraceRecordRequest_Target_Target() +} + +type XctraceRecordRequest_Target_AllProcesses struct { + AllProcesses bool `protobuf:"varint,1,opt,name=all_processes,json=allProcesses,proto3,oneof"` +} + +type XctraceRecordRequest_Target_ProcessToAttach struct { + ProcessToAttach string `protobuf:"bytes,2,opt,name=process_to_attach,json=processToAttach,proto3,oneof"` +} + +type XctraceRecordRequest_Target_LaunchProcess struct { + LaunchProcess *XctraceRecordRequest_LauchProcess `protobuf:"bytes,3,opt,name=launch_process,json=launchProcess,proto3,oneof"` +} + +func (*XctraceRecordRequest_Target_AllProcesses) isXctraceRecordRequest_Target_Target() {} + +func (*XctraceRecordRequest_Target_ProcessToAttach) isXctraceRecordRequest_Target_Target() {} + +func (*XctraceRecordRequest_Target_LaunchProcess) isXctraceRecordRequest_Target_Target() {} + +type XctraceRecordRequest_Start struct { + state protoimpl.MessageState `protogen:"open.v1"` + TemplateName string `protobuf:"bytes,1,opt,name=template_name,json=templateName,proto3" json:"template_name,omitempty"` + TimeLimit float64 `protobuf:"fixed64,2,opt,name=time_limit,json=timeLimit,proto3" json:"time_limit,omitempty"` + Package string `protobuf:"bytes,3,opt,name=package,proto3" json:"package,omitempty"` + Target *XctraceRecordRequest_Target `protobuf:"bytes,4,opt,name=target,proto3" json:"target,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctraceRecordRequest_Start) Reset() { + *x = XctraceRecordRequest_Start{} + mi := &file_idb_proto_msgTypes[121] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctraceRecordRequest_Start) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctraceRecordRequest_Start) ProtoMessage() {} + +func (x *XctraceRecordRequest_Start) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[121] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctraceRecordRequest_Start.ProtoReflect.Descriptor instead. +func (*XctraceRecordRequest_Start) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{58, 2} +} + +func (x *XctraceRecordRequest_Start) GetTemplateName() string { + if x != nil { + return x.TemplateName + } + return "" +} + +func (x *XctraceRecordRequest_Start) GetTimeLimit() float64 { + if x != nil { + return x.TimeLimit + } + return 0 +} + +func (x *XctraceRecordRequest_Start) GetPackage() string { + if x != nil { + return x.Package + } + return "" +} + +func (x *XctraceRecordRequest_Start) GetTarget() *XctraceRecordRequest_Target { + if x != nil { + return x.Target + } + return nil +} + +type XctraceRecordRequest_Stop struct { + state protoimpl.MessageState `protogen:"open.v1"` + Timeout float64 `protobuf:"fixed64,1,opt,name=timeout,proto3" json:"timeout,omitempty"` + Args []string `protobuf:"bytes,2,rep,name=args,proto3" json:"args,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctraceRecordRequest_Stop) Reset() { + *x = XctraceRecordRequest_Stop{} + mi := &file_idb_proto_msgTypes[122] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctraceRecordRequest_Stop) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctraceRecordRequest_Stop) ProtoMessage() {} + +func (x *XctraceRecordRequest_Stop) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[122] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctraceRecordRequest_Stop.ProtoReflect.Descriptor instead. +func (*XctraceRecordRequest_Stop) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{58, 3} +} + +func (x *XctraceRecordRequest_Stop) GetTimeout() float64 { + if x != nil { + return x.Timeout + } + return 0 +} + +func (x *XctraceRecordRequest_Stop) GetArgs() []string { + if x != nil { + return x.Args + } + return nil +} + +type DebugServerRequest_Start struct { + state protoimpl.MessageState `protogen:"open.v1"` + BundleId string `protobuf:"bytes,1,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebugServerRequest_Start) Reset() { + *x = DebugServerRequest_Start{} + mi := &file_idb_proto_msgTypes[124] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebugServerRequest_Start) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebugServerRequest_Start) ProtoMessage() {} + +func (x *DebugServerRequest_Start) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[124] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebugServerRequest_Start.ProtoReflect.Descriptor instead. +func (*DebugServerRequest_Start) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{60, 0} +} + +func (x *DebugServerRequest_Start) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +type DebugServerRequest_Status struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebugServerRequest_Status) Reset() { + *x = DebugServerRequest_Status{} + mi := &file_idb_proto_msgTypes[125] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebugServerRequest_Status) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebugServerRequest_Status) ProtoMessage() {} + +func (x *DebugServerRequest_Status) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[125] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebugServerRequest_Status.ProtoReflect.Descriptor instead. +func (*DebugServerRequest_Status) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{60, 1} +} + +type DebugServerRequest_Stop struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebugServerRequest_Stop) Reset() { + *x = DebugServerRequest_Stop{} + mi := &file_idb_proto_msgTypes[126] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebugServerRequest_Stop) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebugServerRequest_Stop) ProtoMessage() {} + +func (x *DebugServerRequest_Stop) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[126] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebugServerRequest_Stop.ProtoReflect.Descriptor instead. +func (*DebugServerRequest_Stop) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{60, 2} +} + +type DebugServerRequest_Pipe struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebugServerRequest_Pipe) Reset() { + *x = DebugServerRequest_Pipe{} + mi := &file_idb_proto_msgTypes[127] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebugServerRequest_Pipe) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebugServerRequest_Pipe) ProtoMessage() {} + +func (x *DebugServerRequest_Pipe) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[127] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebugServerRequest_Pipe.ProtoReflect.Descriptor instead. +func (*DebugServerRequest_Pipe) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{60, 3} +} + +func (x *DebugServerRequest_Pipe) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type DebugServerResponse_Pipe struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebugServerResponse_Pipe) Reset() { + *x = DebugServerResponse_Pipe{} + mi := &file_idb_proto_msgTypes[128] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebugServerResponse_Pipe) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebugServerResponse_Pipe) ProtoMessage() {} + +func (x *DebugServerResponse_Pipe) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[128] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebugServerResponse_Pipe.ProtoReflect.Descriptor instead. +func (*DebugServerResponse_Pipe) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{61, 0} +} + +func (x *DebugServerResponse_Pipe) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type DebugServerResponse_Status struct { + state protoimpl.MessageState `protogen:"open.v1"` + LldbBootstrapCommands []string `protobuf:"bytes,1,rep,name=lldb_bootstrap_commands,json=lldbBootstrapCommands,proto3" json:"lldb_bootstrap_commands,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DebugServerResponse_Status) Reset() { + *x = DebugServerResponse_Status{} + mi := &file_idb_proto_msgTypes[129] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DebugServerResponse_Status) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DebugServerResponse_Status) ProtoMessage() {} + +func (x *DebugServerResponse_Status) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[129] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DebugServerResponse_Status.ProtoReflect.Descriptor instead. +func (*DebugServerResponse_Status) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{61, 1} +} + +func (x *DebugServerResponse_Status) GetLldbBootstrapCommands() []string { + if x != nil { + return x.LldbBootstrapCommands + } + return nil +} + +type XctestListBundlesResponse_Bundles struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` + Architectures []string `protobuf:"bytes,3,rep,name=architectures,proto3" json:"architectures,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestListBundlesResponse_Bundles) Reset() { + *x = XctestListBundlesResponse_Bundles{} + mi := &file_idb_proto_msgTypes[130] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestListBundlesResponse_Bundles) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestListBundlesResponse_Bundles) ProtoMessage() {} + +func (x *XctestListBundlesResponse_Bundles) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[130] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestListBundlesResponse_Bundles.ProtoReflect.Descriptor instead. +func (*XctestListBundlesResponse_Bundles) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{68, 0} +} + +func (x *XctestListBundlesResponse_Bundles) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *XctestListBundlesResponse_Bundles) GetBundleId() string { + if x != nil { + return x.BundleId + } + return "" +} + +func (x *XctestListBundlesResponse_Bundles) GetArchitectures() []string { + if x != nil { + return x.Architectures + } + return nil +} + +type XctestRunRequest_Logic struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestRunRequest_Logic) Reset() { + *x = XctestRunRequest_Logic{} + mi := &file_idb_proto_msgTypes[131] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestRunRequest_Logic) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestRunRequest_Logic) ProtoMessage() {} + +func (x *XctestRunRequest_Logic) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[131] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestRunRequest_Logic.ProtoReflect.Descriptor instead. +func (*XctestRunRequest_Logic) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{71, 0} +} + +type XctestRunRequest_Application struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppBundleId string `protobuf:"bytes,1,opt,name=app_bundle_id,json=appBundleId,proto3" json:"app_bundle_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestRunRequest_Application) Reset() { + *x = XctestRunRequest_Application{} + mi := &file_idb_proto_msgTypes[132] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestRunRequest_Application) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestRunRequest_Application) ProtoMessage() {} + +func (x *XctestRunRequest_Application) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[132] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestRunRequest_Application.ProtoReflect.Descriptor instead. +func (*XctestRunRequest_Application) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{71, 1} +} + +func (x *XctestRunRequest_Application) GetAppBundleId() string { + if x != nil { + return x.AppBundleId + } + return "" +} + +type XctestRunRequest_UI struct { + state protoimpl.MessageState `protogen:"open.v1"` + AppBundleId string `protobuf:"bytes,1,opt,name=app_bundle_id,json=appBundleId,proto3" json:"app_bundle_id,omitempty"` + TestHostAppBundleId string `protobuf:"bytes,2,opt,name=test_host_app_bundle_id,json=testHostAppBundleId,proto3" json:"test_host_app_bundle_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestRunRequest_UI) Reset() { + *x = XctestRunRequest_UI{} + mi := &file_idb_proto_msgTypes[133] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestRunRequest_UI) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestRunRequest_UI) ProtoMessage() {} + +func (x *XctestRunRequest_UI) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[133] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestRunRequest_UI.ProtoReflect.Descriptor instead. +func (*XctestRunRequest_UI) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{71, 2} +} + +func (x *XctestRunRequest_UI) GetAppBundleId() string { + if x != nil { + return x.AppBundleId + } + return "" +} + +func (x *XctestRunRequest_UI) GetTestHostAppBundleId() string { + if x != nil { + return x.TestHostAppBundleId + } + return "" +} + +type XctestRunRequest_Mode struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Mode: + // + // *XctestRunRequest_Mode_Logic + // *XctestRunRequest_Mode_Application + // *XctestRunRequest_Mode_Ui + Mode isXctestRunRequest_Mode_Mode `protobuf_oneof:"mode"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestRunRequest_Mode) Reset() { + *x = XctestRunRequest_Mode{} + mi := &file_idb_proto_msgTypes[134] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestRunRequest_Mode) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestRunRequest_Mode) ProtoMessage() {} + +func (x *XctestRunRequest_Mode) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[134] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestRunRequest_Mode.ProtoReflect.Descriptor instead. +func (*XctestRunRequest_Mode) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{71, 3} +} + +func (x *XctestRunRequest_Mode) GetMode() isXctestRunRequest_Mode_Mode { + if x != nil { + return x.Mode + } + return nil +} + +func (x *XctestRunRequest_Mode) GetLogic() *XctestRunRequest_Logic { + if x != nil { + if x, ok := x.Mode.(*XctestRunRequest_Mode_Logic); ok { + return x.Logic + } + } + return nil +} + +func (x *XctestRunRequest_Mode) GetApplication() *XctestRunRequest_Application { + if x != nil { + if x, ok := x.Mode.(*XctestRunRequest_Mode_Application); ok { + return x.Application + } + } + return nil +} + +func (x *XctestRunRequest_Mode) GetUi() *XctestRunRequest_UI { + if x != nil { + if x, ok := x.Mode.(*XctestRunRequest_Mode_Ui); ok { + return x.Ui + } + } + return nil +} + +type isXctestRunRequest_Mode_Mode interface { + isXctestRunRequest_Mode_Mode() +} + +type XctestRunRequest_Mode_Logic struct { + Logic *XctestRunRequest_Logic `protobuf:"bytes,1,opt,name=logic,proto3,oneof"` +} + +type XctestRunRequest_Mode_Application struct { + Application *XctestRunRequest_Application `protobuf:"bytes,2,opt,name=application,proto3,oneof"` +} + +type XctestRunRequest_Mode_Ui struct { + Ui *XctestRunRequest_UI `protobuf:"bytes,3,opt,name=ui,proto3,oneof"` +} + +func (*XctestRunRequest_Mode_Logic) isXctestRunRequest_Mode_Mode() {} + +func (*XctestRunRequest_Mode_Application) isXctestRunRequest_Mode_Mode() {} + +func (*XctestRunRequest_Mode_Ui) isXctestRunRequest_Mode_Mode() {} + +type XctestRunRequest_CodeCoverage struct { + state protoimpl.MessageState `protogen:"open.v1"` + Collect bool `protobuf:"varint,1,opt,name=collect,proto3" json:"collect,omitempty"` + Format XctestRunRequest_CodeCoverage_Format `protobuf:"varint,2,opt,name=format,proto3,enum=idb.XctestRunRequest_CodeCoverage_Format" json:"format,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestRunRequest_CodeCoverage) Reset() { + *x = XctestRunRequest_CodeCoverage{} + mi := &file_idb_proto_msgTypes[135] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestRunRequest_CodeCoverage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestRunRequest_CodeCoverage) ProtoMessage() {} + +func (x *XctestRunRequest_CodeCoverage) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[135] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestRunRequest_CodeCoverage.ProtoReflect.Descriptor instead. +func (*XctestRunRequest_CodeCoverage) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{71, 4} +} + +func (x *XctestRunRequest_CodeCoverage) GetCollect() bool { + if x != nil { + return x.Collect + } + return false +} + +func (x *XctestRunRequest_CodeCoverage) GetFormat() XctestRunRequest_CodeCoverage_Format { + if x != nil { + return x.Format + } + return XctestRunRequest_CodeCoverage_EXPORTED +} + +type XctestRunResponse_TestRunInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status XctestRunResponse_TestRunInfo_Status `protobuf:"varint,1,opt,name=status,proto3,enum=idb.XctestRunResponse_TestRunInfo_Status" json:"status,omitempty"` + BundleName string `protobuf:"bytes,2,opt,name=bundle_name,json=bundleName,proto3" json:"bundle_name,omitempty"` + ClassName string `protobuf:"bytes,3,opt,name=class_name,json=className,proto3" json:"class_name,omitempty"` + MethodName string `protobuf:"bytes,4,opt,name=method_name,json=methodName,proto3" json:"method_name,omitempty"` + Duration float64 `protobuf:"fixed64,5,opt,name=duration,proto3" json:"duration,omitempty"` + FailureInfo *XctestRunResponse_TestRunInfo_TestRunFailureInfo `protobuf:"bytes,6,opt,name=failure_info,json=failureInfo,proto3" json:"failure_info,omitempty"` + Logs []string `protobuf:"bytes,7,rep,name=logs,proto3" json:"logs,omitempty"` + ActivityLogs []*XctestRunResponse_TestRunInfo_TestActivity `protobuf:"bytes,8,rep,name=activityLogs,proto3" json:"activityLogs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestRunResponse_TestRunInfo) Reset() { + *x = XctestRunResponse_TestRunInfo{} + mi := &file_idb_proto_msgTypes[137] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestRunResponse_TestRunInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestRunResponse_TestRunInfo) ProtoMessage() {} + +func (x *XctestRunResponse_TestRunInfo) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[137] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestRunResponse_TestRunInfo.ProtoReflect.Descriptor instead. +func (*XctestRunResponse_TestRunInfo) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{72, 0} +} + +func (x *XctestRunResponse_TestRunInfo) GetStatus() XctestRunResponse_TestRunInfo_Status { + if x != nil { + return x.Status + } + return XctestRunResponse_TestRunInfo_PASSED +} + +func (x *XctestRunResponse_TestRunInfo) GetBundleName() string { + if x != nil { + return x.BundleName + } + return "" +} + +func (x *XctestRunResponse_TestRunInfo) GetClassName() string { + if x != nil { + return x.ClassName + } + return "" +} + +func (x *XctestRunResponse_TestRunInfo) GetMethodName() string { + if x != nil { + return x.MethodName + } + return "" +} + +func (x *XctestRunResponse_TestRunInfo) GetDuration() float64 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *XctestRunResponse_TestRunInfo) GetFailureInfo() *XctestRunResponse_TestRunInfo_TestRunFailureInfo { + if x != nil { + return x.FailureInfo + } + return nil +} + +func (x *XctestRunResponse_TestRunInfo) GetLogs() []string { + if x != nil { + return x.Logs + } + return nil +} + +func (x *XctestRunResponse_TestRunInfo) GetActivityLogs() []*XctestRunResponse_TestRunInfo_TestActivity { + if x != nil { + return x.ActivityLogs + } + return nil +} + +type XctestRunResponse_TestRunInfo_TestRunFailureInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + FailureMessage string `protobuf:"bytes,1,opt,name=failure_message,json=failureMessage,proto3" json:"failure_message,omitempty"` + File string `protobuf:"bytes,2,opt,name=file,proto3" json:"file,omitempty"` + Line uint64 `protobuf:"varint,3,opt,name=line,proto3" json:"line,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestRunResponse_TestRunInfo_TestRunFailureInfo) Reset() { + *x = XctestRunResponse_TestRunInfo_TestRunFailureInfo{} + mi := &file_idb_proto_msgTypes[138] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestRunResponse_TestRunInfo_TestRunFailureInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestRunResponse_TestRunInfo_TestRunFailureInfo) ProtoMessage() {} + +func (x *XctestRunResponse_TestRunInfo_TestRunFailureInfo) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[138] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestRunResponse_TestRunInfo_TestRunFailureInfo.ProtoReflect.Descriptor instead. +func (*XctestRunResponse_TestRunInfo_TestRunFailureInfo) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{72, 0, 0} +} + +func (x *XctestRunResponse_TestRunInfo_TestRunFailureInfo) GetFailureMessage() string { + if x != nil { + return x.FailureMessage + } + return "" +} + +func (x *XctestRunResponse_TestRunInfo_TestRunFailureInfo) GetFile() string { + if x != nil { + return x.File + } + return "" +} + +func (x *XctestRunResponse_TestRunInfo_TestRunFailureInfo) GetLine() uint64 { + if x != nil { + return x.Line + } + return 0 +} + +type XctestRunResponse_TestRunInfo_TestAttachment struct { + state protoimpl.MessageState `protogen:"open.v1"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Timestamp float64 `protobuf:"fixed64,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + UniformTypeIdentifier string `protobuf:"bytes,4,opt,name=uniform_type_identifier,json=uniformTypeIdentifier,proto3" json:"uniform_type_identifier,omitempty"` + UserInfoJson []byte `protobuf:"bytes,5,opt,name=user_info_json,json=userInfoJson,proto3" json:"user_info_json,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestRunResponse_TestRunInfo_TestAttachment) Reset() { + *x = XctestRunResponse_TestRunInfo_TestAttachment{} + mi := &file_idb_proto_msgTypes[139] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestRunResponse_TestRunInfo_TestAttachment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestRunResponse_TestRunInfo_TestAttachment) ProtoMessage() {} + +func (x *XctestRunResponse_TestRunInfo_TestAttachment) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[139] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestRunResponse_TestRunInfo_TestAttachment.ProtoReflect.Descriptor instead. +func (*XctestRunResponse_TestRunInfo_TestAttachment) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{72, 0, 1} +} + +func (x *XctestRunResponse_TestRunInfo_TestAttachment) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +func (x *XctestRunResponse_TestRunInfo_TestAttachment) GetTimestamp() float64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *XctestRunResponse_TestRunInfo_TestAttachment) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *XctestRunResponse_TestRunInfo_TestAttachment) GetUniformTypeIdentifier() string { + if x != nil { + return x.UniformTypeIdentifier + } + return "" +} + +func (x *XctestRunResponse_TestRunInfo_TestAttachment) GetUserInfoJson() []byte { + if x != nil { + return x.UserInfoJson + } + return nil +} + +type XctestRunResponse_TestRunInfo_TestActivity struct { + state protoimpl.MessageState `protogen:"open.v1"` + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` + Duration float64 `protobuf:"fixed64,2,opt,name=duration,proto3" json:"duration,omitempty"` + Uuid string `protobuf:"bytes,3,opt,name=uuid,proto3" json:"uuid,omitempty"` + ActivityType string `protobuf:"bytes,4,opt,name=activity_type,json=activityType,proto3" json:"activity_type,omitempty"` + Start float64 `protobuf:"fixed64,5,opt,name=start,proto3" json:"start,omitempty"` + Finish float64 `protobuf:"fixed64,6,opt,name=finish,proto3" json:"finish,omitempty"` + Name string `protobuf:"bytes,7,opt,name=name,proto3" json:"name,omitempty"` + Attachments []*XctestRunResponse_TestRunInfo_TestAttachment `protobuf:"bytes,8,rep,name=attachments,proto3" json:"attachments,omitempty"` + SubActivities []*XctestRunResponse_TestRunInfo_TestActivity `protobuf:"bytes,9,rep,name=sub_activities,json=subActivities,proto3" json:"sub_activities,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) Reset() { + *x = XctestRunResponse_TestRunInfo_TestActivity{} + mi := &file_idb_proto_msgTypes[140] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XctestRunResponse_TestRunInfo_TestActivity) ProtoMessage() {} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[140] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XctestRunResponse_TestRunInfo_TestActivity.ProtoReflect.Descriptor instead. +func (*XctestRunResponse_TestRunInfo_TestActivity) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{72, 0, 2} +} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) GetDuration() float64 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) GetUuid() string { + if x != nil { + return x.Uuid + } + return "" +} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) GetActivityType() string { + if x != nil { + return x.ActivityType + } + return "" +} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) GetStart() float64 { + if x != nil { + return x.Start + } + return 0 +} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) GetFinish() float64 { + if x != nil { + return x.Finish + } + return 0 +} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) GetAttachments() []*XctestRunResponse_TestRunInfo_TestAttachment { + if x != nil { + return x.Attachments + } + return nil +} + +func (x *XctestRunResponse_TestRunInfo_TestActivity) GetSubActivities() []*XctestRunResponse_TestRunInfo_TestActivity { + if x != nil { + return x.SubActivities + } + return nil +} + +type PushRequest_Inner struct { + state protoimpl.MessageState `protogen:"open.v1"` + DstPath string `protobuf:"bytes,2,opt,name=dst_path,json=dstPath,proto3" json:"dst_path,omitempty"` + Container *FileContainer `protobuf:"bytes,3,opt,name=container,proto3" json:"container,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PushRequest_Inner) Reset() { + *x = PushRequest_Inner{} + mi := &file_idb_proto_msgTypes[141] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PushRequest_Inner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushRequest_Inner) ProtoMessage() {} + +func (x *PushRequest_Inner) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[141] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushRequest_Inner.ProtoReflect.Descriptor instead. +func (*PushRequest_Inner) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{84, 0} +} + +func (x *PushRequest_Inner) GetDstPath() string { + if x != nil { + return x.DstPath + } + return "" +} + +func (x *PushRequest_Inner) GetContainer() *FileContainer { + if x != nil { + return x.Container + } + return nil +} + +type TailRequest_Start struct { + state protoimpl.MessageState `protogen:"open.v1"` + Container *FileContainer `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailRequest_Start) Reset() { + *x = TailRequest_Start{} + mi := &file_idb_proto_msgTypes[142] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailRequest_Start) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailRequest_Start) ProtoMessage() {} + +func (x *TailRequest_Start) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[142] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailRequest_Start.ProtoReflect.Descriptor instead. +func (*TailRequest_Start) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{88, 0} +} + +func (x *TailRequest_Start) GetContainer() *FileContainer { + if x != nil { + return x.Container + } + return nil +} + +func (x *TailRequest_Start) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +type TailRequest_Stop struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TailRequest_Stop) Reset() { + *x = TailRequest_Stop{} + mi := &file_idb_proto_msgTypes[143] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TailRequest_Stop) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TailRequest_Stop) ProtoMessage() {} + +func (x *TailRequest_Stop) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[143] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TailRequest_Stop.ProtoReflect.Descriptor instead. +func (*TailRequest_Stop) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{88, 1} +} + +type DapRequest_Start struct { + state protoimpl.MessageState `protogen:"open.v1"` + DebuggerPkgId string `protobuf:"bytes,1,opt,name=debugger_pkg_id,json=debuggerPkgId,proto3" json:"debugger_pkg_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DapRequest_Start) Reset() { + *x = DapRequest_Start{} + mi := &file_idb_proto_msgTypes[144] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DapRequest_Start) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DapRequest_Start) ProtoMessage() {} + +func (x *DapRequest_Start) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[144] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DapRequest_Start.ProtoReflect.Descriptor instead. +func (*DapRequest_Start) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{93, 0} +} + +func (x *DapRequest_Start) GetDebuggerPkgId() string { + if x != nil { + return x.DebuggerPkgId + } + return "" +} + +type DapRequest_Pipe struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DapRequest_Pipe) Reset() { + *x = DapRequest_Pipe{} + mi := &file_idb_proto_msgTypes[145] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DapRequest_Pipe) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DapRequest_Pipe) ProtoMessage() {} + +func (x *DapRequest_Pipe) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[145] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DapRequest_Pipe.ProtoReflect.Descriptor instead. +func (*DapRequest_Pipe) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{93, 1} +} + +func (x *DapRequest_Pipe) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type DapRequest_Stop struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DapRequest_Stop) Reset() { + *x = DapRequest_Stop{} + mi := &file_idb_proto_msgTypes[146] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DapRequest_Stop) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DapRequest_Stop) ProtoMessage() {} + +func (x *DapRequest_Stop) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[146] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DapRequest_Stop.ProtoReflect.Descriptor instead. +func (*DapRequest_Stop) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{93, 2} +} + +type DapResponse_Event struct { + state protoimpl.MessageState `protogen:"open.v1"` + Desc string `protobuf:"bytes,1,opt,name=desc,proto3" json:"desc,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DapResponse_Event) Reset() { + *x = DapResponse_Event{} + mi := &file_idb_proto_msgTypes[147] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DapResponse_Event) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DapResponse_Event) ProtoMessage() {} + +func (x *DapResponse_Event) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[147] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DapResponse_Event.ProtoReflect.Descriptor instead. +func (*DapResponse_Event) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{94, 0} +} + +func (x *DapResponse_Event) GetDesc() string { + if x != nil { + return x.Desc + } + return "" +} + +type DapResponse_Pipe struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DapResponse_Pipe) Reset() { + *x = DapResponse_Pipe{} + mi := &file_idb_proto_msgTypes[148] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DapResponse_Pipe) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DapResponse_Pipe) ProtoMessage() {} + +func (x *DapResponse_Pipe) ProtoReflect() protoreflect.Message { + mi := &file_idb_proto_msgTypes[148] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DapResponse_Pipe.ProtoReflect.Descriptor instead. +func (*DapResponse_Pipe) Descriptor() ([]byte, []int) { + return file_idb_proto_rawDescGZIP(), []int{94, 1} +} + +func (x *DapResponse_Pipe) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +var File_idb_proto protoreflect.FileDescriptor + +const file_idb_proto_rawDesc = "" + + "\n" + + "\tidb.proto\x12\x03idb\"\xbd\x01\n" + + "\aPayload\x12\x1d\n" + + "\tfile_path\x18\x01 \x01(\tH\x00R\bfilePath\x12\x14\n" + + "\x04data\x18\x02 \x01(\fH\x00R\x04data\x12\x12\n" + + "\x03url\x18\x03 \x01(\tH\x00R\x03url\x12<\n" + + "\vcompression\x18\x04 \x01(\x0e2\x18.idb.Payload.CompressionH\x00R\vcompression\"!\n" + + "\vCompression\x12\b\n" + + "\x04GZIP\x10\x00\x12\b\n" + + "\x04ZSTD\x10\x01B\b\n" + + "\x06source\"\x84\x01\n" + + "\rProcessOutput\x12:\n" + + "\tinterface\x18\x01 \x01(\x0e2\x1c.idb.ProcessOutput.InterfaceR\tinterface\x12\x12\n" + + "\x04data\x18\x02 \x01(\fR\x04data\"#\n" + + "\tInterface\x12\n" + + "\n" + + "\x06STDOUT\x10\x00\x12\n" + + "\n" + + "\x06STDERR\x10\x01\"Z\n" + + "\rCompanionInfo\x12\x12\n" + + "\x04udid\x18\x01 \x01(\tR\x04udid\x12\x19\n" + + "\bis_local\x18\x04 \x01(\bR\aisLocal\x12\x1a\n" + + "\bmetadata\x18\x06 \x01(\fR\bmetadata\"\x83\x03\n" + + "\x0eSettingRequest\x12R\n" + + "\x10hardwareKeyboard\x18\x01 \x01(\v2$.idb.SettingRequest.HardwareKeyboardH\x00R\x10hardwareKeyboard\x12I\n" + + "\rstringSetting\x18\x02 \x01(\v2!.idb.SettingRequest.StringSettingH\x00R\rstringSetting\x1a,\n" + + "\x10HardwareKeyboard\x12\x18\n" + + "\aenabled\x18\x01 \x01(\bR\aenabled\x1a\x98\x01\n" + + "\rStringSetting\x12&\n" + + "\asetting\x18\x01 \x01(\x0e2\f.idb.SettingR\asetting\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x16\n" + + "\x06domain\x18\x04 \x01(\tR\x06domain\x12\x1d\n" + + "\n" + + "value_type\x18\x05 \x01(\tR\tvalueTypeB\t\n" + + "\asetting\"\x11\n" + + "\x0fSettingResponse\"g\n" + + "\x11GetSettingRequest\x12&\n" + + "\asetting\x18\x01 \x01(\x0e2\f.idb.SettingR\asetting\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x16\n" + + "\x06domain\x18\x03 \x01(\tR\x06domain\"*\n" + + "\x12GetSettingResponse\x12\x14\n" + + "\x05value\x18\x01 \x01(\tR\x05value\"<\n" + + "\x12ListSettingRequest\x12&\n" + + "\asetting\x18\x01 \x01(\x0e2\f.idb.SettingR\asetting\"-\n" + + "\x13ListSettingResponse\x12\x16\n" + + "\x06values\x18\x01 \x03(\tR\x06values\"G\n" + + "\x0fListAppsRequest\x124\n" + + "\x16suppress_process_state\x18\x01 \x01(\bR\x14suppressProcessState\"=\n" + + "\x10ListAppsResponse\x12)\n" + + "\x04apps\x18\x01 \x03(\v2\x15.idb.InstalledAppInfoR\x04apps\"\xe5\x02\n" + + "\x10InstalledAppInfo\x12\x1b\n" + + "\tbundle_id\x18\x01 \x01(\tR\bbundleId\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12$\n" + + "\rarchitectures\x18\x03 \x03(\tR\rarchitectures\x12!\n" + + "\finstall_type\x18\x04 \x01(\tR\vinstallType\x12J\n" + + "\rprocess_state\x18\x05 \x01(\x0e2%.idb.InstalledAppInfo.AppProcessStateR\fprocessState\x12\x1e\n" + + "\n" + + "debuggable\x18\x06 \x01(\bR\n" + + "debuggable\x12-\n" + + "\x12process_identifier\x18\a \x01(\x04R\x11processIdentifier\"<\n" + + "\x0fAppProcessState\x12\v\n" + + "\aUNKNOWN\x10\x00\x12\x0f\n" + + "\vNOT_RUNNING\x10\x01\x12\v\n" + + "\aRUNNING\x10\x02\"\xf7\x04\n" + + "\x0eInstallRequest\x12C\n" + + "\vdestination\x18\x01 \x01(\x0e2\x1f.idb.InstallRequest.DestinationH\x00R\vdestination\x12(\n" + + "\apayload\x18\x02 \x01(\v2\f.idb.PayloadH\x00R\apayload\x12\x1d\n" + + "\tname_hint\x18\x03 \x01(\tH\x00R\bnameHint\x12)\n" + + "\x0fmake_debuggable\x18\x04 \x01(\bH\x00R\x0emakeDebuggable\x12\x1d\n" + + "\tbundle_id\x18\x05 \x01(\tH\x00R\bbundleId\x12U\n" + + "\x13link_dsym_to_bundle\x18\x06 \x01(\v2$.idb.InstallRequest.LinkDsymToBundleH\x00R\x10linkDsymToBundle\x12>\n" + + "\x1aoverride_modification_time\x18\a \x01(\bH\x00R\x18overrideModificationTime\x1a\xa4\x01\n" + + "\x10LinkDsymToBundle\x12P\n" + + "\vbundle_type\x18\x01 \x01(\x0e2/.idb.InstallRequest.LinkDsymToBundle.BundleTypeR\n" + + "bundleType\x12\x1b\n" + + "\tbundle_id\x18\x02 \x01(\tR\bbundleId\"!\n" + + "\n" + + "BundleType\x12\a\n" + + "\x03APP\x10\x00\x12\n" + + "\n" + + "\x06XCTEST\x10\x01\"F\n" + + "\vDestination\x12\a\n" + + "\x03APP\x10\x00\x12\n" + + "\n" + + "\x06XCTEST\x10\x01\x12\t\n" + + "\x05DYLIB\x10\x02\x12\b\n" + + "\x04DSYM\x10\x03\x12\r\n" + + "\tFRAMEWORK\x10\x04B\a\n" + + "\x05value\"U\n" + + "\x0fInstallResponse\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04uuid\x18\x02 \x01(\tR\x04uuid\x12\x1a\n" + + "\bprogress\x18\x03 \x01(\x01R\bprogress\"\x13\n" + + "\x11ScreenshotRequest\"V\n" + + "\x12ScreenshotResponse\x12\x1d\n" + + "\n" + + "image_data\x18\x01 \x01(\fR\timageData\x12!\n" + + "\fimage_format\x18\x02 \x01(\tR\vimageFormat\"\x0e\n" + + "\fFocusRequest\"\x0f\n" + + "\rFocusResponse\"#\n" + + "\x05Point\x12\f\n" + + "\x01x\x18\x01 \x01(\x01R\x01x\x12\f\n" + + "\x01y\x18\x02 \x01(\x01R\x01y\"\x9c\x01\n" + + "\x18AccessibilityInfoRequest\x12 \n" + + "\x05point\x18\x02 \x01(\v2\n" + + ".idb.PointR\x05point\x12<\n" + + "\x06format\x18\x03 \x01(\x0e2$.idb.AccessibilityInfoRequest.FormatR\x06format\" \n" + + "\x06Format\x12\n" + + "\n" + + "\x06LEGACY\x10\x00\x12\n" + + "\n" + + "\x06NESTED\x10\x01\"/\n" + + "\x19AccessibilityInfoResponse\x12\x12\n" + + "\x04json\x18\x01 \x01(\tR\x04json\"\xf4\x01\n" + + "\x0eApproveRequest\x12\x1b\n" + + "\tbundle_id\x18\x01 \x01(\tR\bbundleId\x12@\n" + + "\vpermissions\x18\x02 \x03(\x0e2\x1e.idb.ApproveRequest.PermissionR\vpermissions\x12\x16\n" + + "\x06scheme\x18\x03 \x01(\tR\x06scheme\"k\n" + + "\n" + + "Permission\x12\n" + + "\n" + + "\x06PHOTOS\x10\x00\x12\n" + + "\n" + + "\x06CAMERA\x10\x01\x12\f\n" + + "\bCONTACTS\x10\x02\x12\a\n" + + "\x03URL\x10\x03\x12\f\n" + + "\bLOCATION\x10\x04\x12\x10\n" + + "\fNOTIFICATION\x10\x05\x12\x0e\n" + + "\n" + + "MICROPHONE\x10\x06\"\x11\n" + + "\x0fApproveResponse\"\xf2\x01\n" + + "\rRevokeRequest\x12\x1b\n" + + "\tbundle_id\x18\x01 \x01(\tR\bbundleId\x12?\n" + + "\vpermissions\x18\x02 \x03(\x0e2\x1d.idb.RevokeRequest.PermissionR\vpermissions\x12\x16\n" + + "\x06scheme\x18\x03 \x01(\tR\x06scheme\"k\n" + + "\n" + + "Permission\x12\n" + + "\n" + + "\x06PHOTOS\x10\x00\x12\n" + + "\n" + + "\x06CAMERA\x10\x01\x12\f\n" + + "\bCONTACTS\x10\x02\x12\a\n" + + "\x03URL\x10\x03\x12\f\n" + + "\bLOCATION\x10\x04\x12\x10\n" + + "\fNOTIFICATION\x10\x05\x12\x0e\n" + + "\n" + + "MICROPHONE\x10\x06\"\x10\n" + + "\x0eRevokeResponse\"\x16\n" + + "\x14ClearKeychainRequest\"\x17\n" + + "\x15ClearKeychainResponse\"?\n" + + "\x12SetLocationRequest\x12)\n" + + "\blocation\x18\x01 \x01(\v2\r.idb.LocationR\blocation\"D\n" + + "\bLocation\x12\x1a\n" + + "\blatitude\x18\x01 \x01(\x01R\blatitude\x12\x1c\n" + + "\tlongitude\x18\x02 \x01(\x01R\tlongitude\"\x15\n" + + "\x13SetLocationResponse\"/\n" + + "\x10UninstallRequest\x12\x1b\n" + + "\tbundle_id\x18\x01 \x01(\tR\bbundleId\"\x13\n" + + "\x11UninstallResponse\"/\n" + + "\x10TerminateRequest\x12\x1b\n" + + "\tbundle_id\x18\x01 \x01(\tR\bbundleId\"\x13\n" + + "\x11TerminateResponse\"\"\n" + + "\x0eOpenUrlRequest\x12\x10\n" + + "\x03url\x18\x01 \x01(\tR\x03url\"\x11\n" + + "\x0fOpenUrlResponse\"?\n" + + "\x15ContactsUpdateRequest\x12&\n" + + "\apayload\x18\x01 \x01(\v2\f.idb.PayloadR\apayload\"\x18\n" + + "\x16ContactsUpdateResponse\"G\n" + + "\x18TargetDescriptionRequest\x12+\n" + + "\x11fetch_diagnostics\x18\x01 \x01(\bR\x10fetchDiagnostics\"\x94\x01\n" + + "\x19TargetDescriptionResponse\x12E\n" + + "\x12target_description\x18\x01 \x01(\v2\x16.idb.TargetDescriptionR\x11targetDescription\x120\n" + + "\tcompanion\x18\x02 \x01(\v2\x12.idb.CompanionInfoR\tcompanion\"\xf4\x06\n" + + "\bHIDEvent\x12.\n" + + "\x05press\x18\x01 \x01(\v2\x16.idb.HIDEvent.HIDPressH\x00R\x05press\x12.\n" + + "\x05swipe\x18\x02 \x01(\v2\x16.idb.HIDEvent.HIDSwipeH\x00R\x05swipe\x12.\n" + + "\x05delay\x18\x03 \x01(\v2\x16.idb.HIDEvent.HIDDelayH\x00R\x05delay\x1a,\n" + + "\bHIDTouch\x12 \n" + + "\x05point\x18\x01 \x01(\v2\n" + + ".idb.PointR\x05point\x1a@\n" + + "\tHIDButton\x123\n" + + "\x06button\x18\x01 \x01(\x0e2\x1b.idb.HIDEvent.HIDButtonTypeR\x06button\x1a\"\n" + + "\x06HIDKey\x12\x18\n" + + "\akeycode\x18\x01 \x01(\x04R\akeycode\x1a\xa7\x01\n" + + "\x0eHIDPressAction\x12.\n" + + "\x05touch\x18\x01 \x01(\v2\x16.idb.HIDEvent.HIDTouchH\x00R\x05touch\x121\n" + + "\x06button\x18\x02 \x01(\v2\x17.idb.HIDEvent.HIDButtonH\x00R\x06button\x12(\n" + + "\x03key\x18\x03 \x01(\v2\x14.idb.HIDEvent.HIDKeyH\x00R\x03keyB\b\n" + + "\x06action\x1az\n" + + "\bHIDPress\x124\n" + + "\x06action\x18\x01 \x01(\v2\x1c.idb.HIDEvent.HIDPressActionR\x06action\x128\n" + + "\tdirection\x18\x02 \x01(\x0e2\x1a.idb.HIDEvent.HIDDirectionR\tdirection\x1a|\n" + + "\bHIDSwipe\x12 \n" + + "\x05start\x18\x01 \x01(\v2\n" + + ".idb.PointR\x05start\x12\x1c\n" + + "\x03end\x18\x02 \x01(\v2\n" + + ".idb.PointR\x03end\x12\x14\n" + + "\x05delta\x18\x05 \x01(\x01R\x05delta\x12\x1a\n" + + "\bduration\x18\x06 \x01(\x01R\bduration\x1a&\n" + + "\bHIDDelay\x12\x1a\n" + + "\bduration\x18\x01 \x01(\x01R\bduration\" \n" + + "\fHIDDirection\x12\b\n" + + "\x04DOWN\x10\x00\x12\x06\n" + + "\x02UP\x10\x01\"M\n" + + "\rHIDButtonType\x12\r\n" + + "\tAPPLE_PAY\x10\x00\x12\b\n" + + "\x04HOME\x10\x01\x12\b\n" + + "\x04LOCK\x10\x02\x12\x0f\n" + + "\vSIDE_BUTTON\x10\x03\x12\b\n" + + "\x04SIRI\x10\x04B\a\n" + + "\x05event\"\r\n" + + "\vHIDResponse\"\xb4\x01\n" + + "\x0eConnectRequest\x12=\n" + + "\bmetadata\x18\x01 \x03(\v2!.idb.ConnectRequest.MetadataEntryR\bmetadata\x12&\n" + + "\x0flocal_file_path\x18\x04 \x01(\tR\rlocalFilePath\x1a;\n" + + "\rMetadataEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"C\n" + + "\x0fConnectResponse\x120\n" + + "\tcompanion\x18\x01 \x01(\v2\x12.idb.CompanionInfoR\tcompanion\"\xa2\x01\n" + + "\x10ScreenDimensions\x12\x14\n" + + "\x05width\x18\x01 \x01(\x04R\x05width\x12\x16\n" + + "\x06height\x18\x02 \x01(\x04R\x06height\x12\x18\n" + + "\adensity\x18\x03 \x01(\x01R\adensity\x12!\n" + + "\fwidth_points\x18\x04 \x01(\x04R\vwidthPoints\x12#\n" + + "\rheight_points\x18\x05 \x01(\x04R\fheightPoints\"\xb7\x02\n" + + "\x11TargetDescription\x12\x12\n" + + "\x04udid\x18\x01 \x01(\tR\x04udid\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12B\n" + + "\x11screen_dimensions\x18\x03 \x01(\v2\x15.idb.ScreenDimensionsR\x10screenDimensions\x12\x14\n" + + "\x05state\x18\x04 \x01(\tR\x05state\x12\x1f\n" + + "\vtarget_type\x18\x05 \x01(\tR\n" + + "targetType\x12\x1d\n" + + "\n" + + "os_version\x18\x06 \x01(\tR\tosVersion\x12\"\n" + + "\farchitecture\x18\a \x01(\tR\farchitecture\x12\x1a\n" + + "\bextended\x18\t \x01(\fR\bextended\x12 \n" + + "\vdiagnostics\x18\n" + + " \x01(\fR\vdiagnostics\"\x7f\n" + + "\n" + + "LogRequest\x12\x1c\n" + + "\targuments\x18\x01 \x03(\tR\targuments\x12.\n" + + "\x06source\x18\x02 \x01(\x0e2\x16.idb.LogRequest.SourceR\x06source\"#\n" + + "\x06Source\x12\n" + + "\n" + + "\x06TARGET\x10\x00\x12\r\n" + + "\tCOMPANION\x10\x01\"%\n" + + "\vLogResponse\x12\x16\n" + + "\x06output\x18\x01 \x01(\fR\x06output\"\xa9\x01\n" + + "\rRecordRequest\x120\n" + + "\x05start\x18\x01 \x01(\v2\x18.idb.RecordRequest.StartH\x00R\x05start\x12-\n" + + "\x04stop\x18\x02 \x01(\v2\x17.idb.RecordRequest.StopH\x00R\x04stop\x1a$\n" + + "\x05Start\x12\x1b\n" + + "\tfile_path\x18\x01 \x01(\tR\bfilePath\x1a\x06\n" + + "\x04StopB\t\n" + + "\acontrol\"e\n" + + "\x0eRecordResponse\x12\x1f\n" + + "\n" + + "log_output\x18\x01 \x01(\fH\x00R\tlogOutput\x12(\n" + + "\apayload\x18\x02 \x01(\v2\f.idb.PayloadH\x00R\apayloadB\b\n" + + "\x06output\"\xb8\x03\n" + + "\x12VideoStreamRequest\x125\n" + + "\x05start\x18\x01 \x01(\v2\x1d.idb.VideoStreamRequest.StartH\x00R\x05start\x122\n" + + "\x04stop\x18\x02 \x01(\v2\x1c.idb.VideoStreamRequest.StopH\x00R\x04stop\x1a\xe3\x01\n" + + "\x05Start\x12\x1b\n" + + "\tfile_path\x18\x01 \x01(\tR\bfilePath\x12\x10\n" + + "\x03fps\x18\x02 \x01(\x04R\x03fps\x126\n" + + "\x06format\x18\x03 \x01(\x0e2\x1e.idb.VideoStreamRequest.FormatR\x06format\x12/\n" + + "\x13compression_quality\x18\x04 \x01(\x01R\x12compressionQuality\x12!\n" + + "\fscale_factor\x18\x05 \x01(\x01R\vscaleFactor\x12\x1f\n" + + "\vavg_bitrate\x18\x06 \x01(\x01R\n" + + "avgBitrate\x1a\x06\n" + + "\x04Stop\">\n" + + "\x06Format\x12\b\n" + + "\x04H264\x10\x00\x12\b\n" + + "\x04RBGA\x10\x01\x12\t\n" + + "\x05MJPEG\x10\x02\x12\v\n" + + "\aMINICAP\x10\x03\x12\b\n" + + "\x04I420\x10\x04B\t\n" + + "\acontrol\"j\n" + + "\x13VideoStreamResponse\x12\x1f\n" + + "\n" + + "log_output\x18\x01 \x01(\fH\x00R\tlogOutput\x12(\n" + + "\apayload\x18\x02 \x01(\v2\f.idb.PayloadH\x00R\apayloadB\b\n" + + "\x06output\"\xad\x03\n" + + "\rLaunchRequest\x120\n" + + "\x05start\x18\x01 \x01(\v2\x18.idb.LaunchRequest.StartH\x00R\x05start\x12-\n" + + "\x04stop\x18\x02 \x01(\v2\x17.idb.LaunchRequest.StopH\x00R\x04stop\x1a\xa7\x02\n" + + "\x05Start\x12\x1b\n" + + "\tbundle_id\x18\x01 \x01(\tR\bbundleId\x123\n" + + "\x03env\x18\x02 \x03(\v2!.idb.LaunchRequest.Start.EnvEntryR\x03env\x12\x19\n" + + "\bapp_args\x18\x03 \x03(\tR\aappArgs\x122\n" + + "\x15foreground_if_running\x18\x04 \x01(\bR\x13foregroundIfRunning\x12\x19\n" + + "\bwait_for\x18\x05 \x01(\bR\awaitFor\x12*\n" + + "\x11wait_for_debugger\x18\x06 \x01(\bR\x0fwaitForDebugger\x1a6\n" + + "\bEnvEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a\x06\n" + + "\x04StopB\t\n" + + "\acontrol\"k\n" + + "\x0eLaunchResponse\x12*\n" + + "\x06output\x18\x03 \x01(\v2\x12.idb.ProcessOutputR\x06output\x12-\n" + + "\bdebugger\x18\x04 \x01(\v2\x11.idb.DebuggerInfoR\bdebugger\"9\n" + + "\x0fAddMediaRequest\x12&\n" + + "\apayload\x18\x01 \x01(\v2\f.idb.PayloadR\apayload\"\x12\n" + + "\x10AddMediaResponse\"\x9e\x06\n" + + "\x15InstrumentsRunRequest\x128\n" + + "\x05start\x18\x01 \x01(\v2 .idb.InstrumentsRunRequest.StartH\x00R\x05start\x125\n" + + "\x04stop\x18\x02 \x01(\v2\x1f.idb.InstrumentsRunRequest.StopH\x00R\x04stop\x1a\xd4\x01\n" + + "\x12InstrumentsTimings\x12+\n" + + "\x11terminate_timeout\x18\x01 \x01(\x01R\x10terminateTimeout\x120\n" + + "\x14launch_retry_timeout\x18\x02 \x01(\x01R\x12launchRetryTimeout\x120\n" + + "\x14launch_error_timeout\x18\x03 \x01(\x01R\x12launchErrorTimeout\x12-\n" + + "\x12operation_duration\x18\x04 \x01(\x01R\x11operationDuration\x1a\xf3\x02\n" + + "\x05Start\x12#\n" + + "\rtemplate_name\x18\x02 \x01(\tR\ftemplateName\x12\"\n" + + "\rapp_bundle_id\x18\x03 \x01(\tR\vappBundleId\x12S\n" + + "\venvironment\x18\x04 \x03(\v21.idb.InstrumentsRunRequest.Start.EnvironmentEntryR\venvironment\x12\x1c\n" + + "\targuments\x18\x05 \x03(\tR\targuments\x12G\n" + + "\atimings\x18\x06 \x01(\v2-.idb.InstrumentsRunRequest.InstrumentsTimingsR\atimings\x12%\n" + + "\x0etool_arguments\x18\a \x03(\tR\rtoolArguments\x1a>\n" + + "\x10EnvironmentEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a<\n" + + "\x04Stop\x124\n" + + "\x16post_process_arguments\x18\x01 \x03(\tR\x14postProcessArgumentsB\t\n" + + "\acontrol\"\xec\x01\n" + + "\x16InstrumentsRunResponse\x12\x1f\n" + + "\n" + + "log_output\x18\x01 \x01(\fH\x00R\tlogOutput\x12(\n" + + "\apayload\x18\x02 \x01(\v2\f.idb.PayloadH\x00R\apayload\x129\n" + + "\x05state\x18\x03 \x01(\x0e2!.idb.InstrumentsRunResponse.StateH\x00R\x05state\"B\n" + + "\x05State\x12\v\n" + + "\aUNKNOWN\x10\x00\x12\x17\n" + + "\x13RUNNING_INSTRUMENTS\x10\x01\x12\x13\n" + + "\x0fPOST_PROCESSING\x10\x02B\b\n" + + "\x06output\"\xe1\x06\n" + + "\x14XctraceRecordRequest\x127\n" + + "\x05start\x18\x01 \x01(\v2\x1f.idb.XctraceRecordRequest.StartH\x00R\x05start\x124\n" + + "\x04stop\x18\x02 \x01(\v2\x1e.idb.XctraceRecordRequest.StopH\x00R\x04stop\x1a\xbb\x02\n" + + "\fLauchProcess\x12*\n" + + "\x11process_to_launch\x18\x01 \x01(\tR\x0fprocessToLaunch\x12\x1f\n" + + "\vlaunch_args\x18\x02 \x03(\tR\n" + + "launchArgs\x12!\n" + + "\ftarget_stdin\x18\x03 \x01(\tR\vtargetStdin\x12#\n" + + "\rtarget_stdout\x18\x04 \x01(\tR\ftargetStdout\x12W\n" + + "\vprocess_env\x18\x05 \x03(\v26.idb.XctraceRecordRequest.LauchProcess.ProcessEnvEntryR\n" + + "processEnv\x1a=\n" + + "\x0fProcessEnvEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a\xb8\x01\n" + + "\x06Target\x12%\n" + + "\rall_processes\x18\x01 \x01(\bH\x00R\fallProcesses\x12,\n" + + "\x11process_to_attach\x18\x02 \x01(\tH\x00R\x0fprocessToAttach\x12O\n" + + "\x0elaunch_process\x18\x03 \x01(\v2&.idb.XctraceRecordRequest.LauchProcessH\x00R\rlaunchProcessB\b\n" + + "\x06target\x1a\x9f\x01\n" + + "\x05Start\x12#\n" + + "\rtemplate_name\x18\x01 \x01(\tR\ftemplateName\x12\x1d\n" + + "\n" + + "time_limit\x18\x02 \x01(\x01R\ttimeLimit\x12\x18\n" + + "\apackage\x18\x03 \x01(\tR\apackage\x128\n" + + "\x06target\x18\x04 \x01(\v2 .idb.XctraceRecordRequest.TargetR\x06target\x1a4\n" + + "\x04Stop\x12\x18\n" + + "\atimeout\x18\x01 \x01(\x01R\atimeout\x12\x12\n" + + "\x04args\x18\x02 \x03(\tR\x04argsB\t\n" + + "\acontrol\"\xcc\x01\n" + + "\x15XctraceRecordResponse\x12\x12\n" + + "\x03log\x18\x01 \x01(\fH\x00R\x03log\x12(\n" + + "\apayload\x18\x02 \x01(\v2\f.idb.PayloadH\x00R\apayload\x128\n" + + "\x05state\x18\x03 \x01(\x0e2 .idb.XctraceRecordResponse.StateH\x00R\x05state\"1\n" + + "\x05State\x12\v\n" + + "\aUNKNOWN\x10\x00\x12\v\n" + + "\aRUNNING\x10\x01\x12\x0e\n" + + "\n" + + "PROCESSING\x10\x02B\b\n" + + "\x06output\"\xcc\x02\n" + + "\x12DebugServerRequest\x125\n" + + "\x05start\x18\x01 \x01(\v2\x1d.idb.DebugServerRequest.StartH\x00R\x05start\x122\n" + + "\x04stop\x18\x02 \x01(\v2\x1c.idb.DebugServerRequest.StopH\x00R\x04stop\x128\n" + + "\x06status\x18\x03 \x01(\v2\x1e.idb.DebugServerRequest.StatusH\x00R\x06status\x122\n" + + "\x04pipe\x18\x04 \x01(\v2\x1c.idb.DebugServerRequest.PipeH\x00R\x04pipe\x1a$\n" + + "\x05Start\x12\x1b\n" + + "\tbundle_id\x18\x01 \x01(\tR\bbundleId\x1a\b\n" + + "\x06Status\x1a\x06\n" + + "\x04Stop\x1a\x1a\n" + + "\x04Pipe\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04dataB\t\n" + + "\acontrol\"\xee\x01\n" + + "\x13DebugServerResponse\x129\n" + + "\x06status\x18\x01 \x01(\v2\x1f.idb.DebugServerResponse.StatusH\x00R\x06status\x123\n" + + "\x04pipe\x18\x02 \x01(\v2\x1d.idb.DebugServerResponse.PipeH\x00R\x04pipe\x1a\x1a\n" + + "\x04Pipe\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\x1a@\n" + + "\x06Status\x126\n" + + "\x17lldb_bootstrap_commands\x18\x01 \x03(\tR\x15lldbBootstrapCommandsB\t\n" + + "\acontrol\"&\n" + + "\x10CrashShowRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"9\n" + + "\x10CrashLogResponse\x12%\n" + + "\x04list\x18\x01 \x03(\v2\x11.idb.CrashLogInfoR\x04list\"\x9b\x02\n" + + "\fCrashLogInfo\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" + + "\tbundle_id\x18\x02 \x01(\tR\bbundleId\x12!\n" + + "\fprocess_name\x18\x03 \x01(\tR\vprocessName\x12.\n" + + "\x13parent_process_name\x18\x04 \x01(\tR\x11parentProcessName\x12-\n" + + "\x12process_identifier\x18\x05 \x01(\x04R\x11processIdentifier\x12:\n" + + "\x19parent_process_identifier\x18\x06 \x01(\x04R\x17parentProcessIdentifier\x12\x1c\n" + + "\ttimestamp\x18\a \x01(\x04R\ttimestamp\"V\n" + + "\x11CrashShowResponse\x12%\n" + + "\x04info\x18\x01 \x01(\v2\x11.idb.CrashLogInfoR\x04info\x12\x1a\n" + + "\bcontents\x18\x02 \x01(\tR\bcontents\"n\n" + + "\rCrashLogQuery\x12\x14\n" + + "\x05since\x18\x01 \x01(\x04R\x05since\x12\x16\n" + + "\x06before\x18\x02 \x01(\x04R\x06before\x12\x1b\n" + + "\tbundle_id\x18\x03 \x01(\tR\bbundleId\x12\x12\n" + + "\x04name\x18\x04 \x01(\tR\x04name\"\x1a\n" + + "\x18XctestListBundlesRequest\"\xbf\x01\n" + + "\x19XctestListBundlesResponse\x12@\n" + + "\abundles\x18\x01 \x03(\v2&.idb.XctestListBundlesResponse.BundlesR\abundles\x1a`\n" + + "\aBundles\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" + + "\tbundle_id\x18\x02 \x01(\tR\bbundleId\x12$\n" + + "\rarchitectures\x18\x03 \x03(\tR\rarchitectures\"T\n" + + "\x16XctestListTestsRequest\x12\x1f\n" + + "\vbundle_name\x18\x01 \x01(\tR\n" + + "bundleName\x12\x19\n" + + "\bapp_path\x18\x02 \x01(\tR\aappPath\"/\n" + + "\x17XctestListTestsResponse\x12\x14\n" + + "\x05names\x18\x01 \x03(\tR\x05names\"\xa7\t\n" + + "\x10XctestRunRequest\x12.\n" + + "\x04mode\x18\x01 \x01(\v2\x1a.idb.XctestRunRequest.ModeR\x04mode\x12$\n" + + "\x0etest_bundle_id\x18\x02 \x01(\tR\ftestBundleId\x12 \n" + + "\ftests_to_run\x18\x03 \x03(\tR\n" + + "testsToRun\x12\"\n" + + "\rtests_to_skip\x18\x04 \x03(\tR\vtestsToSkip\x12\x1c\n" + + "\targuments\x18\x05 \x03(\tR\targuments\x12H\n" + + "\venvironment\x18\x06 \x03(\v2&.idb.XctestRunRequest.EnvironmentEntryR\venvironment\x12\x18\n" + + "\atimeout\x18\a \x01(\x04R\atimeout\x12+\n" + + "\x11report_activities\x18\b \x01(\bR\x10reportActivities\x12)\n" + + "\x10collect_coverage\x18\t \x01(\bR\x0fcollectCoverage\x12-\n" + + "\x12report_attachments\x18\n" + + " \x01(\bR\x11reportAttachments\x12!\n" + + "\fcollect_logs\x18\v \x01(\bR\vcollectLogs\x12*\n" + + "\x11wait_for_debugger\x18\f \x01(\bR\x0fwaitForDebugger\x12G\n" + + "\rcode_coverage\x18\r \x01(\v2\".idb.XctestRunRequest.CodeCoverageR\fcodeCoverage\x122\n" + + "\x15collect_result_bundle\x18\x0e \x01(\bR\x13collectResultBundle\x1a\a\n" + + "\x05Logic\x1a1\n" + + "\vApplication\x12\"\n" + + "\rapp_bundle_id\x18\x01 \x01(\tR\vappBundleId\x1a^\n" + + "\x02UI\x12\"\n" + + "\rapp_bundle_id\x18\x01 \x01(\tR\vappBundleId\x124\n" + + "\x17test_host_app_bundle_id\x18\x02 \x01(\tR\x13testHostAppBundleId\x1a\xb6\x01\n" + + "\x04Mode\x123\n" + + "\x05logic\x18\x01 \x01(\v2\x1b.idb.XctestRunRequest.LogicH\x00R\x05logic\x12E\n" + + "\vapplication\x18\x02 \x01(\v2!.idb.XctestRunRequest.ApplicationH\x00R\vapplication\x12*\n" + + "\x02ui\x18\x03 \x01(\v2\x18.idb.XctestRunRequest.UIH\x00R\x02uiB\x06\n" + + "\x04mode\x1a\x8c\x01\n" + + "\fCodeCoverage\x12\x18\n" + + "\acollect\x18\x01 \x01(\bR\acollect\x12A\n" + + "\x06format\x18\x02 \x01(\x0e2).idb.XctestRunRequest.CodeCoverage.FormatR\x06format\"\x1f\n" + + "\x06Format\x12\f\n" + + "\bEXPORTED\x10\x00\x12\a\n" + + "\x03RAW\x10\x01\x1a>\n" + + "\x10EnvironmentEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb9\f\n" + + "\x11XctestRunResponse\x125\n" + + "\x06status\x18\x01 \x01(\x0e2\x1d.idb.XctestRunResponse.StatusR\x06status\x12<\n" + + "\aresults\x18\x02 \x03(\v2\".idb.XctestRunResponse.TestRunInfoR\aresults\x12\x1d\n" + + "\n" + + "log_output\x18\x03 \x03(\tR\tlogOutput\x121\n" + + "\rresult_bundle\x18\x04 \x01(\v2\f.idb.PayloadR\fresultBundle\x12#\n" + + "\rcoverage_json\x18\x05 \x01(\tR\fcoverageJson\x121\n" + + "\rlog_directory\x18\x06 \x01(\v2\f.idb.PayloadR\flogDirectory\x12-\n" + + "\bdebugger\x18\a \x01(\v2\x11.idb.DebuggerInfoR\bdebugger\x12:\n" + + "\x12code_coverage_data\x18\b \x01(\v2\f.idb.PayloadR\x10codeCoverageData\x1a\xce\b\n" + + "\vTestRunInfo\x12A\n" + + "\x06status\x18\x01 \x01(\x0e2).idb.XctestRunResponse.TestRunInfo.StatusR\x06status\x12\x1f\n" + + "\vbundle_name\x18\x02 \x01(\tR\n" + + "bundleName\x12\x1d\n" + + "\n" + + "class_name\x18\x03 \x01(\tR\tclassName\x12\x1f\n" + + "\vmethod_name\x18\x04 \x01(\tR\n" + + "methodName\x12\x1a\n" + + "\bduration\x18\x05 \x01(\x01R\bduration\x12X\n" + + "\ffailure_info\x18\x06 \x01(\v25.idb.XctestRunResponse.TestRunInfo.TestRunFailureInfoR\vfailureInfo\x12\x12\n" + + "\x04logs\x18\a \x03(\tR\x04logs\x12S\n" + + "\factivityLogs\x18\b \x03(\v2/.idb.XctestRunResponse.TestRunInfo.TestActivityR\factivityLogs\x1ae\n" + + "\x12TestRunFailureInfo\x12'\n" + + "\x0ffailure_message\x18\x01 \x01(\tR\x0efailureMessage\x12\x12\n" + + "\x04file\x18\x02 \x01(\tR\x04file\x12\x12\n" + + "\x04line\x18\x03 \x01(\x04R\x04line\x1a\xba\x01\n" + + "\x0eTestAttachment\x12\x18\n" + + "\apayload\x18\x01 \x01(\fR\apayload\x12\x1c\n" + + "\ttimestamp\x18\x02 \x01(\x01R\ttimestamp\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x126\n" + + "\x17uniform_type_identifier\x18\x04 \x01(\tR\x15uniformTypeIdentifier\x12$\n" + + "\x0euser_info_json\x18\x05 \x01(\fR\fuserInfoJson\x1a\xe8\x02\n" + + "\fTestActivity\x12\x14\n" + + "\x05title\x18\x01 \x01(\tR\x05title\x12\x1a\n" + + "\bduration\x18\x02 \x01(\x01R\bduration\x12\x12\n" + + "\x04uuid\x18\x03 \x01(\tR\x04uuid\x12#\n" + + "\ractivity_type\x18\x04 \x01(\tR\factivityType\x12\x14\n" + + "\x05start\x18\x05 \x01(\x01R\x05start\x12\x16\n" + + "\x06finish\x18\x06 \x01(\x01R\x06finish\x12\x12\n" + + "\x04name\x18\a \x01(\tR\x04name\x12S\n" + + "\vattachments\x18\b \x03(\v21.idb.XctestRunResponse.TestRunInfo.TestAttachmentR\vattachments\x12V\n" + + "\x0esub_activities\x18\t \x03(\v2/.idb.XctestRunResponse.TestRunInfo.TestActivityR\rsubActivities\"-\n" + + "\x06Status\x12\n" + + "\n" + + "\x06PASSED\x10\x00\x12\n" + + "\n" + + "\x06FAILED\x10\x01\x12\v\n" + + "\aCRASHED\x10\x02\"I\n" + + "\x06Status\x12\v\n" + + "\aRUNNING\x10\x00\x12\x17\n" + + "\x13TERMINATED_NORMALLY\x10\x01\x12\x19\n" + + "\x15TERMINATED_ABNORMALLY\x10\x02\"\xff\x02\n" + + "\rFileContainer\x12+\n" + + "\x04kind\x18\x01 \x01(\x0e2\x17.idb.FileContainer.KindR\x04kind\x12\x1b\n" + + "\tbundle_id\x18\x02 \x01(\tR\bbundleId\"\xa3\x02\n" + + "\x04Kind\x12\b\n" + + "\x04NONE\x10\x00\x12\x0f\n" + + "\vAPPLICATION\x10\x01\x12\b\n" + + "\x04ROOT\x10\x02\x12\t\n" + + "\x05MEDIA\x10\x03\x12\v\n" + + "\aCRASHES\x10\x04\x12\x19\n" + + "\x15PROVISIONING_PROFILES\x10\x05\x12\x10\n" + + "\fMDM_PROFILES\x10\x06\x12\x15\n" + + "\x11SPRINGBOARD_ICONS\x10\a\x12\r\n" + + "\tWALLPAPER\x10\b\x12\x0f\n" + + "\vDISK_IMAGES\x10\t\x12\x13\n" + + "\x0fGROUP_CONTAINER\x10\n" + + "\x12\x19\n" + + "\x15APPLICATION_CONTAINER\x10\v\x12\r\n" + + "\tAUXILLARY\x10\f\x12\n" + + "\n" + + "\x06XCTEST\x10\r\x12\t\n" + + "\x05DYLIB\x10\x0e\x12\b\n" + + "\x04DSYM\x10\x0f\x12\r\n" + + "\tFRAMEWORK\x10\x10\x12\v\n" + + "\aSYMBOLS\x10\x11\"\x1e\n" + + "\bFileInfo\x12\x12\n" + + "\x04path\x18\x01 \x01(\tR\x04path\"Y\n" + + "\vFileListing\x12%\n" + + "\x06parent\x18\x01 \x01(\v2\r.idb.FileInfoR\x06parent\x12#\n" + + "\x05files\x18\x02 \x03(\v2\r.idb.FileInfoR\x05files\"_\n" + + "\n" + + "LsResponse\x12#\n" + + "\x05files\x18\x01 \x03(\v2\r.idb.FileInfoR\x05files\x12,\n" + + "\blistings\x18\x02 \x03(\v2\x10.idb.FileListingR\blistings\"g\n" + + "\tLsRequest\x12\x12\n" + + "\x04path\x18\x02 \x01(\tR\x04path\x120\n" + + "\tcontainer\x18\x03 \x01(\v2\x12.idb.FileContainerR\tcontainer\x12\x14\n" + + "\x05paths\x18\x04 \x03(\tR\x05paths\"T\n" + + "\fMkdirRequest\x12\x12\n" + + "\x04path\x18\x02 \x01(\tR\x04path\x120\n" + + "\tcontainer\x18\x03 \x01(\v2\x12.idb.FileContainerR\tcontainer\"\x0f\n" + + "\rMkdirResponse\"u\n" + + "\tMvRequest\x12\x1b\n" + + "\tsrc_paths\x18\x02 \x03(\tR\bsrcPaths\x12\x19\n" + + "\bdst_path\x18\x03 \x01(\tR\adstPath\x120\n" + + "\tcontainer\x18\x04 \x01(\v2\x12.idb.FileContainerR\tcontainer\"\f\n" + + "\n" + + "MvResponse\"S\n" + + "\tRmRequest\x12\x14\n" + + "\x05paths\x18\x02 \x03(\tR\x05paths\x120\n" + + "\tcontainer\x18\x03 \x01(\v2\x12.idb.FileContainerR\tcontainer\"\f\n" + + "\n" + + "RmResponse\"\xc6\x01\n" + + "\vPushRequest\x12(\n" + + "\apayload\x18\x01 \x01(\v2\f.idb.PayloadH\x00R\apayload\x12.\n" + + "\x05inner\x18\x02 \x01(\v2\x16.idb.PushRequest.InnerH\x00R\x05inner\x1aT\n" + + "\x05Inner\x12\x19\n" + + "\bdst_path\x18\x02 \x01(\tR\adstPath\x120\n" + + "\tcontainer\x18\x03 \x01(\v2\x12.idb.FileContainerR\tcontainerB\a\n" + + "\x05value\"\x0e\n" + + "\fPushResponse\"u\n" + + "\vPullRequest\x12\x19\n" + + "\bsrc_path\x18\x02 \x01(\tR\asrcPath\x12\x19\n" + + "\bdst_path\x18\x03 \x01(\tR\adstPath\x120\n" + + "\tcontainer\x18\x04 \x01(\v2\x12.idb.FileContainerR\tcontainer\"6\n" + + "\fPullResponse\x12&\n" + + "\apayload\x18\x01 \x01(\v2\f.idb.PayloadR\apayload\"\xcc\x01\n" + + "\vTailRequest\x12.\n" + + "\x05start\x18\x01 \x01(\v2\x16.idb.TailRequest.StartH\x00R\x05start\x12+\n" + + "\x04stop\x18\x02 \x01(\v2\x15.idb.TailRequest.StopH\x00R\x04stop\x1aM\n" + + "\x05Start\x120\n" + + "\tcontainer\x18\x01 \x01(\v2\x12.idb.FileContainerR\tcontainer\x12\x12\n" + + "\x04path\x18\x02 \x01(\tR\x04path\x1a\x06\n" + + "\x04StopB\t\n" + + "\acontrol\"\"\n" + + "\fTailResponse\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\"H\n" + + "\fDebuggerInfo\x12\x10\n" + + "\x03pid\x18\x01 \x01(\x04R\x03pid\x12\x12\n" + + "\x04host\x18\x02 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x03 \x01(\x04R\x04port\"Y\n" + + "\x17SendNotificationRequest\x12\x1b\n" + + "\tbundle_id\x18\x01 \x01(\tR\bbundleId\x12!\n" + + "\fjson_payload\x18\x02 \x01(\tR\vjsonPayload\"\x1a\n" + + "\x18SendNotificationResponse\"\xf3\x01\n" + + "\n" + + "DapRequest\x12-\n" + + "\x05start\x18\x01 \x01(\v2\x15.idb.DapRequest.StartH\x00R\x05start\x12*\n" + + "\x04pipe\x18\x02 \x01(\v2\x14.idb.DapRequest.PipeH\x00R\x04pipe\x12*\n" + + "\x04stop\x18\x03 \x01(\v2\x14.idb.DapRequest.StopH\x00R\x04stop\x1a/\n" + + "\x05Start\x12&\n" + + "\x0fdebugger_pkg_id\x18\x01 \x01(\tR\rdebuggerPkgId\x1a\x1a\n" + + "\x04Pipe\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\x1a\x06\n" + + "\x04StopB\t\n" + + "\acontrol\"\xe8\x01\n" + + "\vDapResponse\x122\n" + + "\astarted\x18\x01 \x01(\v2\x16.idb.DapResponse.EventH\x00R\astarted\x12/\n" + + "\x06stdout\x18\x02 \x01(\v2\x15.idb.DapResponse.PipeH\x00R\x06stdout\x122\n" + + "\astopped\x18\x03 \x01(\v2\x16.idb.DapResponse.EventH\x00R\astopped\x1a\x1b\n" + + "\x05Event\x12\x12\n" + + "\x04desc\x18\x01 \x01(\tR\x04desc\x1a\x1a\n" + + "\x04Pipe\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04dataB\a\n" + + "\x05event\"\x1e\n" + + "\x1cSimulateMemoryWarningRequest\"\x1f\n" + + "\x1dSimulateMemoryWarningResponse*\x1e\n" + + "\aSetting\x12\n" + + "\n" + + "\x06LOCALE\x10\x00\x12\a\n" + + "\x03ANY\x10\x012\xa0\x15\n" + + "\x10CompanionService\x126\n" + + "\aconnect\x12\x13.idb.ConnectRequest\x1a\x14.idb.ConnectResponse\"\x00\x12F\n" + + "\vdebugserver\x12\x17.idb.DebugServerRequest\x1a\x18.idb.DebugServerResponse\"\x00(\x010\x01\x12.\n" + + "\x03dap\x12\x0f.idb.DapRequest\x1a\x10.idb.DapResponse\"\x00(\x010\x01\x12K\n" + + "\bdescribe\x12\x1d.idb.TargetDescriptionRequest\x1a\x1e.idb.TargetDescriptionResponse\"\x00\x12:\n" + + "\ainstall\x12\x13.idb.InstallRequest\x1a\x14.idb.InstallResponse\"\x00(\x010\x01\x12P\n" + + "\x0finstruments_run\x12\x1a.idb.InstrumentsRunRequest\x1a\x1b.idb.InstrumentsRunResponse\"\x00(\x010\x01\x12,\n" + + "\x03log\x12\x0f.idb.LogRequest\x1a\x10.idb.LogResponse\"\x000\x01\x12M\n" + + "\x0exctrace_record\x12\x19.idb.XctraceRecordRequest\x1a\x1a.idb.XctraceRecordResponse\"\x00(\x010\x01\x12U\n" + + "\x12accessibility_info\x12\x1d.idb.AccessibilityInfoRequest\x1a\x1e.idb.AccessibilityInfoResponse\"\x00\x120\n" + + "\x05focus\x12\x11.idb.FocusRequest\x1a\x12.idb.FocusResponse\"\x00\x12*\n" + + "\x03hid\x12\r.idb.HIDEvent\x1a\x10.idb.HIDResponse\"\x00(\x01\x126\n" + + "\bopen_url\x12\x13.idb.OpenUrlRequest\x1a\x13.idb.OpenUrlRequest\"\x00\x12C\n" + + "\fset_location\x12\x17.idb.SetLocationRequest\x1a\x18.idb.SetLocationResponse\"\x00\x12R\n" + + "\x11send_notification\x12\x1c.idb.SendNotificationRequest\x1a\x1d.idb.SendNotificationResponse\"\x00\x12b\n" + + "\x17simulate_memory_warning\x12!.idb.SimulateMemoryWarningRequest\x1a\".idb.SimulateMemoryWarningResponse\"\x00\x126\n" + + "\aapprove\x12\x13.idb.ApproveRequest\x1a\x14.idb.ApproveResponse\"\x00\x123\n" + + "\x06revoke\x12\x12.idb.RevokeRequest\x1a\x13.idb.RevokeResponse\"\x00\x12I\n" + + "\x0eclear_keychain\x12\x19.idb.ClearKeychainRequest\x1a\x1a.idb.ClearKeychainResponse\"\x00\x12L\n" + + "\x0fcontacts_update\x12\x1a.idb.ContactsUpdateRequest\x1a\x1b.idb.ContactsUpdateResponse\"\x00\x126\n" + + "\asetting\x12\x13.idb.SettingRequest\x1a\x14.idb.SettingResponse\"\x00\x12@\n" + + "\vget_setting\x12\x16.idb.GetSettingRequest\x1a\x17.idb.GetSettingResponse\"\x00\x12D\n" + + "\rlist_settings\x12\x17.idb.ListSettingRequest\x1a\x18.idb.ListSettingResponse\"\x00\x127\n" + + "\x06launch\x12\x12.idb.LaunchRequest\x1a\x13.idb.LaunchResponse\"\x00(\x010\x01\x12:\n" + + "\tlist_apps\x12\x14.idb.ListAppsRequest\x1a\x15.idb.ListAppsResponse\"\x00\x12<\n" + + "\tterminate\x12\x15.idb.TerminateRequest\x1a\x16.idb.TerminateResponse\"\x00\x12<\n" + + "\tuninstall\x12\x15.idb.UninstallRequest\x1a\x16.idb.UninstallResponse\"\x00\x12<\n" + + "\tadd_media\x12\x14.idb.AddMediaRequest\x1a\x15.idb.AddMediaResponse\"\x00(\x01\x127\n" + + "\x06record\x12\x12.idb.RecordRequest\x1a\x13.idb.RecordResponse\"\x00(\x010\x01\x12?\n" + + "\n" + + "screenshot\x12\x16.idb.ScreenshotRequest\x1a\x17.idb.ScreenshotResponse\"\x00\x12G\n" + + "\fvideo_stream\x12\x17.idb.VideoStreamRequest\x1a\x18.idb.VideoStreamResponse\"\x00(\x010\x01\x12;\n" + + "\fcrash_delete\x12\x12.idb.CrashLogQuery\x1a\x15.idb.CrashLogResponse\"\x00\x129\n" + + "\n" + + "crash_list\x12\x12.idb.CrashLogQuery\x1a\x15.idb.CrashLogResponse\"\x00\x12=\n" + + "\n" + + "crash_show\x12\x15.idb.CrashShowRequest\x1a\x16.idb.CrashShowResponse\"\x00\x12V\n" + + "\x13xctest_list_bundles\x12\x1d.idb.XctestListBundlesRequest\x1a\x1e.idb.XctestListBundlesResponse\"\x00\x12P\n" + + "\x11xctest_list_tests\x12\x1b.idb.XctestListTestsRequest\x1a\x1c.idb.XctestListTestsResponse\"\x00\x12?\n" + + "\n" + + "xctest_run\x12\x15.idb.XctestRunRequest\x1a\x16.idb.XctestRunResponse\"\x000\x01\x12'\n" + + "\x02ls\x12\x0e.idb.LsRequest\x1a\x0f.idb.LsResponse\"\x00\x120\n" + + "\x05mkdir\x12\x11.idb.MkdirRequest\x1a\x12.idb.MkdirResponse\"\x00\x12'\n" + + "\x02mv\x12\x0e.idb.MvRequest\x1a\x0f.idb.MvResponse\"\x00\x12'\n" + + "\x02rm\x12\x0e.idb.RmRequest\x1a\x0f.idb.RmResponse\"\x00\x12/\n" + + "\x04pull\x12\x10.idb.PullRequest\x1a\x11.idb.PullResponse\"\x000\x01\x12/\n" + + "\x04push\x12\x10.idb.PushRequest\x1a\x11.idb.PushResponse\"\x00(\x01\x121\n" + + "\x04tail\x12\x10.idb.TailRequest\x1a\x11.idb.TailResponse\"\x00(\x010\x01BZZXgithub.com/priyanshujain/sanderling/internal/driver/ioscompanion/companionpb;companionpbb\x06proto3" + +var ( + file_idb_proto_rawDescOnce sync.Once + file_idb_proto_rawDescData []byte +) + +func file_idb_proto_rawDescGZIP() []byte { + file_idb_proto_rawDescOnce.Do(func() { + file_idb_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_idb_proto_rawDesc), len(file_idb_proto_rawDesc))) + }) + return file_idb_proto_rawDescData +} + +var file_idb_proto_enumTypes = make([]protoimpl.EnumInfo, 19) +var file_idb_proto_msgTypes = make([]protoimpl.MessageInfo, 149) +var file_idb_proto_goTypes = []any{ + (Setting)(0), // 0: idb.Setting + (Payload_Compression)(0), // 1: idb.Payload.Compression + (ProcessOutput_Interface)(0), // 2: idb.ProcessOutput.Interface + (InstalledAppInfo_AppProcessState)(0), // 3: idb.InstalledAppInfo.AppProcessState + (InstallRequest_Destination)(0), // 4: idb.InstallRequest.Destination + (InstallRequest_LinkDsymToBundle_BundleType)(0), // 5: idb.InstallRequest.LinkDsymToBundle.BundleType + (AccessibilityInfoRequest_Format)(0), // 6: idb.AccessibilityInfoRequest.Format + (ApproveRequest_Permission)(0), // 7: idb.ApproveRequest.Permission + (RevokeRequest_Permission)(0), // 8: idb.RevokeRequest.Permission + (HIDEvent_HIDDirection)(0), // 9: idb.HIDEvent.HIDDirection + (HIDEvent_HIDButtonType)(0), // 10: idb.HIDEvent.HIDButtonType + (LogRequest_Source)(0), // 11: idb.LogRequest.Source + (VideoStreamRequest_Format)(0), // 12: idb.VideoStreamRequest.Format + (InstrumentsRunResponse_State)(0), // 13: idb.InstrumentsRunResponse.State + (XctraceRecordResponse_State)(0), // 14: idb.XctraceRecordResponse.State + (XctestRunRequest_CodeCoverage_Format)(0), // 15: idb.XctestRunRequest.CodeCoverage.Format + (XctestRunResponse_Status)(0), // 16: idb.XctestRunResponse.Status + (XctestRunResponse_TestRunInfo_Status)(0), // 17: idb.XctestRunResponse.TestRunInfo.Status + (FileContainer_Kind)(0), // 18: idb.FileContainer.Kind + (*Payload)(nil), // 19: idb.Payload + (*ProcessOutput)(nil), // 20: idb.ProcessOutput + (*CompanionInfo)(nil), // 21: idb.CompanionInfo + (*SettingRequest)(nil), // 22: idb.SettingRequest + (*SettingResponse)(nil), // 23: idb.SettingResponse + (*GetSettingRequest)(nil), // 24: idb.GetSettingRequest + (*GetSettingResponse)(nil), // 25: idb.GetSettingResponse + (*ListSettingRequest)(nil), // 26: idb.ListSettingRequest + (*ListSettingResponse)(nil), // 27: idb.ListSettingResponse + (*ListAppsRequest)(nil), // 28: idb.ListAppsRequest + (*ListAppsResponse)(nil), // 29: idb.ListAppsResponse + (*InstalledAppInfo)(nil), // 30: idb.InstalledAppInfo + (*InstallRequest)(nil), // 31: idb.InstallRequest + (*InstallResponse)(nil), // 32: idb.InstallResponse + (*ScreenshotRequest)(nil), // 33: idb.ScreenshotRequest + (*ScreenshotResponse)(nil), // 34: idb.ScreenshotResponse + (*FocusRequest)(nil), // 35: idb.FocusRequest + (*FocusResponse)(nil), // 36: idb.FocusResponse + (*Point)(nil), // 37: idb.Point + (*AccessibilityInfoRequest)(nil), // 38: idb.AccessibilityInfoRequest + (*AccessibilityInfoResponse)(nil), // 39: idb.AccessibilityInfoResponse + (*ApproveRequest)(nil), // 40: idb.ApproveRequest + (*ApproveResponse)(nil), // 41: idb.ApproveResponse + (*RevokeRequest)(nil), // 42: idb.RevokeRequest + (*RevokeResponse)(nil), // 43: idb.RevokeResponse + (*ClearKeychainRequest)(nil), // 44: idb.ClearKeychainRequest + (*ClearKeychainResponse)(nil), // 45: idb.ClearKeychainResponse + (*SetLocationRequest)(nil), // 46: idb.SetLocationRequest + (*Location)(nil), // 47: idb.Location + (*SetLocationResponse)(nil), // 48: idb.SetLocationResponse + (*UninstallRequest)(nil), // 49: idb.UninstallRequest + (*UninstallResponse)(nil), // 50: idb.UninstallResponse + (*TerminateRequest)(nil), // 51: idb.TerminateRequest + (*TerminateResponse)(nil), // 52: idb.TerminateResponse + (*OpenUrlRequest)(nil), // 53: idb.OpenUrlRequest + (*OpenUrlResponse)(nil), // 54: idb.OpenUrlResponse + (*ContactsUpdateRequest)(nil), // 55: idb.ContactsUpdateRequest + (*ContactsUpdateResponse)(nil), // 56: idb.ContactsUpdateResponse + (*TargetDescriptionRequest)(nil), // 57: idb.TargetDescriptionRequest + (*TargetDescriptionResponse)(nil), // 58: idb.TargetDescriptionResponse + (*HIDEvent)(nil), // 59: idb.HIDEvent + (*HIDResponse)(nil), // 60: idb.HIDResponse + (*ConnectRequest)(nil), // 61: idb.ConnectRequest + (*ConnectResponse)(nil), // 62: idb.ConnectResponse + (*ScreenDimensions)(nil), // 63: idb.ScreenDimensions + (*TargetDescription)(nil), // 64: idb.TargetDescription + (*LogRequest)(nil), // 65: idb.LogRequest + (*LogResponse)(nil), // 66: idb.LogResponse + (*RecordRequest)(nil), // 67: idb.RecordRequest + (*RecordResponse)(nil), // 68: idb.RecordResponse + (*VideoStreamRequest)(nil), // 69: idb.VideoStreamRequest + (*VideoStreamResponse)(nil), // 70: idb.VideoStreamResponse + (*LaunchRequest)(nil), // 71: idb.LaunchRequest + (*LaunchResponse)(nil), // 72: idb.LaunchResponse + (*AddMediaRequest)(nil), // 73: idb.AddMediaRequest + (*AddMediaResponse)(nil), // 74: idb.AddMediaResponse + (*InstrumentsRunRequest)(nil), // 75: idb.InstrumentsRunRequest + (*InstrumentsRunResponse)(nil), // 76: idb.InstrumentsRunResponse + (*XctraceRecordRequest)(nil), // 77: idb.XctraceRecordRequest + (*XctraceRecordResponse)(nil), // 78: idb.XctraceRecordResponse + (*DebugServerRequest)(nil), // 79: idb.DebugServerRequest + (*DebugServerResponse)(nil), // 80: idb.DebugServerResponse + (*CrashShowRequest)(nil), // 81: idb.CrashShowRequest + (*CrashLogResponse)(nil), // 82: idb.CrashLogResponse + (*CrashLogInfo)(nil), // 83: idb.CrashLogInfo + (*CrashShowResponse)(nil), // 84: idb.CrashShowResponse + (*CrashLogQuery)(nil), // 85: idb.CrashLogQuery + (*XctestListBundlesRequest)(nil), // 86: idb.XctestListBundlesRequest + (*XctestListBundlesResponse)(nil), // 87: idb.XctestListBundlesResponse + (*XctestListTestsRequest)(nil), // 88: idb.XctestListTestsRequest + (*XctestListTestsResponse)(nil), // 89: idb.XctestListTestsResponse + (*XctestRunRequest)(nil), // 90: idb.XctestRunRequest + (*XctestRunResponse)(nil), // 91: idb.XctestRunResponse + (*FileContainer)(nil), // 92: idb.FileContainer + (*FileInfo)(nil), // 93: idb.FileInfo + (*FileListing)(nil), // 94: idb.FileListing + (*LsResponse)(nil), // 95: idb.LsResponse + (*LsRequest)(nil), // 96: idb.LsRequest + (*MkdirRequest)(nil), // 97: idb.MkdirRequest + (*MkdirResponse)(nil), // 98: idb.MkdirResponse + (*MvRequest)(nil), // 99: idb.MvRequest + (*MvResponse)(nil), // 100: idb.MvResponse + (*RmRequest)(nil), // 101: idb.RmRequest + (*RmResponse)(nil), // 102: idb.RmResponse + (*PushRequest)(nil), // 103: idb.PushRequest + (*PushResponse)(nil), // 104: idb.PushResponse + (*PullRequest)(nil), // 105: idb.PullRequest + (*PullResponse)(nil), // 106: idb.PullResponse + (*TailRequest)(nil), // 107: idb.TailRequest + (*TailResponse)(nil), // 108: idb.TailResponse + (*DebuggerInfo)(nil), // 109: idb.DebuggerInfo + (*SendNotificationRequest)(nil), // 110: idb.SendNotificationRequest + (*SendNotificationResponse)(nil), // 111: idb.SendNotificationResponse + (*DapRequest)(nil), // 112: idb.DapRequest + (*DapResponse)(nil), // 113: idb.DapResponse + (*SimulateMemoryWarningRequest)(nil), // 114: idb.SimulateMemoryWarningRequest + (*SimulateMemoryWarningResponse)(nil), // 115: idb.SimulateMemoryWarningResponse + (*SettingRequest_HardwareKeyboard)(nil), // 116: idb.SettingRequest.HardwareKeyboard + (*SettingRequest_StringSetting)(nil), // 117: idb.SettingRequest.StringSetting + (*InstallRequest_LinkDsymToBundle)(nil), // 118: idb.InstallRequest.LinkDsymToBundle + (*HIDEvent_HIDTouch)(nil), // 119: idb.HIDEvent.HIDTouch + (*HIDEvent_HIDButton)(nil), // 120: idb.HIDEvent.HIDButton + (*HIDEvent_HIDKey)(nil), // 121: idb.HIDEvent.HIDKey + (*HIDEvent_HIDPressAction)(nil), // 122: idb.HIDEvent.HIDPressAction + (*HIDEvent_HIDPress)(nil), // 123: idb.HIDEvent.HIDPress + (*HIDEvent_HIDSwipe)(nil), // 124: idb.HIDEvent.HIDSwipe + (*HIDEvent_HIDDelay)(nil), // 125: idb.HIDEvent.HIDDelay + nil, // 126: idb.ConnectRequest.MetadataEntry + (*RecordRequest_Start)(nil), // 127: idb.RecordRequest.Start + (*RecordRequest_Stop)(nil), // 128: idb.RecordRequest.Stop + (*VideoStreamRequest_Start)(nil), // 129: idb.VideoStreamRequest.Start + (*VideoStreamRequest_Stop)(nil), // 130: idb.VideoStreamRequest.Stop + (*LaunchRequest_Start)(nil), // 131: idb.LaunchRequest.Start + (*LaunchRequest_Stop)(nil), // 132: idb.LaunchRequest.Stop + nil, // 133: idb.LaunchRequest.Start.EnvEntry + (*InstrumentsRunRequest_InstrumentsTimings)(nil), // 134: idb.InstrumentsRunRequest.InstrumentsTimings + (*InstrumentsRunRequest_Start)(nil), // 135: idb.InstrumentsRunRequest.Start + (*InstrumentsRunRequest_Stop)(nil), // 136: idb.InstrumentsRunRequest.Stop + nil, // 137: idb.InstrumentsRunRequest.Start.EnvironmentEntry + (*XctraceRecordRequest_LauchProcess)(nil), // 138: idb.XctraceRecordRequest.LauchProcess + (*XctraceRecordRequest_Target)(nil), // 139: idb.XctraceRecordRequest.Target + (*XctraceRecordRequest_Start)(nil), // 140: idb.XctraceRecordRequest.Start + (*XctraceRecordRequest_Stop)(nil), // 141: idb.XctraceRecordRequest.Stop + nil, // 142: idb.XctraceRecordRequest.LauchProcess.ProcessEnvEntry + (*DebugServerRequest_Start)(nil), // 143: idb.DebugServerRequest.Start + (*DebugServerRequest_Status)(nil), // 144: idb.DebugServerRequest.Status + (*DebugServerRequest_Stop)(nil), // 145: idb.DebugServerRequest.Stop + (*DebugServerRequest_Pipe)(nil), // 146: idb.DebugServerRequest.Pipe + (*DebugServerResponse_Pipe)(nil), // 147: idb.DebugServerResponse.Pipe + (*DebugServerResponse_Status)(nil), // 148: idb.DebugServerResponse.Status + (*XctestListBundlesResponse_Bundles)(nil), // 149: idb.XctestListBundlesResponse.Bundles + (*XctestRunRequest_Logic)(nil), // 150: idb.XctestRunRequest.Logic + (*XctestRunRequest_Application)(nil), // 151: idb.XctestRunRequest.Application + (*XctestRunRequest_UI)(nil), // 152: idb.XctestRunRequest.UI + (*XctestRunRequest_Mode)(nil), // 153: idb.XctestRunRequest.Mode + (*XctestRunRequest_CodeCoverage)(nil), // 154: idb.XctestRunRequest.CodeCoverage + nil, // 155: idb.XctestRunRequest.EnvironmentEntry + (*XctestRunResponse_TestRunInfo)(nil), // 156: idb.XctestRunResponse.TestRunInfo + (*XctestRunResponse_TestRunInfo_TestRunFailureInfo)(nil), // 157: idb.XctestRunResponse.TestRunInfo.TestRunFailureInfo + (*XctestRunResponse_TestRunInfo_TestAttachment)(nil), // 158: idb.XctestRunResponse.TestRunInfo.TestAttachment + (*XctestRunResponse_TestRunInfo_TestActivity)(nil), // 159: idb.XctestRunResponse.TestRunInfo.TestActivity + (*PushRequest_Inner)(nil), // 160: idb.PushRequest.Inner + (*TailRequest_Start)(nil), // 161: idb.TailRequest.Start + (*TailRequest_Stop)(nil), // 162: idb.TailRequest.Stop + (*DapRequest_Start)(nil), // 163: idb.DapRequest.Start + (*DapRequest_Pipe)(nil), // 164: idb.DapRequest.Pipe + (*DapRequest_Stop)(nil), // 165: idb.DapRequest.Stop + (*DapResponse_Event)(nil), // 166: idb.DapResponse.Event + (*DapResponse_Pipe)(nil), // 167: idb.DapResponse.Pipe +} +var file_idb_proto_depIdxs = []int32{ + 1, // 0: idb.Payload.compression:type_name -> idb.Payload.Compression + 2, // 1: idb.ProcessOutput.interface:type_name -> idb.ProcessOutput.Interface + 116, // 2: idb.SettingRequest.hardwareKeyboard:type_name -> idb.SettingRequest.HardwareKeyboard + 117, // 3: idb.SettingRequest.stringSetting:type_name -> idb.SettingRequest.StringSetting + 0, // 4: idb.GetSettingRequest.setting:type_name -> idb.Setting + 0, // 5: idb.ListSettingRequest.setting:type_name -> idb.Setting + 30, // 6: idb.ListAppsResponse.apps:type_name -> idb.InstalledAppInfo + 3, // 7: idb.InstalledAppInfo.process_state:type_name -> idb.InstalledAppInfo.AppProcessState + 4, // 8: idb.InstallRequest.destination:type_name -> idb.InstallRequest.Destination + 19, // 9: idb.InstallRequest.payload:type_name -> idb.Payload + 118, // 10: idb.InstallRequest.link_dsym_to_bundle:type_name -> idb.InstallRequest.LinkDsymToBundle + 37, // 11: idb.AccessibilityInfoRequest.point:type_name -> idb.Point + 6, // 12: idb.AccessibilityInfoRequest.format:type_name -> idb.AccessibilityInfoRequest.Format + 7, // 13: idb.ApproveRequest.permissions:type_name -> idb.ApproveRequest.Permission + 8, // 14: idb.RevokeRequest.permissions:type_name -> idb.RevokeRequest.Permission + 47, // 15: idb.SetLocationRequest.location:type_name -> idb.Location + 19, // 16: idb.ContactsUpdateRequest.payload:type_name -> idb.Payload + 64, // 17: idb.TargetDescriptionResponse.target_description:type_name -> idb.TargetDescription + 21, // 18: idb.TargetDescriptionResponse.companion:type_name -> idb.CompanionInfo + 123, // 19: idb.HIDEvent.press:type_name -> idb.HIDEvent.HIDPress + 124, // 20: idb.HIDEvent.swipe:type_name -> idb.HIDEvent.HIDSwipe + 125, // 21: idb.HIDEvent.delay:type_name -> idb.HIDEvent.HIDDelay + 126, // 22: idb.ConnectRequest.metadata:type_name -> idb.ConnectRequest.MetadataEntry + 21, // 23: idb.ConnectResponse.companion:type_name -> idb.CompanionInfo + 63, // 24: idb.TargetDescription.screen_dimensions:type_name -> idb.ScreenDimensions + 11, // 25: idb.LogRequest.source:type_name -> idb.LogRequest.Source + 127, // 26: idb.RecordRequest.start:type_name -> idb.RecordRequest.Start + 128, // 27: idb.RecordRequest.stop:type_name -> idb.RecordRequest.Stop + 19, // 28: idb.RecordResponse.payload:type_name -> idb.Payload + 129, // 29: idb.VideoStreamRequest.start:type_name -> idb.VideoStreamRequest.Start + 130, // 30: idb.VideoStreamRequest.stop:type_name -> idb.VideoStreamRequest.Stop + 19, // 31: idb.VideoStreamResponse.payload:type_name -> idb.Payload + 131, // 32: idb.LaunchRequest.start:type_name -> idb.LaunchRequest.Start + 132, // 33: idb.LaunchRequest.stop:type_name -> idb.LaunchRequest.Stop + 20, // 34: idb.LaunchResponse.output:type_name -> idb.ProcessOutput + 109, // 35: idb.LaunchResponse.debugger:type_name -> idb.DebuggerInfo + 19, // 36: idb.AddMediaRequest.payload:type_name -> idb.Payload + 135, // 37: idb.InstrumentsRunRequest.start:type_name -> idb.InstrumentsRunRequest.Start + 136, // 38: idb.InstrumentsRunRequest.stop:type_name -> idb.InstrumentsRunRequest.Stop + 19, // 39: idb.InstrumentsRunResponse.payload:type_name -> idb.Payload + 13, // 40: idb.InstrumentsRunResponse.state:type_name -> idb.InstrumentsRunResponse.State + 140, // 41: idb.XctraceRecordRequest.start:type_name -> idb.XctraceRecordRequest.Start + 141, // 42: idb.XctraceRecordRequest.stop:type_name -> idb.XctraceRecordRequest.Stop + 19, // 43: idb.XctraceRecordResponse.payload:type_name -> idb.Payload + 14, // 44: idb.XctraceRecordResponse.state:type_name -> idb.XctraceRecordResponse.State + 143, // 45: idb.DebugServerRequest.start:type_name -> idb.DebugServerRequest.Start + 145, // 46: idb.DebugServerRequest.stop:type_name -> idb.DebugServerRequest.Stop + 144, // 47: idb.DebugServerRequest.status:type_name -> idb.DebugServerRequest.Status + 146, // 48: idb.DebugServerRequest.pipe:type_name -> idb.DebugServerRequest.Pipe + 148, // 49: idb.DebugServerResponse.status:type_name -> idb.DebugServerResponse.Status + 147, // 50: idb.DebugServerResponse.pipe:type_name -> idb.DebugServerResponse.Pipe + 83, // 51: idb.CrashLogResponse.list:type_name -> idb.CrashLogInfo + 83, // 52: idb.CrashShowResponse.info:type_name -> idb.CrashLogInfo + 149, // 53: idb.XctestListBundlesResponse.bundles:type_name -> idb.XctestListBundlesResponse.Bundles + 153, // 54: idb.XctestRunRequest.mode:type_name -> idb.XctestRunRequest.Mode + 155, // 55: idb.XctestRunRequest.environment:type_name -> idb.XctestRunRequest.EnvironmentEntry + 154, // 56: idb.XctestRunRequest.code_coverage:type_name -> idb.XctestRunRequest.CodeCoverage + 16, // 57: idb.XctestRunResponse.status:type_name -> idb.XctestRunResponse.Status + 156, // 58: idb.XctestRunResponse.results:type_name -> idb.XctestRunResponse.TestRunInfo + 19, // 59: idb.XctestRunResponse.result_bundle:type_name -> idb.Payload + 19, // 60: idb.XctestRunResponse.log_directory:type_name -> idb.Payload + 109, // 61: idb.XctestRunResponse.debugger:type_name -> idb.DebuggerInfo + 19, // 62: idb.XctestRunResponse.code_coverage_data:type_name -> idb.Payload + 18, // 63: idb.FileContainer.kind:type_name -> idb.FileContainer.Kind + 93, // 64: idb.FileListing.parent:type_name -> idb.FileInfo + 93, // 65: idb.FileListing.files:type_name -> idb.FileInfo + 93, // 66: idb.LsResponse.files:type_name -> idb.FileInfo + 94, // 67: idb.LsResponse.listings:type_name -> idb.FileListing + 92, // 68: idb.LsRequest.container:type_name -> idb.FileContainer + 92, // 69: idb.MkdirRequest.container:type_name -> idb.FileContainer + 92, // 70: idb.MvRequest.container:type_name -> idb.FileContainer + 92, // 71: idb.RmRequest.container:type_name -> idb.FileContainer + 19, // 72: idb.PushRequest.payload:type_name -> idb.Payload + 160, // 73: idb.PushRequest.inner:type_name -> idb.PushRequest.Inner + 92, // 74: idb.PullRequest.container:type_name -> idb.FileContainer + 19, // 75: idb.PullResponse.payload:type_name -> idb.Payload + 161, // 76: idb.TailRequest.start:type_name -> idb.TailRequest.Start + 162, // 77: idb.TailRequest.stop:type_name -> idb.TailRequest.Stop + 163, // 78: idb.DapRequest.start:type_name -> idb.DapRequest.Start + 164, // 79: idb.DapRequest.pipe:type_name -> idb.DapRequest.Pipe + 165, // 80: idb.DapRequest.stop:type_name -> idb.DapRequest.Stop + 166, // 81: idb.DapResponse.started:type_name -> idb.DapResponse.Event + 167, // 82: idb.DapResponse.stdout:type_name -> idb.DapResponse.Pipe + 166, // 83: idb.DapResponse.stopped:type_name -> idb.DapResponse.Event + 0, // 84: idb.SettingRequest.StringSetting.setting:type_name -> idb.Setting + 5, // 85: idb.InstallRequest.LinkDsymToBundle.bundle_type:type_name -> idb.InstallRequest.LinkDsymToBundle.BundleType + 37, // 86: idb.HIDEvent.HIDTouch.point:type_name -> idb.Point + 10, // 87: idb.HIDEvent.HIDButton.button:type_name -> idb.HIDEvent.HIDButtonType + 119, // 88: idb.HIDEvent.HIDPressAction.touch:type_name -> idb.HIDEvent.HIDTouch + 120, // 89: idb.HIDEvent.HIDPressAction.button:type_name -> idb.HIDEvent.HIDButton + 121, // 90: idb.HIDEvent.HIDPressAction.key:type_name -> idb.HIDEvent.HIDKey + 122, // 91: idb.HIDEvent.HIDPress.action:type_name -> idb.HIDEvent.HIDPressAction + 9, // 92: idb.HIDEvent.HIDPress.direction:type_name -> idb.HIDEvent.HIDDirection + 37, // 93: idb.HIDEvent.HIDSwipe.start:type_name -> idb.Point + 37, // 94: idb.HIDEvent.HIDSwipe.end:type_name -> idb.Point + 12, // 95: idb.VideoStreamRequest.Start.format:type_name -> idb.VideoStreamRequest.Format + 133, // 96: idb.LaunchRequest.Start.env:type_name -> idb.LaunchRequest.Start.EnvEntry + 137, // 97: idb.InstrumentsRunRequest.Start.environment:type_name -> idb.InstrumentsRunRequest.Start.EnvironmentEntry + 134, // 98: idb.InstrumentsRunRequest.Start.timings:type_name -> idb.InstrumentsRunRequest.InstrumentsTimings + 142, // 99: idb.XctraceRecordRequest.LauchProcess.process_env:type_name -> idb.XctraceRecordRequest.LauchProcess.ProcessEnvEntry + 138, // 100: idb.XctraceRecordRequest.Target.launch_process:type_name -> idb.XctraceRecordRequest.LauchProcess + 139, // 101: idb.XctraceRecordRequest.Start.target:type_name -> idb.XctraceRecordRequest.Target + 150, // 102: idb.XctestRunRequest.Mode.logic:type_name -> idb.XctestRunRequest.Logic + 151, // 103: idb.XctestRunRequest.Mode.application:type_name -> idb.XctestRunRequest.Application + 152, // 104: idb.XctestRunRequest.Mode.ui:type_name -> idb.XctestRunRequest.UI + 15, // 105: idb.XctestRunRequest.CodeCoverage.format:type_name -> idb.XctestRunRequest.CodeCoverage.Format + 17, // 106: idb.XctestRunResponse.TestRunInfo.status:type_name -> idb.XctestRunResponse.TestRunInfo.Status + 157, // 107: idb.XctestRunResponse.TestRunInfo.failure_info:type_name -> idb.XctestRunResponse.TestRunInfo.TestRunFailureInfo + 159, // 108: idb.XctestRunResponse.TestRunInfo.activityLogs:type_name -> idb.XctestRunResponse.TestRunInfo.TestActivity + 158, // 109: idb.XctestRunResponse.TestRunInfo.TestActivity.attachments:type_name -> idb.XctestRunResponse.TestRunInfo.TestAttachment + 159, // 110: idb.XctestRunResponse.TestRunInfo.TestActivity.sub_activities:type_name -> idb.XctestRunResponse.TestRunInfo.TestActivity + 92, // 111: idb.PushRequest.Inner.container:type_name -> idb.FileContainer + 92, // 112: idb.TailRequest.Start.container:type_name -> idb.FileContainer + 61, // 113: idb.CompanionService.connect:input_type -> idb.ConnectRequest + 79, // 114: idb.CompanionService.debugserver:input_type -> idb.DebugServerRequest + 112, // 115: idb.CompanionService.dap:input_type -> idb.DapRequest + 57, // 116: idb.CompanionService.describe:input_type -> idb.TargetDescriptionRequest + 31, // 117: idb.CompanionService.install:input_type -> idb.InstallRequest + 75, // 118: idb.CompanionService.instruments_run:input_type -> idb.InstrumentsRunRequest + 65, // 119: idb.CompanionService.log:input_type -> idb.LogRequest + 77, // 120: idb.CompanionService.xctrace_record:input_type -> idb.XctraceRecordRequest + 38, // 121: idb.CompanionService.accessibility_info:input_type -> idb.AccessibilityInfoRequest + 35, // 122: idb.CompanionService.focus:input_type -> idb.FocusRequest + 59, // 123: idb.CompanionService.hid:input_type -> idb.HIDEvent + 53, // 124: idb.CompanionService.open_url:input_type -> idb.OpenUrlRequest + 46, // 125: idb.CompanionService.set_location:input_type -> idb.SetLocationRequest + 110, // 126: idb.CompanionService.send_notification:input_type -> idb.SendNotificationRequest + 114, // 127: idb.CompanionService.simulate_memory_warning:input_type -> idb.SimulateMemoryWarningRequest + 40, // 128: idb.CompanionService.approve:input_type -> idb.ApproveRequest + 42, // 129: idb.CompanionService.revoke:input_type -> idb.RevokeRequest + 44, // 130: idb.CompanionService.clear_keychain:input_type -> idb.ClearKeychainRequest + 55, // 131: idb.CompanionService.contacts_update:input_type -> idb.ContactsUpdateRequest + 22, // 132: idb.CompanionService.setting:input_type -> idb.SettingRequest + 24, // 133: idb.CompanionService.get_setting:input_type -> idb.GetSettingRequest + 26, // 134: idb.CompanionService.list_settings:input_type -> idb.ListSettingRequest + 71, // 135: idb.CompanionService.launch:input_type -> idb.LaunchRequest + 28, // 136: idb.CompanionService.list_apps:input_type -> idb.ListAppsRequest + 51, // 137: idb.CompanionService.terminate:input_type -> idb.TerminateRequest + 49, // 138: idb.CompanionService.uninstall:input_type -> idb.UninstallRequest + 73, // 139: idb.CompanionService.add_media:input_type -> idb.AddMediaRequest + 67, // 140: idb.CompanionService.record:input_type -> idb.RecordRequest + 33, // 141: idb.CompanionService.screenshot:input_type -> idb.ScreenshotRequest + 69, // 142: idb.CompanionService.video_stream:input_type -> idb.VideoStreamRequest + 85, // 143: idb.CompanionService.crash_delete:input_type -> idb.CrashLogQuery + 85, // 144: idb.CompanionService.crash_list:input_type -> idb.CrashLogQuery + 81, // 145: idb.CompanionService.crash_show:input_type -> idb.CrashShowRequest + 86, // 146: idb.CompanionService.xctest_list_bundles:input_type -> idb.XctestListBundlesRequest + 88, // 147: idb.CompanionService.xctest_list_tests:input_type -> idb.XctestListTestsRequest + 90, // 148: idb.CompanionService.xctest_run:input_type -> idb.XctestRunRequest + 96, // 149: idb.CompanionService.ls:input_type -> idb.LsRequest + 97, // 150: idb.CompanionService.mkdir:input_type -> idb.MkdirRequest + 99, // 151: idb.CompanionService.mv:input_type -> idb.MvRequest + 101, // 152: idb.CompanionService.rm:input_type -> idb.RmRequest + 105, // 153: idb.CompanionService.pull:input_type -> idb.PullRequest + 103, // 154: idb.CompanionService.push:input_type -> idb.PushRequest + 107, // 155: idb.CompanionService.tail:input_type -> idb.TailRequest + 62, // 156: idb.CompanionService.connect:output_type -> idb.ConnectResponse + 80, // 157: idb.CompanionService.debugserver:output_type -> idb.DebugServerResponse + 113, // 158: idb.CompanionService.dap:output_type -> idb.DapResponse + 58, // 159: idb.CompanionService.describe:output_type -> idb.TargetDescriptionResponse + 32, // 160: idb.CompanionService.install:output_type -> idb.InstallResponse + 76, // 161: idb.CompanionService.instruments_run:output_type -> idb.InstrumentsRunResponse + 66, // 162: idb.CompanionService.log:output_type -> idb.LogResponse + 78, // 163: idb.CompanionService.xctrace_record:output_type -> idb.XctraceRecordResponse + 39, // 164: idb.CompanionService.accessibility_info:output_type -> idb.AccessibilityInfoResponse + 36, // 165: idb.CompanionService.focus:output_type -> idb.FocusResponse + 60, // 166: idb.CompanionService.hid:output_type -> idb.HIDResponse + 53, // 167: idb.CompanionService.open_url:output_type -> idb.OpenUrlRequest + 48, // 168: idb.CompanionService.set_location:output_type -> idb.SetLocationResponse + 111, // 169: idb.CompanionService.send_notification:output_type -> idb.SendNotificationResponse + 115, // 170: idb.CompanionService.simulate_memory_warning:output_type -> idb.SimulateMemoryWarningResponse + 41, // 171: idb.CompanionService.approve:output_type -> idb.ApproveResponse + 43, // 172: idb.CompanionService.revoke:output_type -> idb.RevokeResponse + 45, // 173: idb.CompanionService.clear_keychain:output_type -> idb.ClearKeychainResponse + 56, // 174: idb.CompanionService.contacts_update:output_type -> idb.ContactsUpdateResponse + 23, // 175: idb.CompanionService.setting:output_type -> idb.SettingResponse + 25, // 176: idb.CompanionService.get_setting:output_type -> idb.GetSettingResponse + 27, // 177: idb.CompanionService.list_settings:output_type -> idb.ListSettingResponse + 72, // 178: idb.CompanionService.launch:output_type -> idb.LaunchResponse + 29, // 179: idb.CompanionService.list_apps:output_type -> idb.ListAppsResponse + 52, // 180: idb.CompanionService.terminate:output_type -> idb.TerminateResponse + 50, // 181: idb.CompanionService.uninstall:output_type -> idb.UninstallResponse + 74, // 182: idb.CompanionService.add_media:output_type -> idb.AddMediaResponse + 68, // 183: idb.CompanionService.record:output_type -> idb.RecordResponse + 34, // 184: idb.CompanionService.screenshot:output_type -> idb.ScreenshotResponse + 70, // 185: idb.CompanionService.video_stream:output_type -> idb.VideoStreamResponse + 82, // 186: idb.CompanionService.crash_delete:output_type -> idb.CrashLogResponse + 82, // 187: idb.CompanionService.crash_list:output_type -> idb.CrashLogResponse + 84, // 188: idb.CompanionService.crash_show:output_type -> idb.CrashShowResponse + 87, // 189: idb.CompanionService.xctest_list_bundles:output_type -> idb.XctestListBundlesResponse + 89, // 190: idb.CompanionService.xctest_list_tests:output_type -> idb.XctestListTestsResponse + 91, // 191: idb.CompanionService.xctest_run:output_type -> idb.XctestRunResponse + 95, // 192: idb.CompanionService.ls:output_type -> idb.LsResponse + 98, // 193: idb.CompanionService.mkdir:output_type -> idb.MkdirResponse + 100, // 194: idb.CompanionService.mv:output_type -> idb.MvResponse + 102, // 195: idb.CompanionService.rm:output_type -> idb.RmResponse + 106, // 196: idb.CompanionService.pull:output_type -> idb.PullResponse + 104, // 197: idb.CompanionService.push:output_type -> idb.PushResponse + 108, // 198: idb.CompanionService.tail:output_type -> idb.TailResponse + 156, // [156:199] is the sub-list for method output_type + 113, // [113:156] is the sub-list for method input_type + 113, // [113:113] is the sub-list for extension type_name + 113, // [113:113] is the sub-list for extension extendee + 0, // [0:113] is the sub-list for field type_name +} + +func init() { file_idb_proto_init() } +func file_idb_proto_init() { + if File_idb_proto != nil { + return + } + file_idb_proto_msgTypes[0].OneofWrappers = []any{ + (*Payload_FilePath)(nil), + (*Payload_Data)(nil), + (*Payload_Url)(nil), + (*Payload_Compression_)(nil), + } + file_idb_proto_msgTypes[3].OneofWrappers = []any{ + (*SettingRequest_HardwareKeyboard_)(nil), + (*SettingRequest_StringSetting_)(nil), + } + file_idb_proto_msgTypes[12].OneofWrappers = []any{ + (*InstallRequest_Destination_)(nil), + (*InstallRequest_Payload)(nil), + (*InstallRequest_NameHint)(nil), + (*InstallRequest_MakeDebuggable)(nil), + (*InstallRequest_BundleId)(nil), + (*InstallRequest_LinkDsymToBundle_)(nil), + (*InstallRequest_OverrideModificationTime)(nil), + } + file_idb_proto_msgTypes[40].OneofWrappers = []any{ + (*HIDEvent_Press)(nil), + (*HIDEvent_Swipe)(nil), + (*HIDEvent_Delay)(nil), + } + file_idb_proto_msgTypes[48].OneofWrappers = []any{ + (*RecordRequest_Start_)(nil), + (*RecordRequest_Stop_)(nil), + } + file_idb_proto_msgTypes[49].OneofWrappers = []any{ + (*RecordResponse_LogOutput)(nil), + (*RecordResponse_Payload)(nil), + } + file_idb_proto_msgTypes[50].OneofWrappers = []any{ + (*VideoStreamRequest_Start_)(nil), + (*VideoStreamRequest_Stop_)(nil), + } + file_idb_proto_msgTypes[51].OneofWrappers = []any{ + (*VideoStreamResponse_LogOutput)(nil), + (*VideoStreamResponse_Payload)(nil), + } + file_idb_proto_msgTypes[52].OneofWrappers = []any{ + (*LaunchRequest_Start_)(nil), + (*LaunchRequest_Stop_)(nil), + } + file_idb_proto_msgTypes[56].OneofWrappers = []any{ + (*InstrumentsRunRequest_Start_)(nil), + (*InstrumentsRunRequest_Stop_)(nil), + } + file_idb_proto_msgTypes[57].OneofWrappers = []any{ + (*InstrumentsRunResponse_LogOutput)(nil), + (*InstrumentsRunResponse_Payload)(nil), + (*InstrumentsRunResponse_State_)(nil), + } + file_idb_proto_msgTypes[58].OneofWrappers = []any{ + (*XctraceRecordRequest_Start_)(nil), + (*XctraceRecordRequest_Stop_)(nil), + } + file_idb_proto_msgTypes[59].OneofWrappers = []any{ + (*XctraceRecordResponse_Log)(nil), + (*XctraceRecordResponse_Payload)(nil), + (*XctraceRecordResponse_State_)(nil), + } + file_idb_proto_msgTypes[60].OneofWrappers = []any{ + (*DebugServerRequest_Start_)(nil), + (*DebugServerRequest_Stop_)(nil), + (*DebugServerRequest_Status_)(nil), + (*DebugServerRequest_Pipe_)(nil), + } + file_idb_proto_msgTypes[61].OneofWrappers = []any{ + (*DebugServerResponse_Status_)(nil), + (*DebugServerResponse_Pipe_)(nil), + } + file_idb_proto_msgTypes[84].OneofWrappers = []any{ + (*PushRequest_Payload)(nil), + (*PushRequest_Inner_)(nil), + } + file_idb_proto_msgTypes[88].OneofWrappers = []any{ + (*TailRequest_Start_)(nil), + (*TailRequest_Stop_)(nil), + } + file_idb_proto_msgTypes[93].OneofWrappers = []any{ + (*DapRequest_Start_)(nil), + (*DapRequest_Pipe_)(nil), + (*DapRequest_Stop_)(nil), + } + file_idb_proto_msgTypes[94].OneofWrappers = []any{ + (*DapResponse_Started)(nil), + (*DapResponse_Stdout)(nil), + (*DapResponse_Stopped)(nil), + } + file_idb_proto_msgTypes[103].OneofWrappers = []any{ + (*HIDEvent_HIDPressAction_Touch)(nil), + (*HIDEvent_HIDPressAction_Button)(nil), + (*HIDEvent_HIDPressAction_Key)(nil), + } + file_idb_proto_msgTypes[120].OneofWrappers = []any{ + (*XctraceRecordRequest_Target_AllProcesses)(nil), + (*XctraceRecordRequest_Target_ProcessToAttach)(nil), + (*XctraceRecordRequest_Target_LaunchProcess)(nil), + } + file_idb_proto_msgTypes[134].OneofWrappers = []any{ + (*XctestRunRequest_Mode_Logic)(nil), + (*XctestRunRequest_Mode_Application)(nil), + (*XctestRunRequest_Mode_Ui)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_idb_proto_rawDesc), len(file_idb_proto_rawDesc)), + NumEnums: 19, + NumMessages: 149, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_idb_proto_goTypes, + DependencyIndexes: file_idb_proto_depIdxs, + EnumInfos: file_idb_proto_enumTypes, + MessageInfos: file_idb_proto_msgTypes, + }.Build() + File_idb_proto = out.File + file_idb_proto_goTypes = nil + file_idb_proto_depIdxs = nil +} diff --git a/internal/driver/ioscompanion/companionpb/idb_grpc.pb.go b/internal/driver/ioscompanion/companionpb/idb_grpc.pb.go new file mode 100644 index 0000000..8c7a6e4 --- /dev/null +++ b/internal/driver/ioscompanion/companionpb/idb_grpc.pb.go @@ -0,0 +1,1687 @@ +// +// Copyright (c) Meta Platforms, Inc. and affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +// Pinned to the simulator companion v1.1.8 and matches the embedded binary. +// Do NOT regenerate this file from upstream HEAD: HEAD has drifted from the +// pinned binary, so regenerating would produce stubs that no longer match the +// companion this driver speaks to. The only local change versus the upstream +// source is the go_package option below, which targets the committed stub +// package internal/driver/ioscompanion/companionpb. Regenerate the stubs from +// the repository root with: +// buf generate proto/idbcompanion --template proto/idbcompanion/buf.gen.yaml + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.0 +// - protoc (unknown) +// source: idb.proto + +package companionpb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + CompanionService_Connect_FullMethodName = "/idb.CompanionService/connect" + CompanionService_Debugserver_FullMethodName = "/idb.CompanionService/debugserver" + CompanionService_Dap_FullMethodName = "/idb.CompanionService/dap" + CompanionService_Describe_FullMethodName = "/idb.CompanionService/describe" + CompanionService_Install_FullMethodName = "/idb.CompanionService/install" + CompanionService_InstrumentsRun_FullMethodName = "/idb.CompanionService/instruments_run" + CompanionService_Log_FullMethodName = "/idb.CompanionService/log" + CompanionService_XctraceRecord_FullMethodName = "/idb.CompanionService/xctrace_record" + CompanionService_AccessibilityInfo_FullMethodName = "/idb.CompanionService/accessibility_info" + CompanionService_Focus_FullMethodName = "/idb.CompanionService/focus" + CompanionService_Hid_FullMethodName = "/idb.CompanionService/hid" + CompanionService_OpenUrl_FullMethodName = "/idb.CompanionService/open_url" + CompanionService_SetLocation_FullMethodName = "/idb.CompanionService/set_location" + CompanionService_SendNotification_FullMethodName = "/idb.CompanionService/send_notification" + CompanionService_SimulateMemoryWarning_FullMethodName = "/idb.CompanionService/simulate_memory_warning" + CompanionService_Approve_FullMethodName = "/idb.CompanionService/approve" + CompanionService_Revoke_FullMethodName = "/idb.CompanionService/revoke" + CompanionService_ClearKeychain_FullMethodName = "/idb.CompanionService/clear_keychain" + CompanionService_ContactsUpdate_FullMethodName = "/idb.CompanionService/contacts_update" + CompanionService_Setting_FullMethodName = "/idb.CompanionService/setting" + CompanionService_GetSetting_FullMethodName = "/idb.CompanionService/get_setting" + CompanionService_ListSettings_FullMethodName = "/idb.CompanionService/list_settings" + CompanionService_Launch_FullMethodName = "/idb.CompanionService/launch" + CompanionService_ListApps_FullMethodName = "/idb.CompanionService/list_apps" + CompanionService_Terminate_FullMethodName = "/idb.CompanionService/terminate" + CompanionService_Uninstall_FullMethodName = "/idb.CompanionService/uninstall" + CompanionService_AddMedia_FullMethodName = "/idb.CompanionService/add_media" + CompanionService_Record_FullMethodName = "/idb.CompanionService/record" + CompanionService_Screenshot_FullMethodName = "/idb.CompanionService/screenshot" + CompanionService_VideoStream_FullMethodName = "/idb.CompanionService/video_stream" + CompanionService_CrashDelete_FullMethodName = "/idb.CompanionService/crash_delete" + CompanionService_CrashList_FullMethodName = "/idb.CompanionService/crash_list" + CompanionService_CrashShow_FullMethodName = "/idb.CompanionService/crash_show" + CompanionService_XctestListBundles_FullMethodName = "/idb.CompanionService/xctest_list_bundles" + CompanionService_XctestListTests_FullMethodName = "/idb.CompanionService/xctest_list_tests" + CompanionService_XctestRun_FullMethodName = "/idb.CompanionService/xctest_run" + CompanionService_Ls_FullMethodName = "/idb.CompanionService/ls" + CompanionService_Mkdir_FullMethodName = "/idb.CompanionService/mkdir" + CompanionService_Mv_FullMethodName = "/idb.CompanionService/mv" + CompanionService_Rm_FullMethodName = "/idb.CompanionService/rm" + CompanionService_Pull_FullMethodName = "/idb.CompanionService/pull" + CompanionService_Push_FullMethodName = "/idb.CompanionService/push" + CompanionService_Tail_FullMethodName = "/idb.CompanionService/tail" +) + +// CompanionServiceClient is the client API for CompanionService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// The idb companion service definition. +type CompanionServiceClient interface { + // Management + Connect(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (*ConnectResponse, error) + Debugserver(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[DebugServerRequest, DebugServerResponse], error) + Dap(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[DapRequest, DapResponse], error) + Describe(ctx context.Context, in *TargetDescriptionRequest, opts ...grpc.CallOption) (*TargetDescriptionResponse, error) + Install(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[InstallRequest, InstallResponse], error) + InstrumentsRun(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[InstrumentsRunRequest, InstrumentsRunResponse], error) + Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogResponse], error) + XctraceRecord(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[XctraceRecordRequest, XctraceRecordResponse], error) + // Interaction + AccessibilityInfo(ctx context.Context, in *AccessibilityInfoRequest, opts ...grpc.CallOption) (*AccessibilityInfoResponse, error) + Focus(ctx context.Context, in *FocusRequest, opts ...grpc.CallOption) (*FocusResponse, error) + Hid(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[HIDEvent, HIDResponse], error) + OpenUrl(ctx context.Context, in *OpenUrlRequest, opts ...grpc.CallOption) (*OpenUrlRequest, error) + SetLocation(ctx context.Context, in *SetLocationRequest, opts ...grpc.CallOption) (*SetLocationResponse, error) + SendNotification(ctx context.Context, in *SendNotificationRequest, opts ...grpc.CallOption) (*SendNotificationResponse, error) + SimulateMemoryWarning(ctx context.Context, in *SimulateMemoryWarningRequest, opts ...grpc.CallOption) (*SimulateMemoryWarningResponse, error) + // Settings + Approve(ctx context.Context, in *ApproveRequest, opts ...grpc.CallOption) (*ApproveResponse, error) + Revoke(ctx context.Context, in *RevokeRequest, opts ...grpc.CallOption) (*RevokeResponse, error) + ClearKeychain(ctx context.Context, in *ClearKeychainRequest, opts ...grpc.CallOption) (*ClearKeychainResponse, error) + ContactsUpdate(ctx context.Context, in *ContactsUpdateRequest, opts ...grpc.CallOption) (*ContactsUpdateResponse, error) + Setting(ctx context.Context, in *SettingRequest, opts ...grpc.CallOption) (*SettingResponse, error) + GetSetting(ctx context.Context, in *GetSettingRequest, opts ...grpc.CallOption) (*GetSettingResponse, error) + ListSettings(ctx context.Context, in *ListSettingRequest, opts ...grpc.CallOption) (*ListSettingResponse, error) + // App + Launch(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[LaunchRequest, LaunchResponse], error) + ListApps(ctx context.Context, in *ListAppsRequest, opts ...grpc.CallOption) (*ListAppsResponse, error) + Terminate(ctx context.Context, in *TerminateRequest, opts ...grpc.CallOption) (*TerminateResponse, error) + Uninstall(ctx context.Context, in *UninstallRequest, opts ...grpc.CallOption) (*UninstallResponse, error) + // Video/Audio + AddMedia(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[AddMediaRequest, AddMediaResponse], error) + Record(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[RecordRequest, RecordResponse], error) + Screenshot(ctx context.Context, in *ScreenshotRequest, opts ...grpc.CallOption) (*ScreenshotResponse, error) + VideoStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[VideoStreamRequest, VideoStreamResponse], error) + // Crash Operations + CrashDelete(ctx context.Context, in *CrashLogQuery, opts ...grpc.CallOption) (*CrashLogResponse, error) + CrashList(ctx context.Context, in *CrashLogQuery, opts ...grpc.CallOption) (*CrashLogResponse, error) + CrashShow(ctx context.Context, in *CrashShowRequest, opts ...grpc.CallOption) (*CrashShowResponse, error) + // xctest operations + XctestListBundles(ctx context.Context, in *XctestListBundlesRequest, opts ...grpc.CallOption) (*XctestListBundlesResponse, error) + XctestListTests(ctx context.Context, in *XctestListTestsRequest, opts ...grpc.CallOption) (*XctestListTestsResponse, error) + XctestRun(ctx context.Context, in *XctestRunRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[XctestRunResponse], error) + // File Operations + Ls(ctx context.Context, in *LsRequest, opts ...grpc.CallOption) (*LsResponse, error) + Mkdir(ctx context.Context, in *MkdirRequest, opts ...grpc.CallOption) (*MkdirResponse, error) + Mv(ctx context.Context, in *MvRequest, opts ...grpc.CallOption) (*MvResponse, error) + Rm(ctx context.Context, in *RmRequest, opts ...grpc.CallOption) (*RmResponse, error) + Pull(ctx context.Context, in *PullRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PullResponse], error) + Push(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[PushRequest, PushResponse], error) + Tail(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TailRequest, TailResponse], error) +} + +type companionServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCompanionServiceClient(cc grpc.ClientConnInterface) CompanionServiceClient { + return &companionServiceClient{cc} +} + +func (c *companionServiceClient) Connect(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (*ConnectResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ConnectResponse) + err := c.cc.Invoke(ctx, CompanionService_Connect_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Debugserver(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[DebugServerRequest, DebugServerResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[0], CompanionService_Debugserver_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[DebugServerRequest, DebugServerResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_DebugserverClient = grpc.BidiStreamingClient[DebugServerRequest, DebugServerResponse] + +func (c *companionServiceClient) Dap(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[DapRequest, DapResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[1], CompanionService_Dap_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[DapRequest, DapResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_DapClient = grpc.BidiStreamingClient[DapRequest, DapResponse] + +func (c *companionServiceClient) Describe(ctx context.Context, in *TargetDescriptionRequest, opts ...grpc.CallOption) (*TargetDescriptionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TargetDescriptionResponse) + err := c.cc.Invoke(ctx, CompanionService_Describe_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Install(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[InstallRequest, InstallResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[2], CompanionService_Install_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[InstallRequest, InstallResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_InstallClient = grpc.BidiStreamingClient[InstallRequest, InstallResponse] + +func (c *companionServiceClient) InstrumentsRun(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[InstrumentsRunRequest, InstrumentsRunResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[3], CompanionService_InstrumentsRun_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[InstrumentsRunRequest, InstrumentsRunResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_InstrumentsRunClient = grpc.BidiStreamingClient[InstrumentsRunRequest, InstrumentsRunResponse] + +func (c *companionServiceClient) Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[4], CompanionService_Log_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[LogRequest, LogResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_LogClient = grpc.ServerStreamingClient[LogResponse] + +func (c *companionServiceClient) XctraceRecord(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[XctraceRecordRequest, XctraceRecordResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[5], CompanionService_XctraceRecord_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[XctraceRecordRequest, XctraceRecordResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_XctraceRecordClient = grpc.BidiStreamingClient[XctraceRecordRequest, XctraceRecordResponse] + +func (c *companionServiceClient) AccessibilityInfo(ctx context.Context, in *AccessibilityInfoRequest, opts ...grpc.CallOption) (*AccessibilityInfoResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AccessibilityInfoResponse) + err := c.cc.Invoke(ctx, CompanionService_AccessibilityInfo_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Focus(ctx context.Context, in *FocusRequest, opts ...grpc.CallOption) (*FocusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(FocusResponse) + err := c.cc.Invoke(ctx, CompanionService_Focus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Hid(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[HIDEvent, HIDResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[6], CompanionService_Hid_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[HIDEvent, HIDResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_HidClient = grpc.ClientStreamingClient[HIDEvent, HIDResponse] + +func (c *companionServiceClient) OpenUrl(ctx context.Context, in *OpenUrlRequest, opts ...grpc.CallOption) (*OpenUrlRequest, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(OpenUrlRequest) + err := c.cc.Invoke(ctx, CompanionService_OpenUrl_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) SetLocation(ctx context.Context, in *SetLocationRequest, opts ...grpc.CallOption) (*SetLocationResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SetLocationResponse) + err := c.cc.Invoke(ctx, CompanionService_SetLocation_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) SendNotification(ctx context.Context, in *SendNotificationRequest, opts ...grpc.CallOption) (*SendNotificationResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SendNotificationResponse) + err := c.cc.Invoke(ctx, CompanionService_SendNotification_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) SimulateMemoryWarning(ctx context.Context, in *SimulateMemoryWarningRequest, opts ...grpc.CallOption) (*SimulateMemoryWarningResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SimulateMemoryWarningResponse) + err := c.cc.Invoke(ctx, CompanionService_SimulateMemoryWarning_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Approve(ctx context.Context, in *ApproveRequest, opts ...grpc.CallOption) (*ApproveResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ApproveResponse) + err := c.cc.Invoke(ctx, CompanionService_Approve_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Revoke(ctx context.Context, in *RevokeRequest, opts ...grpc.CallOption) (*RevokeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RevokeResponse) + err := c.cc.Invoke(ctx, CompanionService_Revoke_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) ClearKeychain(ctx context.Context, in *ClearKeychainRequest, opts ...grpc.CallOption) (*ClearKeychainResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ClearKeychainResponse) + err := c.cc.Invoke(ctx, CompanionService_ClearKeychain_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) ContactsUpdate(ctx context.Context, in *ContactsUpdateRequest, opts ...grpc.CallOption) (*ContactsUpdateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ContactsUpdateResponse) + err := c.cc.Invoke(ctx, CompanionService_ContactsUpdate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Setting(ctx context.Context, in *SettingRequest, opts ...grpc.CallOption) (*SettingResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SettingResponse) + err := c.cc.Invoke(ctx, CompanionService_Setting_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) GetSetting(ctx context.Context, in *GetSettingRequest, opts ...grpc.CallOption) (*GetSettingResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSettingResponse) + err := c.cc.Invoke(ctx, CompanionService_GetSetting_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) ListSettings(ctx context.Context, in *ListSettingRequest, opts ...grpc.CallOption) (*ListSettingResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSettingResponse) + err := c.cc.Invoke(ctx, CompanionService_ListSettings_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Launch(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[LaunchRequest, LaunchResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[7], CompanionService_Launch_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[LaunchRequest, LaunchResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_LaunchClient = grpc.BidiStreamingClient[LaunchRequest, LaunchResponse] + +func (c *companionServiceClient) ListApps(ctx context.Context, in *ListAppsRequest, opts ...grpc.CallOption) (*ListAppsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListAppsResponse) + err := c.cc.Invoke(ctx, CompanionService_ListApps_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Terminate(ctx context.Context, in *TerminateRequest, opts ...grpc.CallOption) (*TerminateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TerminateResponse) + err := c.cc.Invoke(ctx, CompanionService_Terminate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Uninstall(ctx context.Context, in *UninstallRequest, opts ...grpc.CallOption) (*UninstallResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UninstallResponse) + err := c.cc.Invoke(ctx, CompanionService_Uninstall_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) AddMedia(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[AddMediaRequest, AddMediaResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[8], CompanionService_AddMedia_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[AddMediaRequest, AddMediaResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_AddMediaClient = grpc.ClientStreamingClient[AddMediaRequest, AddMediaResponse] + +func (c *companionServiceClient) Record(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[RecordRequest, RecordResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[9], CompanionService_Record_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[RecordRequest, RecordResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_RecordClient = grpc.BidiStreamingClient[RecordRequest, RecordResponse] + +func (c *companionServiceClient) Screenshot(ctx context.Context, in *ScreenshotRequest, opts ...grpc.CallOption) (*ScreenshotResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ScreenshotResponse) + err := c.cc.Invoke(ctx, CompanionService_Screenshot_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) VideoStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[VideoStreamRequest, VideoStreamResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[10], CompanionService_VideoStream_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[VideoStreamRequest, VideoStreamResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_VideoStreamClient = grpc.BidiStreamingClient[VideoStreamRequest, VideoStreamResponse] + +func (c *companionServiceClient) CrashDelete(ctx context.Context, in *CrashLogQuery, opts ...grpc.CallOption) (*CrashLogResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CrashLogResponse) + err := c.cc.Invoke(ctx, CompanionService_CrashDelete_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) CrashList(ctx context.Context, in *CrashLogQuery, opts ...grpc.CallOption) (*CrashLogResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CrashLogResponse) + err := c.cc.Invoke(ctx, CompanionService_CrashList_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) CrashShow(ctx context.Context, in *CrashShowRequest, opts ...grpc.CallOption) (*CrashShowResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CrashShowResponse) + err := c.cc.Invoke(ctx, CompanionService_CrashShow_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) XctestListBundles(ctx context.Context, in *XctestListBundlesRequest, opts ...grpc.CallOption) (*XctestListBundlesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(XctestListBundlesResponse) + err := c.cc.Invoke(ctx, CompanionService_XctestListBundles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) XctestListTests(ctx context.Context, in *XctestListTestsRequest, opts ...grpc.CallOption) (*XctestListTestsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(XctestListTestsResponse) + err := c.cc.Invoke(ctx, CompanionService_XctestListTests_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) XctestRun(ctx context.Context, in *XctestRunRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[XctestRunResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[11], CompanionService_XctestRun_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[XctestRunRequest, XctestRunResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_XctestRunClient = grpc.ServerStreamingClient[XctestRunResponse] + +func (c *companionServiceClient) Ls(ctx context.Context, in *LsRequest, opts ...grpc.CallOption) (*LsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LsResponse) + err := c.cc.Invoke(ctx, CompanionService_Ls_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Mkdir(ctx context.Context, in *MkdirRequest, opts ...grpc.CallOption) (*MkdirResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MkdirResponse) + err := c.cc.Invoke(ctx, CompanionService_Mkdir_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Mv(ctx context.Context, in *MvRequest, opts ...grpc.CallOption) (*MvResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MvResponse) + err := c.cc.Invoke(ctx, CompanionService_Mv_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Rm(ctx context.Context, in *RmRequest, opts ...grpc.CallOption) (*RmResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RmResponse) + err := c.cc.Invoke(ctx, CompanionService_Rm_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *companionServiceClient) Pull(ctx context.Context, in *PullRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PullResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[12], CompanionService_Pull_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[PullRequest, PullResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_PullClient = grpc.ServerStreamingClient[PullResponse] + +func (c *companionServiceClient) Push(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[PushRequest, PushResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[13], CompanionService_Push_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[PushRequest, PushResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_PushClient = grpc.ClientStreamingClient[PushRequest, PushResponse] + +func (c *companionServiceClient) Tail(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TailRequest, TailResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CompanionService_ServiceDesc.Streams[14], CompanionService_Tail_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[TailRequest, TailResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_TailClient = grpc.BidiStreamingClient[TailRequest, TailResponse] + +// CompanionServiceServer is the server API for CompanionService service. +// All implementations must embed UnimplementedCompanionServiceServer +// for forward compatibility. +// +// The idb companion service definition. +type CompanionServiceServer interface { + // Management + Connect(context.Context, *ConnectRequest) (*ConnectResponse, error) + Debugserver(grpc.BidiStreamingServer[DebugServerRequest, DebugServerResponse]) error + Dap(grpc.BidiStreamingServer[DapRequest, DapResponse]) error + Describe(context.Context, *TargetDescriptionRequest) (*TargetDescriptionResponse, error) + Install(grpc.BidiStreamingServer[InstallRequest, InstallResponse]) error + InstrumentsRun(grpc.BidiStreamingServer[InstrumentsRunRequest, InstrumentsRunResponse]) error + Log(*LogRequest, grpc.ServerStreamingServer[LogResponse]) error + XctraceRecord(grpc.BidiStreamingServer[XctraceRecordRequest, XctraceRecordResponse]) error + // Interaction + AccessibilityInfo(context.Context, *AccessibilityInfoRequest) (*AccessibilityInfoResponse, error) + Focus(context.Context, *FocusRequest) (*FocusResponse, error) + Hid(grpc.ClientStreamingServer[HIDEvent, HIDResponse]) error + OpenUrl(context.Context, *OpenUrlRequest) (*OpenUrlRequest, error) + SetLocation(context.Context, *SetLocationRequest) (*SetLocationResponse, error) + SendNotification(context.Context, *SendNotificationRequest) (*SendNotificationResponse, error) + SimulateMemoryWarning(context.Context, *SimulateMemoryWarningRequest) (*SimulateMemoryWarningResponse, error) + // Settings + Approve(context.Context, *ApproveRequest) (*ApproveResponse, error) + Revoke(context.Context, *RevokeRequest) (*RevokeResponse, error) + ClearKeychain(context.Context, *ClearKeychainRequest) (*ClearKeychainResponse, error) + ContactsUpdate(context.Context, *ContactsUpdateRequest) (*ContactsUpdateResponse, error) + Setting(context.Context, *SettingRequest) (*SettingResponse, error) + GetSetting(context.Context, *GetSettingRequest) (*GetSettingResponse, error) + ListSettings(context.Context, *ListSettingRequest) (*ListSettingResponse, error) + // App + Launch(grpc.BidiStreamingServer[LaunchRequest, LaunchResponse]) error + ListApps(context.Context, *ListAppsRequest) (*ListAppsResponse, error) + Terminate(context.Context, *TerminateRequest) (*TerminateResponse, error) + Uninstall(context.Context, *UninstallRequest) (*UninstallResponse, error) + // Video/Audio + AddMedia(grpc.ClientStreamingServer[AddMediaRequest, AddMediaResponse]) error + Record(grpc.BidiStreamingServer[RecordRequest, RecordResponse]) error + Screenshot(context.Context, *ScreenshotRequest) (*ScreenshotResponse, error) + VideoStream(grpc.BidiStreamingServer[VideoStreamRequest, VideoStreamResponse]) error + // Crash Operations + CrashDelete(context.Context, *CrashLogQuery) (*CrashLogResponse, error) + CrashList(context.Context, *CrashLogQuery) (*CrashLogResponse, error) + CrashShow(context.Context, *CrashShowRequest) (*CrashShowResponse, error) + // xctest operations + XctestListBundles(context.Context, *XctestListBundlesRequest) (*XctestListBundlesResponse, error) + XctestListTests(context.Context, *XctestListTestsRequest) (*XctestListTestsResponse, error) + XctestRun(*XctestRunRequest, grpc.ServerStreamingServer[XctestRunResponse]) error + // File Operations + Ls(context.Context, *LsRequest) (*LsResponse, error) + Mkdir(context.Context, *MkdirRequest) (*MkdirResponse, error) + Mv(context.Context, *MvRequest) (*MvResponse, error) + Rm(context.Context, *RmRequest) (*RmResponse, error) + Pull(*PullRequest, grpc.ServerStreamingServer[PullResponse]) error + Push(grpc.ClientStreamingServer[PushRequest, PushResponse]) error + Tail(grpc.BidiStreamingServer[TailRequest, TailResponse]) error + mustEmbedUnimplementedCompanionServiceServer() +} + +// UnimplementedCompanionServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCompanionServiceServer struct{} + +func (UnimplementedCompanionServiceServer) Connect(context.Context, *ConnectRequest) (*ConnectResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Connect not implemented") +} +func (UnimplementedCompanionServiceServer) Debugserver(grpc.BidiStreamingServer[DebugServerRequest, DebugServerResponse]) error { + return status.Error(codes.Unimplemented, "method Debugserver not implemented") +} +func (UnimplementedCompanionServiceServer) Dap(grpc.BidiStreamingServer[DapRequest, DapResponse]) error { + return status.Error(codes.Unimplemented, "method Dap not implemented") +} +func (UnimplementedCompanionServiceServer) Describe(context.Context, *TargetDescriptionRequest) (*TargetDescriptionResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Describe not implemented") +} +func (UnimplementedCompanionServiceServer) Install(grpc.BidiStreamingServer[InstallRequest, InstallResponse]) error { + return status.Error(codes.Unimplemented, "method Install not implemented") +} +func (UnimplementedCompanionServiceServer) InstrumentsRun(grpc.BidiStreamingServer[InstrumentsRunRequest, InstrumentsRunResponse]) error { + return status.Error(codes.Unimplemented, "method InstrumentsRun not implemented") +} +func (UnimplementedCompanionServiceServer) Log(*LogRequest, grpc.ServerStreamingServer[LogResponse]) error { + return status.Error(codes.Unimplemented, "method Log not implemented") +} +func (UnimplementedCompanionServiceServer) XctraceRecord(grpc.BidiStreamingServer[XctraceRecordRequest, XctraceRecordResponse]) error { + return status.Error(codes.Unimplemented, "method XctraceRecord not implemented") +} +func (UnimplementedCompanionServiceServer) AccessibilityInfo(context.Context, *AccessibilityInfoRequest) (*AccessibilityInfoResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AccessibilityInfo not implemented") +} +func (UnimplementedCompanionServiceServer) Focus(context.Context, *FocusRequest) (*FocusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Focus not implemented") +} +func (UnimplementedCompanionServiceServer) Hid(grpc.ClientStreamingServer[HIDEvent, HIDResponse]) error { + return status.Error(codes.Unimplemented, "method Hid not implemented") +} +func (UnimplementedCompanionServiceServer) OpenUrl(context.Context, *OpenUrlRequest) (*OpenUrlRequest, error) { + return nil, status.Error(codes.Unimplemented, "method OpenUrl not implemented") +} +func (UnimplementedCompanionServiceServer) SetLocation(context.Context, *SetLocationRequest) (*SetLocationResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SetLocation not implemented") +} +func (UnimplementedCompanionServiceServer) SendNotification(context.Context, *SendNotificationRequest) (*SendNotificationResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SendNotification not implemented") +} +func (UnimplementedCompanionServiceServer) SimulateMemoryWarning(context.Context, *SimulateMemoryWarningRequest) (*SimulateMemoryWarningResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SimulateMemoryWarning not implemented") +} +func (UnimplementedCompanionServiceServer) Approve(context.Context, *ApproveRequest) (*ApproveResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Approve not implemented") +} +func (UnimplementedCompanionServiceServer) Revoke(context.Context, *RevokeRequest) (*RevokeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Revoke not implemented") +} +func (UnimplementedCompanionServiceServer) ClearKeychain(context.Context, *ClearKeychainRequest) (*ClearKeychainResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ClearKeychain not implemented") +} +func (UnimplementedCompanionServiceServer) ContactsUpdate(context.Context, *ContactsUpdateRequest) (*ContactsUpdateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ContactsUpdate not implemented") +} +func (UnimplementedCompanionServiceServer) Setting(context.Context, *SettingRequest) (*SettingResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Setting not implemented") +} +func (UnimplementedCompanionServiceServer) GetSetting(context.Context, *GetSettingRequest) (*GetSettingResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetSetting not implemented") +} +func (UnimplementedCompanionServiceServer) ListSettings(context.Context, *ListSettingRequest) (*ListSettingResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSettings not implemented") +} +func (UnimplementedCompanionServiceServer) Launch(grpc.BidiStreamingServer[LaunchRequest, LaunchResponse]) error { + return status.Error(codes.Unimplemented, "method Launch not implemented") +} +func (UnimplementedCompanionServiceServer) ListApps(context.Context, *ListAppsRequest) (*ListAppsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListApps not implemented") +} +func (UnimplementedCompanionServiceServer) Terminate(context.Context, *TerminateRequest) (*TerminateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Terminate not implemented") +} +func (UnimplementedCompanionServiceServer) Uninstall(context.Context, *UninstallRequest) (*UninstallResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Uninstall not implemented") +} +func (UnimplementedCompanionServiceServer) AddMedia(grpc.ClientStreamingServer[AddMediaRequest, AddMediaResponse]) error { + return status.Error(codes.Unimplemented, "method AddMedia not implemented") +} +func (UnimplementedCompanionServiceServer) Record(grpc.BidiStreamingServer[RecordRequest, RecordResponse]) error { + return status.Error(codes.Unimplemented, "method Record not implemented") +} +func (UnimplementedCompanionServiceServer) Screenshot(context.Context, *ScreenshotRequest) (*ScreenshotResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Screenshot not implemented") +} +func (UnimplementedCompanionServiceServer) VideoStream(grpc.BidiStreamingServer[VideoStreamRequest, VideoStreamResponse]) error { + return status.Error(codes.Unimplemented, "method VideoStream not implemented") +} +func (UnimplementedCompanionServiceServer) CrashDelete(context.Context, *CrashLogQuery) (*CrashLogResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CrashDelete not implemented") +} +func (UnimplementedCompanionServiceServer) CrashList(context.Context, *CrashLogQuery) (*CrashLogResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CrashList not implemented") +} +func (UnimplementedCompanionServiceServer) CrashShow(context.Context, *CrashShowRequest) (*CrashShowResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CrashShow not implemented") +} +func (UnimplementedCompanionServiceServer) XctestListBundles(context.Context, *XctestListBundlesRequest) (*XctestListBundlesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method XctestListBundles not implemented") +} +func (UnimplementedCompanionServiceServer) XctestListTests(context.Context, *XctestListTestsRequest) (*XctestListTestsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method XctestListTests not implemented") +} +func (UnimplementedCompanionServiceServer) XctestRun(*XctestRunRequest, grpc.ServerStreamingServer[XctestRunResponse]) error { + return status.Error(codes.Unimplemented, "method XctestRun not implemented") +} +func (UnimplementedCompanionServiceServer) Ls(context.Context, *LsRequest) (*LsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Ls not implemented") +} +func (UnimplementedCompanionServiceServer) Mkdir(context.Context, *MkdirRequest) (*MkdirResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Mkdir not implemented") +} +func (UnimplementedCompanionServiceServer) Mv(context.Context, *MvRequest) (*MvResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Mv not implemented") +} +func (UnimplementedCompanionServiceServer) Rm(context.Context, *RmRequest) (*RmResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Rm not implemented") +} +func (UnimplementedCompanionServiceServer) Pull(*PullRequest, grpc.ServerStreamingServer[PullResponse]) error { + return status.Error(codes.Unimplemented, "method Pull not implemented") +} +func (UnimplementedCompanionServiceServer) Push(grpc.ClientStreamingServer[PushRequest, PushResponse]) error { + return status.Error(codes.Unimplemented, "method Push not implemented") +} +func (UnimplementedCompanionServiceServer) Tail(grpc.BidiStreamingServer[TailRequest, TailResponse]) error { + return status.Error(codes.Unimplemented, "method Tail not implemented") +} +func (UnimplementedCompanionServiceServer) mustEmbedUnimplementedCompanionServiceServer() {} +func (UnimplementedCompanionServiceServer) testEmbeddedByValue() {} + +// UnsafeCompanionServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CompanionServiceServer will +// result in compilation errors. +type UnsafeCompanionServiceServer interface { + mustEmbedUnimplementedCompanionServiceServer() +} + +func RegisterCompanionServiceServer(s grpc.ServiceRegistrar, srv CompanionServiceServer) { + // If the following call panics, it indicates UnimplementedCompanionServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CompanionService_ServiceDesc, srv) +} + +func _CompanionService_Connect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ConnectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Connect(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Connect_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Connect(ctx, req.(*ConnectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Debugserver_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).Debugserver(&grpc.GenericServerStream[DebugServerRequest, DebugServerResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_DebugserverServer = grpc.BidiStreamingServer[DebugServerRequest, DebugServerResponse] + +func _CompanionService_Dap_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).Dap(&grpc.GenericServerStream[DapRequest, DapResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_DapServer = grpc.BidiStreamingServer[DapRequest, DapResponse] + +func _CompanionService_Describe_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TargetDescriptionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Describe(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Describe_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Describe(ctx, req.(*TargetDescriptionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Install_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).Install(&grpc.GenericServerStream[InstallRequest, InstallResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_InstallServer = grpc.BidiStreamingServer[InstallRequest, InstallResponse] + +func _CompanionService_InstrumentsRun_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).InstrumentsRun(&grpc.GenericServerStream[InstrumentsRunRequest, InstrumentsRunResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_InstrumentsRunServer = grpc.BidiStreamingServer[InstrumentsRunRequest, InstrumentsRunResponse] + +func _CompanionService_Log_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(LogRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(CompanionServiceServer).Log(m, &grpc.GenericServerStream[LogRequest, LogResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_LogServer = grpc.ServerStreamingServer[LogResponse] + +func _CompanionService_XctraceRecord_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).XctraceRecord(&grpc.GenericServerStream[XctraceRecordRequest, XctraceRecordResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_XctraceRecordServer = grpc.BidiStreamingServer[XctraceRecordRequest, XctraceRecordResponse] + +func _CompanionService_AccessibilityInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AccessibilityInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).AccessibilityInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_AccessibilityInfo_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).AccessibilityInfo(ctx, req.(*AccessibilityInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Focus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(FocusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Focus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Focus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Focus(ctx, req.(*FocusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Hid_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).Hid(&grpc.GenericServerStream[HIDEvent, HIDResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_HidServer = grpc.ClientStreamingServer[HIDEvent, HIDResponse] + +func _CompanionService_OpenUrl_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OpenUrlRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).OpenUrl(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_OpenUrl_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).OpenUrl(ctx, req.(*OpenUrlRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_SetLocation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetLocationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).SetLocation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_SetLocation_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).SetLocation(ctx, req.(*SetLocationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_SendNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendNotificationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).SendNotification(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_SendNotification_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).SendNotification(ctx, req.(*SendNotificationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_SimulateMemoryWarning_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SimulateMemoryWarningRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).SimulateMemoryWarning(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_SimulateMemoryWarning_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).SimulateMemoryWarning(ctx, req.(*SimulateMemoryWarningRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Approve_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApproveRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Approve(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Approve_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Approve(ctx, req.(*ApproveRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Revoke_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevokeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Revoke(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Revoke_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Revoke(ctx, req.(*RevokeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_ClearKeychain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClearKeychainRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).ClearKeychain(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_ClearKeychain_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).ClearKeychain(ctx, req.(*ClearKeychainRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_ContactsUpdate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ContactsUpdateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).ContactsUpdate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_ContactsUpdate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).ContactsUpdate(ctx, req.(*ContactsUpdateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Setting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SettingRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Setting(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Setting_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Setting(ctx, req.(*SettingRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_GetSetting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSettingRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).GetSetting(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_GetSetting_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).GetSetting(ctx, req.(*GetSettingRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_ListSettings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSettingRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).ListSettings(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_ListSettings_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).ListSettings(ctx, req.(*ListSettingRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Launch_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).Launch(&grpc.GenericServerStream[LaunchRequest, LaunchResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_LaunchServer = grpc.BidiStreamingServer[LaunchRequest, LaunchResponse] + +func _CompanionService_ListApps_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAppsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).ListApps(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_ListApps_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).ListApps(ctx, req.(*ListAppsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Terminate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TerminateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Terminate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Terminate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Terminate(ctx, req.(*TerminateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Uninstall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UninstallRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Uninstall(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Uninstall_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Uninstall(ctx, req.(*UninstallRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_AddMedia_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).AddMedia(&grpc.GenericServerStream[AddMediaRequest, AddMediaResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_AddMediaServer = grpc.ClientStreamingServer[AddMediaRequest, AddMediaResponse] + +func _CompanionService_Record_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).Record(&grpc.GenericServerStream[RecordRequest, RecordResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_RecordServer = grpc.BidiStreamingServer[RecordRequest, RecordResponse] + +func _CompanionService_Screenshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ScreenshotRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Screenshot(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Screenshot_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Screenshot(ctx, req.(*ScreenshotRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_VideoStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).VideoStream(&grpc.GenericServerStream[VideoStreamRequest, VideoStreamResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_VideoStreamServer = grpc.BidiStreamingServer[VideoStreamRequest, VideoStreamResponse] + +func _CompanionService_CrashDelete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CrashLogQuery) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).CrashDelete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_CrashDelete_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).CrashDelete(ctx, req.(*CrashLogQuery)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_CrashList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CrashLogQuery) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).CrashList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_CrashList_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).CrashList(ctx, req.(*CrashLogQuery)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_CrashShow_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CrashShowRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).CrashShow(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_CrashShow_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).CrashShow(ctx, req.(*CrashShowRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_XctestListBundles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(XctestListBundlesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).XctestListBundles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_XctestListBundles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).XctestListBundles(ctx, req.(*XctestListBundlesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_XctestListTests_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(XctestListTestsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).XctestListTests(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_XctestListTests_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).XctestListTests(ctx, req.(*XctestListTestsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_XctestRun_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(XctestRunRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(CompanionServiceServer).XctestRun(m, &grpc.GenericServerStream[XctestRunRequest, XctestRunResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_XctestRunServer = grpc.ServerStreamingServer[XctestRunResponse] + +func _CompanionService_Ls_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Ls(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Ls_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Ls(ctx, req.(*LsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Mkdir_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MkdirRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Mkdir(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Mkdir_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Mkdir(ctx, req.(*MkdirRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Mv_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MvRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Mv(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Mv_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Mv(ctx, req.(*MvRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Rm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RmRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CompanionServiceServer).Rm(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CompanionService_Rm_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CompanionServiceServer).Rm(ctx, req.(*RmRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CompanionService_Pull_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(PullRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(CompanionServiceServer).Pull(m, &grpc.GenericServerStream[PullRequest, PullResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_PullServer = grpc.ServerStreamingServer[PullResponse] + +func _CompanionService_Push_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).Push(&grpc.GenericServerStream[PushRequest, PushResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_PushServer = grpc.ClientStreamingServer[PushRequest, PushResponse] + +func _CompanionService_Tail_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CompanionServiceServer).Tail(&grpc.GenericServerStream[TailRequest, TailResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CompanionService_TailServer = grpc.BidiStreamingServer[TailRequest, TailResponse] + +// CompanionService_ServiceDesc is the grpc.ServiceDesc for CompanionService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CompanionService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "idb.CompanionService", + HandlerType: (*CompanionServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "connect", + Handler: _CompanionService_Connect_Handler, + }, + { + MethodName: "describe", + Handler: _CompanionService_Describe_Handler, + }, + { + MethodName: "accessibility_info", + Handler: _CompanionService_AccessibilityInfo_Handler, + }, + { + MethodName: "focus", + Handler: _CompanionService_Focus_Handler, + }, + { + MethodName: "open_url", + Handler: _CompanionService_OpenUrl_Handler, + }, + { + MethodName: "set_location", + Handler: _CompanionService_SetLocation_Handler, + }, + { + MethodName: "send_notification", + Handler: _CompanionService_SendNotification_Handler, + }, + { + MethodName: "simulate_memory_warning", + Handler: _CompanionService_SimulateMemoryWarning_Handler, + }, + { + MethodName: "approve", + Handler: _CompanionService_Approve_Handler, + }, + { + MethodName: "revoke", + Handler: _CompanionService_Revoke_Handler, + }, + { + MethodName: "clear_keychain", + Handler: _CompanionService_ClearKeychain_Handler, + }, + { + MethodName: "contacts_update", + Handler: _CompanionService_ContactsUpdate_Handler, + }, + { + MethodName: "setting", + Handler: _CompanionService_Setting_Handler, + }, + { + MethodName: "get_setting", + Handler: _CompanionService_GetSetting_Handler, + }, + { + MethodName: "list_settings", + Handler: _CompanionService_ListSettings_Handler, + }, + { + MethodName: "list_apps", + Handler: _CompanionService_ListApps_Handler, + }, + { + MethodName: "terminate", + Handler: _CompanionService_Terminate_Handler, + }, + { + MethodName: "uninstall", + Handler: _CompanionService_Uninstall_Handler, + }, + { + MethodName: "screenshot", + Handler: _CompanionService_Screenshot_Handler, + }, + { + MethodName: "crash_delete", + Handler: _CompanionService_CrashDelete_Handler, + }, + { + MethodName: "crash_list", + Handler: _CompanionService_CrashList_Handler, + }, + { + MethodName: "crash_show", + Handler: _CompanionService_CrashShow_Handler, + }, + { + MethodName: "xctest_list_bundles", + Handler: _CompanionService_XctestListBundles_Handler, + }, + { + MethodName: "xctest_list_tests", + Handler: _CompanionService_XctestListTests_Handler, + }, + { + MethodName: "ls", + Handler: _CompanionService_Ls_Handler, + }, + { + MethodName: "mkdir", + Handler: _CompanionService_Mkdir_Handler, + }, + { + MethodName: "mv", + Handler: _CompanionService_Mv_Handler, + }, + { + MethodName: "rm", + Handler: _CompanionService_Rm_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "debugserver", + Handler: _CompanionService_Debugserver_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "dap", + Handler: _CompanionService_Dap_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "install", + Handler: _CompanionService_Install_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "instruments_run", + Handler: _CompanionService_InstrumentsRun_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "log", + Handler: _CompanionService_Log_Handler, + ServerStreams: true, + }, + { + StreamName: "xctrace_record", + Handler: _CompanionService_XctraceRecord_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "hid", + Handler: _CompanionService_Hid_Handler, + ClientStreams: true, + }, + { + StreamName: "launch", + Handler: _CompanionService_Launch_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "add_media", + Handler: _CompanionService_AddMedia_Handler, + ClientStreams: true, + }, + { + StreamName: "record", + Handler: _CompanionService_Record_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "video_stream", + Handler: _CompanionService_VideoStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "xctest_run", + Handler: _CompanionService_XctestRun_Handler, + ServerStreams: true, + }, + { + StreamName: "pull", + Handler: _CompanionService_Pull_Handler, + ServerStreams: true, + }, + { + StreamName: "push", + Handler: _CompanionService_Push_Handler, + ClientStreams: true, + }, + { + StreamName: "tail", + Handler: _CompanionService_Tail_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "idb.proto", +} diff --git a/internal/driver/ioscompanion/driver.go b/internal/driver/ioscompanion/driver.go new file mode 100644 index 0000000..9fb4725 --- /dev/null +++ b/internal/driver/ioscompanion/driver.go @@ -0,0 +1,1118 @@ +// Package ioscompanion drives an iOS simulator through the native simulator +// companion. This file implements the DeviceDriver surface on top of the +// brand-free transport, supervises the companion child process, and recovers +// from a dropped connection with one in-place restart. +package ioscompanion + +import ( + "bytes" + "context" + "errors" + "fmt" + "image" + _ "image/png" + "io" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "syscall" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/priyanshujain/sanderling/internal/driver" + "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/companionassets" + "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/transport" + "github.com/priyanshujain/sanderling/internal/hierarchy" +) + +// startupTimeout bounds how long New waits for the spawned companion to accept +// a connection and answer a health probe. +const startupTimeout = 30 * time.Second + +// runnerStartupTimeout bounds the in-simulator runner's startup. The runner is +// hosted by a test session whose cold start is far slower than the companion's. +const runnerStartupTimeout = 120 * time.Second + +// shutdownGrace bounds how long the companion child gets to exit after SIGTERM +// before it is killed. A variable so the kill-escalation test can shrink it. +var shutdownGrace = 15 * time.Second + +// longPressHoldMilliseconds is how long LongPress holds the finger down. +const longPressHoldMilliseconds = 600 + +// Options configures a Driver. +type Options struct { + // UniqueDeviceIdentifier selects the booted simulator the companion drives. + UniqueDeviceIdentifier string + // BundleID is the app under test. Launch and Terminate act on it. + BundleID string + // AppPath is the .app bundle directory. Required for clear-state reinstall; + // when empty, clear state falls back to resetting the data container. + AppPath string + // Output receives companion stdout and stderr plus driver warnings. + Output io.Writer + // DoubleTapGapMilliseconds overrides the synthesized double-tap gap. + DoubleTapGapMilliseconds float64 + + // spawnChild, dialCompanion, and pickAddress are test seams. Production + // leaves them nil and New wires the real extraction, spawn, and dial. + spawnChild func(ctx context.Context, address string) (*exec.Cmd, error) + dialCompanion func(address string) (transport.Companion, error) + pickAddress func() (string, error) +} + +// Driver implements driver.DeviceDriver against an iOS simulator companion. +type Driver struct { + companion transport.Companion + udid string + bundleID string + appPath string + output io.Writer + + screenWidth int + screenHeight int + + doubleTapGapMilliseconds float64 + + // mu guards Snapshot's hierarchy+screenshot pairing and the lastTap record. + mu sync.Mutex + lastTap struct { + x, y float64 + set bool + } + + clearStateWarned bool + + // restart rebuilds the transport in place after a connection-level failure. + // It is a seam so tests exercise the supervision logic without spawning a + // real companion. restarting guards against re-entrant restarts. + restart func(ctx context.Context) error + restarting bool + address string + + // resetContainer wipes the app data container for the clear-state fallback. + // A seam so tests skip the xcrun shell-out. + resetContainer func(ctx context.Context) error + + // reinstallApp uninstalls and reinstalls the app bundle for clear-state. + // A seam so tests skip the simctl shell-outs. + reinstallApp func(ctx context.Context) error + + // grantPaste pre-authorizes the app's pasteboard access. A seam so tests + // skip the sqlite shell-out. + grantPaste func(ctx context.Context) error + + // idleClock drives WaitForIdle's settle poll. A seam so tests substitute a + // fake clock and avoid the real settle cap. + idleClock Clock + spawnChild func(ctx context.Context, address string) (*exec.Cmd, error) + dial func(address string) (transport.Companion, error) + child *exec.Cmd + + // The hybrid simulator companion pairs the legacy child (HID gestures, + // lifecycle, screenshot) with an in-simulator runner that serves + // collapse-free accessibility snapshots and native unicode typing. + // runnerClient is nil on the legacy-only path. + runnerClient transport.Companion + runnerChild *exec.Cmd + runnerAddress string + spawnRunner func(ctx context.Context, address string) (*exec.Cmd, error) + dialRunner func(address string) (transport.Companion, error) + hybrid bool + + // processContext owns the companion child's lifetime: it is derived from + // New's context (so a canceled run still reaps the child) and canceled by + // Close. Spawning under a startup-scoped context would SIGTERM the child + // the moment startup finishes. + processContext context.Context + processCancel context.CancelFunc +} + +// New extracts the embedded companion, spawns it against the configured +// simulator, dials the transport, health-probes it, and caches the screen +// point dimensions. Call Close when done to stop the child. +func New(ctx context.Context, options Options) (*Driver, error) { + if options.UniqueDeviceIdentifier == "" { + return nil, errors.New("ios companion: UniqueDeviceIdentifier is required") + } + output := options.Output + if output == nil { + output = io.Discard + } + gap := options.DoubleTapGapMilliseconds + if gap <= 0 { + gap = DefaultDoubleTapGapMilliseconds + } + + driverInstance := &Driver{ + udid: options.UniqueDeviceIdentifier, + bundleID: options.BundleID, + appPath: options.AppPath, + output: output, + doubleTapGapMilliseconds: gap, + spawnChild: options.spawnChild, + dial: options.dialCompanion, + hybrid: hybridCompanionEnabled(), + } + if driverInstance.spawnChild == nil { + driverInstance.spawnChild = driverInstance.realSpawnChild + } + if driverInstance.dial == nil { + driverInstance.dial = transport.Dial + } + if driverInstance.spawnRunner == nil { + driverInstance.spawnRunner = driverInstance.realSpawnRunner + } + if driverInstance.dialRunner == nil { + driverInstance.dialRunner = func(address string) (transport.Companion, error) { + return transport.DialRunner(address, driverInstance.udid, driverInstance.bundleID) + } + } + pickAddress := options.pickAddress + if pickAddress == nil { + pickAddress = pickLoopbackAddress + } + + address, err := pickAddress() + if err != nil { + return nil, err + } + driverInstance.address = address + driverInstance.restart = driverInstance.respawnAndRedial + driverInstance.resetContainer = driverInstance.resetDataContainer + driverInstance.reinstallApp = driverInstance.simctlReinstall + driverInstance.grantPaste = driverInstance.grantPasteboardAccess + driverInstance.processContext, driverInstance.processCancel = context.WithCancel(ctx) + + if err := driverInstance.bringUp(ctx); err != nil { + return nil, err + } + if driverInstance.hybrid { + if err := driverInstance.bringUpRunner(ctx); err != nil { + driverInstance.Close() + return nil, fmt.Errorf("simulator runner: %w (set SANDERLING_SIMULATOR_COMPANION=legacy to bypass)", err) + } + } + + description, err := driverInstance.companion.Describe(ctx) + if err != nil { + driverInstance.Close() + return nil, fmt.Errorf("describe target: %w", err) + } + driverInstance.screenWidth = description.WidthPoints + driverInstance.screenHeight = description.HeightPoints + return driverInstance, nil +} + +// bringUp spawns the companion child, waits for the listener, dials, and +// confirms health. It is used by New and by the in-place restart. +func (d *Driver) bringUp(ctx context.Context) error { + startupCtx, cancel := context.WithTimeout(ctx, startupTimeout) + defer cancel() + + child, err := d.spawnChild(d.processContext, d.address) + if err != nil { + return fmt.Errorf("spawn companion: %w", err) + } + d.child = child + + if err := waitForListener(startupCtx, d.address); err != nil { + d.stopChild() + return fmt.Errorf("companion listener: %w", err) + } + + companion, err := d.dial(d.address) + if err != nil { + d.stopChild() + return fmt.Errorf("dial companion: %w", err) + } + d.companion = companion + + if err := d.waitForHealth(startupCtx); err != nil { + _ = companion.Close() + d.stopChild() + return fmt.Errorf("companion health: %w", err) + } + return nil +} + +// waitForHealth probes AccessibilityInfo until it succeeds or the context +// expires. A successful describe-all means the companion is attached to the +// simulator and ready to serve. +func (d *Driver) waitForHealth(ctx context.Context) error { + ticker := time.NewTicker(250 * time.Millisecond) + defer ticker.Stop() + for { + if _, err := d.companion.AccessibilityInfo(ctx); err == nil { + return nil + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + } + } +} + +// respawnAndRedial tears down the current transports and children, then brings +// fresh ones up at the same addresses. Used as the supervision restart. On the +// hybrid path both halves restart together: their failure modes overlap (a +// rebooted simulator drops both) and one orchestration keeps recovery simple. +func (d *Driver) respawnAndRedial(ctx context.Context) error { + // The dead transports are closed but kept in place until their fresh + // replacements land: if the restart fails, later calls error gracefully on + // the closed transport instead of dereferencing nil, and a later incident + // earns another restart attempt. + if d.companion != nil { + _ = d.companion.Close() + } + d.stopChild() + if d.runnerClient != nil { + _ = d.runnerClient.Close() + } + d.stopRunnerChild() + if err := d.bringUp(ctx); err != nil { + return err + } + if d.hybrid { + return d.bringUpRunner(ctx) + } + return nil +} + +// hybridCompanionEnabled reports whether the simulator driver should pair the +// legacy companion with the in-simulator runner. The hybrid is the default; +// SANDERLING_SIMULATOR_COMPANION=legacy forces the legacy companion alone. +func hybridCompanionEnabled() bool { + return os.Getenv("SANDERLING_SIMULATOR_COMPANION") != "legacy" +} + +// bringUpRunner spawns the in-simulator runner, waits for its listener, dials, +// and confirms it serves snapshots. Used by New and the in-place restart. +func (d *Driver) bringUpRunner(ctx context.Context) error { + startupCtx, cancel := context.WithTimeout(ctx, runnerStartupTimeout) + defer cancel() + + // A fresh port every bring-up: after a restart the dying session's + // listener may still answer on the old port and would satisfy the wait + // below with a dead server. + address, err := pickLoopbackAddress() + if err != nil { + return err + } + d.runnerAddress = address + + child, err := d.spawnRunner(d.processContext, d.runnerAddress) + if err != nil { + return fmt.Errorf("spawn runner: %w", err) + } + d.runnerChild = child + + if err := waitForListener(startupCtx, d.runnerAddress); err != nil { + d.stopRunnerChild() + return fmt.Errorf("runner listener: %w", err) + } + + client, err := d.dialRunner(d.runnerAddress) + if err != nil { + d.stopRunnerChild() + return fmt.Errorf("dial runner: %w", err) + } + + ticker := time.NewTicker(250 * time.Millisecond) + defer ticker.Stop() + for { + if _, healthErr := client.AccessibilityInfo(startupCtx); healthErr == nil { + break + } + select { + case <-startupCtx.Done(): + _ = client.Close() + d.stopRunnerChild() + return fmt.Errorf("runner health: %w", startupCtx.Err()) + case <-ticker.C: + } + } + d.runnerClient = client + return nil +} + +// withRecovery runs call, and on a connection-level failure performs one +// in-place restart before retrying the call once. A non-connection error, or a +// second failure of any kind, surfaces to the caller. The restart budget is per +// failure incident: each healthy call resets restarting to false, so a later +// drop earns its own single restart. +func (d *Driver) withRecovery(ctx context.Context, call func() error) error { + err := call() + if err == nil || !isConnectionError(err) || d.restarting || d.restart == nil { + return err + } + d.restarting = true + defer func() { d.restarting = false }() + fmt.Fprintf(d.output, "companion connection lost (%v); restarting once\n", err) + // The restart runs under the driver's own lifetime context, not the + // failed call's: an action whose deadline already expired must not doom + // the recovery that later actions depend on. + restartCtx := d.processContext + if restartCtx == nil { + restartCtx = ctx + } + if restartErr := d.restart(restartCtx); restartErr != nil { + return fmt.Errorf("companion restart failed: %w (original: %v)", restartErr, err) + } + return call() +} + +// isConnectionError reports whether err is a dropped-connection signal that a +// restart can recover from: the transport's unavailable sentinel, a gRPC +// Unavailable status, or an EOF. +func isConnectionError(err error) bool { + if err == nil { + return false + } + if errors.Is(err, io.EOF) || errors.Is(err, transport.ErrCompanionUnavailable) { + return true + } + if statusValue, ok := status.FromError(err); ok { + return statusValue.Code() == codes.Unavailable + } + return false +} + +func (d *Driver) Launch(ctx context.Context, bundleID string, clearState bool, env map[string]string) error { + if bundleID != "" { + d.bundleID = bundleID + } + if len(env) > 0 { + // The launch Start message carries an env map, but this backend does + // not pass it through: passing it would change the app's process + // environment in ways the rest of the run does not account for. Reject + // loudly rather than silently dropping the request. + return errors.New("ios companion: launch with environment variables is unsupported on this backend") + } + + // Terminate first so the launch is a clean cold start regardless of the + // app's prior state. A not-running app is not an error here. + _ = d.withRecovery(ctx, func() error { return d.lifecycleCompanion().Terminate(ctx, d.bundleID) }) + + if clearState { + if err := d.clearAppState(ctx); err != nil { + return err + } + } + + // Grant the app pasteboard access before it runs so unicode input (which + // must go through the pasteboard, since HID cannot express it) never trips + // the iOS paste-permission prompt. clearState reinstall resets the grant, + // so it is reapplied on every launch. Best effort: if it fails, the paste + // path still handles the prompt, just slower. The hybrid path types + // natively and never touches the pasteboard, so it skips the grant. + if d.bundleID != "" && d.runnerTyper() == nil { + if err := d.grantPaste(ctx); err != nil { + fmt.Fprintf(d.output, "grant pasteboard access failed (continuing): %v\n", err) + } + } + + if err := d.withRecovery(ctx, func() error { + return d.lifecycleCompanion().Launch(ctx, d.bundleID, true) + }); err != nil { + return fmt.Errorf("launch %s: %w", d.bundleID, err) + } + return nil +} + +// lifecycleCompanion is the transport that owns app launch and terminate: the +// in-simulator runner when the hybrid is active, otherwise the legacy +// companion. Lifecycle performed outside the runner's automation session +// leaves the session's app proxies bound to dead processes, after which +// snapshots hang and typing asserts. +func (d *Driver) lifecycleCompanion() transport.Companion { + if d.runnerClient != nil { + return d.runnerClient + } + return d.companion +} + +// clearAppState resets the app to a first-launch state. With an app path it +// uninstalls and reinstalls; without one it falls back to wiping the app's data +// container and warns once that a full reinstall needs the app path. +func (d *Driver) clearAppState(ctx context.Context) error { + if d.appPath != "" { + if err := d.reinstallApp(ctx); err != nil { + return fmt.Errorf("reinstall %s: %w", d.appPath, err) + } + return nil + } + if !d.clearStateWarned { + fmt.Fprintln(d.output, "clear-state requested without an app path: resetting the data container only; pass the app path for a full reinstall") + d.clearStateWarned = true + } + return d.resetContainer(ctx) +} + +// simctlReinstall uninstalls and reinstalls the app bundle via simctl. App +// lifecycle stays with simctl: the companion's install RPC misreads current +// simulator targets' architectures and rejects valid bundles. +func (d *Driver) simctlReinstall(ctx context.Context) error { + _ = exec.CommandContext(ctx, "xcrun", "simctl", "uninstall", d.udid, d.bundleID).Run() + output, err := exec.CommandContext(ctx, "xcrun", "simctl", "install", d.udid, d.appPath).CombinedOutput() + if err != nil { + return fmt.Errorf("simctl install: %w: %s", err, strings.TrimSpace(string(output))) + } + return nil +} + +// grantPasteboardAccess authorizes the app to read the pasteboard without the +// iOS permission prompt, by writing an allow row into the simulator's privacy +// (TCC) database. This is the simulator counterpart to `simctl privacy grant`, +// which does not expose the pasteboard service. Without it, every unicode input +// (which must paste, since HID cannot express unicode) blocks on a modal that +// costs seconds; with it the paste lands in one frame. +func (d *Driver) grantPasteboardAccess(ctx context.Context) error { + databasePath := filepath.Join( + os.Getenv("HOME"), "Library", "Developer", "CoreSimulator", "Devices", + d.udid, "data", "Library", "TCC", "TCC.db", + ) + if _, err := os.Stat(databasePath); err != nil { + return fmt.Errorf("locate privacy database: %w", err) + } + statement := fmt.Sprintf( + "INSERT OR REPLACE INTO access "+ + "(service,client,client_type,auth_value,auth_reason,auth_version,indirect_object_identifier) "+ + "VALUES ('kTCCServicePasteboard','%s',0,2,4,1,'UNUSED');", + d.bundleID, + ) + output, err := exec.CommandContext(ctx, "sqlite3", databasePath, statement).CombinedOutput() + if err != nil { + return fmt.Errorf("write privacy grant: %w: %s", err, strings.TrimSpace(string(output))) + } + return nil +} + +// resetDataContainer deletes the contents of the app's data container so the +// next launch starts with empty storage. +func (d *Driver) resetDataContainer(ctx context.Context) error { + output, err := exec.CommandContext(ctx, "xcrun", "simctl", "get_app_container", d.udid, d.bundleID, "data").Output() + if err != nil { + return fmt.Errorf("get app container: %w", err) + } + container := string(bytes.TrimSpace(output)) + if container == "" { + return nil + } + entries, err := os.ReadDir(container) + if err != nil { + return fmt.Errorf("read app container: %w", err) + } + for _, entry := range entries { + if err := os.RemoveAll(filepath.Join(container, entry.Name())); err != nil { + return fmt.Errorf("clear app container: %w", err) + } + } + return nil +} + +func (d *Driver) Terminate(ctx context.Context) error { + return d.withRecovery(ctx, func() error { return d.lifecycleCompanion().Terminate(ctx, d.bundleID) }) +} + +func (d *Driver) Tap(ctx context.Context, x, y int) error { + d.mu.Lock() + d.lastTap.x = float64(x) + d.lastTap.y = float64(y) + d.lastTap.set = true + d.mu.Unlock() + return d.withRecovery(ctx, func() error { + return d.companion.SendHID(ctx, tapEvents(float64(x), float64(y))...) + }) +} + +func (d *Driver) DoubleTap(ctx context.Context, x, y int) error { + return d.withRecovery(ctx, func() error { + return d.companion.SendHID(ctx, doubleTapEvents(float64(x), float64(y), d.doubleTapGapMilliseconds)...) + }) +} + +func (d *Driver) LongPress(ctx context.Context, x, y int) error { + return d.withRecovery(ctx, func() error { + return d.companion.SendHID(ctx, longPressEvents(float64(x), float64(y), longPressHoldMilliseconds)...) + }) +} + +func (d *Driver) Swipe(ctx context.Context, fromX, fromY, toX, toY int, duration time.Duration) error { + seconds := duration.Seconds() + if seconds <= 0 { + seconds = 0.25 + } + return d.withRecovery(ctx, func() error { + return d.companion.SendHID(ctx, transport.SwipeEvent( + float64(fromX), float64(fromY), float64(toX), float64(toY), seconds)) + }) +} + +func (d *Driver) PressKey(ctx context.Context, key string) error { + if d.textEditor() != nil { + return d.withRecovery(ctx, func() error { + return d.textEditor().PressKey(ctx, key) + }) + } + usage, ok := pressKeyUsage(key) + if !ok { + return fmt.Errorf("ios companion: unsupported key %q", key) + } + return d.withRecovery(ctx, func() error { + return d.companion.SendHID(ctx, transport.KeyDown(usage), transport.KeyUp(usage)) + }) +} + +// textEditor returns the companion's native text-editing capability, or nil +// when the transport does not implement it. Resolved per call because a +// restart replaces d.companion. +func (d *Driver) textEditor() transport.TextEditor { + if editor, ok := d.companion.(transport.TextEditor); ok { + return editor + } + return nil +} + +// runnerTyper returns the in-simulator runner's native typing capability, or +// nil outside the hybrid path. Resolved per call because a restart replaces +// d.runnerClient. +func (d *Driver) runnerTyper() transport.TextTyper { + if typer, ok := d.runnerClient.(transport.TextTyper); ok { + return typer + } + return nil +} + +// pressKeyUsage maps the logical key names mobile runs emit to a HID usage. +// Only Return/Enter has a hardware-keyboard equivalent on the simulator; other +// names (notably "back" and "home") have no HID key and report unsupported. +func pressKeyUsage(key string) (uint32, bool) { + switch key { + case "enter", "return", "Enter", "Return": + return usageReturn, true + default: + return 0, false + } +} + +func (d *Driver) TapSelector(ctx context.Context, selector string) error { + x, y, err := d.resolveSelectorCenter(ctx, selector) + if err != nil { + return err + } + return d.Tap(ctx, x, y) +} + +func (d *Driver) DoubleTapSelector(ctx context.Context, selector string) error { + x, y, err := d.resolveSelectorCenter(ctx, selector) + if err != nil { + return err + } + return d.DoubleTap(ctx, x, y) +} + +// resolveSelectorCenter fetches a fresh hierarchy and returns the center of the +// first element matching selector. +func (d *Driver) resolveSelectorCenter(ctx context.Context, selector string) (int, int, error) { + hierarchyJSON, err := d.Hierarchy(ctx) + if err != nil { + return 0, 0, err + } + tree, err := hierarchy.Parse(hierarchyJSON) + if err != nil { + return 0, 0, fmt.Errorf("parse hierarchy: %w", err) + } + element := tree.Find(selector) + if element == nil { + return 0, 0, fmt.Errorf("selector %q matched no element", selector) + } + x, y := element.Bounds.Center() + return x, y, nil +} + +func (d *Driver) InputText(ctx context.Context, text string) error { + // Hybrid path. Mappable text rides one HID stream: select-all chord plus + // keystrokes, atomic and strictly ordered on a single channel. Unicode + // (which HID cannot express) is typed natively by the runner after the + // chord; chord and typing ride different channels with no ordering + // guarantee between them, so the clear is verified through a snapshot + // before the first keystroke goes out. + if typer := d.runnerTyper(); typer != nil { + if !usesPasteboard(text) { + events := append(clearFieldEvents(), keyPressEvents(typeStringPresses(text))...) + return d.withRecovery(ctx, func() error { + return d.companion.SendHID(ctx, events...) + }) + } + return d.withRecovery(ctx, func() error { + if err := d.companion.SendHID(ctx, clearFieldEvents()...); err != nil { + return fmt.Errorf("clear field: %w", err) + } + d.waitFieldCleared(ctx) + return d.runnerTyper().TypeText(ctx, text, false) + }) + } + // A text-editing companion replaces the field's content natively, which + // covers unicode without the pasteboard and its permission dialog. + if d.textEditor() != nil { + return d.withRecovery(ctx, func() error { + return d.textEditor().InputText(ctx, text) + }) + } + // The field target is only needed for the pasteboard path. Resolving it + // requires a describe-all, so the fast keyboard path skips that round-trip + // and lets inputText send the key presses directly. + var field fieldTarget + if usesPasteboard(text) { + field = d.resolveInputField(ctx) + } + return inputText(ctx, d.makeRunner(), text, field) +} + +// fieldClearedWaitCap and fieldClearedPoll bound the verify-cleared loop +// between the HID clear chord and the runner's native typing. +const fieldClearedWaitCap = 1200 * time.Millisecond +const fieldClearedPoll = 150 * time.Millisecond + +// waitFieldCleared polls the focused field (the editable element under the +// last tap) until its value reads empty, so the clear chord has demonstrably +// landed before typing starts on the other channel. Best effort: when the +// field cannot be resolved or the cap elapses, typing proceeds anyway. +func (d *Driver) waitFieldCleared(ctx context.Context) { + d.mu.Lock() + tap := d.lastTap + d.mu.Unlock() + if !tap.set { + return + } + deadline := time.Now().Add(fieldClearedWaitCap) + for time.Now().Before(deadline) { + dump, err := d.describeAllRaw(ctx) + if err != nil { + return + } + cleared := true + for _, element := range decodeDump(dump) { + if !isEditable(element.Type) { + continue + } + frame := element.Frame + if !finite(frame.X) || !finite(frame.Y) || !finite(frame.Width) || !finite(frame.Height) { + continue + } + if tap.x < frame.X || tap.x > frame.X+frame.Width || + tap.y < frame.Y || tap.y > frame.Y+frame.Height { + continue + } + if value := stringValue(element.AXValue); value != "" && value != emptyFieldValueSentinel { + cleared = false + } + break + } + if cleared { + return + } + select { + case <-ctx.Done(): + return + case <-time.After(fieldClearedPoll): + } + } +} + +// resolveInputField finds the editable element under the last tap so the +// pasteboard fallback can confirm the paste landed and refocus after dismissing +// the permission dialog. The runner always taps a field before typing, so +// lastTap names the focus point. An empty fieldTarget is returned when no +// editable element contains the tap (the fast keyboard path ignores it). +func (d *Driver) resolveInputField(ctx context.Context) fieldTarget { + d.mu.Lock() + tap := d.lastTap + d.mu.Unlock() + if !tap.set { + return fieldTarget{} + } + dump, err := d.describeAll(ctx) + if err != nil { + return fieldTarget{} + } + for _, element := range decodeDump(dump) { + if !isEditable(element.Type) { + continue + } + frame := element.Frame + if !finite(frame.X) || !finite(frame.Y) || !finite(frame.Width) || !finite(frame.Height) { + continue + } + if tap.x < frame.X || tap.x > frame.X+frame.Width || + tap.y < frame.Y || tap.y > frame.Y+frame.Height { + continue + } + return fieldTarget{ + identifier: stringValue(element.AXUniqueID), + centerX: frame.X + frame.Width/2, + centerY: frame.Y + frame.Height/2, + } + } + return fieldTarget{} +} + +func (d *Driver) EraseText(ctx context.Context, characterCount int) error { + if d.textEditor() != nil { + return d.withRecovery(ctx, func() error { + return d.textEditor().EraseText(ctx, characterCount) + }) + } + return eraseText(ctx, d.makeRunner(), characterCount) +} + +func (d *Driver) Hierarchy(ctx context.Context) (string, error) { + dump, err := d.describeAll(ctx) + if err != nil { + return "", err + } + mapped, err := MapHierarchy(dump, d.screenWidth, d.screenHeight) + if err != nil { + return "", err + } + return string(mapped), nil +} + +func (d *Driver) Screenshot(ctx context.Context) (driver.Image, error) { + var data []byte + err := d.withRecovery(ctx, func() error { + var screenshotErr error + data, _, screenshotErr = d.companion.Screenshot(ctx) + return screenshotErr + }) + if err != nil { + return driver.Image{}, fmt.Errorf("screenshot: %w", err) + } + return decodeScreenshot(data) +} + +func (d *Driver) Snapshot(ctx context.Context) (string, driver.Image, error) { + d.mu.Lock() + defer d.mu.Unlock() + + // The hierarchy and the screenshot ride different transports on the + // hybrid path, so they are captured concurrently. Only the hierarchy leg + // runs under withRecovery: two concurrent recoveries would race the + // restart bookkeeping, and a screenshot connection failure surfaces as a + // plain error that the next serialized call recovers from. The goroutine + // works through a captured local because a hierarchy-leg recovery + // reassigns d.companion mid-flight; a screenshot against the torn-down + // transport then fails as a plain error rather than racing the field. + var data []byte + screenshotDone := make(chan error, 1) + companion := d.companion + go func() { + imageData, _, callErr := companion.Screenshot(ctx) + data = imageData + screenshotDone <- callErr + }() + + dump, err := d.describeAll(ctx) + screenshotErr := <-screenshotDone + if err != nil { + return "", driver.Image{}, err + } + if screenshotErr != nil { + return "", driver.Image{}, fmt.Errorf("screenshot: %w", screenshotErr) + } + mapped, err := MapHierarchy(dump, d.screenWidth, d.screenHeight) + if err != nil { + return "", driver.Image{}, err + } + image, err := decodeScreenshot(data) + if err != nil { + return string(mapped), driver.Image{}, err + } + return string(mapped), image, nil +} + +// WaitForIdle polls the hierarchy until it settles. The duration argument is +// ignored: the ported settle constants (StabilityPollCap and friends) own the +// cap, matching the companion's own settle behavior. +func (d *Driver) WaitForIdle(ctx context.Context, _ time.Duration) error { + clock := d.idleClock + if clock == nil { + clock = SystemClock() + } + PollUntilStable(ctx, clock, func() *hierarchy.Tree { + dump, err := d.describeAllRaw(ctx) + if err != nil || dumpIsCollapsed(dump) { + // A collapsed dump is the bridge mid-transition; report it + // transitional so the streak resets and the poll waits for the + // real tree rather than settling on the empty shell. + return nil + } + mapped, err := MapHierarchy(dump, d.screenWidth, d.screenHeight) + if err != nil { + return nil + } + tree, err := hierarchy.Parse(string(mapped)) + if err != nil { + return nil + } + return tree + }) + return nil +} + +// RecentLogs returns no entries: the companion log RPC is a follow-up, so v1 +// reports an empty slice rather than failing. +func (d *Driver) RecentLogs(_ context.Context, _ time.Time, _ string) ([]driver.LogEntry, error) { + return []driver.LogEntry{}, nil +} + +func (d *Driver) Metrics(_ context.Context, _ string) (driver.Metrics, error) { + return driver.Metrics{}, nil +} + +func (d *Driver) Health(_ context.Context) (driver.Health, error) { + return driver.Health{Ready: true, Platform: "ios"}, nil +} + +// ForegroundApp reports the foreground app. It returns the app under test when +// it is running; otherwise it names another running user app, or "" when none +// is. The companion exposes process state but not a foreground flag, so "the +// app under test is running" stands in for "in the foreground". +func (d *Driver) ForegroundApp(ctx context.Context) (string, error) { + var apps []transport.InstalledApp + if err := d.withRecovery(ctx, func() error { + var listErr error + apps, listErr = d.companion.ListApps(ctx) + return listErr + }); err != nil { + return "", err + } + other := "" + for _, app := range apps { + if app.ProcessState != transport.ProcessStateRunning { + continue + } + if app.BundleID == d.bundleID { + return d.bundleID, nil + } + if app.InstallType == "user" && other == "" { + other = app.BundleID + } + } + return other, nil +} + +// collapsedDumpRetries and collapsedDumpDelay bound how long describeAll waits +// out a collapsed accessibility dump. The bridge briefly reports only the app +// shell (no UI content) during cold start and screen transitions; it recovers +// within a few hundred milliseconds. Re-fetching past the collapse keeps the +// runner from acting on, and snapshotting, an empty tree. +const collapsedDumpRetries = 6 +const collapsedDumpDelay = 150 * time.Millisecond + +// snapshotCompanion is the transport that serves accessibility dumps: the +// in-simulator runner when the hybrid is active (its snapshots never collapse), +// otherwise the legacy companion. +func (d *Driver) snapshotCompanion() transport.Companion { + if d.runnerClient != nil { + return d.runnerClient + } + return d.companion +} + +// describeAllRaw fetches the flat accessibility dump with one-restart recovery +// and no collapse handling. The settle loop uses it: it treats a collapsed dump +// as transitional itself, so an inner retry here would double the wait. +func (d *Driver) describeAllRaw(ctx context.Context) ([]byte, error) { + var dump []byte + err := d.withRecovery(ctx, func() error { + info, infoErr := d.snapshotCompanion().AccessibilityInfo(ctx) + if infoErr != nil { + return infoErr + } + dump = []byte(info) + return nil + }) + return dump, err +} + +// describeAll fetches the flat accessibility dump, retrying past a transient +// collapsed dump so one-shot reads (Snapshot, Hierarchy) see real UI content. +func (d *Driver) describeAll(ctx context.Context) ([]byte, error) { + dump, err := d.describeAllRaw(ctx) + if err != nil { + return dump, err + } + for attempt := 0; attempt < collapsedDumpRetries && dumpIsCollapsed(dump); attempt++ { + select { + case <-ctx.Done(): + return dump, nil + case <-time.After(collapsedDumpDelay): + } + next, nextErr := d.describeAllRaw(ctx) + if nextErr != nil { + return dump, nil + } + dump = next + } + return dump, nil +} + +// makeRunner builds the input runner backed by the current transport. The text +// runner does not route through withRecovery: it is invoked synchronously +// inside a single InputText call and a mid-paste connection drop surfaces as a +// normal error the runner retries. +func (d *Driver) makeRunner() runner { + return simctlRunner{companion: d.companion, udid: d.udid} +} + +// Close stops the companion and runner children and releases the transports. +func (d *Driver) Close() { + if d.companion != nil { + _ = d.companion.Close() + d.companion = nil + } + d.stopChild() + if d.runnerClient != nil { + _ = d.runnerClient.Close() + d.runnerClient = nil + } + d.stopRunnerChild() + if d.processCancel != nil { + d.processCancel() + } +} + +// stopChild terminates the companion child gracefully (SIGTERM, grace window, +// then SIGKILL) so it leaves no orphan behind. +func (d *Driver) stopChild() { + child := d.child + d.child = nil + stopProcess(child) +} + +// stopRunnerChild terminates the runner's hosting session the same way. The +// session tears down its in-simulator children on SIGTERM; killing it outright +// would orphan them. +func (d *Driver) stopRunnerChild() { + child := d.runnerChild + d.runnerChild = nil + stopProcess(child) +} + +func stopProcess(child *exec.Cmd) { + if child == nil || child.Process == nil { + return + } + if err := child.Process.Signal(syscall.SIGTERM); err != nil { + _ = child.Process.Kill() + _ = child.Wait() + return + } + done := make(chan struct{}) + go func() { + _ = child.Wait() + close(done) + }() + select { + case <-done: + case <-time.After(shutdownGrace): + _ = child.Process.Kill() + <-done + } +} + +// decodeScreenshot sniffs the PNG magic and decodes the pixel dimensions. The +// companion leaves image_format empty in practice, so the magic bytes are the +// only reliable format signal. No scaling is applied: the dimensions are pixels. +func decodeScreenshot(data []byte) (driver.Image, error) { + if len(data) < 8 || !bytes.HasPrefix(data, []byte("\x89PNG\r\n\x1a\n")) { + return driver.Image{}, errors.New("screenshot: response is not a PNG") + } + config, _, err := image.DecodeConfig(bytes.NewReader(data)) + if err != nil { + return driver.Image{}, fmt.Errorf("decode screenshot: %w", err) + } + return driver.Image{PNG: data, Width: config.Width, Height: config.Height}, nil +} + +// realSpawnChild extracts the embedded companion and starts it on the given +// address. Cancel sends SIGTERM so the companion detaches cleanly from the +// simulator; WaitDelay bounds the grace before the runtime kills it. +func (d *Driver) realSpawnChild(ctx context.Context, address string) (*exec.Cmd, error) { + extractDirectory := filepath.Join(os.TempDir(), "sanderling-companion") + binaryPath, err := companionassets.Extract(extractDirectory) + if err != nil { + return nil, fmt.Errorf("extract companion: %w", err) + } + _, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + command := exec.CommandContext(ctx, binaryPath, "--udid", d.udid, "--grpc-port", port) + command.Stdout = d.output + command.Stderr = d.output + // The companion echoes its whole environment into the run log at startup, + // so it gets a minimal one: secrets in the parent environment must never + // reach run artifacts. + command.Env = []string{ + "HOME=" + os.Getenv("HOME"), + "PATH=/usr/bin:/bin", + "TMPDIR=" + os.TempDir(), + } + command.Cancel = func() error { return command.Process.Signal(syscall.SIGTERM) } + command.WaitDelay = shutdownGrace + if err := command.Start(); err != nil { + return nil, fmt.Errorf("start companion: %w", err) + } + fmt.Fprintf(d.output, "companion pid=%d listening on %s\n", command.Process.Pid, address) + return command, nil +} + +// pickLoopbackAddress reserves a free loopback port and returns its address. +func pickLoopbackAddress() (string, error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return "", err + } + defer listener.Close() + return listener.Addr().String(), nil +} + +// waitForListener blocks until address accepts a TCP connection or ctx expires. +func waitForListener(ctx context.Context, address string) error { + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + for { + dialer := net.Dialer{Timeout: time.Second} + conn, err := dialer.DialContext(ctx, "tcp", address) + if err == nil { + _ = conn.Close() + return nil + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + } + } +} + +// ReplacesTextOnInput reports that InputText replaces the field's content, so +// the runner skips its pre-erase. The driver clears the field inside InputText, +// which is robust even when a collapsed accessibility bridge would make the +// runner read the field length as zero and wrongly skip erasing. +func (d *Driver) ReplacesTextOnInput() bool { return true } + +var ( + _ driver.DeviceDriver = (*Driver)(nil) + _ driver.ForegroundChecker = (*Driver)(nil) + _ driver.TextReplacer = (*Driver)(nil) +) diff --git a/internal/driver/ioscompanion/driver_test.go b/internal/driver/ioscompanion/driver_test.go new file mode 100644 index 0000000..32ff96e --- /dev/null +++ b/internal/driver/ioscompanion/driver_test.go @@ -0,0 +1,853 @@ +package ioscompanion + +import ( + "bytes" + "context" + "errors" + "fmt" + "image" + "image/color" + "image/png" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/priyanshujain/sanderling/internal/driver" + "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/transport" +) + +// fakeCompanion is an in-package stand-in for transport.Companion. It records +// the order of calls and returns scripted results, so the driver's decision +// logic is testable without a live simulator. +type fakeCompanion struct { + // callsMutex guards calls: Snapshot captures the hierarchy and the + // screenshot concurrently. + callsMutex sync.Mutex + calls []string + + accessibilityJSON string + accessibilityErr error + screenshotData []byte + describe transport.ScreenDescription + apps []transport.InstalledApp + + // hidErr fires on the next SendHID then clears, letting a test inject one + // transient failure. + hidErr error +} + +func (f *fakeCompanion) record(name string) { + f.callsMutex.Lock() + defer f.callsMutex.Unlock() + f.calls = append(f.calls, name) +} + +func (f *fakeCompanion) recorded() []string { + f.callsMutex.Lock() + defer f.callsMutex.Unlock() + out := make([]string, len(f.calls)) + copy(out, f.calls) + return out +} + +func (f *fakeCompanion) AccessibilityInfo(context.Context) (string, error) { + f.record("accessibility") + return f.accessibilityJSON, f.accessibilityErr +} + +func (f *fakeCompanion) Describe(context.Context) (transport.ScreenDescription, error) { + f.record("describe") + return f.describe, nil +} + +func (f *fakeCompanion) SendHID(context.Context, ...transport.HIDEvent) error { + f.record("hid") + if f.hidErr != nil { + err := f.hidErr + f.hidErr = nil + return err + } + return nil +} + +func (f *fakeCompanion) Screenshot(context.Context) ([]byte, string, error) { + f.record("screenshot") + return f.screenshotData, "", nil +} + +func (f *fakeCompanion) Launch(_ context.Context, _ string, _ bool) error { + f.record("launch") + return nil +} + +func (f *fakeCompanion) Terminate(context.Context, string) error { + f.record("terminate") + return nil +} + +func (f *fakeCompanion) ListApps(context.Context) ([]transport.InstalledApp, error) { + f.record("listapps") + return f.apps, nil +} + +func (f *fakeCompanion) Install(context.Context, string) error { + f.record("install") + return nil +} + +func (f *fakeCompanion) Uninstall(context.Context, string) error { + f.record("uninstall") + return nil +} + +func (f *fakeCompanion) Close() error { + f.record("close") + return nil +} + +var _ transport.Companion = (*fakeCompanion)(nil) + +// newTestDriver returns a Driver wired to companion with no child process and a +// no-op container reset, so tests never touch the filesystem or spawn anything. +func newTestDriver(companion transport.Companion) *Driver { + d := &Driver{ + companion: companion, + bundleID: "com.example.app", + output: &bytes.Buffer{}, + doubleTapGapMilliseconds: DefaultDoubleTapGapMilliseconds, + screenWidth: 390, + screenHeight: 844, + } + d.resetContainer = func(context.Context) error { return nil } + d.reinstallApp = func(context.Context) error { return nil } + d.grantPaste = func(context.Context) error { return nil } + return d +} + +func samplePNG(t *testing.T, width, height int) []byte { + t.Helper() + img := image.NewRGBA(image.Rect(0, 0, width, height)) + img.Set(0, 0, color.RGBA{R: 1, A: 255}) + var buffer bytes.Buffer + if err := png.Encode(&buffer, img); err != nil { + t.Fatalf("encode png: %v", err) + } + return buffer.Bytes() +} + +func TestLaunchTerminatesFirstThenLaunches(t *testing.T) { + companion := &fakeCompanion{accessibilityJSON: "[]"} + d := newTestDriver(companion) + if err := d.Launch(context.Background(), "", false, nil); err != nil { + t.Fatalf("Launch: %v", err) + } + if companion.calls[0] != "terminate" { + t.Fatalf("first call = %q, want terminate", companion.calls[0]) + } + if indexOf(companion.calls, "launch") < indexOf(companion.calls, "terminate") { + t.Fatalf("launch must follow terminate; got %v", companion.calls) + } +} + +func TestLaunchGrantsPasteboardBeforeLaunch(t *testing.T) { + companion := &fakeCompanion{accessibilityJSON: "[]"} + d := newTestDriver(companion) + granted := false + d.grantPaste = func(context.Context) error { granted = true; return nil } + if err := d.Launch(context.Background(), "", false, nil); err != nil { + t.Fatalf("Launch: %v", err) + } + if !granted { + t.Fatal("Launch must grant pasteboard access so unicode input skips the permission prompt") + } +} + +func TestLaunchContinuesWhenGrantFails(t *testing.T) { + companion := &fakeCompanion{accessibilityJSON: "[]"} + d := newTestDriver(companion) + d.grantPaste = func(context.Context) error { return errors.New("no privacy db") } + if err := d.Launch(context.Background(), "", false, nil); err != nil { + t.Fatalf("Launch must continue when the grant fails: %v", err) + } + if indexOf(companion.calls, "launch") < 0 { + t.Fatalf("app must still launch; got %v", companion.calls) + } +} + +func TestLaunchClearStateReinstallsWithAppPath(t *testing.T) { + companion := &fakeCompanion{accessibilityJSON: "[]"} + d := newTestDriver(companion) + d.appPath = "/tmp/Sample.app" + reinstalls := 0 + d.reinstallApp = func(context.Context) error { reinstalls++; return nil } + if err := d.Launch(context.Background(), "", true, nil); err != nil { + t.Fatalf("Launch: %v", err) + } + if reinstalls != 1 { + t.Fatalf("clear-state with app path must reinstall exactly once; got %d", reinstalls) + } + if indexOf(companion.calls, "launch") < indexOf(companion.calls, "terminate") { + t.Fatalf("launch must still follow terminate; got %v", companion.calls) + } +} + +func TestLaunchClearStateFallbackWarnsOnce(t *testing.T) { + companion := &fakeCompanion{accessibilityJSON: "[]"} + output := &bytes.Buffer{} + d := newTestDriver(companion) + d.output = output + resets := 0 + d.resetContainer = func(context.Context) error { resets++; return nil } + + for i := 0; i < 2; i++ { + if err := d.Launch(context.Background(), "", true, nil); err != nil { + t.Fatalf("Launch %d: %v", i, err) + } + } + if resets != 2 { + t.Fatalf("resetContainer called %d times, want 2", resets) + } + warnings := strings.Count(output.String(), "resetting the data container only") + if warnings != 1 { + t.Fatalf("warning emitted %d times, want once", warnings) + } + for _, call := range companion.calls { + if call == "install" || call == "uninstall" { + t.Fatalf("fallback path must not install/uninstall; got %v", companion.calls) + } + } +} + +func TestLaunchRejectsEnvironment(t *testing.T) { + d := newTestDriver(&fakeCompanion{accessibilityJSON: "[]"}) + err := d.Launch(context.Background(), "", false, map[string]string{"K": "V"}) + if err == nil || !strings.Contains(err.Error(), "environment") { + t.Fatalf("Launch with env: err = %v, want unsupported-environment error", err) + } +} + +func TestSnapshotPairsHierarchyAndScreenshot(t *testing.T) { + companion := &fakeCompanion{ + accessibilityJSON: "[]", + screenshotData: samplePNG(t, 390, 844), + } + d := newTestDriver(companion) + _, image, err := d.Snapshot(context.Background()) + if err != nil { + t.Fatalf("Snapshot: %v", err) + } + if image.Width != 390 || image.Height != 844 { + t.Fatalf("image dims = %dx%d, want 390x844", image.Width, image.Height) + } + // The two captures run concurrently (they ride different transports on + // the hybrid path), so both must happen but in no particular order. + calls := companion.recorded() + if indexOf(calls, "accessibility") < 0 || indexOf(calls, "screenshot") < 0 { + t.Fatalf("snapshot must capture hierarchy and screenshot; got %v", calls) + } +} + +// blockingScreenshotCompanion holds its Screenshot until proceed closes, so a +// test can keep the screenshot leg in flight while the hierarchy leg recovers. +type blockingScreenshotCompanion struct { + fakeCompanion + proceed chan struct{} +} + +func (b *blockingScreenshotCompanion) Screenshot(ctx context.Context) ([]byte, string, error) { + <-b.proceed + return b.fakeCompanion.Screenshot(ctx) +} + +func TestSnapshotRestartDuringScreenshotDoesNotRace(t *testing.T) { + // The hierarchy leg drops its connection, forcing withRecovery to restart + // while the screenshot goroutine is still in flight. The restart reassigns + // d.companion the way respawnAndRedial does; the goroutine must keep + // working through the transport it captured rather than racing the field. + first := &blockingScreenshotCompanion{ + fakeCompanion: fakeCompanion{ + accessibilityErr: status.Error(codes.Unavailable, "companion gone"), + screenshotData: samplePNG(t, 390, 844), + }, + proceed: make(chan struct{}), + } + replacement := &fakeCompanion{ + accessibilityJSON: "[]", + screenshotData: samplePNG(t, 390, 844), + } + d := newTestDriver(first) + d.restart = func(context.Context) error { + d.companion = replacement + close(first.proceed) + return nil + } + _, image, err := d.Snapshot(context.Background()) + if err != nil { + t.Fatalf("Snapshot should recover: %v", err) + } + if image.Width != 390 || image.Height != 844 { + t.Fatalf("image dims = %dx%d, want 390x844", image.Width, image.Height) + } +} + +func TestScreenshotRejectsNonPNG(t *testing.T) { + d := newTestDriver(&fakeCompanion{screenshotData: []byte("not a png")}) + if _, err := d.Screenshot(context.Background()); err == nil { + t.Fatal("Screenshot of non-PNG should error") + } +} + +func TestInputTextFastPathSkipsFieldResolution(t *testing.T) { + companion := &fakeCompanion{accessibilityJSON: "[]"} + d := newTestDriver(companion) + // "abc" is fully mappable and under the paste threshold, so the fast + // keyboard path runs and never reads the accessibility dump for a field + // target. + if err := d.InputText(context.Background(), "abc"); err != nil { + t.Fatalf("InputText: %v", err) + } + if indexOf(companion.calls, "hid") < 0 { + t.Fatalf("fast path must send HID; got %v", companion.calls) + } + if indexOf(companion.calls, "accessibility") >= 0 { + t.Fatalf("fast path must not resolve a field via accessibility; got %v", companion.calls) + } +} + +func TestInputTextPasteboardResolvesFieldFromLastTap(t *testing.T) { + // The dump carries one editable field whose frame contains the last tap. + dump := `[{"type":"TextField","AXUniqueId":"field-1","frame":{"x":10,"y":100,"width":200,"height":40}}]` + companion := &fakeCompanion{accessibilityJSON: dump, screenshotData: samplePNG(t, 10, 10)} + d := newTestDriver(companion) + // Tap inside the field so lastTap lands on it. + if err := d.Tap(context.Background(), 50, 120); err != nil { + t.Fatalf("Tap: %v", err) + } + field := d.resolveInputField(context.Background()) + if field.identifier != "field-1" { + t.Fatalf("resolved field id = %q, want field-1", field.identifier) + } + if field.centerX != 110 || field.centerY != 120 { + t.Fatalf("field center = (%v,%v), want (110,120)", field.centerX, field.centerY) + } +} + +func TestResolveInputFieldEmptyWhenTapOutsideAnyField(t *testing.T) { + dump := `[{"type":"TextField","AXUniqueId":"field-1","frame":{"x":10,"y":100,"width":200,"height":40}}]` + d := newTestDriver(&fakeCompanion{accessibilityJSON: dump}) + if err := d.Tap(context.Background(), 5, 5); err != nil { + t.Fatalf("Tap: %v", err) + } + if field := d.resolveInputField(context.Background()); field != (fieldTarget{}) { + t.Fatalf("field = %+v, want empty", field) + } +} + +func TestSupervisionRestartsOnceThenSucceeds(t *testing.T) { + companion := &fakeCompanion{ + accessibilityJSON: "[]", + hidErr: status.Error(codes.Unavailable, "connection refused"), + } + d := newTestDriver(companion) + restarts := 0 + d.restart = func(context.Context) error { + restarts++ + return nil + } + // First SendHID fails with Unavailable; the recovery restarts once and the + // retry succeeds (hidErr cleared itself). + if err := d.Tap(context.Background(), 10, 10); err != nil { + t.Fatalf("Tap should recover: %v", err) + } + if restarts != 1 { + t.Fatalf("restarts = %d, want exactly 1", restarts) + } +} + +func TestSupervisionDoesNotRestartOnNormalError(t *testing.T) { + companion := &fakeCompanion{hidErr: errors.New("bad argument")} + d := newTestDriver(companion) + restarts := 0 + d.restart = func(context.Context) error { restarts++; return nil } + if err := d.Tap(context.Background(), 10, 10); err == nil { + t.Fatal("non-connection error should surface") + } + if restarts != 0 { + t.Fatalf("restarts = %d, want 0 on a normal error", restarts) + } +} + +func TestSupervisionBudgetResetsBetweenIncidents(t *testing.T) { + companion := &fakeCompanion{accessibilityJSON: "[]"} + d := newTestDriver(companion) + restarts := 0 + d.restart = func(context.Context) error { restarts++; return nil } + + companion.hidErr = status.Error(codes.Unavailable, "drop one") + if err := d.Tap(context.Background(), 1, 1); err != nil { + t.Fatalf("first incident: %v", err) + } + companion.hidErr = status.Error(codes.Unavailable, "drop two") + if err := d.Tap(context.Background(), 2, 2); err != nil { + t.Fatalf("second incident: %v", err) + } + if restarts != 2 { + t.Fatalf("restarts = %d, want 2 (one per incident)", restarts) + } +} + +func TestPressKeyEnterSupportedOthersUnsupported(t *testing.T) { + d := newTestDriver(&fakeCompanion{}) + if err := d.PressKey(context.Background(), "enter"); err != nil { + t.Fatalf("PressKey enter: %v", err) + } + if err := d.PressKey(context.Background(), "return"); err != nil { + t.Fatalf("PressKey return: %v", err) + } + for _, key := range []string{"back", "home", "tab"} { + if err := d.PressKey(context.Background(), key); err == nil { + t.Fatalf("PressKey %q should be unsupported", key) + } + } +} + +func TestWaitForIdleSettlesOnStableTree(t *testing.T) { + companion := &fakeCompanion{accessibilityJSON: "[]"} + d := newTestDriver(companion) + // A fake clock advances time only on Sleep, so the settle poll runs to its + // cap instantly instead of taking the real two seconds. + d.idleClock = &fakeClock{} + if err := d.WaitForIdle(context.Background(), time.Second); err != nil { + t.Fatalf("WaitForIdle: %v", err) + } + if indexOf(companion.calls, "accessibility") < 0 { + t.Fatalf("WaitForIdle must poll the hierarchy; got %v", companion.calls) + } +} + +func TestForegroundAppPrefersAppUnderTest(t *testing.T) { + companion := &fakeCompanion{apps: []transport.InstalledApp{ + {BundleID: "com.example.app", ProcessState: transport.ProcessStateRunning, InstallType: "user"}, + {BundleID: "com.other.app", ProcessState: transport.ProcessStateRunning, InstallType: "user"}, + }} + d := newTestDriver(companion) + got, err := d.ForegroundApp(context.Background()) + if err != nil { + t.Fatalf("ForegroundApp: %v", err) + } + if got != "com.example.app" { + t.Fatalf("ForegroundApp = %q, want com.example.app", got) + } +} + +func TestForegroundAppFallsBackToOtherRunningUserApp(t *testing.T) { + companion := &fakeCompanion{apps: []transport.InstalledApp{ + {BundleID: "com.example.app", ProcessState: transport.ProcessStateNotRunning, InstallType: "user"}, + {BundleID: "com.other.app", ProcessState: transport.ProcessStateRunning, InstallType: "user"}, + }} + d := newTestDriver(companion) + got, err := d.ForegroundApp(context.Background()) + if err != nil { + t.Fatalf("ForegroundApp: %v", err) + } + if got != "com.other.app" { + t.Fatalf("ForegroundApp = %q, want com.other.app", got) + } +} + +func TestHealthReportsIOS(t *testing.T) { + d := newTestDriver(&fakeCompanion{}) + health, err := d.Health(context.Background()) + if err != nil { + t.Fatalf("Health: %v", err) + } + if !health.Ready || health.Platform != "ios" { + t.Fatalf("Health = %+v, want ready ios", health) + } +} + +// The driver must satisfy DeviceDriver, ForegroundChecker, and TextReplacer +// (its InputText clears the field then types, so the runner skips its +// blackout-fragile pre-erase), but must NOT satisfy FocusedWindowChecker. +func TestCapabilityAssertions(t *testing.T) { + var instance any = newTestDriver(&fakeCompanion{}) + if _, ok := instance.(driver.DeviceDriver); !ok { + t.Fatal("Driver must implement DeviceDriver") + } + if _, ok := instance.(driver.ForegroundChecker); !ok { + t.Fatal("Driver must implement ForegroundChecker") + } + replacer, ok := instance.(driver.TextReplacer) + if !ok { + t.Fatal("Driver must implement TextReplacer") + } + if !replacer.ReplacesTextOnInput() { + t.Fatal("ReplacesTextOnInput must report true") + } + if _, ok := instance.(driver.FocusedWindowChecker); ok { + t.Fatal("Driver must NOT implement FocusedWindowChecker") + } +} + +func indexOf(slice []string, value string) int { + for i, item := range slice { + if item == value { + return i + } + } + return -1 +} + +// TestNewChildOutlivesStartup proves the companion child is spawned under the +// driver-lifetime context, not the startup-scoped one: a startup context that +// is canceled when New returns would SIGTERM the child mid-run. +func TestNewChildOutlivesStartup(t *testing.T) { + // The legacy-only path keeps this test focused on the companion child; + // the hybrid default would also demand runner assets. + t.Setenv("SANDERLING_SIMULATOR_COMPANION", "legacy") + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer listener.Close() + go func() { + for { + connection, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + _ = connection.Close() + } + }() + + var spawnContext context.Context + options := Options{ + UniqueDeviceIdentifier: "FAKE-UDID", + pickAddress: func() (string, error) { return listener.Addr().String(), nil }, + spawnChild: func(ctx context.Context, _ string) (*exec.Cmd, error) { + spawnContext = ctx + return &exec.Cmd{}, nil + }, + dialCompanion: func(string) (transport.Companion, error) { + return &fakeCompanion{accessibilityJSON: "[]"}, nil + }, + } + + d, err := New(context.Background(), options) + if err != nil { + t.Fatal(err) + } + select { + case <-spawnContext.Done(): + t.Fatal("spawn context canceled after New returned; child would receive SIGTERM mid-run") + default: + } + + d.Close() + select { + case <-spawnContext.Done(): + default: + t.Fatal("spawn context still alive after Close; child lifetime leaks") + } +} + +// fakeTextEditingCompanion extends fakeCompanion with the optional TextEditor +// capability so routing through the native text path is testable. +type fakeTextEditingCompanion struct { + fakeCompanion + + inputTexts []string + eraseCounts []int + pressedKeys []string +} + +func (f *fakeTextEditingCompanion) InputText(_ context.Context, text string) error { + f.record("inputtext") + f.inputTexts = append(f.inputTexts, text) + return nil +} + +func (f *fakeTextEditingCompanion) EraseText(_ context.Context, characterCount int) error { + f.record("erasetext") + f.eraseCounts = append(f.eraseCounts, characterCount) + return nil +} + +func (f *fakeTextEditingCompanion) PressKey(_ context.Context, key string) error { + f.record("presskey") + f.pressedKeys = append(f.pressedKeys, key) + return nil +} + +var _ transport.TextEditor = (*fakeTextEditingCompanion)(nil) + +func TestInputTextRoutesThroughTextEditor(t *testing.T) { + companion := &fakeTextEditingCompanion{} + d := newTestDriver(companion) + if err := d.InputText(context.Background(), "héllo 🌟"); err != nil { + t.Fatal(err) + } + if len(companion.inputTexts) != 1 || companion.inputTexts[0] != "héllo 🌟" { + t.Fatalf("inputTexts = %v, want the typed text once", companion.inputTexts) + } + for _, call := range companion.calls { + if call == "hid" { + t.Fatal("text editor path must not compose HID streams") + } + } +} + +func TestEraseTextRoutesThroughTextEditor(t *testing.T) { + companion := &fakeTextEditingCompanion{} + d := newTestDriver(companion) + if err := d.EraseText(context.Background(), 7); err != nil { + t.Fatal(err) + } + if len(companion.eraseCounts) != 1 || companion.eraseCounts[0] != 7 { + t.Fatalf("eraseCounts = %v, want [7]", companion.eraseCounts) + } +} + +func TestPressKeyRoutesThroughTextEditor(t *testing.T) { + companion := &fakeTextEditingCompanion{} + d := newTestDriver(companion) + if err := d.PressKey(context.Background(), "enter"); err != nil { + t.Fatal(err) + } + if len(companion.pressedKeys) != 1 || companion.pressedKeys[0] != "enter" { + t.Fatalf("pressedKeys = %v, want [enter]", companion.pressedKeys) + } + for _, call := range companion.calls { + if call == "hid" { + t.Fatal("text editor path must not compose HID streams") + } + } +} + +func TestIsConnectionErrorRecognizesUnavailableSentinel(t *testing.T) { + wrapped := errors.Join(errors.New("dial tcp: connection refused"), transport.ErrCompanionUnavailable) + if !isConnectionError(wrapped) { + t.Fatal("wrapped ErrCompanionUnavailable must count as a connection error") + } + if isConnectionError(errors.New("ordinary failure")) { + t.Fatal("ordinary errors must not count as connection errors") + } +} + +// fakeRunnerCompanion stands in for the in-simulator runner half of the hybrid: +// it serves snapshots and native typing. +type fakeRunnerCompanion struct { + fakeCompanion + + typed []struct { + text string + replace bool + } +} + +func (f *fakeRunnerCompanion) TypeText(_ context.Context, text string, replace bool) error { + f.record("typetext") + f.typed = append(f.typed, struct { + text string + replace bool + }{text, replace}) + return nil +} + +var _ transport.TextTyper = (*fakeRunnerCompanion)(nil) + +func newHybridTestDriver(legacy *fakeCompanion, runner *fakeRunnerCompanion) *Driver { + d := newTestDriver(legacy) + d.hybrid = true + d.runnerClient = runner + return d +} + +func TestHybridInputTextClearsViaHIDThenTypes(t *testing.T) { + legacy := &fakeCompanion{accessibilityJSON: "[]"} + runner := &fakeRunnerCompanion{} + d := newHybridTestDriver(legacy, runner) + + if err := d.InputText(context.Background(), "héllo 🌟"); err != nil { + t.Fatal(err) + } + if len(legacy.calls) != 1 || legacy.calls[0] != "hid" { + t.Fatalf("legacy calls = %v, want exactly the clear chord", legacy.calls) + } + if len(runner.typed) != 1 || runner.typed[0].text != "héllo 🌟" || runner.typed[0].replace { + t.Fatalf("typed = %+v, want the text appended without replace", runner.typed) + } +} + +func TestHybridSnapshotsComeFromRunner(t *testing.T) { + legacy := &fakeCompanion{accessibilityJSON: `[{"type":"Application","frame":{"x":0,"y":0,"width":1,"height":1},"enabled":true}]`} + runner := &fakeRunnerCompanion{} + runner.accessibilityJSON = `[{"type":"Button","AXUniqueId":"FromRunner","frame":{"x":0,"y":0,"width":10,"height":10},"enabled":true}]` + d := newHybridTestDriver(legacy, runner) + + hierarchy, err := d.Hierarchy(context.Background()) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(hierarchy, "FromRunner") { + t.Fatalf("hierarchy = %s, want runner content", hierarchy) + } + for _, call := range legacy.calls { + if call == "accessibility" { + t.Fatal("hybrid must not read snapshots from the legacy companion") + } + } +} + +func TestHybridLaunchSkipsPasteGrant(t *testing.T) { + legacy := &fakeCompanion{accessibilityJSON: "[]"} + runner := &fakeRunnerCompanion{} + d := newHybridTestDriver(legacy, runner) + granted := 0 + d.grantPaste = func(context.Context) error { granted++; return nil } + + if err := d.Launch(context.Background(), "", false, nil); err != nil { + t.Fatal(err) + } + if granted != 0 { + t.Fatalf("grantPaste called %d times, want 0 on the hybrid path", granted) + } +} + +func TestHybridGesturesStayOnLegacyHID(t *testing.T) { + legacy := &fakeCompanion{accessibilityJSON: "[]"} + runner := &fakeRunnerCompanion{} + d := newHybridTestDriver(legacy, runner) + + if err := d.DoubleTap(context.Background(), 10, 20); err != nil { + t.Fatal(err) + } + if len(legacy.calls) != 1 || legacy.calls[0] != "hid" { + t.Fatalf("legacy calls = %v, want the double-tap HID stream", legacy.calls) + } + for _, call := range runner.calls { + if call == "hid" { + t.Fatal("runner must not receive gesture streams") + } + } +} + +func TestHybridRunnerErrorTriggersRestart(t *testing.T) { + legacy := &fakeCompanion{accessibilityJSON: "[]"} + runner := &fakeRunnerCompanion{} + runner.accessibilityErr = fmt.Errorf("read snapshot: %w", transport.ErrCompanionUnavailable) + d := newHybridTestDriver(legacy, runner) + restarts := 0 + d.restart = func(context.Context) error { + restarts++ + runner.accessibilityErr = nil + return nil + } + + if _, err := d.Hierarchy(context.Background()); err != nil { + t.Fatal(err) + } + if restarts != 1 { + t.Fatalf("restarts = %d, want 1", restarts) + } +} + +func TestBindTestRunPortSubstitutesPlaceholder(t *testing.T) { + directory := t.TempDir() + source := filepath.Join(directory, "runner.xctestrun") + if err := os.WriteFile(source, []byte("__COMPANION_PORT__"), 0o644); err != nil { + t.Fatal(err) + } + bound, err := bindTestRunPort(source, "27999") + if err != nil { + t.Fatal(err) + } + if filepath.Dir(bound) != directory { + t.Fatalf("bound copy %s must sit next to the original", bound) + } + content, err := os.ReadFile(bound) + if err != nil { + t.Fatal(err) + } + if string(content) != "27999" { + t.Fatalf("bound content = %s", content) + } +} + +func TestBindTestRunPortRejectsMissingPlaceholder(t *testing.T) { + directory := t.TempDir() + source := filepath.Join(directory, "runner.xctestrun") + if err := os.WriteFile(source, []byte("fixed"), 0o644); err != nil { + t.Fatal(err) + } + if _, err := bindTestRunPort(source, "27999"); err == nil { + t.Fatal("expected an error for a configuration without the placeholder") + } +} + +func TestHybridMappableTextRidesOneHIDStream(t *testing.T) { + legacy := &fakeCompanion{accessibilityJSON: "[]"} + runner := &fakeRunnerCompanion{} + d := newHybridTestDriver(legacy, runner) + + if err := d.InputText(context.Background(), "Travel 42"); err != nil { + t.Fatal(err) + } + if len(legacy.recorded()) != 1 || legacy.recorded()[0] != "hid" { + t.Fatalf("legacy calls = %v, want one combined chord-and-keystrokes stream", legacy.recorded()) + } + if len(runner.typed) != 0 { + t.Fatalf("typed = %+v, want no native typing for mappable text", runner.typed) + } +} + +func TestHybridUnicodeWaitsForClearedFieldBeforeTyping(t *testing.T) { + legacy := &fakeCompanion{accessibilityJSON: "[]"} + runner := &fakeRunnerCompanion{} + // The focused field still shows old content on the first read and is + // empty on the second; typing must come after the cleared read. + first := `[{"type":"TextField","AXUniqueId":"F","AXValue":"old","frame":{"x":0,"y":0,"width":100,"height":40},"enabled":true}]` + second := `[{"type":"TextField","AXUniqueId":"F","AXValue":"","frame":{"x":0,"y":0,"width":100,"height":40},"enabled":true}]` + reads := 0 + d := newHybridTestDriver(legacy, runner) + d.mu.Lock() + d.lastTap.x, d.lastTap.y, d.lastTap.set = 50, 20, true + d.mu.Unlock() + // Swap the dump after the first read through a wrapper companion. + wrapped := &sequencedDumpCompanion{fakeRunnerCompanion: runner, dumps: []string{first, second}, reads: &reads} + d.runnerClient = wrapped + + if err := d.InputText(context.Background(), "héllo 🌟"); err != nil { + t.Fatal(err) + } + if reads < 2 { + t.Fatalf("reads = %d, want at least 2 (poll until cleared)", reads) + } + if len(wrapped.typed) != 1 || wrapped.typed[0].text != "héllo 🌟" || wrapped.typed[0].replace { + t.Fatalf("typed = %+v", wrapped.typed) + } +} + +// sequencedDumpCompanion serves scripted dumps in order, repeating the last. +type sequencedDumpCompanion struct { + *fakeRunnerCompanion + dumps []string + reads *int +} + +func (s *sequencedDumpCompanion) AccessibilityInfo(context.Context) (string, error) { + index := *s.reads + if index >= len(s.dumps) { + index = len(s.dumps) - 1 + } + *s.reads++ + return s.dumps[index], nil +} diff --git a/internal/driver/ioscompanion/hierarchymap.go b/internal/driver/ioscompanion/hierarchymap.go new file mode 100644 index 0000000..8b0db9d --- /dev/null +++ b/internal/driver/ioscompanion/hierarchymap.go @@ -0,0 +1,171 @@ +package ioscompanion + +import ( + "encoding/json" + "math" + "strconv" +) + +// rawFrame is the simulator companion frame, in points, with float coordinates. +type rawFrame struct { + X float64 `json:"x"` + Y float64 `json:"y"` + Width float64 `json:"width"` + Height float64 `json:"height"` +} + +// emptyFieldValueSentinel is what the accessibility bridge reports as the +// AXValue of an empty editable field. It is bridge state, not app content, so +// it maps to an empty value rather than surfacing as literal field text. +const emptyFieldValueSentinel = "Invalid" + +// dumpIsCollapsed reports whether a flat describe-all dump carries no real UI +// content: it is empty or holds only the application shell. The accessibility +// bridge briefly returns this state during cold start and screen transitions +// before the real tree reappears. +func dumpIsCollapsed(dump []byte) bool { + elements := decodeDump(dump) + for _, element := range elements { + if element.Type != "" && element.Type != "Application" { + return false + } + } + return true +} + +// rawElement is one entry in the flat describe-all dump returned by the +// simulator companion. Only the fields the mapper consumes are declared; +// unknown fields are ignored. +type rawElement struct { + Frame rawFrame `json:"frame"` + AXUniqueID *string `json:"AXUniqueId"` + AXLabel *string `json:"AXLabel"` + AXValue *string `json:"AXValue"` + Type string `json:"type"` + Enabled bool `json:"enabled"` +} + +// treeNode mirrors the TreeNode JSON the hierarchy package parses. Bool fields +// are pointers so absent ones marshal to null and stay absent for the consumer. +type treeNode struct { + Attributes map[string]string `json:"attributes"` + Children []treeNode `json:"children,omitempty"` + Clickable *bool `json:"clickable,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Editable *bool `json:"editable,omitempty"` +} + +// MapHierarchy converts a flat describe-all dump from the simulator companion +// into the TreeNode JSON consumed by hierarchy.Parse. The result is a single +// synthesized root covering the whole screen with every dump element as a +// direct child. Coordinates are points throughout; nothing is scaled. +// +// The function is total: malformed input yields an empty-but-valid root rather +// than an error, and individual malformed elements are skipped. +func MapHierarchy(dump []byte, screenWidth, screenHeight int) ([]byte, error) { + root := treeNode{ + Attributes: map[string]string{ + "bounds": boundsString(0, 0, screenWidth, screenHeight), + "class": "Window", + }, + } + + // Decode element-by-element so a single malformed entry (a bad frame, an + // out-of-range number) is skipped rather than discarding the whole dump. + var rawElements []json.RawMessage + if len(dump) > 0 { + _ = json.Unmarshal(dump, &rawElements) + } + for _, raw := range rawElements { + var element rawElement + if err := json.Unmarshal(raw, &element); err != nil { + continue + } + if child, ok := mapElement(&element); ok { + root.Children = append(root.Children, child) + } + } + + return json.Marshal(root) +} + +func mapElement(element *rawElement) (treeNode, bool) { + if element.Type == "" { + return treeNode{}, false + } + + frame := element.Frame + if !finite(frame.X) || !finite(frame.Y) || !finite(frame.Width) || !finite(frame.Height) { + return treeNode{}, false + } + + left := roundCoord(frame.X) + top := roundCoord(frame.Y) + attributes := map[string]string{ + "bounds": boundsString(left, top, left+roundCoord(frame.Width), top+roundCoord(frame.Height)), + "class": element.Type, + } + + if id := stringValue(element.AXUniqueID); id != "" { + attributes["identifier"] = id + } + + value := stringValue(element.AXValue) + if value == emptyFieldValueSentinel { + // An empty editable field reads as this sentinel through the bridge; + // it is not app content, so treat the field as empty. + value = "" + } + label := stringValue(element.AXLabel) + editable := isEditable(element.Type) + + if value != "" { + attributes["text"] = value + if label != "" { + attributes["accessibilityText"] = label + } + } else if editable && label != "" { + // An empty text field surfaces its placeholder as the AXLabel. + attributes["hintText"] = label + } else if label != "" { + attributes["accessibilityText"] = label + } + + enabled := element.Enabled + node := treeNode{Attributes: attributes, Enabled: &enabled} + + if editable { + yes := true + node.Editable = &yes + } + if element.Type == "Button" { + yes := true + node.Clickable = &yes + } + + return node, true +} + +func isEditable(elementType string) bool { + return elementType == "TextArea" || elementType == "TextField" +} + +func stringValue(pointer *string) string { + if pointer == nil { + return "" + } + return *pointer +} + +func finite(value float64) bool { + return !math.IsNaN(value) && !math.IsInf(value, 0) +} + +func roundCoord(value float64) int { + return int(math.Round(value)) +} + +func boundsString(left, top, right, bottom int) string { + return "[" + strconv.Itoa(left) + "," + strconv.Itoa(top) + "][" + + strconv.Itoa(right) + "," + strconv.Itoa(bottom) + "]" +} diff --git a/internal/driver/ioscompanion/hierarchymap_test.go b/internal/driver/ioscompanion/hierarchymap_test.go new file mode 100644 index 0000000..7c488c0 --- /dev/null +++ b/internal/driver/ioscompanion/hierarchymap_test.go @@ -0,0 +1,260 @@ +package ioscompanion + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/priyanshujain/sanderling/internal/hierarchy" +) + +func mapAndParse(t *testing.T, dump []byte, width, height int) *hierarchy.Tree { + t.Helper() + treeJSON, err := MapHierarchy(dump, width, height) + if err != nil { + t.Fatalf("MapHierarchy: %v", err) + } + tree, err := hierarchy.Parse(string(treeJSON)) + if err != nil { + t.Fatalf("hierarchy.Parse: %v", err) + } + return tree +} + +func readDump(t *testing.T, name string) []byte { + t.Helper() + data, err := os.ReadFile(filepath.Join("testdata", name)) + if err != nil { + t.Fatalf("read %s: %v", name, err) + } + return data +} + +func TestGoldenLoginResolvesIdentifier(t *testing.T) { + tree := mapAndParse(t, readDump(t, "login-describe.json"), 402, 874) + + element := tree.Find("id:LoginEmail") + if element == nil { + t.Fatal("id:LoginEmail did not resolve") + } + if element.ResourceID != "LoginEmail" { + t.Fatalf("ResourceID = %q, want LoginEmail", element.ResourceID) + } + if element.Class != "TextArea" { + t.Fatalf("Class = %q, want TextArea", element.Class) + } + // LoginEmail is an empty field, so its placeholder is the hintText. + if element.Attributes["hintText"] != "Email" { + t.Fatalf("hintText = %q, want Email", element.Attributes["hintText"]) + } + if !element.Editable { + t.Fatal("LoginEmail should be editable") + } + wantBounds := hierarchy.Bounds{Left: 34, Top: 125, Right: 368, Bottom: 173} + if element.Bounds != wantBounds { + t.Fatalf("Bounds = %+v, want %+v", element.Bounds, wantBounds) + } +} + +func TestGoldenScopedQueryUsesSpatialFallback(t *testing.T) { + tree := mapAndParse(t, readDump(t, "accounts-describe.json"), 402, 874) + + // The Application node spans the full screen but the flat tree gives it no + // descendants. A scoped query for content inside it must resolve through the + // hierarchy package's spatial-containment fallback. + container := tree.FindNode("class:Application") + if container == nil { + t.Fatal("class:Application did not resolve") + } + if len(container.Children) != 0 { + t.Fatalf("expected a flat tree with no Application descendants, got %d", len(container.Children)) + } + + scoped := container.Find("desc:Accounts") + if scoped == nil { + t.Fatal("scoped desc:Accounts did not resolve via spatial fallback") + } + if scoped.Description != "Accounts" { + t.Fatalf("scoped Description = %q, want Accounts", scoped.Description) + } + + // Smallest-area ranking should put the compact label ahead of any larger + // spatially-contained match. + scopedButton := container.Find("id:AddAccountButton") + if scopedButton == nil { + t.Fatal("scoped id:AddAccountButton did not resolve via spatial fallback") + } + if scopedButton.ResourceID != "AddAccountButton" { + t.Fatalf("scoped ResourceID = %q, want AddAccountButton", scopedButton.ResourceID) + } +} + +func parseSingle(t *testing.T, dump string) *hierarchy.Element { + t.Helper() + tree := mapAndParse(t, []byte(dump), 400, 800) + if len(tree.Elements) < 2 { + t.Fatalf("expected root plus one child, got %d elements", len(tree.Elements)) + } + // Element 0 is the synthesized root; element 1 is the mapped child. + return tree.Elements[1] +} + +func TestPlaceholderMapsToHintText(t *testing.T) { + dump := `[{"type":"TextField","frame":{"x":10,"y":20,"width":100,"height":40}, + "AXUniqueId":"Search","AXLabel":"Search accounts","AXValue":null,"enabled":true}]` + element := parseSingle(t, dump) + if element.Attributes["hintText"] != "Search accounts" { + t.Fatalf("hintText = %q, want Search accounts", element.Attributes["hintText"]) + } + if element.Attributes["accessibilityText"] != "" { + t.Fatalf("accessibilityText = %q, want empty", element.Attributes["accessibilityText"]) + } + if element.Text != "" { + t.Fatalf("text = %q, want empty", element.Text) + } +} + +func TestNonEmptyValueMapsToText(t *testing.T) { + dump := `[{"type":"TextField","frame":{"x":0,"y":0,"width":10,"height":10}, + "AXUniqueId":"Search","AXLabel":"Search accounts","AXValue":"groceries","enabled":true}]` + element := parseSingle(t, dump) + if element.Text != "groceries" { + t.Fatalf("text = %q, want groceries", element.Text) + } + // With a value present, the label is a real accessibility label, not a hint. + if element.Attributes["accessibilityText"] != "Search accounts" { + t.Fatalf("accessibilityText = %q, want Search accounts", element.Attributes["accessibilityText"]) + } + if element.Attributes["hintText"] != "" { + t.Fatalf("hintText = %q, want empty", element.Attributes["hintText"]) + } +} + +func TestEditableAndClickableFlags(t *testing.T) { + cases := []struct { + elementType string + wantEditable bool + wantClickable bool + }{ + {"TextField", true, false}, + {"TextArea", true, false}, + {"Button", false, true}, + {"StaticText", false, false}, + } + for _, testCase := range cases { + dump := `[{"type":"` + testCase.elementType + `","frame":{"x":0,"y":0,"width":10,"height":10},"enabled":true}]` + element := parseSingle(t, dump) + if element.Editable != testCase.wantEditable { + t.Errorf("%s editable = %v, want %v", testCase.elementType, element.Editable, testCase.wantEditable) + } + if element.Clickable != testCase.wantClickable { + t.Errorf("%s clickable = %v, want %v", testCase.elementType, element.Clickable, testCase.wantClickable) + } + } +} + +func TestEnabledMapsToTopLevelBool(t *testing.T) { + dump := `[{"type":"Button","frame":{"x":0,"y":0,"width":10,"height":10},"enabled":false}]` + element := parseSingle(t, dump) + if element.Enabled { + t.Fatal("element should be disabled") + } + if element.Attributes["enabled"] != "false" { + t.Fatalf("enabled attr = %q, want false", element.Attributes["enabled"]) + } +} + +func TestBoundsArithmetic(t *testing.T) { + dump := `[{"type":"Button","frame":{"x":34,"y":125.33333,"width":334,"height":48.00001},"enabled":true}]` + element := parseSingle(t, dump) + want := hierarchy.Bounds{Left: 34, Top: 125, Right: 368, Bottom: 173} + if element.Bounds != want { + t.Fatalf("Bounds = %+v, want %+v", element.Bounds, want) + } +} + +func TestMalformedElementsSkipped(t *testing.T) { + // First element has no type and must be skipped; second has a NaN frame and + // must be skipped; third is valid. + dump := `[ + {"frame":{"x":0,"y":0,"width":10,"height":10},"enabled":true}, + {"type":"Button","frame":{"x":0,"y":0,"width":1e999,"height":10},"enabled":true}, + {"type":"Button","frame":{"x":0,"y":0,"width":10,"height":10},"AXUniqueId":"OK","enabled":true} + ]` + tree := mapAndParse(t, []byte(dump), 400, 800) + // Root plus exactly one valid child. + if len(tree.Elements) != 2 { + t.Fatalf("expected 2 elements (root + 1 valid), got %d", len(tree.Elements)) + } + if tree.Find("id:OK") == nil { + t.Fatal("valid element OK should resolve") + } +} + +func TestEmptyAndGarbageInputAreTotal(t *testing.T) { + for _, dump := range [][]byte{nil, {}, []byte("not json"), []byte("{}"), []byte("[]")} { + treeJSON, err := MapHierarchy(dump, 100, 200) + if err != nil { + t.Fatalf("MapHierarchy(%q) error: %v", dump, err) + } + var node treeNode + if err := json.Unmarshal(treeJSON, &node); err != nil { + t.Fatalf("result not valid TreeNode JSON for %q: %v", dump, err) + } + if node.Attributes["bounds"] != "[0,0][100,200]" { + t.Fatalf("root bounds = %q, want [0,0][100,200]", node.Attributes["bounds"]) + } + } +} + +func TestRootIsFlatWithAllChildren(t *testing.T) { + tree := mapAndParse(t, readDump(t, "login-describe.json"), 402, 874) + if tree.Root == nil { + t.Fatal("nil root") + } + // The login dump has 10 elements; all map to direct children of the root. + if len(tree.Root.Children) != 10 { + t.Fatalf("root children = %d, want 10", len(tree.Root.Children)) + } + wantRoot := hierarchy.Bounds{Left: 0, Top: 0, Right: 402, Bottom: 874} + if tree.Root.Bounds != wantRoot { + t.Fatalf("root bounds = %+v, want %+v", tree.Root.Bounds, wantRoot) + } +} + +func TestMapHierarchySentinelValueMapsAsEmpty(t *testing.T) { + dump := `[{"type":"TextField","AXUniqueId":"F","AXLabel":"Email","AXValue":"Invalid","frame":{"x":0,"y":0,"width":10,"height":10},"enabled":true}]` + mapped, err := MapHierarchy([]byte(dump), 100, 100) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(mapped), "Invalid") { + t.Fatalf("sentinel leaked into mapped tree: %s", mapped) + } + if !strings.Contains(string(mapped), "hintText") { + t.Fatalf("empty editable field should map label to hintText: %s", mapped) + } +} + +func TestDumpIsCollapsed(t *testing.T) { + cases := []struct { + name string + dump string + want bool + }{ + {name: "empty array", dump: `[]`, want: true}, + {name: "application only", dump: `[{"type":"Application","frame":{"x":0,"y":0,"width":390,"height":844}}]`, want: true}, + {name: "malformed", dump: `nope`, want: true}, + {name: "real screen", dump: `[{"type":"Application"},{"type":"TextArea","AXUniqueId":"LoginEmail","frame":{"x":0,"y":0,"width":1,"height":1}}]`, want: false}, + {name: "single button", dump: `[{"type":"Button","frame":{"x":0,"y":0,"width":1,"height":1}}]`, want: false}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if got := dumpIsCollapsed([]byte(c.dump)); got != c.want { + t.Fatalf("got %v, want %v", got, c.want) + } + }) + } +} diff --git a/internal/driver/ioscompanion/input.go b/internal/driver/ioscompanion/input.go new file mode 100644 index 0000000..b853765 --- /dev/null +++ b/internal/driver/ioscompanion/input.go @@ -0,0 +1,434 @@ +// Package ioscompanion drives an iOS simulator through the native simulator +// companion. This file composes text input and gestures into HID streams and +// implements the pasteboard fallback for text that the hardware keyboard +// cannot type (anything outside the mappable rune set, such as accented +// letters or emoji). +package ioscompanion + +import ( + "context" + "encoding/json" + "fmt" + "os/exec" + "strings" + "time" + + "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/transport" +) + +// DefaultDoubleTapGapMilliseconds is a sensible inter-tap gap for a synthesized +// double tap. The driver owns the real default; gesture composers take the gap +// as a parameter so the value stays configurable. +const DefaultDoubleTapGapMilliseconds = 70 + +// pasteVerifyTimeout bounds the whole paste-and-verify loop. Dismissing the +// permission dialog blacks out the accessibility bridge for around 2.5s (the +// dump collapses to the root element), and the pasted value only becomes +// readable once it recovers, so the budget has to outlast that blackout. +const pasteVerifyTimeout = 8 * time.Second + +// pastePoll is the interval between describe-all reads while verifying a paste. +const pastePoll = 250 * time.Millisecond + +// dialogSettle is the pause after tapping the dialog's allow button and after +// refocusing the field, before the paste chord is resent. +const dialogSettle = 200 * time.Millisecond + +// runner abstracts the simulator-companion side effects InputText needs so the +// decision logic stays testable without a live device. The driver supplies a +// real implementation; tests supply a fake. +type runner interface { + // setPasteboard places text on the simulator pasteboard. + setPasteboard(ctx context.Context, text string) error + // sendHID sends one HID stream to the companion. + sendHID(ctx context.Context, events ...transport.HIDEvent) error + // describeAll returns the flat describe-all accessibility dump. + describeAll(ctx context.Context) ([]byte, error) + // sleep waits, respecting context cancellation. + sleep(ctx context.Context, duration time.Duration) error +} + +// simctlRunner is the production runner. It shells out to simctl for the +// pasteboard and uses the transport companion for everything else. +type simctlRunner struct { + companion transport.Companion + udid string +} + +func (r simctlRunner) setPasteboard(ctx context.Context, text string) error { + command := exec.CommandContext(ctx, "xcrun", "simctl", "pbcopy", r.udid) + command.Stdin = strings.NewReader(text) + return command.Run() +} + +func (r simctlRunner) sendHID(ctx context.Context, events ...transport.HIDEvent) error { + return r.companion.SendHID(ctx, events...) +} + +func (r simctlRunner) describeAll(ctx context.Context) ([]byte, error) { + info, err := r.companion.AccessibilityInfo(ctx) + if err != nil { + return nil, err + } + return []byte(info), nil +} + +func (r simctlRunner) sleep(ctx context.Context, duration time.Duration) error { + timer := time.NewTimer(duration) + defer timer.Stop() + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + return nil + } +} + +// fieldTarget identifies the focused field for the pasteboard path: its +// AXUniqueId (to confirm the paste landed) and its on-screen center (to refocus +// after dismissing the permission dialog). +type fieldTarget struct { + identifier string + centerX float64 + centerY float64 +} + +// usesPasteboard reports whether text takes the pasteboard path. Only +// unmappable runes force it: on this OS generation every external pasteboard +// write re-triggers the paste-permission dialog and dismissing it blacks out +// the accessibility bridge for seconds, so the hardware keyboard stays the +// default for everything it can express. +func usesPasteboard(text string) bool { + _, skipped := typeString(text) + return len(skipped) > 0 +} + +// inputText replaces the focused field's content with text. It selects any +// existing content and deletes it first, so the result is the typed text alone +// regardless of what the field held. Replacing (rather than relying on the +// runner's pre-erase) keeps input correct even when the accessibility bridge is +// momentarily collapsed and the runner cannot read the field's length. Mappable +// text goes through the hardware keyboard in one HID stream; anything else falls +// back to the pasteboard. The field target is only consulted on the pasteboard +// path. +func inputText(ctx context.Context, run runner, text string, field fieldTarget) error { + if !usesPasteboard(text) { + events := append(clearFieldEvents(), keyPressEvents(typeStringPresses(text))...) + return run.sendHID(ctx, events...) + } + if err := run.sendHID(ctx, clearFieldEvents()...); err != nil { + return fmt.Errorf("clear field: %w", err) + } + return pasteText(ctx, run, text, field) +} + +// typeStringPresses is typeString's presses, dropping the skipped runes (the +// caller already decided this text is fully mappable). +func typeStringPresses(text string) []KeyPress { + presses, _ := typeString(text) + return presses +} + +// selectionApplyDelayMilliseconds is the in-stream pause between the +// select-all chord and the deleting backspace. The chord's selection applies +// asynchronously in the app; a backspace fired in the same instant deletes +// one character at the cursor instead of the selection, which on a full field +// silently turns replace into append. Long content needs the most time, and +// this pause covers it with margin. +const selectionApplyDelayMilliseconds = 150 + +// deleteApplyDelayMilliseconds is the in-stream pause after the deleting +// backspace, so following keystrokes land in the emptied field. +const deleteApplyDelayMilliseconds = 40 + +// clearFieldEvents selects the whole field (command+A), waits for the +// selection to apply, deletes it, and waits for the delete to apply, so a +// following type or paste lands in an empty field. On an already-empty field +// the select selects nothing and the delete is a no-op. +func clearFieldEvents() []transport.HIDEvent { + events := append(selectAllChordEvents(), transport.Delay(selectionApplyDelayMilliseconds)) + events = append(events, keyPressEvents(backspaces(1))...) + return append(events, transport.Delay(deleteApplyDelayMilliseconds)) +} + +// pasteText copies the full text to the pasteboard, sends the paste chord +// once, and polls until the field reflects the text. The chord is re-sent ONLY +// after dismissing a permission dialog (the dialog swallowed that paste); +// re-sending it on a slow render would paste the text twice. When the field +// cannot be verified (no identifier resolved), one chord plus a settle is the +// best available behavior. +func pasteText(ctx context.Context, run runner, text string, field fieldTarget) error { + if err := run.setPasteboard(ctx, text); err != nil { + return fmt.Errorf("set pasteboard: %w", err) + } + if err := run.sendHID(ctx, pasteChordEvents()...); err != nil { + return fmt.Errorf("send paste chord: %w", err) + } + verifiable := field.identifier != "" + maxPolls := int(pasteVerifyTimeout / pastePoll) + for poll := 0; poll < maxPolls; poll++ { + dump, err := run.describeAll(ctx) + if err != nil { + return fmt.Errorf("describe accessibility: %w", err) + } + if pasteLanded(dump, field.identifier, text) { + return nil + } + if button, found := findAllowPasteButton(dump); found { + // The dialog swallowed the paste; dismiss it, refocus, and resend + // the chord exactly once. Dismissing blacks out the bridge, so the + // landed value only appears on a later poll. + if err := run.sendHID(ctx, tapEvents(button.centerX, button.centerY)...); err != nil { + return fmt.Errorf("tap allow button: %w", err) + } + if err := run.sleep(ctx, dialogSettle); err != nil { + return err + } + if err := run.sendHID(ctx, tapEvents(field.centerX, field.centerY)...); err != nil { + return fmt.Errorf("refocus field: %w", err) + } + if err := run.sleep(ctx, dialogSettle); err != nil { + return err + } + if err := run.sendHID(ctx, pasteChordEvents()...); err != nil { + return fmt.Errorf("send paste chord: %w", err) + } + continue + } + if !verifiable { + // Without a field identifier the paste cannot be confirmed. The + // chord went out and no dialog is blocking it, so one settle is the + // best available behavior. + return run.sleep(ctx, pastePoll) + } + // Field not yet showing the text: either the bridge is still blacked + // out from the dialog or the paste has not rendered. Keep polling until + // the value lands or the budget runs out. + if err := run.sleep(ctx, pastePoll); err != nil { + return err + } + } + return fmt.Errorf("paste did not land within %s", pasteVerifyTimeout) +} + +// eraseBackspaceThreshold is the largest erase still sent as individual +// backspaces. Backspaces render progressively on the simulator (tens of +// milliseconds per character), so clearing a long field key-by-key leaves the +// screen churning long after the HID call returns and races whatever input +// follows. Above the threshold the field is cleared atomically instead. +const eraseBackspaceThreshold = 3 + +// eraseText deletes characterCount characters from the focused field. Small +// counts go as backspaces in one HID stream; larger counts clear the whole +// field via select-all plus one backspace. The runner asks for the field's +// full length when it pre-erases (replace semantics), so treating a large +// count as clear-the-field matches its intent while landing in one frame. +func eraseText(ctx context.Context, run runner, characterCount int) error { + if characterCount <= 0 { + return nil + } + if characterCount <= eraseBackspaceThreshold { + return run.sendHID(ctx, keyPressEvents(backspaces(characterCount))...) + } + return run.sendHID(ctx, clearFieldEvents()...) +} + +// keyPressEvents flattens key presses into a HID event stream. A shifted press +// is wrapped with left-shift down before and up after, so the shift modifier is +// held only for that key. +func keyPressEvents(presses []KeyPress) []transport.HIDEvent { + events := make([]transport.HIDEvent, 0, len(presses)*2) + for _, press := range presses { + if press.Shift { + events = append(events, transport.KeyDown(usageLeftShift)) + } + events = append(events, transport.KeyDown(press.Usage), transport.KeyUp(press.Usage)) + if press.Shift { + events = append(events, transport.KeyUp(usageLeftShift)) + } + } + return events +} + +// pasteChordEvents is the command+V chord: command down, V down, V up, +// command up. +func pasteChordEvents() []transport.HIDEvent { + return []transport.HIDEvent{ + transport.KeyDown(LeftGUI), + transport.KeyDown(VKey), + transport.KeyUp(VKey), + transport.KeyUp(LeftGUI), + } +} + +// selectAllChordEvents is the command+A chord selecting the focused field's +// whole content. +func selectAllChordEvents() []transport.HIDEvent { + return []transport.HIDEvent{ + transport.KeyDown(LeftGUI), + transport.KeyDown(usageA), + transport.KeyUp(usageA), + transport.KeyUp(LeftGUI), + } +} + +// tapEvents is a single tap: finger down then up at one point. +func tapEvents(x, y float64) []transport.HIDEvent { + return []transport.HIDEvent{transport.TouchDown(x, y), transport.TouchUp(x, y)} +} + +// doubleTapEvents is two taps in one stream separated by gapMilliseconds. +func doubleTapEvents(x, y float64, gapMilliseconds float64) []transport.HIDEvent { + return []transport.HIDEvent{ + transport.TouchDown(x, y), transport.TouchUp(x, y), + transport.Delay(gapMilliseconds), + transport.TouchDown(x, y), transport.TouchUp(x, y), + } +} + +// longPressEvents is a finger held down for holdMilliseconds before lifting. +func longPressEvents(x, y float64, holdMilliseconds float64) []transport.HIDEvent { + return []transport.HIDEvent{ + transport.TouchDown(x, y), + transport.Delay(holdMilliseconds), + transport.TouchUp(x, y), + } +} + +// allowPasteButton is the located allow button of the paste-permission dialog. +type allowPasteButton struct { + centerX float64 + centerY float64 +} + +// allowPasteLabels are the known en-US labels of the paste dialog's accept +// button. iOS also surfaces a reject button ("Don't Allow Paste"), so a plain +// "Allow" substring match would be ambiguous; the located labels are matched +// exactly against the trimmed AXLabel. +var allowPasteLabels = []string{"Allow Paste", "Allow"} + +// findAllowPasteButton locates the allow button of the paste-permission dialog +// in a describe-all dump. It first matches a button whose label is a known +// allow label. Failing that, it applies a conservative fallback: if exactly one +// enabled button is present in the dump, that lone button is taken to be the +// dialog's allow control. The fallback is deliberately narrow so it cannot fire +// on an ordinary screen full of buttons; the dialog is modal and collapses the +// dump to its own controls. +func findAllowPasteButton(dump []byte) (allowPasteButton, bool) { + elements := decodeDump(dump) + + for _, element := range elements { + if element.Type != "Button" { + continue + } + label := strings.TrimSpace(stringValue(element.AXLabel)) + if isRejectPasteLabel(label) { + continue + } + for _, allow := range allowPasteLabels { + if label == allow { + if center, ok := buttonCenter(element); ok { + return center, true + } + } + } + } + + var soleEnabled allowPasteButton + enabledButtons := 0 + for _, element := range elements { + if element.Type != "Button" || !element.Enabled { + continue + } + if isRejectPasteLabel(strings.TrimSpace(stringValue(element.AXLabel))) { + continue + } + center, ok := buttonCenter(element) + if !ok { + continue + } + enabledButtons++ + soleEnabled = center + } + if enabledButtons == 1 { + return soleEnabled, true + } + return allowPasteButton{}, false +} + +// isRejectPasteLabel reports whether label is the dialog's reject button. iOS +// renders the apostrophe as a right single quotation mark (U+2019), so both the +// curly and straight forms are checked. +func isRejectPasteLabel(label string) bool { + return strings.Contains(label, "Don’t Allow Paste") || + strings.Contains(label, "Don't Allow Paste") +} + +func buttonCenter(element rawElement) (allowPasteButton, bool) { + frame := element.Frame + if !finite(frame.X) || !finite(frame.Y) || !finite(frame.Width) || !finite(frame.Height) { + return allowPasteButton{}, false + } + if frame.Width == 0 && frame.Height == 0 { + return allowPasteButton{}, false + } + return allowPasteButton{ + centerX: frame.X + frame.Width/2, + centerY: frame.Y + frame.Height/2, + }, true +} + +// pasteLanded reports whether the field identified by fieldIdentifier now shows +// expectedText in its AXValue. The paste appends at the cursor, so a substring +// match (rather than equality) is used: the field may already hold text. A +// secure field masks its value as bullets, making content verification +// impossible; a non-empty all-bullet value counts as landed. +func pasteLanded(dump []byte, fieldIdentifier, expectedText string) bool { + if fieldIdentifier == "" || expectedText == "" { + return false + } + for _, element := range decodeDump(dump) { + if stringValue(element.AXUniqueID) != fieldIdentifier { + continue + } + value := stringValue(element.AXValue) + if strings.Contains(value, expectedText) { + return true + } + return isMaskedValue(value) + } + return false +} + +// isMaskedValue reports whether value is a secure field's masked content: +// non-empty and made up entirely of bullet characters. +func isMaskedValue(value string) bool { + if value == "" { + return false + } + for _, r := range value { + if r != '•' { + return false + } + } + return true +} + +// decodeDump parses a flat describe-all dump into elements, reusing the same +// element shape and per-element tolerance as the hierarchy mapper: a single +// malformed entry is skipped rather than discarding the whole dump. +func decodeDump(dump []byte) []rawElement { + var rawElements []json.RawMessage + if len(dump) > 0 { + _ = json.Unmarshal(dump, &rawElements) + } + elements := make([]rawElement, 0, len(rawElements)) + for _, raw := range rawElements { + var element rawElement + if err := json.Unmarshal(raw, &element); err != nil { + continue + } + elements = append(elements, element) + } + return elements +} diff --git a/internal/driver/ioscompanion/input_test.go b/internal/driver/ioscompanion/input_test.go new file mode 100644 index 0000000..070c0b5 --- /dev/null +++ b/internal/driver/ioscompanion/input_test.go @@ -0,0 +1,433 @@ +package ioscompanion + +import ( + "context" + "os" + "path/filepath" + "reflect" + "testing" + "time" + + "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/transport" +) + +func eventsEqual(t *testing.T, got, want []transport.HIDEvent) { + t.Helper() + if len(got) != len(want) { + t.Fatalf("event count: got %d, want %d", len(got), len(want)) + } + for i := range want { + if !reflect.DeepEqual(got[i], want[i]) { + t.Fatalf("event %d differs", i) + } + } +} + +func TestKeyPressEvents(t *testing.T) { + tests := []struct { + name string + presses []KeyPress + want []transport.HIDEvent + }{ + { + name: "lowercase letter is down then up, no shift", + presses: []KeyPress{{Usage: usageA}}, + want: []transport.HIDEvent{transport.KeyDown(usageA), transport.KeyUp(usageA)}, + }, + { + name: "shifted letter wraps with left shift down and up", + presses: []KeyPress{{Usage: usageA, Shift: true}}, + want: []transport.HIDEvent{ + transport.KeyDown(usageLeftShift), + transport.KeyDown(usageA), + transport.KeyUp(usageA), + transport.KeyUp(usageLeftShift), + }, + }, + { + name: "empty input yields no events", + presses: nil, + want: []transport.HIDEvent{}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + eventsEqual(t, keyPressEvents(test.presses), test.want) + }) + } +} + +func TestKeyPressEventsMixedString(t *testing.T) { + presses, skipped := typeString("aB") + if len(skipped) != 0 { + t.Fatalf("unexpected skipped runes: %v", skipped) + } + got := keyPressEvents(presses) + want := []transport.HIDEvent{ + transport.KeyDown(usageA), transport.KeyUp(usageA), + transport.KeyDown(usageLeftShift), + transport.KeyDown(usageA + 1), + transport.KeyUp(usageA + 1), + transport.KeyUp(usageLeftShift), + } + eventsEqual(t, got, want) +} + +func TestPasteChordEvents(t *testing.T) { + want := []transport.HIDEvent{ + transport.KeyDown(LeftGUI), + transport.KeyDown(VKey), + transport.KeyUp(VKey), + transport.KeyUp(LeftGUI), + } + eventsEqual(t, pasteChordEvents(), want) +} + +func TestTapEvents(t *testing.T) { + want := []transport.HIDEvent{transport.TouchDown(12, 34), transport.TouchUp(12, 34)} + eventsEqual(t, tapEvents(12, 34), want) +} + +func TestDoubleTapEvents(t *testing.T) { + want := []transport.HIDEvent{ + transport.TouchDown(5, 6), transport.TouchUp(5, 6), + transport.Delay(70), + transport.TouchDown(5, 6), transport.TouchUp(5, 6), + } + eventsEqual(t, doubleTapEvents(5, 6, DefaultDoubleTapGapMilliseconds), want) +} + +func TestLongPressEvents(t *testing.T) { + want := []transport.HIDEvent{ + transport.TouchDown(8, 9), + transport.Delay(500), + transport.TouchUp(8, 9), + } + eventsEqual(t, longPressEvents(8, 9, 500), want) +} + +func loadDialogDump(t *testing.T) []byte { + t.Helper() + dump, err := os.ReadFile(filepath.Join("testdata", "paste-dialog.json")) + if err != nil { + t.Fatalf("read testdata: %v", err) + } + return dump +} + +func TestFindAllowPasteButton(t *testing.T) { + tests := []struct { + name string + dump string + wantFound bool + wantX float64 + wantY float64 + }{ + { + name: "exact Allow Paste wins over Don't Allow Paste", + dump: string(loadDialogDump(t)), + wantFound: true, + wantX: 280, // 210 + 140/2 + wantY: 465, // 440 + 50/2 + }, + { + name: "plain Allow label matches", + dump: `[{"type":"Button","AXLabel":"Allow","frame":{"x":100,"y":100,"width":40,"height":20},"enabled":true}]`, + wantFound: true, + wantX: 120, + wantY: 110, + }, + { + name: "sole enabled button fallback", + dump: `[{"type":"StaticText","AXLabel":"Paste?","frame":{"x":0,"y":0,"width":10,"height":10},"enabled":true},{"type":"Button","AXLabel":"OK","frame":{"x":50,"y":60,"width":100,"height":40},"enabled":true}]`, + wantFound: true, + wantX: 100, + wantY: 80, + }, + { + name: "no fallback when several enabled buttons present", + dump: `[{"type":"Button","AXLabel":"A","frame":{"x":0,"y":0,"width":10,"height":10},"enabled":true},{"type":"Button","AXLabel":"B","frame":{"x":20,"y":0,"width":10,"height":10},"enabled":true}]`, + wantFound: false, + }, + { + name: "reject button alone does not match", + dump: `[{"type":"Button","AXLabel":"Don’t Allow Paste","frame":{"x":0,"y":0,"width":10,"height":10},"enabled":true}]`, + wantFound: false, + }, + { + name: "empty dump finds nothing", + dump: ``, + wantFound: false, + }, + { + name: "malformed json finds nothing", + dump: `not json`, + wantFound: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + button, found := findAllowPasteButton([]byte(test.dump)) + if found != test.wantFound { + t.Fatalf("found: got %v, want %v", found, test.wantFound) + } + if !found { + return + } + if button.centerX != test.wantX || button.centerY != test.wantY { + t.Fatalf("center: got (%v,%v), want (%v,%v)", button.centerX, button.centerY, test.wantX, test.wantY) + } + }) + } +} + +func TestPasteLanded(t *testing.T) { + dump := `[{"type":"TextField","AXUniqueId":"NoteField","AXValue":"prefix Café ☕","frame":{"x":0,"y":0,"width":10,"height":10}}]` + tests := []struct { + name string + dump string + identifier string + expected string + want bool + }{ + {name: "substring present", dump: dump, identifier: "NoteField", expected: "Café ☕", want: true}, + {name: "value not yet landed", dump: `[{"type":"TextField","AXUniqueId":"NoteField","AXValue":"prefix"}]`, identifier: "NoteField", expected: "Café", want: false}, + {name: "field absent", dump: dump, identifier: "MissingField", expected: "Café", want: false}, + {name: "empty identifier never matches", dump: dump, identifier: "", expected: "Café", want: false}, + {name: "empty expected never matches", dump: dump, identifier: "NoteField", expected: "", want: false}, + {name: "malformed dump never matches", dump: `nope`, identifier: "NoteField", expected: "Café", want: false}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got := pasteLanded([]byte(test.dump), test.identifier, test.expected); got != test.want { + t.Fatalf("got %v, want %v", got, test.want) + } + }) + } +} + +// fakeRunner records side effects and serves scripted describe-all dumps. +type fakeRunner struct { + pasteboard string + hidStreams [][]transport.HIDEvent + dumps [][]byte + dumpIndex int + setError error + sendError error + describeErr error + sleepCount int +} + +func (f *fakeRunner) setPasteboard(ctx context.Context, text string) error { + if f.setError != nil { + return f.setError + } + f.pasteboard = text + return nil +} + +func (f *fakeRunner) sendHID(ctx context.Context, events ...transport.HIDEvent) error { + if f.sendError != nil { + return f.sendError + } + f.hidStreams = append(f.hidStreams, events) + return nil +} + +func (f *fakeRunner) describeAll(ctx context.Context) ([]byte, error) { + if f.describeErr != nil { + return nil, f.describeErr + } + if f.dumpIndex >= len(f.dumps) { + return f.dumps[len(f.dumps)-1], nil + } + dump := f.dumps[f.dumpIndex] + f.dumpIndex++ + return dump, nil +} + +func (f *fakeRunner) sleep(ctx context.Context, duration time.Duration) error { + f.sleepCount++ + return ctx.Err() +} + +func TestInputTextFastPathUsesHardwareKeyboard(t *testing.T) { + fake := &fakeRunner{} + if err := inputText(context.Background(), fake, "hi", fieldTarget{}); err != nil { + t.Fatalf("inputText: %v", err) + } + if fake.pasteboard != "" { + t.Fatalf("fast path must not touch pasteboard, got %q", fake.pasteboard) + } + if len(fake.hidStreams) != 1 { + t.Fatalf("fast path must send one HID stream, got %d", len(fake.hidStreams)) + } + // One stream: clear the field, then type the runes. + want := append(clearFieldEvents(), + keyPressEvents([]KeyPress{{Usage: usageA + ('h' - 'a')}, {Usage: usageA + ('i' - 'a')}})...) + eventsEqual(t, fake.hidStreams[0], want) +} + +func TestInputTextPasteLandsImmediately(t *testing.T) { + landed := `[{"type":"TextField","AXUniqueId":"F","AXValue":"Café"}]` + fake := &fakeRunner{dumps: [][]byte{[]byte(landed)}} + field := fieldTarget{identifier: "F", centerX: 100, centerY: 200} + if err := inputText(context.Background(), fake, "Café", field); err != nil { + t.Fatalf("inputText: %v", err) + } + if fake.pasteboard != "Café" { + t.Fatalf("pasteboard: got %q", fake.pasteboard) + } + // Streams: clear field, then paste chord. + if len(fake.hidStreams) != 2 { + t.Fatalf("expected clear then paste chord, got %d streams", len(fake.hidStreams)) + } + eventsEqual(t, fake.hidStreams[0], clearFieldEvents()) + eventsEqual(t, fake.hidStreams[1], pasteChordEvents()) +} + +func TestInputTextPasteDismissesDialogThenLands(t *testing.T) { + dialog := string(loadDialogDump(t)) + landed := `[{"type":"TextField","AXUniqueId":"TxnNoteField","AXValue":"Café ☕ 😀"}]` + // First check sees the dialog (which swallowed the initial chord); the + // check after the retried chord sees the landed value. + fake := &fakeRunner{dumps: [][]byte{[]byte(dialog), []byte(landed)}} + field := fieldTarget{identifier: "TxnNoteField", centerX: 195, centerY: 222} + if err := inputText(context.Background(), fake, "Café ☕ 😀", field); err != nil { + t.Fatalf("inputText: %v", err) + } + // Streams: clear field, paste chord, tap allow, refocus field, paste chord again. + if len(fake.hidStreams) != 5 { + t.Fatalf("expected 5 HID streams, got %d", len(fake.hidStreams)) + } + eventsEqual(t, fake.hidStreams[0], clearFieldEvents()) + eventsEqual(t, fake.hidStreams[1], pasteChordEvents()) + eventsEqual(t, fake.hidStreams[2], tapEvents(280, 465)) + eventsEqual(t, fake.hidStreams[3], tapEvents(195, 222)) + eventsEqual(t, fake.hidStreams[4], pasteChordEvents()) +} + +func TestInputTextPasteFailsAfterAllAttempts(t *testing.T) { + stuck := `[{"type":"TextField","AXUniqueId":"F","AXValue":""}]` + fake := &fakeRunner{dumps: [][]byte{[]byte(stuck)}} + field := fieldTarget{identifier: "F", centerX: 1, centerY: 2} + err := inputText(context.Background(), fake, "😀", field) + if err == nil { + t.Fatal("expected error after exhausting the verify budget") + } + // Streams: clear field, then exactly one paste chord (no dialog, so it is + // not re-sent: re-sending on a slow render would paste twice). + if len(fake.hidStreams) != 2 { + t.Fatalf("expected clear then one paste chord, got %d streams", len(fake.hidStreams)) + } + eventsEqual(t, fake.hidStreams[0], clearFieldEvents()) + wantPolls := int(pasteVerifyTimeout / pastePoll) + if fake.sleepCount != wantPolls { + t.Fatalf("expected %d verify polls, got %d", wantPolls, fake.sleepCount) + } +} + +func TestInputTextPasteLandsAfterBridgeBlackout(t *testing.T) { + // The field is absent (bridge blacked out by the dialog) for several + // polls, then the value lands. The loop must keep waiting through the + // blackout instead of failing. + blacked := `[{"type":"Application"}]` + landed := `[{"type":"TextField","AXUniqueId":"F","AXValue":"😀"}]` + dumps := [][]byte{[]byte(blacked), []byte(blacked), []byte(blacked), []byte(landed)} + fake := &fakeRunner{dumps: dumps} + field := fieldTarget{identifier: "F", centerX: 1, centerY: 2} + if err := inputText(context.Background(), fake, "😀", field); err != nil { + t.Fatalf("inputText: %v", err) + } + // Clear field, one chord, no dialog seen, success once the value appears. + if len(fake.hidStreams) != 2 { + t.Fatalf("expected clear then one paste chord, got %d streams", len(fake.hidStreams)) + } +} + +func TestInputTextPasteUnverifiableFieldSingleChord(t *testing.T) { + // No field identifier resolved: one chord, no dialog, success after the + // settle without endless retries. + noField := `[{"type":"StaticText","AXLabel":"whatever"}]` + fake := &fakeRunner{dumps: [][]byte{[]byte(noField)}} + if err := inputText(context.Background(), fake, "😀", fieldTarget{}); err != nil { + t.Fatalf("inputText: %v", err) + } + // Clear field, then one paste chord. + if len(fake.hidStreams) != 2 { + t.Fatalf("expected clear then one paste chord, got %d streams", len(fake.hidStreams)) + } +} + +func TestEraseTextLargeCountClearsAtomically(t *testing.T) { + fake := &fakeRunner{} + if err := eraseText(context.Background(), fake, 40); err != nil { + t.Fatalf("eraseText: %v", err) + } + if len(fake.hidStreams) != 1 { + t.Fatalf("expected one stream, got %d", len(fake.hidStreams)) + } + eventsEqual(t, fake.hidStreams[0], clearFieldEvents()) +} + +func TestEraseTextSendsBackspaces(t *testing.T) { + fake := &fakeRunner{} + if err := eraseText(context.Background(), fake, 3); err != nil { + t.Fatalf("eraseText: %v", err) + } + if len(fake.hidStreams) != 1 { + t.Fatalf("expected one stream, got %d", len(fake.hidStreams)) + } + want := keyPressEvents(backspaces(3)) + eventsEqual(t, fake.hidStreams[0], want) +} + +func TestEraseTextZeroIsNoOp(t *testing.T) { + fake := &fakeRunner{} + if err := eraseText(context.Background(), fake, 0); err != nil { + t.Fatalf("eraseText: %v", err) + } + if len(fake.hidStreams) != 0 { + t.Fatalf("zero count must send nothing, got %d streams", len(fake.hidStreams)) + } +} + +func TestUsesPasteboard(t *testing.T) { + cases := []struct { + text string + want bool + }{ + {"", false}, + {"a", false}, + {"abc", false}, + {"a long but fully mappable ascii string", false}, + {"-1", false}, + {"%s%n", false}, + {"😀", true}, + {"Café", true}, + } + for _, c := range cases { + if got := usesPasteboard(c.text); got != c.want { + t.Errorf("usesPasteboard(%q) = %v, want %v", c.text, got, c.want) + } + } +} + +func TestPasteLandedMaskedSecureField(t *testing.T) { + cases := []struct { + name string + dump string + want bool + }{ + {name: "all bullets counts as landed", dump: `[{"type":"TextField","AXUniqueId":"PW","AXValue":"•••••"}]`, want: true}, + {name: "empty secure field not landed", dump: `[{"type":"TextField","AXUniqueId":"PW","AXValue":""}]`, want: false}, + {name: "mixed bullets and text not masked", dump: `[{"type":"TextField","AXUniqueId":"PW","AXValue":"••a"}]`, want: false}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if got := pasteLanded([]byte(c.dump), "PW", "secret9"); got != c.want { + t.Fatalf("got %v, want %v", got, c.want) + } + }) + } +} diff --git a/internal/driver/ioscompanion/keymap.go b/internal/driver/ioscompanion/keymap.go new file mode 100644 index 0000000..8850e68 --- /dev/null +++ b/internal/driver/ioscompanion/keymap.go @@ -0,0 +1,129 @@ +// Package ioscompanion talks to the simulator companion to drive an iOS +// simulator. This file is pure data: it maps runes to USB HID keyboard +// usage IDs. A later module turns these key presses into HID events. +package ioscompanion + +// USB HID keyboard usage IDs. +const ( + usageA = 4 + usage1 = 30 + usage0 = 39 + usageReturn = 40 + usageTab = 43 + usageSpace = 44 + usageBackspace = 42 + usageMinus = 45 + usageEqual = 46 + usageLeftBracket = 47 + usageRightBracket = 48 + usageBackslash = 49 + usageSemicolon = 51 + usageApostrophe = 52 + usageGrave = 53 + usageComma = 54 + usagePeriod = 55 + usageSlash = 56 + + usageLeftShift = 225 + + // LeftGUI is the command key. VKey is the letter V. Together they form + // the paste chord (command+V). + LeftGUI = 227 + VKey = 25 +) + +// KeyPress is a single key with whether the shift modifier is held. +type KeyPress struct { + Usage uint32 + Shift bool +} + +// shiftedSymbols maps a shifted-symbol rune to the unshifted key it lives on. +var shiftedSymbols = map[rune]uint32{ + '!': usage1, + '@': usage1 + 1, + '#': usage1 + 2, + '$': usage1 + 3, + '%': usage1 + 4, + '^': usage1 + 5, + '&': usage1 + 6, + '*': usage1 + 7, + '(': usage1 + 8, + ')': usage0, + '_': usageMinus, + '+': usageEqual, + '{': usageLeftBracket, + '}': usageRightBracket, + '|': usageBackslash, + ':': usageSemicolon, + '"': usageApostrophe, + '~': usageGrave, + '<': usageComma, + '>': usagePeriod, + '?': usageSlash, +} + +// unshiftedSymbols maps a symbol rune typed without shift to its key. +var unshiftedSymbols = map[rune]uint32{ + '-': usageMinus, + '=': usageEqual, + '[': usageLeftBracket, + ']': usageRightBracket, + '\\': usageBackslash, + ';': usageSemicolon, + '\'': usageApostrophe, + '`': usageGrave, + ',': usageComma, + '.': usagePeriod, + '/': usageSlash, +} + +// charKey returns the key press for a rune and whether the rune is mappable. +func charKey(r rune) (KeyPress, bool) { + switch { + case r >= 'a' && r <= 'z': + return KeyPress{Usage: uint32(usageA + (r - 'a'))}, true + case r >= 'A' && r <= 'Z': + return KeyPress{Usage: uint32(usageA + (r - 'A')), Shift: true}, true + case r >= '1' && r <= '9': + return KeyPress{Usage: uint32(usage1 + (r - '1'))}, true + case r == '0': + return KeyPress{Usage: usage0}, true + case r == ' ': + return KeyPress{Usage: usageSpace}, true + case r == '\n': + return KeyPress{Usage: usageReturn}, true + case r == '\t': + return KeyPress{Usage: usageTab}, true + } + if usage, ok := unshiftedSymbols[r]; ok { + return KeyPress{Usage: usage}, true + } + if usage, ok := shiftedSymbols[r]; ok { + return KeyPress{Usage: usage, Shift: true}, true + } + return KeyPress{}, false +} + +// typeString returns the ordered key presses for every mappable rune in s. +// Unmappable runes are collected in order in skipped. +func typeString(s string) (presses []KeyPress, skipped []rune) { + for _, r := range s { + press, ok := charKey(r) + if !ok { + skipped = append(skipped, r) + continue + } + presses = append(presses, press) + } + return presses, skipped +} + +// backspaces returns n backspace key presses. +func backspaces(n int) []KeyPress { + presses := make([]KeyPress, 0, n) + for i := 0; i < n; i++ { + presses = append(presses, KeyPress{Usage: usageBackspace}) + } + return presses +} diff --git a/internal/driver/ioscompanion/keymap_test.go b/internal/driver/ioscompanion/keymap_test.go new file mode 100644 index 0000000..7bb981e --- /dev/null +++ b/internal/driver/ioscompanion/keymap_test.go @@ -0,0 +1,209 @@ +package ioscompanion + +import ( + "reflect" + "testing" +) + +func TestCharKeyLetters(t *testing.T) { + cases := []struct { + r rune + usage uint32 + shift bool + }{ + {'a', usageA, false}, + {'z', usageA + 25, false}, + {'m', usageA + 12, false}, + {'A', usageA, true}, + {'Z', usageA + 25, true}, + {'M', usageA + 12, true}, + } + for _, c := range cases { + press, ok := charKey(c.r) + if !ok { + t.Fatalf("charKey(%q) reported unmappable", c.r) + } + if press.Usage != c.usage || press.Shift != c.shift { + t.Errorf("charKey(%q) = {%d,%v}, want {%d,%v}", c.r, press.Usage, press.Shift, c.usage, c.shift) + } + } +} + +func TestCharKeyDigits(t *testing.T) { + cases := []struct { + r rune + usage uint32 + }{ + {'1', usage1}, + {'2', usage1 + 1}, + {'3', usage1 + 2}, + {'4', usage1 + 3}, + {'5', usage1 + 4}, + {'6', usage1 + 5}, + {'7', usage1 + 6}, + {'8', usage1 + 7}, + {'9', usage1 + 8}, + {'0', usage0}, + } + for _, c := range cases { + press, ok := charKey(c.r) + if !ok { + t.Fatalf("charKey(%q) reported unmappable", c.r) + } + if press.Usage != c.usage || press.Shift { + t.Errorf("charKey(%q) = {%d,%v}, want {%d,false}", c.r, press.Usage, press.Shift, c.usage) + } + } +} + +func TestCharKeyWhitespace(t *testing.T) { + cases := []struct { + r rune + usage uint32 + }{ + {' ', usageSpace}, + {'\n', usageReturn}, + {'\t', usageTab}, + } + for _, c := range cases { + press, ok := charKey(c.r) + if !ok { + t.Fatalf("charKey(%q) reported unmappable", c.r) + } + if press.Usage != c.usage || press.Shift { + t.Errorf("charKey(%q) = {%d,%v}, want {%d,false}", c.r, press.Usage, press.Shift, c.usage) + } + } +} + +func TestCharKeyUnshiftedSymbols(t *testing.T) { + cases := []struct { + r rune + usage uint32 + }{ + {'-', usageMinus}, + {'=', usageEqual}, + {'[', usageLeftBracket}, + {']', usageRightBracket}, + {'\\', usageBackslash}, + {';', usageSemicolon}, + {'\'', usageApostrophe}, + {'`', usageGrave}, + {',', usageComma}, + {'.', usagePeriod}, + {'/', usageSlash}, + } + for _, c := range cases { + press, ok := charKey(c.r) + if !ok { + t.Fatalf("charKey(%q) reported unmappable", c.r) + } + if press.Usage != c.usage || press.Shift { + t.Errorf("charKey(%q) = {%d,%v}, want {%d,false}", c.r, press.Usage, press.Shift, c.usage) + } + } +} + +func TestCharKeyShiftedSymbols(t *testing.T) { + cases := []struct { + r rune + usage uint32 + }{ + {'!', usage1}, + {'@', usage1 + 1}, + {'#', usage1 + 2}, + {'$', usage1 + 3}, + {'%', usage1 + 4}, + {'^', usage1 + 5}, + {'&', usage1 + 6}, + {'*', usage1 + 7}, + {'(', usage1 + 8}, + {')', usage0}, + {'_', usageMinus}, + {'+', usageEqual}, + {'{', usageLeftBracket}, + {'}', usageRightBracket}, + {'|', usageBackslash}, + {':', usageSemicolon}, + {'"', usageApostrophe}, + {'~', usageGrave}, + {'<', usageComma}, + {'>', usagePeriod}, + {'?', usageSlash}, + } + for _, c := range cases { + press, ok := charKey(c.r) + if !ok { + t.Fatalf("charKey(%q) reported unmappable", c.r) + } + if press.Usage != c.usage || !press.Shift { + t.Errorf("charKey(%q) = {%d,%v}, want {%d,true}", c.r, press.Usage, press.Shift, c.usage) + } + } +} + +func TestCharKeyUnmappable(t *testing.T) { + for _, r := range []rune{'é', '世', '🙂', '\x00'} { + if press, ok := charKey(r); ok { + t.Errorf("charKey(%q) = {%d,%v}, want unmappable", r, press.Usage, press.Shift) + } + } +} + +func TestTypeString(t *testing.T) { + presses, skipped := typeString("Ab1!") + want := []KeyPress{ + {Usage: usageA, Shift: true}, + {Usage: usageA + 1}, + {Usage: usage1}, + {Usage: usage1, Shift: true}, + } + if !reflect.DeepEqual(presses, want) { + t.Errorf("presses = %+v, want %+v", presses, want) + } + if len(skipped) != 0 { + t.Errorf("skipped = %v, want none", skipped) + } +} + +func TestTypeStringSkipsUnmappableInOrder(t *testing.T) { + presses, skipped := typeString("café 🙂x") + wantPresses := []KeyPress{ + {Usage: usageA + 2}, + {Usage: usageA}, + {Usage: usageA + 5}, + {Usage: usageSpace}, + {Usage: usageA + 23}, + } + if !reflect.DeepEqual(presses, wantPresses) { + t.Errorf("presses = %+v, want %+v", presses, wantPresses) + } + wantSkipped := []rune{'é', '🙂'} + if !reflect.DeepEqual(skipped, wantSkipped) { + t.Errorf("skipped = %q, want %q", skipped, wantSkipped) + } +} + +func TestBackspaces(t *testing.T) { + if got := backspaces(0); len(got) != 0 { + t.Errorf("backspaces(0) = %v, want empty", got) + } + got := backspaces(3) + want := []KeyPress{ + {Usage: usageBackspace}, + {Usage: usageBackspace}, + {Usage: usageBackspace}, + } + if !reflect.DeepEqual(got, want) { + t.Errorf("backspaces(3) = %+v, want %+v", got, want) + } +} + +func TestPasteChordConstants(t *testing.T) { + if LeftGUI != 227 { + t.Errorf("LeftGUI = %d, want 227", LeftGUI) + } + if VKey != 25 { + t.Errorf("VKey = %d, want 25", VKey) + } +} diff --git a/internal/driver/ioscompanion/runnerassets/embed.go b/internal/driver/ioscompanion/runnerassets/embed.go new file mode 100644 index 0000000..9464fd7 --- /dev/null +++ b/internal/driver/ioscompanion/runnerassets/embed.go @@ -0,0 +1,140 @@ +// Package runnerassets embeds the in-simulator runner test bundle and its +// xctestrun, and extracts them to disk at runtime. +package runnerassets + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +// xctestrunName is the file name the staged test root uses for the runner's +// xctestrun. The driver substitutes the port placeholder in this file before +// launching, so it must not be renamed. +const xctestrunName = "runner.xctestrun" + +// EmbeddedSize returns the size in bytes of the embedded runner archive. +func EmbeddedSize() int { return len(embeddedArchive) } + +// EmbeddedSHA256 returns the hex-encoded SHA-256 of the embedded archive. +func EmbeddedSHA256() string { + sum := sha256.Sum256(embeddedArchive) + return hex.EncodeToString(sum[:]) +} + +// Extract unpacks the embedded runner archive into dir, preserving the test +// root layout and any symlinks. A .sha256 marker next to the extracted tree +// gates re-extraction: if it already matches, no rewrite happens. Returns the +// absolute path to the extracted runner.xctestrun. +func Extract(dir string) (string, error) { + if len(embeddedArchive) == 0 { + return "", errors.New("runner: binary built without -tags withcompanion; rebuild with `make sanderling`") + } + if err := os.MkdirAll(dir, 0o755); err != nil { + return "", fmt.Errorf("mkdir %s: %w", dir, err) + } + + xctestrunPath, err := filepath.Abs(filepath.Join(dir, xctestrunName)) + if err != nil { + return "", err + } + checksumPath := filepath.Join(dir, "runner.sha256") + checksum := EmbeddedSHA256() + + if existing, err := os.ReadFile(checksumPath); err == nil && string(existing) == checksum { + if _, err := os.Stat(xctestrunPath); err == nil { + return xctestrunPath, nil + } + } + + if err := unpack(dir); err != nil { + return "", err + } + if _, err := os.Stat(xctestrunPath); err != nil { + return "", fmt.Errorf("runner: xctestrun missing after extraction: %w", err) + } + if err := os.WriteFile(checksumPath, []byte(checksum), 0o644); err != nil { + return "", fmt.Errorf("write checksum: %w", err) + } + return xctestrunPath, nil +} + +func unpack(dir string) error { + gzipReader, err := gzip.NewReader(bytes.NewReader(embeddedArchive)) + if err != nil { + return fmt.Errorf("open runner archive: %w", err) + } + defer gzipReader.Close() + + tarReader := tar.NewReader(gzipReader) + for { + header, err := tarReader.Next() + if err == io.EOF { + return nil + } + if err != nil { + return fmt.Errorf("read runner archive: %w", err) + } + + if strings.HasPrefix(filepath.Base(header.Name), "._") { + continue + } + + target, err := safeJoin(dir, header.Name) + if err != nil { + return err + } + + switch header.Typeflag { + case tar.TypeDir: + if err := os.MkdirAll(target, 0o755); err != nil { + return err + } + case tar.TypeSymlink: + if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { + return err + } + os.Remove(target) + if err := os.Symlink(header.Linkname, target); err != nil { + return err + } + case tar.TypeReg: + if err := writeFile(target, tarReader, os.FileMode(header.Mode)); err != nil { + return err + } + } + } +} + +func writeFile(path string, src io.Reader, mode os.FileMode) error { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode) + if err != nil { + return err + } + if _, err := io.Copy(file, src); err != nil { + file.Close() + return err + } + return file.Close() +} + +// safeJoin rejects archive entries that would escape dir. +func safeJoin(dir, name string) (string, error) { + target := filepath.Join(dir, name) + relative, err := filepath.Rel(dir, target) + if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(os.PathSeparator)) { + return "", fmt.Errorf("archive entry escapes destination: %s", name) + } + return target, nil +} diff --git a/internal/driver/ioscompanion/runnerassets/embed_real.go b/internal/driver/ioscompanion/runnerassets/embed_real.go new file mode 100644 index 0000000..28130d7 --- /dev/null +++ b/internal/driver/ioscompanion/runnerassets/embed_real.go @@ -0,0 +1,12 @@ +//go:build withcompanion + +package runnerassets + +import _ "embed" + +//go:embed assets/runner-1.0.0.tar.gz +var embeddedArchive []byte + +// IsPlaceholder reports whether the binary was built without the real runner +// archive embedded. -tags withcompanion builds always return false. +func IsPlaceholder() bool { return false } diff --git a/internal/driver/ioscompanion/runnerassets/embed_stub.go b/internal/driver/ioscompanion/runnerassets/embed_stub.go new file mode 100644 index 0000000..ac2a245 --- /dev/null +++ b/internal/driver/ioscompanion/runnerassets/embed_stub.go @@ -0,0 +1,10 @@ +//go:build !withcompanion + +package runnerassets + +var embeddedArchive []byte + +// IsPlaceholder reports whether the binary was built without the real runner +// archive embedded. Build with `make sanderling` (which passes +// -tags withcompanion) to embed the real runner test bundle. +func IsPlaceholder() bool { return true } diff --git a/internal/driver/ioscompanion/runnerassets/embed_stub_test.go b/internal/driver/ioscompanion/runnerassets/embed_stub_test.go new file mode 100644 index 0000000..46378d4 --- /dev/null +++ b/internal/driver/ioscompanion/runnerassets/embed_stub_test.go @@ -0,0 +1,27 @@ +//go:build !withcompanion + +package runnerassets + +import ( + "strings" + "testing" +) + +func TestStubBuild_IsPlaceholder(t *testing.T) { + if !IsPlaceholder() { + t.Error("default build (no -tags withcompanion) must report a placeholder") + } + if EmbeddedSize() != 0 { + t.Errorf("placeholder build must embed no archive, got %d bytes", EmbeddedSize()) + } +} + +func TestStubBuild_ExtractErrors(t *testing.T) { + _, err := Extract(t.TempDir()) + if err == nil { + t.Fatal("Extract must fail when no archive is embedded") + } + if !strings.Contains(err.Error(), "withcompanion") { + t.Errorf("error should tell the user to rebuild with -tags withcompanion, got %v", err) + } +} diff --git a/internal/driver/ioscompanion/runnerassets/embed_withcompanion_test.go b/internal/driver/ioscompanion/runnerassets/embed_withcompanion_test.go new file mode 100644 index 0000000..59bfee7 --- /dev/null +++ b/internal/driver/ioscompanion/runnerassets/embed_withcompanion_test.go @@ -0,0 +1,143 @@ +//go:build withcompanion + +package runnerassets + +import ( + "crypto/sha256" + "encoding/hex" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestEmbeddedNonZero(t *testing.T) { + if EmbeddedSize() == 0 { + t.Errorf("expected embedded runner archive to be non-empty") + } + if IsPlaceholder() { + t.Errorf("withcompanion build should not be a placeholder") + } +} + +func TestEmbeddedSHA256Matches(t *testing.T) { + sum := sha256.Sum256(embeddedArchive) + if hex.EncodeToString(sum[:]) != EmbeddedSHA256() { + t.Errorf("EmbeddedSHA256 does not match a fresh hash of the archive") + } +} + +func TestExtract_WritesXctestrunAndChecksum(t *testing.T) { + directory := t.TempDir() + path, err := Extract(directory) + if err != nil { + t.Fatal(err) + } + expected, err := filepath.Abs(filepath.Join(directory, xctestrunName)) + if err != nil { + t.Fatal(err) + } + if path != expected { + t.Errorf("unexpected xctestrun path: got %s want %s", path, expected) + } + + contents, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(contents), "__COMPANION_PORT__") { + t.Errorf("extracted xctestrun is missing the __COMPANION_PORT__ placeholder") + } + + checksum, err := os.ReadFile(filepath.Join(directory, "runner.sha256")) + if err != nil { + t.Fatal(err) + } + if string(checksum) != EmbeddedSHA256() { + t.Errorf("checksum file content wrong: %q", checksum) + } +} + +func TestExtract_Layout(t *testing.T) { + directory := t.TempDir() + if _, err := Extract(directory); err != nil { + t.Fatal(err) + } + + runnerApp := filepath.Join(directory, "Debug-iphonesimulator", "CompanionRunnerUITests-Runner.app") + if stat, err := os.Stat(runnerApp); err != nil || !stat.IsDir() { + t.Fatalf("runner app directory missing: %v", err) + } + + binary := filepath.Join(runnerApp, "CompanionRunnerUITests-Runner") + if _, err := os.Stat(binary); err != nil { + t.Errorf("runner app binary missing: %v", err) + } +} + +func TestExtract_SymlinksResolve(t *testing.T) { + directory := t.TempDir() + if _, err := Extract(directory); err != nil { + t.Fatal(err) + } + + // Simulator bundles are flat, so the runner app may contain no symlinks at + // all. Any symlink that is present must resolve to a real target, proving + // the unpack path restores links rather than copying broken stubs. + root := filepath.Join(directory, "Debug-iphonesimulator") + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.Mode()&os.ModeSymlink != 0 { + if _, statErr := os.Stat(path); statErr != nil { + t.Errorf("symlink %s does not resolve: %v", path, statErr) + } + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} + +func TestExtract_ReusesWhenChecksumMatches(t *testing.T) { + directory := t.TempDir() + xctestrunPath, err := Extract(directory) + if err != nil { + t.Fatal(err) + } + sentinel := []byte("SENTINEL-do-not-rewrite") + if err := os.WriteFile(xctestrunPath, sentinel, 0o644); err != nil { + t.Fatal(err) + } + + if _, err := Extract(directory); err != nil { + t.Fatal(err) + } + + after, err := os.ReadFile(xctestrunPath) + if err != nil { + t.Fatal(err) + } + if string(after) != string(sentinel) { + t.Errorf("second extract rewrote the xctestrun; reuse branch should have skipped extraction") + } +} + +func TestExtract_RewritesIfChecksumMissing(t *testing.T) { + directory := t.TempDir() + if _, err := Extract(directory); err != nil { + t.Fatal(err) + } + checksumPath := filepath.Join(directory, "runner.sha256") + if err := os.Remove(checksumPath); err != nil { + t.Fatal(err) + } + if _, err := Extract(directory); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(checksumPath); err != nil { + t.Errorf("checksum should have been rewritten: %v", err) + } +} diff --git a/internal/driver/ioscompanion/runnerspawn.go b/internal/driver/ioscompanion/runnerspawn.go new file mode 100644 index 0000000..96a9eb1 --- /dev/null +++ b/internal/driver/ioscompanion/runnerspawn.go @@ -0,0 +1,89 @@ +package ioscompanion + +import ( + "bytes" + "context" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "syscall" + + "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/runnerassets" +) + +// runnerPortPlaceholder is the marker the prepare script plants in the +// packaged test configuration's environment. The spawner substitutes the +// session's port before launching. +const runnerPortPlaceholder = "__COMPANION_PORT__" + +// realSpawnRunner extracts the embedded runner bundle, writes a test +// configuration bound to the session's port, and starts the hosting test +// session against the configured simulator. Cancel sends SIGTERM so the +// session tears down its in-simulator children cleanly. +func (d *Driver) realSpawnRunner(ctx context.Context, address string) (*exec.Cmd, error) { + extractDirectory := filepath.Join(os.TempDir(), "sanderling-runner") + testRunPath, err := runnerassets.Extract(extractDirectory) + if err != nil { + return nil, fmt.Errorf("extract runner: %w", err) + } + _, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + boundTestRunPath, err := bindTestRunPort(testRunPath, port) + if err != nil { + return nil, err + } + + logPath := filepath.Join(extractDirectory, "runner-session-"+port+".log") + logFile, err := os.Create(logPath) + if err != nil { + return nil, fmt.Errorf("create runner log: %w", err) + } + + command := exec.CommandContext(ctx, "xcrun", "xcodebuild", "test-without-building", + "-xctestrun", boundTestRunPath, + "-destination", "platform=iOS Simulator,id="+d.udid) + // The session log stays out of the run output: the build tool is noisy + // and would pollute the run's error scan. The path is reported once so a + // failed startup is diagnosable. + command.Stdout = logFile + command.Stderr = logFile + command.Env = []string{ + "HOME=" + os.Getenv("HOME"), + "PATH=/usr/bin:/bin", + "TMPDIR=" + os.TempDir(), + } + command.Cancel = func() error { return command.Process.Signal(syscall.SIGTERM) } + command.WaitDelay = shutdownGrace + startErr := command.Start() + // The child holds its own descriptor after Start, so the parent's copy + // closes either way. + logFile.Close() + if startErr != nil { + return nil, fmt.Errorf("start runner session: %w", startErr) + } + fmt.Fprintf(d.output, "runner session pid=%d listening on %s (log: %s)\n", command.Process.Pid, address, logPath) + return command, nil +} + +// bindTestRunPort writes a copy of the extracted test configuration with the +// port placeholder substituted, next to the original so its relative paths +// keep resolving. Returns the bound copy's path. +func bindTestRunPort(testRunPath, port string) (string, error) { + configuration, err := os.ReadFile(testRunPath) + if err != nil { + return "", fmt.Errorf("read test configuration: %w", err) + } + if !bytes.Contains(configuration, []byte(runnerPortPlaceholder)) { + return "", fmt.Errorf("test configuration %s carries no %s placeholder", testRunPath, runnerPortPlaceholder) + } + bound := bytes.ReplaceAll(configuration, []byte(runnerPortPlaceholder), []byte(port)) + boundPath := filepath.Join(filepath.Dir(testRunPath), "runner-"+port+".xctestrun") + if err := os.WriteFile(boundPath, bound, 0o644); err != nil { + return "", fmt.Errorf("write bound test configuration: %w", err) + } + return boundPath, nil +} diff --git a/internal/driver/ioscompanion/settle.go b/internal/driver/ioscompanion/settle.go new file mode 100644 index 0000000..b28287e --- /dev/null +++ b/internal/driver/ioscompanion/settle.go @@ -0,0 +1,198 @@ +// Package ioscompanion drives an iOS simulator through the native simulator +// companion. This file ports the screen-settle (stability polling) logic that +// waits for the on-device UI to stop churning before the runner reads a +// hierarchy and screenshot pair. +package ioscompanion + +import ( + "context" + "strings" + "time" + + "github.com/priyanshujain/sanderling/internal/hierarchy" +) + +// StabilityPollInterval is how long the loop waits between hierarchy probes. +// The companion answers describe-all in tens of milliseconds, so a tight +// interval samples transitions promptly without backing the stream up. +const StabilityPollInterval = 100 * time.Millisecond + +// MinStableStreak is how long the tree must stay structurally identical (and +// non-transitional) before the poll declares settle. Actions whose effect is +// async (a tap that fires a write which later pops the back stack) leave the +// UI momentarily stable before the navigation transition fires; requiring an +// uninterrupted streak means churn that starts during the window resets the +// clock instead of being missed. The streak is shorter than the JVM sidecar's +// (which polls a slower, flakier adb hierarchy): the companion's describe is +// fast and deterministic, so three consecutive identical samples are a solid +// stability signal, and cross-fade transitions are caught separately by the +// route-screen transitional check rather than by streak length. +const MinStableStreak = 300 * time.Millisecond + +// StabilityPollCap bounds total time spent polling so a UI that never settles +// does not block the runner indefinitely. A genuinely still-churning screen +// hands a transitional snapshot to the next step, which settles it, so the cap +// trades a slightly stale read for bounded latency rather than dropping work. +const StabilityPollCap = 1500 * time.Millisecond + +// Clock abstracts time so the poll loop can be driven by a fake in tests +// without sleeping for real. +type Clock interface { + Now() time.Time + Sleep(duration time.Duration) +} + +// systemClock is the production Clock backed by the standard library. +type systemClock struct{} + +func (systemClock) Now() time.Time { return time.Now() } +func (systemClock) Sleep(d time.Duration) { time.Sleep(d) } + +// SystemClock returns a Clock backed by the standard library wall clock. +func SystemClock() Clock { return systemClock{} } + +// PollUntilStable returns once StabilitySnapshot has been non-transitional and +// equal to itself for an uninterrupted stretch of at least MinStableStreak, +// capped at StabilityPollCap. fetch returns the current hierarchy tree (nil on +// fetch failure, treated like a transitional snapshot so the streak resets). +// +// The stable stretch is measured from the start of the earliest read in the +// current run of identical snapshots: a fetch is not instantaneous (a runner +// snapshot takes a fair fraction of the streak itself), and the UI changing +// mid-read would change the snapshot, so the read's own duration is evidence +// of stability. A transitional snapshot, a changed snapshot, or a fetch +// failure resets the run. The function returns when the stretch reaches +// MinStableStreak or the cap elapses, and respects context cancellation. +func PollUntilStable(ctx context.Context, clock Clock, fetch func() *hierarchy.Tree) { + deadline := clock.Now().Add(StabilityPollCap) + runStart := clock.Now() + prior := snapshot(fetch) + for clock.Now().Before(deadline) { + if ctx.Err() != nil { + return + } + clock.Sleep(StabilityPollInterval) + currentStart := clock.Now() + current := snapshot(fetch) + if prior.valid && current.valid && prior.hash == current.hash { + if clock.Now().Sub(runStart) >= MinStableStreak { + return + } + } else { + runStart = currentStart + } + prior = current + } +} + +// stableSnapshot is the result of probing a single hierarchy tree. valid is +// false when the snapshot is transitional (mid route transition) or the fetch +// failed, both of which must reset the stable streak. +type stableSnapshot struct { + hash string + valid bool +} + +func snapshot(fetch func() *hierarchy.Tree) stableSnapshot { + tree := fetch() + if tree == nil { + return stableSnapshot{} + } + return StabilitySnapshot(tree) +} + +// StabilitySnapshot probes a hierarchy tree for both structural shape and route +// transition state. An empty tree is a valid stable snapshot (the blank-tree +// case in the companion). A tree with more than one route Screen is +// transitional and reported invalid: a navigation host keeps both source and +// destination destinations alive during a cross-fade, and a snapshot taken in +// that window is unreliable because lazy lists in the incoming screen mount +// over several frames. Otherwise the snapshot carries the structural hash. +func StabilitySnapshot(tree *hierarchy.Tree) stableSnapshot { + if tree == nil || tree.Root == nil { + return stableSnapshot{valid: true} + } + if CountRouteScreens(tree) > 1 { + return stableSnapshot{} + } + return stableSnapshot{hash: StructuralHash(tree), valid: true} +} + +// routeTagKeys are the attribute keys whose value, when it ends with "Screen", +// marks a node as a route-level destination. Mirrors the companion's set. +var routeTagKeys = []string{ + "resource-id", "resourceId", "testTag", + "identifier", "accessibilityIdentifier", +} + +// CountRouteScreens counts nodes that carry a route-level destination tag (a +// route-tag attribute whose value ends with "Screen"). At most one tag is +// counted per node, matching the companion which breaks on the first match. +func CountRouteScreens(tree *hierarchy.Tree) int { + if tree == nil || tree.Root == nil { + return 0 + } + return countRouteScreens(tree.Root) +} + +func countRouteScreens(node *hierarchy.Node) int { + count := 0 + for _, key := range routeTagKeys { + value, ok := node.Attributes[key] + if !ok { + continue + } + if strings.HasSuffix(value, "Screen") { + count++ + break + } + } + for _, child := range node.Children { + count += countRouteScreens(child) + } + return count +} + +// stableAttributeKeys are the identity attributes folded into the structural +// hash, in this exact order. The transient bounds attribute is deliberately +// excluded so a measure pass that shifts pixels without changing what is on +// screen does not extend the wait; structure (via tree shape) and text are +// included so a real content or layout-tree change does reset stability. +var stableAttributeKeys = []string{ + "resource-id", "resourceId", + "class", "className", + "content-desc", "contentDescription", "accessibilityText", + "text", + "testTag", "identifier", "accessibilityIdentifier", +} + +// StructuralHash walks the tree and concatenates only the stable identity +// attributes (excluding bounds), in tree order, wrapping each node in +// parentheses so tree shape is encoded. The result is compared by equality, so +// it is the hash itself rather than a digest of it. +func StructuralHash(tree *hierarchy.Tree) string { + if tree == nil || tree.Root == nil { + return "" + } + var builder strings.Builder + walkForStructuralHash(tree.Root, &builder) + return builder.String() +} + +func walkForStructuralHash(node *hierarchy.Node, out *strings.Builder) { + out.WriteByte('(') + for _, key := range stableAttributeKeys { + value, ok := node.Attributes[key] + if !ok { + continue + } + out.WriteString(key) + out.WriteByte(':') + out.WriteString(value) + out.WriteByte('|') + } + for _, child := range node.Children { + walkForStructuralHash(child, out) + } + out.WriteByte(')') +} diff --git a/internal/driver/ioscompanion/settle_test.go b/internal/driver/ioscompanion/settle_test.go new file mode 100644 index 0000000..6a97f7b --- /dev/null +++ b/internal/driver/ioscompanion/settle_test.go @@ -0,0 +1,199 @@ +package ioscompanion + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/priyanshujain/sanderling/internal/hierarchy" +) + +// fakeClock advances its internal time only when Sleep is called, so the poll +// loop runs instantly and deterministically without real sleeping. +type fakeClock struct { + now time.Time +} + +func (c *fakeClock) Now() time.Time { return c.now } + +func (c *fakeClock) Sleep(d time.Duration) { c.now = c.now.Add(d) } + +func mustTree(t *testing.T, json string) *hierarchy.Tree { + t.Helper() + tree, err := hierarchy.Parse(json) + if err != nil { + t.Fatalf("parse hierarchy: %v", err) + } + return tree +} + +// fetcher returns a fetch function yielding trees in sequence; once the +// sequence is exhausted the last tree repeats forever. +func fetcher(trees ...*hierarchy.Tree) func() *hierarchy.Tree { + index := 0 + return func() *hierarchy.Tree { + tree := trees[index] + if index < len(trees)-1 { + index++ + } + return tree + } +} + +const singleScreenJSON = `{ + "attributes": {"resource-id": "homeScreen", "text": "Home"}, + "children": [ + {"attributes": {"text": "Welcome", "bounds": "[0,0,100,50]"}, "children": []} + ] +}` + +// twoScreenJSON carries two route Screens (a navigation cross-fade), which must +// be treated as transitional. +const twoScreenJSON = `{ + "attributes": {"resource-id": "rootView"}, + "children": [ + {"attributes": {"testTag": "homeScreen"}, "children": []}, + {"attributes": {"testTag": "detailScreen"}, "children": []} + ] +}` + +func TestCountRouteScreens(t *testing.T) { + tests := []struct { + name string + json string + want int + }{ + {"single screen", singleScreenJSON, 1}, + {"two screens", twoScreenJSON, 2}, + {"no screens", `{"attributes": {"text": "plain"}, "children": []}`, 0}, + { + "one node only counts once across route keys", + `{"attributes": {"resource-id": "fooScreen", "testTag": "barScreen"}, "children": []}`, + 1, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := CountRouteScreens(mustTree(t, test.json)) + if got != test.want { + t.Fatalf("CountRouteScreens = %d, want %d", got, test.want) + } + }) + } +} + +func TestStabilitySnapshotTreatsTwoScreensAsTransitional(t *testing.T) { + snap := StabilitySnapshot(mustTree(t, twoScreenJSON)) + if snap.valid { + t.Fatalf("snapshot with two route Screens should be invalid (transitional)") + } +} + +func TestStructuralHashIgnoresBoundsButNotTextOrStructure(t *testing.T) { + base := `{"attributes": {"text": "Hi", "bounds": "[0,0,10,10]"}, "children": [ + {"attributes": {"text": "child", "bounds": "[0,0,5,5]"}, "children": []} + ]}` + boundsShifted := `{"attributes": {"text": "Hi", "bounds": "[5,5,20,20]"}, "children": [ + {"attributes": {"text": "child", "bounds": "[5,5,9,9]"}, "children": []} + ]}` + textChanged := `{"attributes": {"text": "Bye", "bounds": "[0,0,10,10]"}, "children": [ + {"attributes": {"text": "child", "bounds": "[0,0,5,5]"}, "children": []} + ]}` + structureChanged := `{"attributes": {"text": "Hi", "bounds": "[0,0,10,10]"}, "children": [ + {"attributes": {"text": "child", "bounds": "[0,0,5,5]"}, "children": []}, + {"attributes": {"text": "extra"}, "children": []} + ]}` + + baseHash := StructuralHash(mustTree(t, base)) + if got := StructuralHash(mustTree(t, boundsShifted)); got != baseHash { + t.Fatalf("bounds-only change must not alter hash:\n base=%q\n shift=%q", baseHash, got) + } + if got := StructuralHash(mustTree(t, textChanged)); got == baseHash { + t.Fatalf("text change must alter hash, both were %q", baseHash) + } + if got := StructuralHash(mustTree(t, structureChanged)); got == baseHash { + t.Fatalf("structure change must alter hash, both were %q", baseHash) + } +} + +func TestPollUntilStableTwoScreensNeverStable(t *testing.T) { + clock := &fakeClock{now: time.Unix(0, 0)} + fetch := fetcher(mustTree(t, twoScreenJSON)) + start := clock.Now() + PollUntilStable(context.Background(), clock, fetch) + elapsed := clock.Now().Sub(start) + if elapsed < StabilityPollCap { + t.Fatalf("a perpetually transitional UI must poll until the cap, elapsed=%v cap=%v", elapsed, StabilityPollCap) + } +} + +func TestPollUntilStableReturnsAfterStreak(t *testing.T) { + clock := &fakeClock{now: time.Unix(0, 0)} + fetch := fetcher(mustTree(t, singleScreenJSON)) + start := clock.Now() + PollUntilStable(context.Background(), clock, fetch) + elapsed := clock.Now().Sub(start) + // prior is sampled at t0, then equal snapshots accumulate the streak. The + // streak starts at the first matching tick and must reach MinStableStreak. + if elapsed > StabilityPollCap { + t.Fatalf("stable UI must settle before the cap, elapsed=%v", elapsed) + } + if elapsed < MinStableStreak { + t.Fatalf("must observe a full stable streak before returning, elapsed=%v streak=%v", elapsed, MinStableStreak) + } +} + +func TestPollUntilStableResetsStreakOnMidStreakChange(t *testing.T) { + clock := &fakeClock{now: time.Unix(0, 0)} + stable := mustTree(t, singleScreenJSON) + churned := mustTree(t, `{"attributes": {"resource-id": "homeScreen", "text": "Loading"}, "children": []}`) + // Two stable ticks build part of a streak (250ms, 500ms accumulated since + // the streak start at the first match) then a changed tree resets it, after + // which a fresh full 750ms streak is required. + fetch := fetcher(stable, stable, stable, churned, stable) + start := clock.Now() + PollUntilStable(context.Background(), clock, fetch) + elapsed := clock.Now().Sub(start) + // The streak that succeeds begins after the churn, so total elapsed must + // exceed a single uninterrupted streak. Without a reset the loop would have + // returned far earlier. + if elapsed < MinStableStreak+3*StabilityPollInterval { + t.Fatalf("mid-streak change must reset and require a fresh streak, elapsed=%v", elapsed) + } + if elapsed > StabilityPollCap { + t.Fatalf("expected settle before cap once a clean streak completes, elapsed=%v", elapsed) + } +} + +func TestPollUntilStableCapReturnsWhenNeverStable(t *testing.T) { + clock := &fakeClock{now: time.Unix(0, 0)} + // Each fetch yields a structurally different tree, so the streak never + // grows and only the cap can end the loop. + tick := 0 + fetch := func() *hierarchy.Tree { + tick++ + return mustTree(t, fmt.Sprintf(`{"attributes": {"text": "frame-%d"}, "children": []}`, tick)) + } + start := clock.Now() + PollUntilStable(context.Background(), clock, fetch) + elapsed := clock.Now().Sub(start) + if elapsed < StabilityPollCap { + t.Fatalf("never-stable UI must poll up to the cap, elapsed=%v cap=%v", elapsed, StabilityPollCap) + } + if elapsed > StabilityPollCap+StabilityPollInterval { + t.Fatalf("must not overshoot the cap by more than one interval, elapsed=%v", elapsed) + } +} + +func TestPollUntilStableHonorsContextCancellation(t *testing.T) { + clock := &fakeClock{now: time.Unix(0, 0)} + ctx, cancel := context.WithCancel(context.Background()) + cancel() + fetch := fetcher(mustTree(t, twoScreenJSON)) + start := clock.Now() + PollUntilStable(ctx, clock, fetch) + if elapsed := clock.Now().Sub(start); elapsed > StabilityPollInterval { + t.Fatalf("cancelled context must stop the loop promptly, elapsed=%v", elapsed) + } +} diff --git a/internal/driver/ioscompanion/smoke_test.go b/internal/driver/ioscompanion/smoke_test.go new file mode 100644 index 0000000..e4854e5 --- /dev/null +++ b/internal/driver/ioscompanion/smoke_test.go @@ -0,0 +1,85 @@ +//go:build withcompanion + +package ioscompanion + +import ( + "context" + "os" + "os/exec" + "strings" + "testing" + "time" +) + +// TestSmokeNewHierarchyScreenshot brings up the embedded companion against the +// booted simulator, reads one hierarchy and screenshot, closes, and asserts the +// companion child left no orphan behind. It is gated behind both the +// withcompanion build tag (so the binary is embedded) and an environment +// variable, so it never runs in the default suite. +func TestSmokeNewHierarchyScreenshot(t *testing.T) { + if os.Getenv("SANDERLING_IOS_INTEGRATION") == "" { + t.Skip("set SANDERLING_IOS_INTEGRATION=1 to run the companion smoke test") + } + udid := bootedUDID(t) + + before := companionProcessCount(t, udid) + + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second) + defer cancel() + d, err := New(ctx, Options{UniqueDeviceIdentifier: udid, Output: os.Stderr}) + if err != nil { + t.Fatalf("New: %v", err) + } + + if _, err := d.Hierarchy(ctx); err != nil { + d.Close() + t.Fatalf("Hierarchy: %v", err) + } + if _, err := d.Screenshot(ctx); err != nil { + d.Close() + t.Fatalf("Screenshot: %v", err) + } + d.Close() + + // Give the child a moment to exit after SIGTERM, then confirm no orphan. + deadline := time.Now().Add(10 * time.Second) + for time.Now().Before(deadline) { + if companionProcessCount(t, udid) <= before { + return + } + time.Sleep(200 * time.Millisecond) + } + t.Fatalf("companion process for %s outlived Close (orphan)", udid) +} + +func bootedUDID(t *testing.T) string { + t.Helper() + output, err := exec.Command("xcrun", "simctl", "list", "devices", "booted").Output() + if err != nil { + t.Fatalf("simctl list: %v", err) + } + for _, line := range strings.Split(string(output), "\n") { + if start := strings.Index(line, "("); start >= 0 { + if end := strings.Index(line[start:], ")"); end > 0 { + candidate := line[start+1 : start+end] + if len(candidate) == 36 { + return candidate + } + } + } + } + t.Skip("no booted simulator available") + return "" +} + +func companionProcessCount(t *testing.T, udid string) int { + t.Helper() + output, _ := exec.Command("pgrep", "-f", udid).Output() + count := 0 + for _, line := range strings.Split(strings.TrimSpace(string(output)), "\n") { + if strings.TrimSpace(line) != "" { + count++ + } + } + return count +} diff --git a/internal/driver/ioscompanion/supervision_test.go b/internal/driver/ioscompanion/supervision_test.go new file mode 100644 index 0000000..306a67c --- /dev/null +++ b/internal/driver/ioscompanion/supervision_test.go @@ -0,0 +1,157 @@ +package ioscompanion + +import ( + "context" + "errors" + "net" + "os/exec" + "syscall" + "testing" + "time" + + "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/transport" +) + +// These tests cover the process-supervision logic (stopProcess, bringUp, +// respawnAndRedial) with real child processes and seamed transports, so the +// default suite exercises it without a simulator. + +func TestStopProcessReapsChildOnSigterm(t *testing.T) { + child := exec.Command("sleep", "30") + if err := child.Start(); err != nil { + t.Fatalf("start child: %v", err) + } + start := time.Now() + stopProcess(child) + if elapsed := time.Since(start); elapsed > 5*time.Second { + t.Fatalf("stopProcess took %v; SIGTERM on sleep should reap promptly", elapsed) + } + if child.ProcessState == nil { + t.Fatal("child not reaped: ProcessState is nil") + } + if err := child.Process.Signal(syscall.Signal(0)); err == nil { + t.Fatal("child still signalable after stopProcess") + } +} + +func TestStopProcessEscalatesToKillWhenSigtermIgnored(t *testing.T) { + previousGrace := shutdownGrace + shutdownGrace = 200 * time.Millisecond + defer func() { shutdownGrace = previousGrace }() + + child := exec.Command("sh", "-c", `trap "" TERM; sleep 30`) + if err := child.Start(); err != nil { + t.Fatalf("start child: %v", err) + } + // Give the shell a beat to install its TERM trap. + time.Sleep(100 * time.Millisecond) + stopProcess(child) + if child.ProcessState == nil { + t.Fatal("child not reaped: ProcessState is nil") + } + if child.ProcessState.Success() { + t.Fatal("child should have died by signal, not exited cleanly") + } +} + +func TestStopProcessHandlesNilChild(t *testing.T) { + stopProcess(nil) + stopProcess(&exec.Cmd{}) +} + +// TestRespawnAndRedialReplacesTransportAndChild drives the real restart path +// through the seams: the old transport must be closed, a fresh child spawned, +// and the fresh transport installed. +func TestRespawnAndRedialReplacesTransportAndChild(t *testing.T) { + t.Setenv("SANDERLING_SIMULATOR_COMPANION", "legacy") + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer listener.Close() + go func() { + for { + connection, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + _ = connection.Close() + } + }() + + spawns := 0 + dials := []*fakeCompanion{} + options := Options{ + UniqueDeviceIdentifier: "FAKE-UDID", + pickAddress: func() (string, error) { return listener.Addr().String(), nil }, + spawnChild: func(context.Context, string) (*exec.Cmd, error) { + spawns++ + return &exec.Cmd{}, nil + }, + dialCompanion: func(string) (transport.Companion, error) { + companion := &fakeCompanion{accessibilityJSON: "[]"} + dials = append(dials, companion) + return companion, nil + }, + } + d, err := New(context.Background(), options) + if err != nil { + t.Fatal(err) + } + defer d.Close() + + first := dials[0] + if err := d.respawnAndRedial(context.Background()); err != nil { + t.Fatalf("respawnAndRedial: %v", err) + } + if spawns != 2 { + t.Fatalf("spawns = %d, want 2 (initial bring-up plus restart)", spawns) + } + if len(dials) != 2 || d.companion != transport.Companion(dials[1]) { + t.Fatalf("restart must install the freshly dialed transport") + } + if indexOf(first.recorded(), "close") < 0 { + t.Fatal("restart must close the dead transport") + } +} + +// TestBringUpStopsChildWhenDialFails proves a failed bring-up does not leak +// the child it spawned. +func TestBringUpStopsChildWhenDialFails(t *testing.T) { + t.Setenv("SANDERLING_SIMULATOR_COMPANION", "legacy") + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer listener.Close() + go func() { + for { + connection, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + _ = connection.Close() + } + }() + + child := exec.Command("sleep", "30") + if err := child.Start(); err != nil { + t.Fatalf("start child: %v", err) + } + options := Options{ + UniqueDeviceIdentifier: "FAKE-UDID", + pickAddress: func() (string, error) { return listener.Addr().String(), nil }, + spawnChild: func(context.Context, string) (*exec.Cmd, error) { + return child, nil + }, + dialCompanion: func(string) (transport.Companion, error) { + return nil, errors.New("dial refused") + }, + } + if _, err := New(context.Background(), options); err == nil { + t.Fatal("New should fail when dial fails") + } + if child.ProcessState == nil { + t.Fatal("failed bring-up must reap the spawned child") + } +} diff --git a/internal/driver/ioscompanion/testdata/accounts-describe.json b/internal/driver/ioscompanion/testdata/accounts-describe.json new file mode 100644 index 0000000..9a9fa09 --- /dev/null +++ b/internal/driver/ioscompanion/testdata/accounts-describe.json @@ -0,0 +1 @@ +[{"AXFrame":"{{0, 0}, {402, 874}}","AXUniqueId":null,"frame":{"y":0,"x":0,"width":402,"height":874},"role_description":"application","AXLabel":"Folio","content_required":false,"type":"Application","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXApplication","subrole":null},{"AXFrame":"{{20, 76}, {97.333335876464844, 24}}","AXUniqueId":null,"frame":{"y":76,"x":20,"width":97.333335876464844,"height":24},"role_description":"text","AXLabel":"Accounts","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{340, 71.333335876464844}, {48, 48}}","AXUniqueId":"LogoutButton","frame":{"y":71.333335876464844,"x":340,"width":48,"height":48},"role_description":"button","AXLabel":"Log out","content_required":false,"type":"Button","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXButton","subrole":null},{"AXFrame":"{{20, 100}, {104, 14.333335876464844}}","AXUniqueId":null,"frame":{"y":100,"x":20,"width":104,"height":14.333335876464844},"role_description":"text","AXLabel":"demo@folio.app","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{131.33332824707031, 248.33332824707031}, {139.33332824707031, 17.999984741210938}}","AXUniqueId":null,"frame":{"y":248.33332824707031,"x":131.33332824707031,"width":139.33332824707031,"height":17.999984741210938},"role_description":"text","AXLabel":"No accounts yet","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{36, 272.33334350585938}, {330, 28.666656494140625}}","AXUniqueId":null,"frame":{"y":272.33334350585938,"x":36,"width":330,"height":28.666656494140625},"role_description":"text","AXLabel":"Create your first account to start tracking transactions.","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{20, 716.33331298828125}, {105.66666412353516, 14.33331298828125}}","AXUniqueId":null,"frame":{"y":716.33331298828125,"x":20,"width":105.66666412353516,"height":14.33331298828125},"role_description":"text","AXLabel":"TOTAL BALANCE","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{20, 730.66668701171875}, {85.333335876464844, 33.33331298828125}}","AXUniqueId":null,"frame":{"y":730.66668701171875,"x":20,"width":85.333335876464844,"height":33.33331298828125},"role_description":"text","AXLabel":"$0.00","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{307.66665649414062, 749.66668701171875}, {74.333343505859375, 14.33331298828125}}","AXUniqueId":null,"frame":{"y":749.66668701171875,"x":307.66665649414062,"width":74.333343505859375,"height":14.33331298828125},"role_description":"text","AXLabel":"0 accounts","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{20, 777}, {362, 48}}","AXUniqueId":"AddAccountButton","frame":{"y":777,"x":20,"width":362,"height":48},"role_description":"button","AXLabel":"+ Add account","content_required":false,"type":"Button","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXButton","subrole":null}] diff --git a/internal/driver/ioscompanion/testdata/login-describe.json b/internal/driver/ioscompanion/testdata/login-describe.json new file mode 100644 index 0000000..0d3382f --- /dev/null +++ b/internal/driver/ioscompanion/testdata/login-describe.json @@ -0,0 +1 @@ +[{"AXFrame":"{{0, 0}, {0, 0}}","AXUniqueId":null,"frame":{"y":0,"x":0,"width":0,"height":0},"role_description":"application","AXLabel":null,"content_required":false,"type":"Application","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXApplication","subrole":null},{"AXFrame":"{{20, 106}, {40.666667938232422, 14.333335876464844}}","AXUniqueId":null,"frame":{"y":106,"x":20,"width":40.666667938232422,"height":14.333335876464844},"role_description":"text","AXLabel":"EMAIL","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{34, 125.33333587646484}, {334, 48.000007629394531}}","AXUniqueId":"LoginEmail","frame":{"y":125.33333587646484,"x":34,"width":334,"height":48.000007629394531},"role_description":"text entry area","AXLabel":"Email","content_required":false,"type":"TextArea","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXTextArea","subrole":null},{"AXFrame":"{{20, 186.33332824707031}, {65, 14.333328247070312}}","AXUniqueId":null,"frame":{"y":186.33332824707031,"x":20,"width":65,"height":14.333328247070312},"role_description":"text","AXLabel":"PASSWORD","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{34, 205.66667175292969}, {334, 48}}","AXUniqueId":"LoginPassword","frame":{"y":205.66667175292969,"x":34,"width":334,"height":48},"role_description":"text entry area","AXLabel":"Password","content_required":false,"type":"TextArea","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXTextArea","subrole":null},{"AXFrame":"{{20, 266.66665649414062}, {362, 18}}","AXUniqueId":null,"frame":{"y":266.66665649414062,"x":20,"width":362,"height":18},"role_description":"text","AXLabel":"","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{20, 297.66665649414062}, {362, 48}}","AXUniqueId":"LoginSubmit","frame":{"y":297.66665649414062,"x":20,"width":362,"height":48},"role_description":"button","AXLabel":"Sign in","content_required":false,"type":"Button","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXButton","subrole":null},{"AXFrame":"{{36, 392.66665649414062}, {119, 14.333343505859375}}","AXUniqueId":null,"frame":{"y":392.66665649414062,"x":36,"width":119,"height":14.333343505859375},"role_description":"text","AXLabel":"Demo credentials","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{36, 413}, {195, 18}}","AXUniqueId":null,"frame":{"y":413,"x":36,"width":195,"height":18},"role_description":"text","AXLabel":"email: demo@folio.app","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null},{"AXFrame":"{{36, 437}, {176.33332824707031, 18}}","AXUniqueId":null,"frame":{"y":437,"x":36,"width":176.33332824707031,"height":18},"role_description":"text","AXLabel":"password: ledger123","content_required":false,"type":"StaticText","title":null,"help":null,"custom_actions":[],"AXValue":null,"enabled":true,"role":"AXStaticText","subrole":null}] diff --git a/internal/driver/ioscompanion/testdata/paste-dialog.json b/internal/driver/ioscompanion/testdata/paste-dialog.json new file mode 100644 index 0000000..2149bf6 --- /dev/null +++ b/internal/driver/ioscompanion/testdata/paste-dialog.json @@ -0,0 +1,7 @@ +[ + {"type": "Window", "frame": {"x": 0, "y": 0, "width": 390, "height": 844}, "enabled": true}, + {"type": "TextField", "AXUniqueId": "TxnNoteField", "AXValue": "", "AXLabel": "Note", "frame": {"x": 20, "y": 200, "width": 350, "height": 44}, "enabled": true}, + {"type": "StaticText", "AXLabel": "Allow Pasting", "frame": {"x": 95, "y": 380, "width": 200, "height": 22}, "enabled": true}, + {"type": "Button", "AXLabel": "Don’t Allow Paste", "frame": {"x": 40, "y": 440, "width": 140, "height": 50}, "enabled": true}, + {"type": "Button", "AXLabel": "Allow Paste", "frame": {"x": 210, "y": 440, "width": 140, "height": 50}, "enabled": true} +] diff --git a/internal/driver/ioscompanion/transport/grpc.go b/internal/driver/ioscompanion/transport/grpc.go new file mode 100644 index 0000000..970346a --- /dev/null +++ b/internal/driver/ioscompanion/transport/grpc.go @@ -0,0 +1,295 @@ +package transport + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "context" + "fmt" + "io" + "os" + "path/filepath" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + pb "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/companionpb" +) + +// maxReceiveBytes is generous because screenshots can be large. +const maxReceiveBytes = 64 * 1024 * 1024 + +type grpcCompanion struct { + conn *grpc.ClientConn + client pb.CompanionServiceClient +} + +// Dial connects to the companion listening at address and returns a Companion. +func Dial(address string) (Companion, error) { + conn, err := grpc.NewClient(address, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxReceiveBytes))) + if err != nil { + return nil, err + } + return &grpcCompanion{conn: conn, client: pb.NewCompanionServiceClient(conn)}, nil +} + +func (c *grpcCompanion) Close() error { return c.conn.Close() } + +func (c *grpcCompanion) AccessibilityInfo(ctx context.Context) (string, error) { + resp, err := c.client.AccessibilityInfo(ctx, &pb.AccessibilityInfoRequest{ + Format: pb.AccessibilityInfoRequest_LEGACY, + }) + if err != nil { + return "", err + } + return resp.GetJson(), nil +} + +func (c *grpcCompanion) Describe(ctx context.Context) (ScreenDescription, error) { + resp, err := c.client.Describe(ctx, &pb.TargetDescriptionRequest{}) + if err != nil { + return ScreenDescription{}, err + } + return screenDescriptionFrom(resp), nil +} + +// screenDescriptionFrom extracts the point dimensions and scale from a describe +// response. The generated getters are nil-safe, so a response missing the +// nested messages yields a zero-valued ScreenDescription rather than panicking. +func screenDescriptionFrom(resp *pb.TargetDescriptionResponse) ScreenDescription { + dimensions := resp.GetTargetDescription().GetScreenDimensions() + return ScreenDescription{ + WidthPoints: int(dimensions.GetWidthPoints()), + HeightPoints: int(dimensions.GetHeightPoints()), + Scale: dimensions.GetDensity(), + } +} + +func (c *grpcCompanion) SendHID(ctx context.Context, events ...HIDEvent) error { + stream, err := c.client.Hid(ctx) + if err != nil { + return err + } + for _, e := range events { + message, err := hidEventToProto(e) + if err != nil { + return err + } + if err := stream.Send(message); err != nil { + return err + } + } + _, err = stream.CloseAndRecv() + return err +} + +// hidEventToProto encodes a neutral HID event as this companion's wire event. +// The companion measures delays and swipe durations in seconds. +func hidEventToProto(e HIDEvent) (*pb.HIDEvent, error) { + switch e.Kind { + case HIDKindTouchDown: + return touchProto(e.X, e.Y, pb.HIDEvent_DOWN), nil + case HIDKindTouchUp: + return touchProto(e.X, e.Y, pb.HIDEvent_UP), nil + case HIDKindKeyDown: + return keyProto(e.Usage, pb.HIDEvent_DOWN), nil + case HIDKindKeyUp: + return keyProto(e.Usage, pb.HIDEvent_UP), nil + case HIDKindDelay: + return &pb.HIDEvent{Event: &pb.HIDEvent_Delay{ + Delay: &pb.HIDEvent_HIDDelay{Duration: e.Milliseconds / 1000.0}, + }}, nil + case HIDKindSwipe: + return &pb.HIDEvent{Event: &pb.HIDEvent_Swipe{Swipe: &pb.HIDEvent_HIDSwipe{ + Start: &pb.Point{X: e.FromX, Y: e.FromY}, + End: &pb.Point{X: e.ToX, Y: e.ToY}, + Duration: e.Seconds, + }}}, nil + } + return nil, fmt.Errorf("unknown HID event kind %d", e.Kind) +} + +func touchProto(x, y float64, direction pb.HIDEvent_HIDDirection) *pb.HIDEvent { + return &pb.HIDEvent{Event: &pb.HIDEvent_Press{Press: &pb.HIDEvent_HIDPress{ + Direction: direction, + Action: &pb.HIDEvent_HIDPressAction{Action: &pb.HIDEvent_HIDPressAction_Touch{ + Touch: &pb.HIDEvent_HIDTouch{Point: &pb.Point{X: x, Y: y}}, + }}, + }}} +} + +func keyProto(usage uint32, direction pb.HIDEvent_HIDDirection) *pb.HIDEvent { + return &pb.HIDEvent{Event: &pb.HIDEvent_Press{Press: &pb.HIDEvent_HIDPress{ + Direction: direction, + Action: &pb.HIDEvent_HIDPressAction{Action: &pb.HIDEvent_HIDPressAction_Key{ + Key: &pb.HIDEvent_HIDKey{Keycode: uint64(usage)}, + }}, + }}} +} + +func (c *grpcCompanion) Screenshot(ctx context.Context) ([]byte, string, error) { + resp, err := c.client.Screenshot(ctx, &pb.ScreenshotRequest{}) + if err != nil { + return nil, "", err + } + return resp.GetImageData(), resp.GetImageFormat(), nil +} + +func (c *grpcCompanion) Launch(ctx context.Context, bundleID string, foregroundIfRunning bool) error { + stream, err := c.client.Launch(ctx) + if err != nil { + return err + } + err = stream.Send(&pb.LaunchRequest{Control: &pb.LaunchRequest_Start_{Start: &pb.LaunchRequest_Start{ + BundleId: bundleID, + ForegroundIfRunning: foregroundIfRunning, + }}}) + if err != nil { + return err + } + if _, err := stream.Recv(); err != nil && err != io.EOF { + return err + } + return stream.CloseSend() +} + +func (c *grpcCompanion) Terminate(ctx context.Context, bundleID string) error { + _, err := c.client.Terminate(ctx, &pb.TerminateRequest{BundleId: bundleID}) + return err +} + +func (c *grpcCompanion) Uninstall(ctx context.Context, bundleID string) error { + _, err := c.client.Uninstall(ctx, &pb.UninstallRequest{BundleId: bundleID}) + return err +} + +func (c *grpcCompanion) ListApps(ctx context.Context) ([]InstalledApp, error) { + resp, err := c.client.ListApps(ctx, &pb.ListAppsRequest{}) + if err != nil { + return nil, err + } + apps := make([]InstalledApp, 0, len(resp.GetApps())) + for _, a := range resp.GetApps() { + apps = append(apps, InstalledApp{ + BundleID: a.GetBundleId(), + Name: a.GetName(), + InstallType: a.GetInstallType(), + ProcessState: processStateFromProto(a.GetProcessState()), + Debuggable: a.GetDebuggable(), + ProcessIdentifier: a.GetProcessIdentifier(), + }) + } + return apps, nil +} + +// installChunkBytes keeps each install payload frame comfortably under the +// companion's 16MiB incoming-message cap. +const installChunkBytes = 4 * 1024 * 1024 + +func (c *grpcCompanion) Install(ctx context.Context, appPath string) error { + info, err := os.Stat(appPath) + if err != nil { + return err + } + if !info.IsDir() { + return fmt.Errorf("install: %s is not an app bundle directory", appPath) + } + + stream, err := c.client.Install(ctx) + if err != nil { + return err + } + + // First message sets the destination, then the payload carries the bundle + // as a gzip-compressed tar archive the companion unpacks. + if err := stream.Send(&pb.InstallRequest{Value: &pb.InstallRequest_Destination_{ + Destination: pb.InstallRequest_APP, + }}); err != nil { + return err + } + + archive, err := tarGzipDirectory(appPath) + if err != nil { + return err + } + // The companion caps incoming messages at 16MiB, so the archive streams in + // chunks; the companion concatenates consecutive data payloads. + for _, chunk := range payloadChunks(archive, installChunkBytes) { + if err := stream.Send(&pb.InstallRequest{Value: &pb.InstallRequest_Payload{ + Payload: &pb.Payload{Source: &pb.Payload_Data{Data: chunk}}, + }}); err != nil { + return err + } + } + + if err := stream.CloseSend(); err != nil { + return err + } + for { + if _, err := stream.Recv(); err != nil { + if err == io.EOF { + return nil + } + return err + } + } +} + +// payloadChunks splits data into consecutive slices of at most chunkBytes. +func payloadChunks(data []byte, chunkBytes int) [][]byte { + var chunks [][]byte + for offset := 0; offset < len(data); offset += chunkBytes { + end := min(offset+chunkBytes, len(data)) + chunks = append(chunks, data[offset:end]) + } + return chunks +} + +// tarGzipDirectory packs dir into a gzip-compressed tar archive. Entry paths are +// relative to the parent of dir so the bundle directory itself is preserved. +func tarGzipDirectory(dir string) ([]byte, error) { + var buffer bytes.Buffer + gzipWriter := gzip.NewWriter(&buffer) + tarWriter := tar.NewWriter(gzipWriter) + + base := filepath.Dir(dir) + walkErr := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + relative, err := filepath.Rel(base, path) + if err != nil { + return err + } + header, err := tar.FileInfoHeader(info, "") + if err != nil { + return err + } + header.Name = filepath.ToSlash(relative) + if err := tarWriter.WriteHeader(header); err != nil { + return err + } + if info.IsDir() { + return nil + } + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + _, err = io.Copy(tarWriter, file) + return err + }) + if walkErr != nil { + return nil, walkErr + } + if err := tarWriter.Close(); err != nil { + return nil, err + } + if err := gzipWriter.Close(); err != nil { + return nil, err + } + return buffer.Bytes(), nil +} diff --git a/internal/driver/ioscompanion/transport/grpc_test.go b/internal/driver/ioscompanion/transport/grpc_test.go new file mode 100644 index 0000000..3c3ee66 --- /dev/null +++ b/internal/driver/ioscompanion/transport/grpc_test.go @@ -0,0 +1,158 @@ +package transport + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "io" + "os" + "path/filepath" + "testing" + + pb "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/companionpb" +) + +// grpcCompanion must satisfy Companion. +var _ Companion = (*grpcCompanion)(nil) + +func TestDialReturnsCompanion(t *testing.T) { + companion, err := Dial("127.0.0.1:0") + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer companion.Close() + if companion == nil { + t.Fatal("Dial returned nil companion") + } +} + +func TestProcessStateFromProto(t *testing.T) { + cases := []struct { + in pb.InstalledAppInfo_AppProcessState + want ProcessState + }{ + {pb.InstalledAppInfo_RUNNING, ProcessStateRunning}, + {pb.InstalledAppInfo_NOT_RUNNING, ProcessStateNotRunning}, + {pb.InstalledAppInfo_UNKNOWN, ProcessStateUnknown}, + } + for _, c := range cases { + if got := processStateFromProto(c.in); got != c.want { + t.Errorf("processStateFromProto(%v) = %v, want %v", c.in, got, c.want) + } + } +} + +func TestScreenDescriptionFrom(t *testing.T) { + resp := &pb.TargetDescriptionResponse{ + TargetDescription: &pb.TargetDescription{ + ScreenDimensions: &pb.ScreenDimensions{ + Width: 828, + Height: 1792, + Density: 2, + WidthPoints: 414, + HeightPoints: 896, + }, + }, + } + got := screenDescriptionFrom(resp) + want := ScreenDescription{WidthPoints: 414, HeightPoints: 896, Scale: 2} + if got != want { + t.Errorf("screenDescriptionFrom = %+v, want %+v", got, want) + } +} + +func TestScreenDescriptionFromNilSafe(t *testing.T) { + if got := screenDescriptionFrom(&pb.TargetDescriptionResponse{}); got != (ScreenDescription{}) { + t.Errorf("screenDescriptionFrom(empty) = %+v, want zero", got) + } +} + +func TestTarGzipDirectory(t *testing.T) { + root := t.TempDir() + bundle := filepath.Join(root, "Sample.app") + if err := os.MkdirAll(filepath.Join(bundle, "PlugIns"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(bundle, "Info.plist"), []byte("plist"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(bundle, "PlugIns", "ext"), []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + + archive, err := tarGzipDirectory(bundle) + if err != nil { + t.Fatalf("tarGzipDirectory: %v", err) + } + + names := tarEntryNames(t, archive) + want := map[string]bool{ + "Sample.app": true, + "Sample.app/Info.plist": true, + "Sample.app/PlugIns": true, + "Sample.app/PlugIns/ext": true, + } + for name := range want { + if !names[name] { + t.Errorf("archive missing entry %q; got %v", name, names) + } + } +} + +func tarEntryNames(t *testing.T, archive []byte) map[string]bool { + t.Helper() + gzipReader, err := gzip.NewReader(bytes.NewReader(archive)) + if err != nil { + t.Fatalf("gzip reader: %v", err) + } + defer gzipReader.Close() + tarReader := tar.NewReader(gzipReader) + names := map[string]bool{} + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("tar next: %v", err) + } + names[filepath.ToSlash(filepath.Clean(header.Name))] = true + } + return names +} + +func TestPayloadChunks(t *testing.T) { + cases := []struct { + name string + dataLength int + chunkBytes int + wantSizes []int + }{ + {name: "empty", dataLength: 0, chunkBytes: 4, wantSizes: nil}, + {name: "under one chunk", dataLength: 3, chunkBytes: 4, wantSizes: []int{3}}, + {name: "exact multiple", dataLength: 8, chunkBytes: 4, wantSizes: []int{4, 4}}, + {name: "remainder", dataLength: 10, chunkBytes: 4, wantSizes: []int{4, 4, 2}}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + data := make([]byte, c.dataLength) + for i := range data { + data[i] = byte(i) + } + chunks := payloadChunks(data, c.chunkBytes) + if len(chunks) != len(c.wantSizes) { + t.Fatalf("got %d chunks, want %d", len(chunks), len(c.wantSizes)) + } + var rejoined []byte + for i, chunk := range chunks { + if len(chunk) != c.wantSizes[i] { + t.Fatalf("chunk %d size %d, want %d", i, len(chunk), c.wantSizes[i]) + } + rejoined = append(rejoined, chunk...) + } + if !bytes.Equal(rejoined, data) { + t.Fatal("rejoined chunks differ from input") + } + }) + } +} diff --git a/internal/driver/ioscompanion/transport/hid.go b/internal/driver/ioscompanion/transport/hid.go new file mode 100644 index 0000000..1088dd4 --- /dev/null +++ b/internal/driver/ioscompanion/transport/hid.go @@ -0,0 +1,63 @@ +package transport + +// HIDEventKind discriminates the neutral HID event variants. +type HIDEventKind int + +const ( + HIDKindTouchDown HIDEventKind = iota + HIDKindTouchUp + HIDKindKeyDown + HIDKindKeyUp + HIDKindDelay + HIDKindSwipe +) + +// HIDEvent is one input event in a HID stream, expressed in transport-neutral +// terms so each companion transport encodes it for its own wire format. Build +// one with a builder below; only the fields for the event's kind are set. +type HIDEvent struct { + Kind HIDEventKind + + // X, Y is the touch point for TouchDown and TouchUp. + X, Y float64 + + // Usage is the USB HID usage identifier for KeyDown and KeyUp. + Usage uint32 + + // Milliseconds is the pause length for Delay. + Milliseconds float64 + + // FromX through Seconds describe a Swipe. + FromX, FromY float64 + ToX, ToY float64 + Seconds float64 +} + +// TouchDown presses a finger down at screen point (x, y). +func TouchDown(x, y float64) HIDEvent { return HIDEvent{Kind: HIDKindTouchDown, X: x, Y: y} } + +// TouchUp lifts the finger at screen point (x, y). +func TouchUp(x, y float64) HIDEvent { return HIDEvent{Kind: HIDKindTouchUp, X: x, Y: y} } + +// KeyDown presses the key with the given USB HID usage identifier. +func KeyDown(usage uint32) HIDEvent { return HIDEvent{Kind: HIDKindKeyDown, Usage: usage} } + +// KeyUp releases the key with the given USB HID usage identifier. +func KeyUp(usage uint32) HIDEvent { return HIDEvent{Kind: HIDKindKeyUp, Usage: usage} } + +// Delay pauses the HID stream for the given duration. +func Delay(milliseconds float64) HIDEvent { + return HIDEvent{Kind: HIDKindDelay, Milliseconds: milliseconds} +} + +// SwipeEvent drags from (fromX, fromY) to (toX, toY) over durationSeconds. +func SwipeEvent(fromX, fromY, toX, toY float64, durationSeconds float64) HIDEvent { + return HIDEvent{ + Kind: HIDKindSwipe, + FromX: fromX, + FromY: fromY, + ToX: toX, + ToY: toY, + Seconds: durationSeconds, + } +} diff --git a/internal/driver/ioscompanion/transport/hid_test.go b/internal/driver/ioscompanion/transport/hid_test.go new file mode 100644 index 0000000..18177cd --- /dev/null +++ b/internal/driver/ioscompanion/transport/hid_test.go @@ -0,0 +1,137 @@ +package transport + +import ( + "testing" + + pb "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/companionpb" +) + +func TestBuildersSetKindAndFields(t *testing.T) { + cases := []struct { + name string + event HIDEvent + want HIDEvent + }{ + {"touch down", TouchDown(12, 34), HIDEvent{Kind: HIDKindTouchDown, X: 12, Y: 34}}, + {"touch up", TouchUp(12, 34), HIDEvent{Kind: HIDKindTouchUp, X: 12, Y: 34}}, + {"key down", KeyDown(225), HIDEvent{Kind: HIDKindKeyDown, Usage: 225}}, + {"key up", KeyUp(225), HIDEvent{Kind: HIDKindKeyUp, Usage: 225}}, + {"delay", Delay(250), HIDEvent{Kind: HIDKindDelay, Milliseconds: 250}}, + {"swipe", SwipeEvent(1, 2, 3, 4, 0.5), HIDEvent{ + Kind: HIDKindSwipe, FromX: 1, FromY: 2, ToX: 3, ToY: 4, Seconds: 0.5, + }}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if c.event != c.want { + t.Errorf("event = %+v, want %+v", c.event, c.want) + } + }) + } +} + +func TestTouchEventsToProto(t *testing.T) { + cases := []struct { + name string + event HIDEvent + direction pb.HIDEvent_HIDDirection + }{ + {"down", TouchDown(12, 34), pb.HIDEvent_DOWN}, + {"up", TouchUp(12, 34), pb.HIDEvent_UP}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + message, err := hidEventToProto(c.event) + if err != nil { + t.Fatalf("hidEventToProto: %v", err) + } + press := message.GetPress() + if press == nil { + t.Fatalf("expected press event, got %#v", message.GetEvent()) + } + if press.GetDirection() != c.direction { + t.Errorf("direction = %v, want %v", press.GetDirection(), c.direction) + } + touch := press.GetAction().GetTouch() + if touch == nil { + t.Fatalf("expected touch action") + } + if got := touch.GetPoint(); got.GetX() != 12 || got.GetY() != 34 { + t.Errorf("point = (%v, %v), want (12, 34)", got.GetX(), got.GetY()) + } + }) + } +} + +func TestKeyEventsToProto(t *testing.T) { + cases := []struct { + name string + event HIDEvent + direction pb.HIDEvent_HIDDirection + }{ + {"down", KeyDown(225), pb.HIDEvent_DOWN}, + {"up", KeyUp(225), pb.HIDEvent_UP}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + message, err := hidEventToProto(c.event) + if err != nil { + t.Fatalf("hidEventToProto: %v", err) + } + press := message.GetPress() + if press == nil { + t.Fatalf("expected press event") + } + if press.GetDirection() != c.direction { + t.Errorf("direction = %v, want %v", press.GetDirection(), c.direction) + } + key := press.GetAction().GetKey() + if key == nil { + t.Fatalf("expected key action") + } + if key.GetKeycode() != 225 { + t.Errorf("keycode = %d, want 225", key.GetKeycode()) + } + }) + } +} + +func TestDelayToProtoConvertsToSeconds(t *testing.T) { + message, err := hidEventToProto(Delay(250)) + if err != nil { + t.Fatalf("hidEventToProto: %v", err) + } + delay := message.GetDelay() + if delay == nil { + t.Fatalf("expected delay event") + } + if delay.GetDuration() != 0.25 { + t.Errorf("duration = %v seconds, want 0.25", delay.GetDuration()) + } +} + +func TestSwipeEventToProto(t *testing.T) { + message, err := hidEventToProto(SwipeEvent(1, 2, 3, 4, 0.5)) + if err != nil { + t.Fatalf("hidEventToProto: %v", err) + } + swipe := message.GetSwipe() + if swipe == nil { + t.Fatalf("expected swipe event") + } + if s := swipe.GetStart(); s.GetX() != 1 || s.GetY() != 2 { + t.Errorf("start = (%v, %v), want (1, 2)", s.GetX(), s.GetY()) + } + if e := swipe.GetEnd(); e.GetX() != 3 || e.GetY() != 4 { + t.Errorf("end = (%v, %v), want (3, 4)", e.GetX(), e.GetY()) + } + if swipe.GetDuration() != 0.5 { + t.Errorf("duration = %v, want 0.5", swipe.GetDuration()) + } +} + +func TestUnknownKindToProtoErrors(t *testing.T) { + if _, err := hidEventToProto(HIDEvent{Kind: HIDEventKind(99)}); err == nil { + t.Fatal("expected error for unknown event kind") + } +} diff --git a/internal/driver/ioscompanion/transport/integration_test.go b/internal/driver/ioscompanion/transport/integration_test.go new file mode 100644 index 0000000..25c9dcb --- /dev/null +++ b/internal/driver/ioscompanion/transport/integration_test.go @@ -0,0 +1,105 @@ +package transport + +import ( + "context" + "encoding/json" + "net" + "os" + "os/exec" + "strconv" + "strings" + "testing" + "time" +) + +// TestIntegrationAccessibilityInfo spawns a local companion against a booted +// simulator and exercises a real AccessibilityInfo call. It is gated behind +// SANDERLING_IOS_INTEGRATION so it never runs by default. +func TestIntegrationAccessibilityInfo(t *testing.T) { + if os.Getenv("SANDERLING_IOS_INTEGRATION") == "" { + t.Skip("set SANDERLING_IOS_INTEGRATION=1 to run the integration smoke test") + } + + udid := bootedSimulatorUDID(t) + port := freePort(t) + + companionBinary := "/opt/homebrew/bin/idb_companion" + command := exec.Command(companionBinary, "--udid", udid, "--grpc-port", strconv.Itoa(port)) + if err := command.Start(); err != nil { + t.Fatalf("start companion: %v", err) + } + defer func() { + _ = command.Process.Kill() + _, _ = command.Process.Wait() + }() + + address := net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) + waitForListener(t, address) + + companion, err := Dial(address) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer companion.Close() + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + info, err := companion.AccessibilityInfo(ctx) + if err != nil { + t.Fatalf("AccessibilityInfo: %v", err) + } + if strings.TrimSpace(info) == "" { + t.Fatal("AccessibilityInfo returned empty json") + } +} + +func bootedSimulatorUDID(t *testing.T) string { + t.Helper() + output, err := exec.Command("xcrun", "simctl", "list", "devices", "booted", "--json").Output() + if err != nil { + t.Fatalf("simctl list: %v", err) + } + var parsed struct { + Devices map[string][]struct { + UDID string `json:"udid"` + State string `json:"state"` + } `json:"devices"` + } + if err := json.Unmarshal(output, &parsed); err != nil { + t.Fatalf("parse simctl json: %v", err) + } + for _, devices := range parsed.Devices { + for _, device := range devices { + if device.State == "Booted" { + return device.UDID + } + } + } + t.Skip("no booted simulator available") + return "" +} + +func freePort(t *testing.T) int { + t.Helper() + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("reserve port: %v", err) + } + port := listener.Addr().(*net.TCPAddr).Port + _ = listener.Close() + return port +} + +func waitForListener(t *testing.T, address string) { + t.Helper() + deadline := time.Now().Add(20 * time.Second) + for time.Now().Before(deadline) { + conn, err := net.DialTimeout("tcp", address, time.Second) + if err == nil { + _ = conn.Close() + return + } + time.Sleep(200 * time.Millisecond) + } + t.Fatalf("companion did not listen on %s", address) +} diff --git a/internal/driver/ioscompanion/transport/runner.go b/internal/driver/ioscompanion/transport/runner.go new file mode 100644 index 0000000..af12f81 --- /dev/null +++ b/internal/driver/ioscompanion/transport/runner.go @@ -0,0 +1,342 @@ +package transport + +import ( + "bufio" + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net" + "os" + "os/exec" + "sync" + "time" +) + +// deadlineImmediate is a deadline already in the past, used to interrupt a +// blocked read or write when the context is cancelled. +var deadlineImmediate = time.Unix(1, 0) + +// runnerCompanion speaks newline-delimited JSON over a single persistent TCP +// connection to the in-simulator runner. One request is in flight at a time: +// the mutex serializes call/response pairs over the shared connection. +type runnerCompanion struct { + uniqueDeviceIdentifier string + bundleID string + address string + + mutex sync.Mutex + conn net.Conn + reader *bufio.Reader + nextID int + + // dirty marks the connection desynced: a call was interrupted before its + // response was read, so the next call reconnects to the still-running + // server instead of misreading the stale response. + dirty bool +} + +// DialRunner opens one persistent TCP connection to the simulator runner at +// address and returns a Companion that also implements TextEditor. The +// uniqueDeviceIdentifier targets simctl shell-outs; bundleID is the app the +// snapshot and app-state queries default to. +func DialRunner(address, uniqueDeviceIdentifier, bundleID string) (Companion, error) { + conn, err := net.Dial("tcp", address) + if err != nil { + return nil, fmt.Errorf("runner transport: %w: dial %s: %v", ErrCompanionUnavailable, address, err) + } + return &runnerCompanion{ + uniqueDeviceIdentifier: uniqueDeviceIdentifier, + bundleID: bundleID, + address: address, + conn: conn, + reader: bufio.NewReader(conn), + }, nil +} + +func (c *runnerCompanion) Close() error { return c.conn.Close() } + +// runnerRequest and runnerResponse are the wire envelopes. A response carries +// either result or error, never both. +type runnerRequest struct { + ID int `json:"id"` + Method string `json:"method"` + Params map[string]any `json:"params"` +} + +type runnerResponse struct { + ID int `json:"id"` + Result json.RawMessage `json:"result"` + Error string `json:"error"` +} + +// call sends one request and returns its result payload. Transport-level +// failures wrap ErrCompanionUnavailable; a server-reported error does not. +func (c *runnerCompanion) call(ctx context.Context, method string, params map[string]any) (json.RawMessage, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.dirty { + if err := c.reconnect(); err != nil { + return nil, fmt.Errorf("runner transport: %w: reconnect: %v", ErrCompanionUnavailable, err) + } + } + + if params == nil { + params = map[string]any{} + } + c.nextID++ + id := c.nextID + + // A blocked read or write cannot observe context cancellation directly, so + // AfterFunc trips an immediate deadline to interrupt it. The deadline is + // cleared once the call returns so the next call starts fresh. + stop := context.AfterFunc(ctx, func() { + c.conn.SetDeadline(deadlineImmediate) + }) + // Clearing the deadline must happen after stop() so a late-firing AfterFunc + // cannot re-arm the deadline once the call has completed. Defers run LIFO. + defer c.conn.SetDeadline(time.Time{}) + defer stop() + if deadline, ok := ctx.Deadline(); ok { + c.conn.SetDeadline(deadline) + } + + payload, err := json.Marshal(runnerRequest{ID: id, Method: method, Params: params}) + if err != nil { + return nil, fmt.Errorf("runner transport: %w: marshal %s request: %v", ErrCompanionUnavailable, method, err) + } + payload = append(payload, '\n') + if _, err := c.conn.Write(payload); err != nil { + return nil, c.wrapTransport(ctx, "write", method, err) + } + + line, err := c.reader.ReadBytes('\n') + if err != nil { + return nil, c.wrapTransport(ctx, "read", method, err) + } + + var response runnerResponse + if err := json.Unmarshal(line, &response); err != nil { + return nil, fmt.Errorf("runner transport: %w: decode %s response: %v", ErrCompanionUnavailable, method, err) + } + if response.ID != id { + return nil, fmt.Errorf("runner transport: %w: response id %d does not match request id %d", ErrCompanionUnavailable, response.ID, id) + } + if response.Error != "" { + return nil, fmt.Errorf("runner %s: %s", method, response.Error) + } + return response.Result, nil +} + +// wrapTransport classifies a read/write failure. A caller-imposed cancel or +// deadline is the caller's slowness budget, not a connection loss, so it does +// not carry the unavailable sentinel: a child restart would not make the call +// faster. The connection's own deadline is only ever set from the caller's +// context, so a deadline-exceeded network error is the same budget expiry +// even when it beats the context's done flag by a hair. Either way the +// connection is desynced and reconnects on the next call. +func (c *runnerCompanion) wrapTransport(ctx context.Context, stage, method string, err error) error { + c.dirty = true + if ctxErr := ctx.Err(); ctxErr != nil { + return fmt.Errorf("runner %s interrupted (%s): %w", method, stage, ctxErr) + } + if errors.Is(err, os.ErrDeadlineExceeded) { + return fmt.Errorf("runner %s interrupted (%s): %w", method, stage, err) + } + return fmt.Errorf("runner transport: %w: %s %s: %v", ErrCompanionUnavailable, stage, method, err) +} + +// reconnect replaces the desynced connection with a fresh one to the same +// still-running server. +func (c *runnerCompanion) reconnect() error { + _ = c.conn.Close() + conn, err := net.Dial("tcp", c.address) + if err != nil { + return err + } + c.conn = conn + c.reader = bufio.NewReader(conn) + c.dirty = false + return nil +} + +func (c *runnerCompanion) AccessibilityInfo(ctx context.Context) (string, error) { + result, err := c.call(ctx, "snapshot", map[string]any{"bundleId": c.bundleID}) + if err != nil { + return "", err + } + var payload struct { + Elements []json.RawMessage `json:"elements"` + } + if err := json.Unmarshal(result, &payload); err != nil { + return "", fmt.Errorf("runner transport: %w: decode snapshot elements: %v", ErrCompanionUnavailable, err) + } + elements, err := json.Marshal(payload.Elements) + if err != nil { + return "", fmt.Errorf("runner transport: %w: re-marshal snapshot elements: %v", ErrCompanionUnavailable, err) + } + return string(elements), nil +} + +func (c *runnerCompanion) Describe(ctx context.Context) (ScreenDescription, error) { + result, err := c.call(ctx, "describe", nil) + if err != nil { + return ScreenDescription{}, err + } + var payload struct { + WidthPoints int `json:"widthPoints"` + HeightPoints int `json:"heightPoints"` + Scale float64 `json:"scale"` + } + if err := json.Unmarshal(result, &payload); err != nil { + return ScreenDescription{}, fmt.Errorf("runner transport: %w: decode describe response: %v", ErrCompanionUnavailable, err) + } + return ScreenDescription{ + WidthPoints: payload.WidthPoints, + HeightPoints: payload.HeightPoints, + Scale: payload.Scale, + }, nil +} + +func (c *runnerCompanion) SendHID(ctx context.Context, events ...HIDEvent) error { + encoded := make([]map[string]any, 0, len(events)) + for _, event := range events { + object, err := hidEventToObject(event) + if err != nil { + return err + } + encoded = append(encoded, object) + } + _, err := c.call(ctx, "gesture", map[string]any{"events": encoded}) + return err +} + +// hidEventToObject encodes a neutral HID event as a runner gesture event. The +// runner edits text natively, so keyboard HID events are rejected with an +// ordinary error rather than a connection-level one. +func hidEventToObject(event HIDEvent) (map[string]any, error) { + switch event.Kind { + case HIDKindTouchDown: + return map[string]any{"kind": "touchDown", "x": event.X, "y": event.Y}, nil + case HIDKindTouchUp: + return map[string]any{"kind": "touchUp", "x": event.X, "y": event.Y}, nil + case HIDKindDelay: + return map[string]any{"kind": "delay", "milliseconds": event.Milliseconds}, nil + case HIDKindSwipe: + return map[string]any{ + "kind": "swipe", + "fromX": event.FromX, + "fromY": event.FromY, + "toX": event.ToX, + "toY": event.ToY, + "seconds": event.Seconds, + }, nil + case HIDKindKeyDown, HIDKindKeyUp: + return nil, errors.New("runner companion does not synthesize keyboard HID events; route text through the text editor") + } + return nil, fmt.Errorf("unknown HID event kind %d", event.Kind) +} + +func (c *runnerCompanion) Screenshot(ctx context.Context) ([]byte, string, error) { + result, err := c.call(ctx, "screenshot", nil) + if err != nil { + return nil, "", err + } + var payload struct { + PNGBase64 string `json:"pngBase64"` + } + if err := json.Unmarshal(result, &payload); err != nil { + return nil, "", fmt.Errorf("runner transport: %w: decode screenshot response: %v", ErrCompanionUnavailable, err) + } + data, err := base64.StdEncoding.DecodeString(payload.PNGBase64) + if err != nil { + return nil, "", fmt.Errorf("runner transport: %w: decode screenshot png: %v", ErrCompanionUnavailable, err) + } + return data, "png", nil +} + +func (c *runnerCompanion) Launch(ctx context.Context, bundleID string, foregroundIfRunning bool) error { + _, err := c.call(ctx, "launch", map[string]any{ + "bundleId": bundleID, + "foregroundIfRunning": foregroundIfRunning, + }) + return err +} + +func (c *runnerCompanion) Terminate(ctx context.Context, bundleID string) error { + _, err := c.call(ctx, "terminate", map[string]any{"bundleId": bundleID}) + return err +} + +func (c *runnerCompanion) ListApps(ctx context.Context) ([]InstalledApp, error) { + result, err := c.call(ctx, "appState", map[string]any{"bundleId": c.bundleID}) + if err != nil { + return nil, err + } + var payload struct { + State string `json:"state"` + } + if err := json.Unmarshal(result, &payload); err != nil { + return nil, fmt.Errorf("runner transport: %w: decode appState response: %v", ErrCompanionUnavailable, err) + } + return []InstalledApp{{ + BundleID: c.bundleID, + InstallType: "user", + ProcessState: processStateFromAppState(payload.State), + }}, nil +} + +func processStateFromAppState(state string) ProcessState { + switch state { + case "foreground", "background": + return ProcessStateRunning + case "notRunning": + return ProcessStateNotRunning + default: + return ProcessStateUnknown + } +} + +// Install and Uninstall shell out to simctl. The driver does not call these +// today; they complete the Companion interface. +func (c *runnerCompanion) Install(ctx context.Context, appPath string) error { + command := exec.CommandContext(ctx, "xcrun", "simctl", "install", c.uniqueDeviceIdentifier, appPath) + if output, err := command.CombinedOutput(); err != nil { + return fmt.Errorf("simctl install: %v: %s", err, output) + } + return nil +} + +func (c *runnerCompanion) Uninstall(ctx context.Context, bundleID string) error { + command := exec.CommandContext(ctx, "xcrun", "simctl", "uninstall", c.uniqueDeviceIdentifier, bundleID) + if output, err := command.CombinedOutput(); err != nil { + return fmt.Errorf("simctl uninstall: %v: %s", err, output) + } + return nil +} + +func (c *runnerCompanion) InputText(ctx context.Context, text string) error { + return c.TypeText(ctx, text, true) +} + +func (c *runnerCompanion) TypeText(ctx context.Context, text string, replace bool) error { + _, err := c.call(ctx, "typeText", map[string]any{"text": text, "replace": replace}) + return err +} + +func (c *runnerCompanion) EraseText(ctx context.Context, characterCount int) error { + _, err := c.call(ctx, "eraseText", map[string]any{"count": characterCount}) + return err +} + +func (c *runnerCompanion) PressKey(ctx context.Context, key string) error { + switch key { + case "enter", "return", "Enter", "Return": + _, err := c.call(ctx, "pressKey", map[string]any{"key": "return"}) + return err + default: + return fmt.Errorf("runner companion cannot press key %q; only return is supported", key) + } +} diff --git a/internal/driver/ioscompanion/transport/runner_test.go b/internal/driver/ioscompanion/transport/runner_test.go new file mode 100644 index 0000000..f53ef04 --- /dev/null +++ b/internal/driver/ioscompanion/transport/runner_test.go @@ -0,0 +1,556 @@ +package transport + +import ( + "bufio" + "context" + "encoding/base64" + "encoding/json" + "errors" + "net" + "reflect" + "strconv" + "sync" + "testing" + "time" +) + +var ( + _ Companion = (*runnerCompanion)(nil) + _ TextEditor = (*runnerCompanion)(nil) +) + +// scriptedReply maps a method name to the raw JSON object the fake server writes +// back as the "result" field. A method absent from the script gets an empty +// object result. +type scriptedReply map[string]string + +// fakeServer is an in-process runner stand-in. It accepts a single connection, +// records every decoded request, and answers from a scripted table. +type fakeServer struct { + listener net.Listener + address string + + mutex sync.Mutex + requests []runnerRequest +} + +func startFakeServer(t *testing.T, script scriptedReply) *fakeServer { + t.Helper() + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + server := &fakeServer{listener: listener, address: listener.Addr().String()} + go server.serve(script) + t.Cleanup(func() { listener.Close() }) + return server +} + +func (s *fakeServer) serve(script scriptedReply) { + conn, err := s.listener.Accept() + if err != nil { + return + } + defer conn.Close() + reader := bufio.NewReader(conn) + for { + line, err := reader.ReadBytes('\n') + if err != nil { + return + } + var request runnerRequest + if err := json.Unmarshal(line, &request); err != nil { + return + } + s.mutex.Lock() + s.requests = append(s.requests, request) + s.mutex.Unlock() + + result := script[request.Method] + if result == "" { + result = "{}" + } + response := `{"id":` + strconv.Itoa(request.ID) + `,"result":` + result + "}\n" + if _, err := conn.Write([]byte(response)); err != nil { + return + } + } +} + +func (s *fakeServer) recorded() []runnerRequest { + s.mutex.Lock() + defer s.mutex.Unlock() + out := make([]runnerRequest, len(s.requests)) + copy(out, s.requests) + return out +} + +func dialFake(t *testing.T, server *fakeServer, bundleID string) Companion { + t.Helper() + companion, err := DialRunner(server.address, "UDID-1234", bundleID) + if err != nil { + t.Fatalf("DialRunner: %v", err) + } + t.Cleanup(func() { companion.Close() }) + return companion +} + +func TestSnapshotReMarshalsElements(t *testing.T) { + server := startFakeServer(t, scriptedReply{ + "snapshot": `{"elements":[{"role":"button"},{"role":"text"}]}`, + }) + companion := dialFake(t, server, "com.example.app") + + got, err := companion.AccessibilityInfo(context.Background()) + if err != nil { + t.Fatalf("AccessibilityInfo: %v", err) + } + want := `[{"role":"button"},{"role":"text"}]` + if got != want { + t.Fatalf("elements = %s, want %s", got, want) + } + + requests := server.recorded() + if len(requests) != 1 { + t.Fatalf("recorded %d requests, want 1", len(requests)) + } + if requests[0].Method != "snapshot" { + t.Fatalf("method = %s, want snapshot", requests[0].Method) + } + if requests[0].Params["bundleId"] != "com.example.app" { + t.Fatalf("bundleId = %v, want com.example.app", requests[0].Params["bundleId"]) + } +} + +func TestGestureEncodesDoubleTapStream(t *testing.T) { + server := startFakeServer(t, nil) + companion := dialFake(t, server, "com.example.app") + + err := companion.SendHID(context.Background(), + TouchDown(10, 20), + TouchUp(10, 20), + Delay(60), + TouchDown(10, 20), + TouchUp(10, 20), + ) + if err != nil { + t.Fatalf("SendHID: %v", err) + } + + requests := server.recorded() + if len(requests) != 1 || requests[0].Method != "gesture" { + t.Fatalf("requests = %+v, want one gesture", requests) + } + events, ok := requests[0].Params["events"].([]any) + if !ok { + t.Fatalf("events not an array: %T", requests[0].Params["events"]) + } + want := []map[string]any{ + {"kind": "touchDown", "x": 10.0, "y": 20.0}, + {"kind": "touchUp", "x": 10.0, "y": 20.0}, + {"kind": "delay", "milliseconds": 60.0}, + {"kind": "touchDown", "x": 10.0, "y": 20.0}, + {"kind": "touchUp", "x": 10.0, "y": 20.0}, + } + if len(events) != len(want) { + t.Fatalf("got %d events, want %d", len(events), len(want)) + } + for index, event := range events { + if !reflect.DeepEqual(event, map[string]any(want[index])) { + t.Fatalf("event %d = %v, want %v", index, event, want[index]) + } + } +} + +func TestSendHIDRejectsKeyEvents(t *testing.T) { + server := startFakeServer(t, nil) + companion := dialFake(t, server, "com.example.app") + + err := companion.SendHID(context.Background(), KeyDown(4), KeyUp(4)) + if err == nil { + t.Fatal("expected error for key HID events") + } + if errors.Is(err, ErrCompanionUnavailable) { + t.Fatalf("key rejection should not be a transport error: %v", err) + } + if requests := server.recorded(); len(requests) != 0 { + t.Fatalf("expected nothing sent, got %+v", requests) + } +} + +func TestScreenshotBase64RoundTrip(t *testing.T) { + original := []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a} + encoded := base64.StdEncoding.EncodeToString(original) + server := startFakeServer(t, scriptedReply{ + "screenshot": `{"pngBase64":"` + encoded + `"}`, + }) + companion := dialFake(t, server, "com.example.app") + + data, format, err := companion.Screenshot(context.Background()) + if err != nil { + t.Fatalf("Screenshot: %v", err) + } + if format != "png" { + t.Fatalf("format = %s, want png", format) + } + if !reflect.DeepEqual(data, original) { + t.Fatalf("data = %v, want %v", data, original) + } +} + +func TestDescribeMapsScreenDescription(t *testing.T) { + server := startFakeServer(t, scriptedReply{ + "describe": `{"widthPoints":390,"heightPoints":844,"scale":3.0}`, + }) + companion := dialFake(t, server, "com.example.app") + + description, err := companion.Describe(context.Background()) + if err != nil { + t.Fatalf("Describe: %v", err) + } + want := ScreenDescription{WidthPoints: 390, HeightPoints: 844, Scale: 3.0} + if description != want { + t.Fatalf("description = %+v, want %+v", description, want) + } +} + +func TestTextEditorMethods(t *testing.T) { + server := startFakeServer(t, nil) + companion := dialFake(t, server, "com.example.app") + editor := companion.(TextEditor) + + if err := editor.InputText(context.Background(), "hello"); err != nil { + t.Fatalf("InputText: %v", err) + } + if err := editor.EraseText(context.Background(), 3); err != nil { + t.Fatalf("EraseText: %v", err) + } + if err := editor.PressKey(context.Background(), "Enter"); err != nil { + t.Fatalf("PressKey: %v", err) + } + + requests := server.recorded() + if len(requests) != 3 { + t.Fatalf("recorded %d requests, want 3", len(requests)) + } + if requests[0].Method != "typeText" || requests[0].Params["text"] != "hello" || requests[0].Params["replace"] != true { + t.Fatalf("typeText request = %+v", requests[0]) + } + if requests[1].Method != "eraseText" || requests[1].Params["count"] != 3.0 { + t.Fatalf("eraseText request = %+v", requests[1]) + } + if requests[2].Method != "pressKey" || requests[2].Params["key"] != "return" { + t.Fatalf("pressKey request = %+v", requests[2]) + } +} + +func TestPressKeyRejectsUnknownKey(t *testing.T) { + server := startFakeServer(t, nil) + companion := dialFake(t, server, "com.example.app") + editor := companion.(TextEditor) + + err := editor.PressKey(context.Background(), "home") + if err == nil { + t.Fatal("expected error for unsupported key") + } + if errors.Is(err, ErrCompanionUnavailable) { + t.Fatalf("unsupported key should not be a transport error: %v", err) + } + if requests := server.recorded(); len(requests) != 0 { + t.Fatalf("expected nothing sent, got %+v", requests) + } +} + +func TestLaunchTerminateMapping(t *testing.T) { + server := startFakeServer(t, nil) + companion := dialFake(t, server, "com.example.app") + + if err := companion.Launch(context.Background(), "com.example.target", true); err != nil { + t.Fatalf("Launch: %v", err) + } + if err := companion.Terminate(context.Background(), "com.example.target"); err != nil { + t.Fatalf("Terminate: %v", err) + } + + requests := server.recorded() + if len(requests) != 2 { + t.Fatalf("recorded %d requests, want 2", len(requests)) + } + if requests[0].Method != "launch" || + requests[0].Params["bundleId"] != "com.example.target" || + requests[0].Params["foregroundIfRunning"] != true { + t.Fatalf("launch request = %+v", requests[0]) + } + if requests[1].Method != "terminate" || requests[1].Params["bundleId"] != "com.example.target" { + t.Fatalf("terminate request = %+v", requests[1]) + } +} + +func TestListAppsStateMapping(t *testing.T) { + cases := []struct { + state string + want ProcessState + }{ + {"foreground", ProcessStateRunning}, + {"background", ProcessStateRunning}, + {"notRunning", ProcessStateNotRunning}, + {"unknown", ProcessStateUnknown}, + } + for _, testCase := range cases { + server := startFakeServer(t, scriptedReply{ + "appState": `{"state":"` + testCase.state + `"}`, + }) + companion := dialFake(t, server, "com.example.app") + + apps, err := companion.ListApps(context.Background()) + if err != nil { + t.Fatalf("ListApps(%s): %v", testCase.state, err) + } + if len(apps) != 1 { + t.Fatalf("ListApps(%s) returned %d apps, want 1", testCase.state, len(apps)) + } + app := apps[0] + if app.BundleID != "com.example.app" || app.InstallType != "user" { + t.Fatalf("app fields = %+v", app) + } + if app.ProcessState != testCase.want { + t.Fatalf("state %s -> %v, want %v", testCase.state, app.ProcessState, testCase.want) + } + + requests := server.recorded() + if len(requests) != 1 || requests[0].Method != "appState" || requests[0].Params["bundleId"] != "com.example.app" { + t.Fatalf("appState request = %+v", requests) + } + } +} + +func TestServerErrorIsNotSentinel(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + t.Cleanup(func() { listener.Close() }) + go func() { + conn, err := listener.Accept() + if err != nil { + return + } + defer conn.Close() + reader := bufio.NewReader(conn) + line, err := reader.ReadBytes('\n') + if err != nil { + return + } + var request runnerRequest + json.Unmarshal(line, &request) + conn.Write([]byte(`{"id":` + strconv.Itoa(request.ID) + `,"error":"field not focused"}` + "\n")) + }() + + companion, err := DialRunner(listener.Addr().String(), "UDID", "com.example.app") + if err != nil { + t.Fatalf("DialRunner: %v", err) + } + t.Cleanup(func() { companion.Close() }) + + _, err = companion.Describe(context.Background()) + if err == nil { + t.Fatal("expected server error") + } + if errors.Is(err, ErrCompanionUnavailable) { + t.Fatalf("server error must not wrap the sentinel: %v", err) + } +} + +func TestServerClosesMidCallIsSentinel(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + t.Cleanup(func() { listener.Close() }) + go func() { + conn, err := listener.Accept() + if err != nil { + return + } + // Read the request, then drop the connection without replying. + bufio.NewReader(conn).ReadBytes('\n') + conn.Close() + }() + + companion, err := DialRunner(listener.Addr().String(), "UDID", "com.example.app") + if err != nil { + t.Fatalf("DialRunner: %v", err) + } + t.Cleanup(func() { companion.Close() }) + + _, err = companion.Describe(context.Background()) + if err == nil { + t.Fatal("expected error when server closes mid-call") + } + if !errors.Is(err, ErrCompanionUnavailable) { + t.Fatalf("dropped connection must wrap the sentinel: %v", err) + } +} + +func TestContextCancellationUnblocksCall(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + t.Cleanup(func() { listener.Close() }) + go func() { + conn, err := listener.Accept() + if err != nil { + return + } + // Accept and hold the connection open, never replying. + bufio.NewReader(conn).ReadBytes('\n') + <-make(chan struct{}) + }() + + companion, err := DialRunner(listener.Addr().String(), "UDID", "com.example.app") + if err != nil { + t.Fatalf("DialRunner: %v", err) + } + t.Cleanup(func() { companion.Close() }) + + ctx, cancel := context.WithCancel(context.Background()) + time.AfterFunc(50*time.Millisecond, cancel) + + done := make(chan error, 1) + go func() { + _, callErr := companion.Describe(ctx) + done <- callErr + }() + + select { + case callErr := <-done: + if callErr == nil { + t.Fatal("expected cancellation error") + } + // A caller-imposed cancel is the caller's budget, not a connection + // loss: it must NOT wrap the sentinel, or a slow call would trigger a + // pointless child restart. + if !errors.Is(callErr, context.Canceled) { + t.Fatalf("cancellation error must carry the context error: %v", callErr) + } + if errors.Is(callErr, ErrCompanionUnavailable) { + t.Fatalf("cancellation error must not wrap the sentinel: %v", callErr) + } + case <-time.After(2 * time.Second): + t.Fatal("cancelled call did not return within 2s") + } +} + +func TestInterruptedCallReconnectsOnNextCall(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + t.Cleanup(func() { listener.Close() }) + + // The server holds "describe" hostage and answers everything else, on + // every connection it accepts. A late reply to the interrupted request + // must never be misread by the following call. + accepted := make(chan net.Conn, 4) + go func() { + for { + conn, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + accepted <- conn + go func(c net.Conn) { + reader := bufio.NewReader(c) + for { + line, readErr := reader.ReadBytes('\n') + if readErr != nil { + return + } + var request runnerRequest + if json.Unmarshal(line, &request) != nil { + return + } + if request.Method == "describe" { + continue + } + response := `{"id":` + strconv.Itoa(request.ID) + `,"result":{"ok":true}}` + "\n" + if _, writeErr := c.Write([]byte(response)); writeErr != nil { + return + } + } + }(conn) + } + }() + + companion, err := DialRunner(listener.Addr().String(), "UDID", "com.example.app") + if err != nil { + t.Fatalf("DialRunner: %v", err) + } + t.Cleanup(func() { companion.Close() }) + + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + if _, err := companion.Describe(ctx); err == nil { + t.Fatal("expected the held call to time out") + } + + // The next call must transparently reconnect and succeed. + if err := companion.Terminate(context.Background(), "com.example.app"); err != nil { + t.Fatalf("call after interrupt: %v", err) + } + if len(accepted) != 2 { + t.Fatalf("accepted %d connections, want 2 (reconnect)", len(accepted)) + } +} + +func TestResponseIDMismatchIsSentinel(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + t.Cleanup(func() { listener.Close() }) + go func() { + conn, err := listener.Accept() + if err != nil { + return + } + defer conn.Close() + bufio.NewReader(conn).ReadBytes('\n') + // Reply with an id that cannot match any request. + conn.Write([]byte(`{"id":9999,"result":{}}` + "\n")) + }() + + companion, err := DialRunner(listener.Addr().String(), "UDID", "com.example.app") + if err != nil { + t.Fatalf("DialRunner: %v", err) + } + t.Cleanup(func() { companion.Close() }) + + _, err = companion.Describe(context.Background()) + if err == nil { + t.Fatal("expected id-mismatch error") + } + if !errors.Is(err, ErrCompanionUnavailable) { + t.Fatalf("id mismatch must wrap the sentinel: %v", err) + } +} + +func TestTypeTextAppendsWithoutReplace(t *testing.T) { + server := startFakeServer(t, nil) + companion := dialFake(t, server, "com.example.app") + typer := companion.(TextTyper) + + if err := typer.TypeText(context.Background(), "héllo 🌟", false); err != nil { + t.Fatalf("TypeText: %v", err) + } + + requests := server.recorded() + if len(requests) != 1 { + t.Fatalf("recorded %d requests, want 1", len(requests)) + } + request := requests[0] + if request.Method != "typeText" || request.Params["text"] != "héllo 🌟" || request.Params["replace"] != false { + t.Fatalf("typeText request = %+v", request) + } +} diff --git a/internal/driver/ioscompanion/transport/transport.go b/internal/driver/ioscompanion/transport/transport.go new file mode 100644 index 0000000..7412ebc --- /dev/null +++ b/internal/driver/ioscompanion/transport/transport.go @@ -0,0 +1,116 @@ +// Package transport is the only layer that touches the generated companion +// gRPC stubs. Everything above it speaks the brand-free Companion interface, +// so the companion binary stays swappable behind this boundary. +package transport + +import ( + "context" + "errors" + + pb "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/companionpb" +) + +// Companion drives a single booted iOS simulator through the companion. +// Callers own per-call deadlines by passing a context. +type Companion interface { + // AccessibilityInfo returns the describe-all accessibility tree as the + // raw flat-format JSON string the companion emits. + AccessibilityInfo(ctx context.Context) (string, error) + + // Describe reports the target's screen dimensions in points, along with + // the pixel scale when the companion supplies one. + Describe(ctx context.Context) (ScreenDescription, error) + + // SendHID opens the HID stream, sends every event in order, then closes. + SendHID(ctx context.Context, events ...HIDEvent) error + + // Screenshot captures the current screen, returning the encoded image + // bytes and the image format string the companion reports. + Screenshot(ctx context.Context) (imageData []byte, imageFormat string, err error) + + // Launch brings the app to the foreground, starting it if needed. + Launch(ctx context.Context, bundleID string, foregroundIfRunning bool) error + + // Terminate stops the running app with the given bundle identifier. + Terminate(ctx context.Context, bundleID string) error + + // ListApps reports every installed app and its current process state. + ListApps(ctx context.Context) ([]InstalledApp, error) + + // Install installs a .app bundle directory by streaming it to the + // companion. + Install(ctx context.Context, appPath string) error + + // Uninstall removes the app with the given bundle identifier. + Uninstall(ctx context.Context, bundleID string) error + + // Close releases the underlying connection. + Close() error +} + +// TextEditor is an optional companion capability: the transport edits text +// natively on the device instead of the driver composing keyboard HID streams. +// The driver routes text input through it when the companion implements it. +type TextEditor interface { + // InputText replaces the focused field's content with text. + InputText(ctx context.Context, text string) error + + // EraseText deletes characterCount characters from the focused field. + EraseText(ctx context.Context, characterCount int) error + + // PressKey presses the named logical key (currently only return/enter). + PressKey(ctx context.Context, key string) error +} + +// TextTyper is an optional companion capability: the transport types text +// natively into whatever holds keyboard focus. Unlike TextEditor it exposes +// the replace flag, so a caller can clear the field through another channel +// and append with replace false. +type TextTyper interface { + TypeText(ctx context.Context, text string, replace bool) error +} + +// ErrCompanionUnavailable marks a connection-level failure that a companion +// restart can recover from. Transports wrap dropped-connection errors with it. +var ErrCompanionUnavailable = errors.New("companion connection unavailable") + +// ScreenDescription carries the target screen geometry. Width and Height are +// in points (the coordinate space HID events and the accessibility frames use). +// Scale is the pixel-per-point density, or 0 when the companion did not report +// one. +type ScreenDescription struct { + WidthPoints int + HeightPoints int + Scale float64 +} + +// ProcessState mirrors the companion's notion of whether an app is running. +type ProcessState int + +const ( + ProcessStateUnknown ProcessState = iota + ProcessStateNotRunning + ProcessStateRunning +) + +// InstalledApp describes one installed app. ProcessState and ProcessIdentifier +// together answer whether the app is currently running and in the foreground. +type InstalledApp struct { + BundleID string + Name string + InstallType string + ProcessState ProcessState + Debuggable bool + ProcessIdentifier uint64 +} + +func processStateFromProto(s pb.InstalledAppInfo_AppProcessState) ProcessState { + switch s { + case pb.InstalledAppInfo_RUNNING: + return ProcessStateRunning + case pb.InstalledAppInfo_NOT_RUNNING: + return ProcessStateNotRunning + default: + return ProcessStateUnknown + } +} diff --git a/internal/ios/ios.go b/internal/ios/ios.go index b1af83c..e24f467 100644 --- a/internal/ios/ios.go +++ b/internal/ios/ios.go @@ -26,6 +26,7 @@ type simctlDeviceList struct { // with canned device lists without invoking xcrun. var ( listBooted = bootedSimulator + listBootedAll = bootedSimulators listAvailable = availableSimulators boot = bootSimulator waitForBoot = waitForSimulatorBoot @@ -73,6 +74,59 @@ func BootedUDID(ctx context.Context) string { return d.UDID } +// ResolveTarget decides which iOS target a run drives and whether it is a +// simulator. An explicit query matching a simulator name or UDID (booted or +// available) resolves to that simulator. With no query, exactly one booted +// simulator resolves to it; multiple booted simulators is an error that lists +// them and asks the caller to pass --ios-device. A query that matches no +// simulator resolves as a physical device (udid = query, simulator false) so +// the sidecar path drives it. +func ResolveTarget(ctx context.Context, query string) (udid string, isSimulator bool, err error) { + if query != "" { + booted, err := listBootedAll(ctx) + if err != nil { + return "", false, fmt.Errorf("list booted simulators: %w", err) + } + if match := matchSimulator(query, booted); match != nil { + return match.UDID, true, nil + } + available, err := listAvailable(ctx) + if err != nil { + return "", false, fmt.Errorf("list available simulators: %w", err) + } + if match := matchSimulator(query, available); match != nil { + return match.UDID, true, nil + } + return query, false, nil + } + + booted, err := listBootedAll(ctx) + if err != nil { + return "", false, fmt.Errorf("list booted simulators: %w", err) + } + switch len(booted) { + case 1: + return booted[0].UDID, true, nil + case 0: + return "", false, fmt.Errorf("no booted iOS simulator found") + default: + var lines strings.Builder + for _, device := range booted { + fmt.Fprintf(&lines, "\n %s (%s)", device.Name, device.UDID) + } + return "", false, fmt.Errorf("multiple booted iOS simulators; pass --ios-device to select one:%s", lines.String()) + } +} + +func matchSimulator(query string, devices []simDevice) *simDevice { + for i := range devices { + if devices[i].Name == query || devices[i].UDID == query { + return &devices[i] + } + } + return nil +} + func bootedSimulator(ctx context.Context) (*simDevice, error) { out, err := exec.CommandContext(ctx, "xcrun", "simctl", "list", "devices", "booted", "--json").Output() if err != nil { @@ -81,6 +135,14 @@ func bootedSimulator(ctx context.Context) (*simDevice, error) { return parseBootedDevice(out) } +func bootedSimulators(ctx context.Context) ([]simDevice, error) { + out, err := exec.CommandContext(ctx, "xcrun", "simctl", "list", "devices", "booted", "--json").Output() + if err != nil { + return nil, err + } + return parseBootedDevices(out) +} + func availableSimulators(ctx context.Context) ([]simDevice, error) { out, err := exec.CommandContext(ctx, "xcrun", "simctl", "list", "devices", "available", "--json").Output() if err != nil { @@ -104,6 +166,22 @@ func parseBootedDevice(out []byte) (*simDevice, error) { return nil, nil } +func parseBootedDevices(out []byte) ([]simDevice, error) { + var list simctlDeviceList + if err := json.Unmarshal(out, &list); err != nil { + return nil, err + } + var result []simDevice + for _, devices := range list.Devices { + for i := range devices { + if devices[i].State == "Booted" { + result = append(result, devices[i]) + } + } + } + return result, nil +} + func parseAvailableDevices(out []byte) ([]simDevice, error) { var list simctlDeviceList if err := json.Unmarshal(out, &list); err != nil { diff --git a/internal/ios/ios_test.go b/internal/ios/ios_test.go index d4b1f5b..c75c96d 100644 --- a/internal/ios/ios_test.go +++ b/internal/ios/ios_test.go @@ -208,6 +208,125 @@ func TestEnsureSimulator_BootsPickedSimulator(t *testing.T) { } } +func swapResolveSeams(t *testing.T) { + t.Helper() + origBootedAll, origAvailable := listBootedAll, listAvailable + t.Cleanup(func() { listBootedAll, listAvailable = origBootedAll, origAvailable }) +} + +func TestResolveTarget_ExplicitMatchesBootedSimulator(t *testing.T) { + swapResolveSeams(t) + listBootedAll = func(context.Context) ([]simDevice, error) { + return []simDevice{{UDID: "booted-udid", Name: "iPhone 15", State: "Booted", IsAvailable: true}}, nil + } + listAvailable = func(context.Context) ([]simDevice, error) { + t.Fatal("should not list available when a booted simulator matches") + return nil, nil + } + udid, isSimulator, err := ResolveTarget(context.Background(), "iPhone 15") + if err != nil { + t.Fatal(err) + } + if udid != "booted-udid" || !isSimulator { + t.Errorf("got (%q, %v), want (booted-udid, true)", udid, isSimulator) + } +} + +func TestResolveTarget_ExplicitMatchesAvailableByUDID(t *testing.T) { + swapResolveSeams(t) + listBootedAll = func(context.Context) ([]simDevice, error) { return nil, nil } + listAvailable = func(context.Context) ([]simDevice, error) { + return []simDevice{{UDID: "shutdown-udid", Name: "iPhone 16", IsAvailable: true}}, nil + } + udid, isSimulator, err := ResolveTarget(context.Background(), "shutdown-udid") + if err != nil { + t.Fatal(err) + } + if udid != "shutdown-udid" || !isSimulator { + t.Errorf("got (%q, %v), want (shutdown-udid, true)", udid, isSimulator) + } +} + +func TestResolveTarget_UnknownQueryResolvesAsPhysicalDevice(t *testing.T) { + swapResolveSeams(t) + listBootedAll = func(context.Context) ([]simDevice, error) { return nil, nil } + listAvailable = func(context.Context) ([]simDevice, error) { + return []simDevice{{UDID: "sim-udid", Name: "iPhone 16", IsAvailable: true}}, nil + } + udid, isSimulator, err := ResolveTarget(context.Background(), "00008110-physical-device-udid") + if err != nil { + t.Fatal(err) + } + if udid != "00008110-physical-device-udid" || isSimulator { + t.Errorf("got (%q, %v), want (00008110-physical-device-udid, false)", udid, isSimulator) + } +} + +func TestResolveTarget_NoQuerySingleBooted(t *testing.T) { + swapResolveSeams(t) + listBootedAll = func(context.Context) ([]simDevice, error) { + return []simDevice{{UDID: "only-booted", Name: "iPhone 15", State: "Booted", IsAvailable: true}}, nil + } + udid, isSimulator, err := ResolveTarget(context.Background(), "") + if err != nil { + t.Fatal(err) + } + if udid != "only-booted" || !isSimulator { + t.Errorf("got (%q, %v), want (only-booted, true)", udid, isSimulator) + } +} + +func TestResolveTarget_NoQueryZeroBootedErrors(t *testing.T) { + swapResolveSeams(t) + listBootedAll = func(context.Context) ([]simDevice, error) { return nil, nil } + _, _, err := ResolveTarget(context.Background(), "") + if err == nil { + t.Fatal("expected error when no simulator is booted") + } +} + +func TestResolveTarget_NoQueryMultipleBootedErrors(t *testing.T) { + swapResolveSeams(t) + listBootedAll = func(context.Context) ([]simDevice, error) { + return []simDevice{ + {UDID: "udid-a", Name: "iPhone 15", State: "Booted", IsAvailable: true}, + {UDID: "udid-b", Name: "iPad Pro", State: "Booted", IsAvailable: true}, + }, nil + } + _, _, err := ResolveTarget(context.Background(), "") + if err == nil { + t.Fatal("expected ambiguity error for multiple booted simulators") + } + message := err.Error() + for _, want := range []string{"--ios-device", "iPhone 15", "udid-a", "iPad Pro", "udid-b"} { + if !strings.Contains(message, want) { + t.Errorf("ambiguity error missing %q: %v", want, message) + } + } +} + +func TestResolveTarget_BootedListError(t *testing.T) { + swapResolveSeams(t) + listBootedAll = func(context.Context) ([]simDevice, error) { return nil, errors.New("xcrun blew up") } + if _, _, err := ResolveTarget(context.Background(), ""); err == nil { + t.Fatal("expected booted-list error to propagate") + } +} + +func TestParseBootedDevices_CollectsAllBooted(t *testing.T) { + got, err := parseBootedDevices([]byte(`{"devices":{"r":[ + {"udid":"a","state":"Booted","name":"iPhone 15","isAvailable":true}, + {"udid":"b","state":"Shutdown","name":"iPad","isAvailable":true}, + {"udid":"c","state":"Booted","name":"iPhone 16","isAvailable":true} + ]}}`)) + if err != nil { + t.Fatal(err) + } + if len(got) != 2 { + t.Fatalf("got %d booted, want 2", len(got)) + } +} + func TestEnsureSimulator_BootedListError(t *testing.T) { origListBooted := listBooted t.Cleanup(func() { listBooted = origListBooted }) diff --git a/internal/replay/runs_decode.go b/internal/replay/runs_decode.go index 5f5185f..f805e1c 100644 --- a/internal/replay/runs_decode.go +++ b/internal/replay/runs_decode.go @@ -67,9 +67,9 @@ type violationAttribution struct { func decodeStepSummary(line []byte) (StepSummary, []violationAttribution, error) { var partial struct { - Index int `json:"step"` - Timestamp time.Time `json:"timestamp"` - Screen string `json:"screen,omitempty"` + Index int `json:"step"` + Timestamp time.Time `json:"timestamp"` + Screen string `json:"screen,omitempty"` NextAction *struct { Kind string `json:"kind"` X int `json:"x,omitempty"` diff --git a/internal/replay/server.go b/internal/replay/server.go index f06b9b1..7af9b10 100644 --- a/internal/replay/server.go +++ b/internal/replay/server.go @@ -284,4 +284,3 @@ func ResolveRunsDirectory(argument string) (string, string, error) { } return argument, "", nil } - diff --git a/internal/runner/runner.go b/internal/runner/runner.go index ae5d640..e060133 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -935,4 +935,3 @@ const maxConsecutiveApplyFailures = 3 func isWDADrop(err error) bool { return strings.Contains(err.Error(), "WDA reconnect failed") } - diff --git a/internal/testrun/driver.go b/internal/testrun/driver.go index af4c108..5fa8f46 100644 --- a/internal/testrun/driver.go +++ b/internal/testrun/driver.go @@ -14,14 +14,15 @@ import ( "github.com/priyanshujain/sanderling/internal/android" "github.com/priyanshujain/sanderling/internal/driver" "github.com/priyanshujain/sanderling/internal/driver/chrome" + "github.com/priyanshujain/sanderling/internal/driver/ioscompanion" driverSidecar "github.com/priyanshujain/sanderling/internal/driver/sidecar" - "github.com/priyanshujain/sanderling/internal/ios" "github.com/priyanshujain/sanderling/internal/sidecarassets" ) // buildDriver creates the appropriate DeviceDriver for the platform and returns -// a cleanup function. For web, ChromeDriver is used directly; for android/ios -// the JVM sidecar is extracted, spawned, and dialed. +// a cleanup function. For web, ChromeDriver is used directly. An iOS simulator +// is driven by the native simulator companion (no JVM). Android and physical +// iOS devices use the JVM sidecar, which is extracted, spawned, and dialed. func buildDriver(ctx context.Context, options Options, stdout io.Writer) (driver.DeviceDriver, func(), error) { if err := Preflight(ctx, options.Platform); err != nil { return nil, nil, err @@ -31,6 +32,24 @@ func buildDriver(ctx context.Context, options Options, stdout io.Writer) (driver return d, func() { _ = d.Terminate(context.Background()) }, nil } + if options.Platform == "ios" && options.iosIsSimulator { + d, err := ioscompanion.New(ctx, ioscompanion.Options{ + UniqueDeviceIdentifier: options.iosUDID, + BundleID: options.BundleID, + AppPath: options.IosAppPath, + Output: stdout, + }) + if err != nil { + return nil, nil, fmt.Errorf("ios simulator driver: %w", err) + } + return d, d.Close, nil + } + + // Physical iOS devices and Android use the JVM sidecar, which requires java. + if err := preflightDevice(options.Platform); err != nil { + return nil, nil, err + } + sidecarDirectory := os.TempDir() + "/sanderling-sidecar" jarPath, err := sidecarassets.Extract(sidecarDirectory) if err != nil { @@ -47,8 +66,8 @@ func buildDriver(ctx context.Context, options Options, stdout io.Writer) (driver "--platform", options.Platform, } if options.Platform == "ios" { - if udid := ios.BootedUDID(ctx); udid != "" { - sidecarArgs = append(sidecarArgs, "--udid", udid) + if options.iosUDID != "" { + sidecarArgs = append(sidecarArgs, "--udid", options.iosUDID) } } sidecarCommand := exec.CommandContext(ctx, "java", sidecarArgs...) diff --git a/internal/testrun/preflight.go b/internal/testrun/preflight.go index 558f8b4..a65c88b 100644 --- a/internal/testrun/preflight.go +++ b/internal/testrun/preflight.go @@ -41,18 +41,36 @@ func runPreflight(ctx context.Context, platform string, check preflightFunc) err } return nil case "ios": + // Simulator runs drive the native companion and need no JVM. The java + // requirement is deferred to the physical-device path in buildDriver. if err := check("xcrun"); err != nil { return preflightFailure("ios", err) } - if err := check("java"); err != nil { - return preflightFailure("ios", err) - } return nil default: return fmt.Errorf("preflight: unknown platform %q", platform) } } +// preflightDevice runs the extra host checks the JVM sidecar path needs once we +// know a run targets a physical iOS device. Android already requires java in +// the top-level Preflight, so this only matters for ios. +func preflightDevice(platform string) error { + return runPreflightDevice(platform, preflightCheck) +} + +func runPreflightDevice(platform string, check preflightFunc) error { + if platform != "ios" { + return nil + } + if err := check("java"); err != nil { + // The doctor splits simulator and device checks: java lives under the + // ios-device platform, so the hint must point there. + return preflightFailure("ios-device", err) + } + return nil +} + func preflightFailure(platform string, cause error) error { return fmt.Errorf( "preflight: %w\nrun `sanderling doctor --platform=%s` for full host-readiness checks", diff --git a/internal/testrun/preflight_test.go b/internal/testrun/preflight_test.go index d84d033..e460d07 100644 --- a/internal/testrun/preflight_test.go +++ b/internal/testrun/preflight_test.go @@ -49,7 +49,7 @@ func TestPreflight_AndroidNeedsAdbAndJava(t *testing.T) { } } -func TestPreflight_iOSNeedsXcrunAndJava(t *testing.T) { +func TestPreflight_iOSNeedsXcrun(t *testing.T) { check := func(name string) error { if name == "xcrun" { return errors.New("xcrun not found") @@ -65,6 +65,25 @@ func TestPreflight_iOSNeedsXcrunAndJava(t *testing.T) { } } +func TestPreflight_iOSDoesNotRequireJava(t *testing.T) { + checked := []string{} + check := func(name string) error { + checked = append(checked, name) + if name == "java" { + return errors.New("java not found") + } + return nil + } + if err := runPreflight(context.Background(), "ios", check); err != nil { + t.Fatalf("ios preflight should pass without java, got %v", err) + } + for _, name := range checked { + if name == "java" { + t.Errorf("ios preflight must not check java; simulator runs need no JVM") + } + } +} + func TestPreflight_AllOK(t *testing.T) { check := func(name string) error { return nil } for _, platform := range []string{"web", "android", "ios"} { @@ -74,6 +93,30 @@ func TestPreflight_AllOK(t *testing.T) { } } +func TestPreflightDevice_NonIosIsNoop(t *testing.T) { + for _, platform := range []string{"web", "android"} { + if err := preflightDevice(platform); err != nil { + t.Errorf("%s: preflightDevice should be a no-op, got %v", platform, err) + } + } +} + +func TestPreflightDevice_JavaFailurePointsAtIosDeviceDoctor(t *testing.T) { + check := func(name string) error { + if name == "java" { + return errors.New("java not found") + } + return nil + } + err := runPreflightDevice("ios", check) + if err == nil || !strings.Contains(err.Error(), "java") { + t.Fatalf("expected java error, got %v", err) + } + if !strings.Contains(err.Error(), "sanderling doctor --platform=ios-device") { + t.Errorf("hint must name the ios-device doctor platform: %v", err) + } +} + func TestPreflight_UnknownPlatform(t *testing.T) { check := func(string) error { return nil } if err := runPreflight(context.Background(), "fuchsia", check); err == nil { diff --git a/internal/testrun/testrun.go b/internal/testrun/testrun.go index ed22daf..9d48dce 100644 --- a/internal/testrun/testrun.go +++ b/internal/testrun/testrun.go @@ -23,15 +23,21 @@ const sidecarStartupTimeout = 30 * time.Second // Options are the parameters for a single test pipeline run. type Options struct { - Spec string - BundleID string - Platform string - AVD string - IosDevice string - Duration time.Duration - Seed int64 - Output string - ClearData bool + Spec string + BundleID string + Platform string + AVD string + IosDevice string + IosAppPath string + Duration time.Duration + Seed int64 + Output string + ClearData bool + + // iosUDID and iosIsSimulator are filled by Execute after resolving the iOS + // target, then read by buildDriver to choose the companion or sidecar path. + iosUDID string + iosIsSimulator bool } // Execute runs the full test pipeline: bundle, launch app, verify properties. @@ -42,9 +48,30 @@ func Execute(ctx context.Context, options Options, stdout io.Writer) error { return err } case "ios": - if err := ios.EnsureSimulator(ctx, options.IosDevice, stdout); err != nil { - return err + udid, isSimulator, err := ios.ResolveTarget(ctx, options.IosDevice) + if err != nil { + // No query and nothing booted: keep boot-first behavior, then resolve + // the simulator that EnsureSimulator just brought up. + if options.IosDevice == "" { + if bootErr := ios.EnsureSimulator(ctx, "", stdout); bootErr != nil { + return bootErr + } + udid, isSimulator, err = ios.ResolveTarget(ctx, "") + } + if err != nil { + return err + } + } else if isSimulator { + if err := ios.EnsureSimulator(ctx, options.IosDevice, stdout); err != nil { + return err + } + udid, isSimulator, err = ios.ResolveTarget(ctx, options.IosDevice) + if err != nil { + return err + } } + options.iosUDID = udid + options.iosIsSimulator = isSimulator } prep, err := prepareBundleInputs(options) if err != nil { diff --git a/internal/trace/writer_test.go b/internal/trace/writer_test.go index 8b3f417..71ccf32 100644 --- a/internal/trace/writer_test.go +++ b/internal/trace/writer_test.go @@ -22,13 +22,13 @@ func TestWriteMeta_RoundTrip(t *testing.T) { defer writer.Close() meta := Meta{ - Seed: 42, - SpecPath: "spec.ts", - BundleSHA256: "deadbeef", - Platform: "android", - BundleID: "in.okcredit.merchant", - StartedAt: time.Date(2026, 4, 17, 22, 30, 0, 0, time.UTC), - SanderlingVersion: "0.0.1", + Seed: 42, + SpecPath: "spec.ts", + BundleSHA256: "deadbeef", + Platform: "android", + BundleID: "in.okcredit.merchant", + StartedAt: time.Date(2026, 4, 17, 22, 30, 0, 0, time.UTC), + SanderlingVersion: "0.0.1", } if err := writer.WriteMeta(meta); err != nil { t.Fatal(err) @@ -57,12 +57,12 @@ func TestWriteMeta_EndedAtRoundTrip(t *testing.T) { endedAt := time.Date(2026, 4, 17, 22, 31, 0, 0, time.UTC) meta := Meta{ - Seed: 7, - SpecPath: "spec.ts", - Platform: "android", - BundleID: "in.test", - StartedAt: time.Date(2026, 4, 17, 22, 30, 0, 0, time.UTC), - EndedAt: &endedAt, + Seed: 7, + SpecPath: "spec.ts", + Platform: "android", + BundleID: "in.test", + StartedAt: time.Date(2026, 4, 17, 22, 30, 0, 0, time.UTC), + EndedAt: &endedAt, SanderlingVersion: "0.0.1", } if err := writer.WriteMeta(meta); err != nil { diff --git a/internal/verifier/spec_integration_test.go b/internal/verifier/spec_integration_test.go index b5dcf54..8727e30 100644 --- a/internal/verifier/spec_integration_test.go +++ b/internal/verifier/spec_integration_test.go @@ -47,17 +47,17 @@ func bundleIntegrationSpec(t *testing.T) string { func listSnapshots() Snapshots { return Snapshots{ - "route": json.RawMessage(`"list"`), - "item_count": json.RawMessage(`0`), - "has_submitted": json.RawMessage(`false`), + "route": json.RawMessage(`"list"`), + "item_count": json.RawMessage(`0`), + "has_submitted": json.RawMessage(`false`), } } func formSnapshots() Snapshots { return Snapshots{ - "route": json.RawMessage(`"form"`), - "item_count": json.RawMessage(`0`), - "has_submitted": json.RawMessage(`false`), + "route": json.RawMessage(`"form"`), + "item_count": json.RawMessage(`0`), + "has_submitted": json.RawMessage(`false`), } } diff --git a/proto/idbcompanion/buf.gen.yaml b/proto/idbcompanion/buf.gen.yaml new file mode 100644 index 0000000..6a6fa05 --- /dev/null +++ b/proto/idbcompanion/buf.gen.yaml @@ -0,0 +1,19 @@ +# Dedicated generation template for the pinned simulator companion proto. +# This is intentionally separate from the root buf.gen.yaml so that the +# generated stubs land in the Go internal package rather than under proto/, +# and so a normal `make proto` never touches these committed stubs. +# +# Reproduce the committed stubs from the repository root with: +# buf generate proto/idbcompanion --template proto/idbcompanion/buf.gen.yaml +# The module= options strip the Go module prefix so the files land at +# internal/driver/ioscompanion/companionpb relative to the repository root. +version: v2 +plugins: + - local: protoc-gen-go + out: . + opt: + - module=github.com/priyanshujain/sanderling + - local: protoc-gen-go-grpc + out: . + opt: + - module=github.com/priyanshujain/sanderling diff --git a/proto/idbcompanion/buf.yaml b/proto/idbcompanion/buf.yaml new file mode 100644 index 0000000..67920c1 --- /dev/null +++ b/proto/idbcompanion/buf.yaml @@ -0,0 +1,15 @@ +# Standalone buf module for the pinned simulator companion proto. It is kept +# out of the root workspace so the root `buf lint` and `buf generate` never see +# this file. Lint here is relaxed because the proto is copied verbatim from +# upstream and must stay byte-identical apart from the go_package option. +version: v2 +modules: + - path: . +lint: + use: + - MINIMAL + except: + - PACKAGE_DIRECTORY_MATCH +breaking: + use: + - FILE diff --git a/proto/idbcompanion/idb.proto b/proto/idbcompanion/idb.proto new file mode 100644 index 0000000..47807e9 --- /dev/null +++ b/proto/idbcompanion/idb.proto @@ -0,0 +1,930 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Pinned to the simulator companion v1.1.8 and matches the embedded binary. +// Do NOT regenerate this file from upstream HEAD: HEAD has drifted from the +// pinned binary, so regenerating would produce stubs that no longer match the +// companion this driver speaks to. The only local change versus the upstream +// source is the go_package option below, which targets the committed stub +// package internal/driver/ioscompanion/companionpb. Regenerate the stubs from +// the repository root with: +// buf generate proto/idbcompanion --template proto/idbcompanion/buf.gen.yaml + +syntax = "proto3"; + +package idb; + +option go_package = "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/companionpb;companionpb"; + +// The idb companion service definition. +service CompanionService { + // Management + rpc connect(ConnectRequest) returns (ConnectResponse) {} + rpc debugserver(stream DebugServerRequest) + returns (stream DebugServerResponse) {} + rpc dap(stream DapRequest) returns (stream DapResponse) {} + rpc describe(TargetDescriptionRequest) returns (TargetDescriptionResponse) {} + rpc install(stream InstallRequest) returns (stream InstallResponse) {} + rpc instruments_run(stream InstrumentsRunRequest) + returns (stream InstrumentsRunResponse) {} + rpc log(LogRequest) returns (stream LogResponse) {} + rpc xctrace_record(stream XctraceRecordRequest) + returns (stream XctraceRecordResponse) {} + // Interaction + rpc accessibility_info(AccessibilityInfoRequest) + returns (AccessibilityInfoResponse) {} + rpc focus(FocusRequest) returns (FocusResponse) {} + rpc hid(stream HIDEvent) returns (HIDResponse) {} + rpc open_url(OpenUrlRequest) returns (OpenUrlRequest) {} + rpc set_location(SetLocationRequest) returns (SetLocationResponse) {} + rpc send_notification(SendNotificationRequest) + returns (SendNotificationResponse) {} + rpc simulate_memory_warning(SimulateMemoryWarningRequest) + returns (SimulateMemoryWarningResponse) {} + // Settings + rpc approve(ApproveRequest) returns (ApproveResponse) {} + rpc revoke(RevokeRequest) returns (RevokeResponse) {} + rpc clear_keychain(ClearKeychainRequest) returns (ClearKeychainResponse) {} + rpc contacts_update(ContactsUpdateRequest) returns (ContactsUpdateResponse) {} + rpc setting(SettingRequest) returns (SettingResponse) {} // Sets settings + rpc get_setting(GetSettingRequest) returns (GetSettingResponse) { + } // Gets current setting + rpc list_settings(ListSettingRequest) returns (ListSettingResponse) { + } // Lists available settings + // App + rpc launch(stream LaunchRequest) returns (stream LaunchResponse) {} + rpc list_apps(ListAppsRequest) returns (ListAppsResponse) {} + rpc terminate(TerminateRequest) returns (TerminateResponse) {} + rpc uninstall(UninstallRequest) returns (UninstallResponse) {} + // Video/Audio + rpc add_media(stream AddMediaRequest) returns (AddMediaResponse) {} + rpc record(stream RecordRequest) returns (stream RecordResponse) {} + rpc screenshot(ScreenshotRequest) returns (ScreenshotResponse) {} + rpc video_stream(stream VideoStreamRequest) + returns (stream VideoStreamResponse) {} + // Crash Operations + rpc crash_delete(CrashLogQuery) returns (CrashLogResponse) {} + rpc crash_list(CrashLogQuery) returns (CrashLogResponse) {} + rpc crash_show(CrashShowRequest) returns (CrashShowResponse) {} + // xctest operations + rpc xctest_list_bundles(XctestListBundlesRequest) + returns (XctestListBundlesResponse) {} + rpc xctest_list_tests(XctestListTestsRequest) + returns (XctestListTestsResponse) {} + rpc xctest_run(XctestRunRequest) returns (stream XctestRunResponse) {} + // File Operations + rpc ls(LsRequest) returns (LsResponse) {} + rpc mkdir(MkdirRequest) returns (MkdirResponse) {} + rpc mv(MvRequest) returns (MvResponse) {} + rpc rm(RmRequest) returns (RmResponse) {} + rpc pull(PullRequest) returns (stream PullResponse) {} + rpc push(stream PushRequest) returns (PushResponse) {} + rpc tail(stream TailRequest) returns (stream TailResponse) {} +} + +message Payload { + oneof source { + string file_path = 1; + bytes data = 2; + string url = 3; + Compression compression = 4; + } + enum Compression { + GZIP = 0; + ZSTD = 1; + } +} + +message ProcessOutput { + enum Interface { + STDOUT = 0; + STDERR = 1; + } + Interface interface = 1; + bytes data = 2; +} + +message CompanionInfo { + string udid = 1; + bool is_local = 4; + bytes metadata = 6; +} + +enum Setting { + LOCALE = 0; + ANY = 1; // any kind of app or global setting +} + +message SettingRequest { + message HardwareKeyboard { + bool enabled = 1; + } + message StringSetting { + Setting setting = 1; + string value = 2; + string name = 3; // iff setting == ANY + string domain = 4; // assumed "Apple Global Domain" if empty + /* + * Currently supported value_types and corresponding values are: + * string + * data + * int[eger] + * float + * bool[ean] (true | false | yes | no) + * date + * array ... + * array-add ... + * dict ... + * dict-add ... + * + * Check defaults set help for more details. + */ + string value_type = 5; + } + oneof setting { + HardwareKeyboard hardwareKeyboard = 1; + StringSetting stringSetting = 2; + } +} + +message SettingResponse {} + +message GetSettingRequest { + Setting setting = 1; + string name = 2; // iff setting == ANY + string domain = 3; // assumed "Apple Global Domain" if empty +} + +message GetSettingResponse { + string value = 1; +} + +message ListSettingRequest { + Setting setting = 1; +} + +message ListSettingResponse { + repeated string values = 1; +} + +message ListAppsRequest { + bool suppress_process_state = 1; +} + +message ListAppsResponse { + repeated InstalledAppInfo apps = 1; +} + +message InstalledAppInfo { + string bundle_id = 1; + string name = 2; + repeated string architectures = 3; + string install_type = 4; + enum AppProcessState { + UNKNOWN = 0; + NOT_RUNNING = 1; + RUNNING = 2; + } + AppProcessState process_state = 5; + bool debuggable = 6; + uint64 process_identifier = 7; +} + +message InstallRequest { + enum Destination { + APP = 0; + XCTEST = 1; + DYLIB = 2; + DSYM = 3; + FRAMEWORK = 4; + } + message LinkDsymToBundle { + enum BundleType { + APP = 0; + XCTEST = 1; + } + BundleType bundle_type = 1; + string bundle_id = 2; + } + oneof value { + Destination destination = 1; + Payload payload = 2; + string name_hint = 3; + bool make_debuggable = 4; + string bundle_id = 5; //(2022-03-02) REMOVE, For back compatibility only. + // Link dSYM to app bundle_id + LinkDsymToBundle link_dsym_to_bundle = 6; + bool override_modification_time = 7; + } +} + +message InstallResponse { + string name = 1; + string uuid = 2; + double progress = 3; +} + +message ScreenshotRequest {} + +message ScreenshotResponse { + bytes image_data = 1; + string image_format = 2; +} + +message FocusRequest {} + +message FocusResponse {} + +message Point { + double x = 1; + double y = 2; +} + +message AccessibilityInfoRequest { + enum Format { + LEGACY = 0; + NESTED = 1; + } + Point point = 2; + Format format = 3; +} + +message AccessibilityInfoResponse { + string json = 1; +} + +message ApproveRequest { + enum Permission { + PHOTOS = 0; + CAMERA = 1; + CONTACTS = 2; + URL = 3; + LOCATION = 4; + NOTIFICATION = 5; + MICROPHONE = 6; + } + string bundle_id = 1; + repeated Permission permissions = 2; + string scheme = 3; +} + +message ApproveResponse {} + +message RevokeRequest { + enum Permission { + PHOTOS = 0; + CAMERA = 1; + CONTACTS = 2; + URL = 3; + LOCATION = 4; + NOTIFICATION = 5; + MICROPHONE = 6; + } + string bundle_id = 1; + repeated Permission permissions = 2; + string scheme = 3; +} + +message RevokeResponse {} + +message ClearKeychainRequest {} + +message ClearKeychainResponse {} + +message SetLocationRequest { + Location location = 1; +} + +message Location { + double latitude = 1; + double longitude = 2; +} + +message SetLocationResponse {} + +message UninstallRequest { + string bundle_id = 1; +} + +message UninstallResponse {} + +message TerminateRequest { + string bundle_id = 1; +} + +message TerminateResponse {} + +message OpenUrlRequest { + string url = 1; +} + +message OpenUrlResponse {} + +message ContactsUpdateRequest { + Payload payload = 1; +} + +message ContactsUpdateResponse {} + +message TargetDescriptionRequest { + bool fetch_diagnostics = 1; +} + +message TargetDescriptionResponse { + TargetDescription target_description = 1; + CompanionInfo companion = 2; +} + +message HIDEvent { + enum HIDDirection { + DOWN = 0; + UP = 1; + } + + message HIDTouch { + Point point = 1; + } + + enum HIDButtonType { + APPLE_PAY = 0; + HOME = 1; + LOCK = 2; + SIDE_BUTTON = 3; + SIRI = 4; + } + + message HIDButton { + HIDButtonType button = 1; + } + + message HIDKey { + uint64 keycode = 1; + } + + message HIDPressAction { + oneof action { + HIDTouch touch = 1; + HIDButton button = 2; + HIDKey key = 3; + } + } + + message HIDPress { + HIDPressAction action = 1; + HIDDirection direction = 2; + } + + message HIDSwipe { + Point start = 1; + Point end = 2; + double delta = 5; + double duration = 6; + } + + message HIDDelay { + double duration = 1; + } + + oneof event { + HIDPress press = 1; + HIDSwipe swipe = 2; + HIDDelay delay = 3; + } +} + +message HIDResponse {} + +message ConnectRequest { + map metadata = 1; + string local_file_path = 4; +} + +message ConnectResponse { + CompanionInfo companion = 1; +} + +message ScreenDimensions { + uint64 width = 1; + uint64 height = 2; + double density = 3; + uint64 width_points = 4; + uint64 height_points = 5; +} + +message TargetDescription { + string udid = 1; + string name = 2; + ScreenDimensions screen_dimensions = 3; + string state = 4; + string target_type = 5; + string os_version = 6; + string architecture = 7; + bytes extended = 9; + bytes diagnostics = 10; +} + +message LogRequest { + enum Source { + TARGET = 0; + COMPANION = 1; + } + repeated string arguments = 1; + Source source = 2; +} + +message LogResponse { + bytes output = 1; +} + +message RecordRequest { + message Start { + string file_path = 1; + } + message Stop {} + oneof control { + Start start = 1; + Stop stop = 2; + } +} + +message RecordResponse { + oneof output { + bytes log_output = 1; + Payload payload = 2; + } +} + +message VideoStreamRequest { + enum Format { + H264 = 0; + RBGA = 1; + MJPEG = 2; + MINICAP = 3; + I420 = 4; + } + message Start { + string file_path = 1; + uint64 fps = 2; + Format format = 3; + double compression_quality = 4; + double scale_factor = 5; + double avg_bitrate = 6; + } + message Stop {} + oneof control { + Start start = 1; + Stop stop = 2; + } +} + +message VideoStreamResponse { + oneof output { + bytes log_output = 1; + Payload payload = 2; + } +} + +message LaunchRequest { + message Start { + string bundle_id = 1; + map env = 2; + repeated string app_args = 3; + bool foreground_if_running = 4; + bool wait_for = 5; + bool wait_for_debugger = 6; + } + message Stop {} + oneof control { + Start start = 1; + Stop stop = 2; + } +} + +message LaunchResponse { + ProcessOutput output = 3; + DebuggerInfo debugger = 4; +} + +message AddMediaRequest { + Payload payload = 1; +} + +message AddMediaResponse {} + +message InstrumentsRunRequest { + message InstrumentsTimings { + double terminate_timeout = 1; + double launch_retry_timeout = 2; + double launch_error_timeout = 3; + double operation_duration = 4; + } + message Start { + string template_name = 2; + string app_bundle_id = 3; + map environment = 4; + repeated string arguments = 5; + InstrumentsTimings timings = 6; + repeated string tool_arguments = 7; + } + message Stop { + repeated string post_process_arguments = 1; + } + oneof control { + Start start = 1; + Stop stop = 2; + } +} + +message InstrumentsRunResponse { + enum State { + UNKNOWN = 0; + RUNNING_INSTRUMENTS = 1; + POST_PROCESSING = 2; + } + oneof output { + bytes log_output = 1; + Payload payload = 2; + State state = 3; + } +} + +message XctraceRecordRequest { + message LauchProcess { + string process_to_launch = 1; + repeated string launch_args = 2; + string target_stdin = 3; + string target_stdout = 4; + map process_env = 5; + } + message Target { + oneof target { + bool all_processes = 1; + string process_to_attach = 2; + LauchProcess launch_process = 3; + } + } + message Start { + string template_name = 1; + double time_limit = 2; + string package = 3; + Target target = 4; + } + message Stop { + double timeout = 1; + repeated string args = 2; + } + oneof control { + Start start = 1; + Stop stop = 2; + } +} + +message XctraceRecordResponse { + enum State { + UNKNOWN = 0; + RUNNING = 1; + PROCESSING = 2; + } + oneof output { + bytes log = 1; + Payload payload = 2; + State state = 3; + } +} + +message DebugServerRequest { + message Start { + string bundle_id = 1; + } + message Status {} + message Stop {} + message Pipe { + bytes data = 1; + } + oneof control { + Start start = 1; + Stop stop = 2; + Status status = 3; + Pipe pipe = 4; + } +} + +message DebugServerResponse { + message Pipe { + bytes data = 1; + } + message Status { + repeated string lldb_bootstrap_commands = 1; + } + oneof control { + Status status = 1; + Pipe pipe = 2; + } +} + +// crashes + +message CrashShowRequest { + string name = 1; +} + +message CrashLogResponse { + repeated CrashLogInfo list = 1; +} + +message CrashLogInfo { + string name = 1; + string bundle_id = 2; + string process_name = 3; + string parent_process_name = 4; + uint64 process_identifier = 5; + uint64 parent_process_identifier = 6; + uint64 timestamp = 7; +} + +message CrashShowResponse { + CrashLogInfo info = 1; + string contents = 2; +} + +message CrashLogQuery { + uint64 since = 1; + uint64 before = 2; + string bundle_id = 3; + string name = 4; +} + +// xctest + +message XctestListBundlesRequest {} + +message XctestListBundlesResponse { + message Bundles { + string name = 1; + string bundle_id = 2; + repeated string architectures = 3; + } + repeated Bundles bundles = 1; +} + +message XctestListTestsRequest { + string bundle_name = 1; + string app_path = 2; +} + +message XctestListTestsResponse { + repeated string names = 1; +} + +message XctestRunRequest { + message Logic {} + message Application { + string app_bundle_id = 1; + } + message UI { + string app_bundle_id = 1; + string test_host_app_bundle_id = 2; + } + message Mode { + oneof mode { + Logic logic = 1; + Application application = 2; + UI ui = 3; + } + } + message CodeCoverage { + enum Format { + EXPORTED = 0; + RAW = 1; + } + bool collect = 1; + Format format = 2; + } + Mode mode = 1; + string test_bundle_id = 2; + repeated string tests_to_run = 3; + repeated string tests_to_skip = 4; + repeated string arguments = 5; + map environment = 6; + uint64 timeout = 7; + bool report_activities = 8; + bool collect_coverage = 9; + bool report_attachments = 10; + bool collect_logs = 11; + bool wait_for_debugger = 12; + CodeCoverage code_coverage = 13; + bool collect_result_bundle = 14; +} + +message XctestRunResponse { + message TestRunInfo { + message TestRunFailureInfo { + string failure_message = 1; + string file = 2; + uint64 line = 3; + } + message TestAttachment { + bytes payload = 1; + double timestamp = 2; + string name = 3; + string uniform_type_identifier = 4; + bytes user_info_json = 5; + } + message TestActivity { + string title = 1; + double duration = 2; + string uuid = 3; + string activity_type = 4; + double start = 5; + double finish = 6; + string name = 7; + repeated TestAttachment attachments = 8; + repeated TestActivity sub_activities = 9; + } + enum Status { + PASSED = 0; + FAILED = 1; + CRASHED = 2; + } + Status status = 1; + string bundle_name = 2; + string class_name = 3; + string method_name = 4; + double duration = 5; + TestRunFailureInfo failure_info = 6; + repeated string logs = 7; + repeated TestActivity activityLogs = 8; + } + enum Status { + RUNNING = 0; + TERMINATED_NORMALLY = 1; + TERMINATED_ABNORMALLY = 2; + } + Status status = 1; + repeated TestRunInfo results = 2; + repeated string log_output = 3; + Payload result_bundle = 4; + string coverage_json = 5; + Payload log_directory = 6; + DebuggerInfo debugger = 7; + Payload code_coverage_data = 8; +} + +// File + +message FileContainer { + enum Kind { + NONE = 0; + APPLICATION = 1; + ROOT = 2; + MEDIA = 3; + CRASHES = 4; + PROVISIONING_PROFILES = 5; + MDM_PROFILES = 6; + SPRINGBOARD_ICONS = 7; + WALLPAPER = 8; + DISK_IMAGES = 9; + GROUP_CONTAINER = 10; + APPLICATION_CONTAINER = 11; + AUXILLARY = 12; + XCTEST = 13; + DYLIB = 14; + DSYM = 15; + FRAMEWORK = 16; + SYMBOLS = 17; + } + Kind kind = 1; + string bundle_id = 2; +} + +message FileInfo { + string path = 1; +} + +message FileListing { + FileInfo parent = 1; + repeated FileInfo files = 2; +} + +message LsResponse { + repeated FileInfo files = 1; + repeated FileListing listings = 2; +} + +message LsRequest { + string path = 2; + FileContainer container = 3; + repeated string paths = 4; +} + +message MkdirRequest { + string path = 2; + FileContainer container = 3; +} + +message MkdirResponse {} + +message MvRequest { + repeated string src_paths = 2; + string dst_path = 3; + FileContainer container = 4; +} + +message MvResponse {} + +message RmRequest { + repeated string paths = 2; + FileContainer container = 3; +} + +message RmResponse {} + +message PushRequest { + message Inner { + string dst_path = 2; + FileContainer container = 3; + } + oneof value { + Payload payload = 1; + Inner inner = 2; + } +} + +message PushResponse {} + +message PullRequest { + string src_path = 2; + string dst_path = 3; + FileContainer container = 4; +} + +message PullResponse { + Payload payload = 1; +} + +message TailRequest { + message Start { + FileContainer container = 1; + string path = 2; + } + message Stop {} + oneof control { + Start start = 1; + Stop stop = 2; + } +} + +message TailResponse { + bytes data = 1; +} + +message DebuggerInfo { + uint64 pid = 1; + string host = 2; + uint64 port = 3; +} + +message SendNotificationRequest { + string bundle_id = 1; + string json_payload = 2; +} + +message SendNotificationResponse {} + +message DapRequest { + message Start { + string debugger_pkg_id = 1; + } + message Pipe { + bytes data = 1; + } + message Stop {} + oneof control { + Start start = 1; + Pipe pipe = 2; + Stop stop = 3; + } +} + +message DapResponse { + message Event { + string desc = 1; + } + message Pipe { + bytes data = 1; + } + + oneof event { + Event started = 1; + Pipe stdout = 2; + Event stopped = 3; + } +} + +message SimulateMemoryWarningRequest {} + +message SimulateMemoryWarningResponse {} diff --git a/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt b/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt index 70cda0d..cd00549 100644 --- a/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt +++ b/sidecar/src/main/kotlin/dev/sanderling/sidecar/DriverBackend.kt @@ -786,7 +786,7 @@ class IosDriverBackend(private val udid: String) : DriverBackend { tempFileHandler = tempFileHandler, ) val driverConfig = xcuitest.installer.LocalXCTestInstaller.IOSDriverConfig( - prebuiltRunner = false, + prebuiltRunner = true, sourceDirectory = "driver-iPhoneSimulator", context = xcuitest.installer.Context.CLI, snapshotKeyHonorModalViews = null,