idb_companion/SwiftServer/CompanionServiceProvider.swift (213 lines of code) (raw):

/* * 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. */ import Foundation import IDBGRPCSwift import GRPC import SwiftProtobuf import NIOHPACK import XCTestBootstrap final class CompanionServiceProvider: Idb_CompanionServiceAsyncProvider { private let target: FBiOSTarget private let commandExecutor: FBIDBCommandExecutor private let reporter: FBEventReporter private let logger: FBIDBLogger private let internalCppClient: Idb_CompanionServiceAsyncClientProtocol private let interceptorFactory: Idb_CompanionServiceServerInterceptorFactoryProtocol init(target: FBiOSTarget, commandExecutor: FBIDBCommandExecutor, reporter: FBEventReporter, logger: FBIDBLogger, internalCppClient: Idb_CompanionServiceAsyncClientProtocol, interceptors: Idb_CompanionServiceServerInterceptorFactoryProtocol) { self.target = target self.commandExecutor = commandExecutor self.reporter = reporter self.logger = logger self.internalCppClient = internalCppClient self.interceptorFactory = interceptors } var interceptors: Idb_CompanionServiceServerInterceptorFactoryProtocol? { interceptorFactory } private func shouldHandleNatively(context: GRPCAsyncServerCallContext) -> Bool { return context.userInfo[CallSwiftMethodNatively.self] ?? false } private var targetLogger: FBControlCoreLogger { get throws { guard let logger = target.logger else { throw GRPCStatus(code: .internalError, message: "Target logger not configured") } return logger } } func connect(request: Idb_ConnectRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ConnectResponse { guard shouldHandleNatively(context: context) else { return try await proxy(request: request, context: context) } return try await ConnectMethodHandler(reporter: reporter, logger: logger, target: target) .handle(request: request, context: context) } func debugserver(requestStream: GRPCAsyncRequestStream<Idb_DebugServerRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_DebugServerResponse>, context: GRPCAsyncServerCallContext) async throws { try await proxy(requestStream: requestStream, responseStream: responseStream, context: context) } func dap(requestStream: GRPCAsyncRequestStream<Idb_DapRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_DapResponse>, context: GRPCAsyncServerCallContext) async throws { try await proxy(requestStream: requestStream, responseStream: responseStream, context: context) } func describe(request: Idb_TargetDescriptionRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_TargetDescriptionResponse { guard shouldHandleNatively(context: context) else { return try await proxy(request: request, context: context) } return try await DescribeMethodHandler(reporter: reporter, logger: logger, target: target, commandExecutor: commandExecutor) .handle(request: request, context: context) } func install(requestStream: GRPCAsyncRequestStream<Idb_InstallRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_InstallResponse>, context: GRPCAsyncServerCallContext) async throws { guard shouldHandleNatively(context: context) else { return try await proxy(requestStream: requestStream, responseStream: responseStream, context: context) } try await InstallMethodHandler(commandExecutor: commandExecutor, targetLogger: targetLogger) .handle(requestStream: requestStream, responseStream: responseStream, context: context) } func instruments_run(requestStream: GRPCAsyncRequestStream<Idb_InstrumentsRunRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_InstrumentsRunResponse>, context: GRPCAsyncServerCallContext) async throws { try await proxy(requestStream: requestStream, responseStream: responseStream, context: context) } func log(request: Idb_LogRequest, responseStream: GRPCAsyncResponseStreamWriter<Idb_LogResponse>, context: GRPCAsyncServerCallContext) async throws { try await proxy(request: request, responseStream: responseStream, context: context) } func xctrace_record(requestStream: GRPCAsyncRequestStream<Idb_XctraceRecordRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_XctraceRecordResponse>, context: GRPCAsyncServerCallContext) async throws { try await proxy(requestStream: requestStream, responseStream: responseStream, context: context) } func accessibility_info(request: Idb_AccessibilityInfoRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_AccessibilityInfoResponse { return try await proxy(request: request, context: context) } func focus(request: Idb_FocusRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_FocusResponse { return try await proxy(request: request, context: context) } func hid(requestStream: GRPCAsyncRequestStream<Idb_HIDEvent>, context: GRPCAsyncServerCallContext) async throws -> Idb_HIDResponse { return try await proxy(requestStream: requestStream, context: context) } func open_url(request: Idb_OpenUrlRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_OpenUrlRequest { return try await proxy(request: request, context: context) } func set_location(request: Idb_SetLocationRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SetLocationResponse { return try await proxy(request: request, context: context) } func send_notification(request: Idb_SendNotificationRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SendNotificationResponse { return try await proxy(request: request, context: context) } func simulate_memory_warning(request: Idb_SimulateMemoryWarningRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SimulateMemoryWarningResponse { return try await proxy(request: request, context: context) } func approve(request: Idb_ApproveRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ApproveResponse { return try await proxy(request: request, context: context) } func clear_keychain(request: Idb_ClearKeychainRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ClearKeychainResponse { return try await proxy(request: request, context: context) } func contacts_update(request: Idb_ContactsUpdateRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ContactsUpdateResponse { return try await proxy(request: request, context: context) } func setting(request: Idb_SettingRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SettingResponse { return try await proxy(request: request, context: context) } func get_setting(request: Idb_GetSettingRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_GetSettingResponse { return try await proxy(request: request, context: context) } func list_settings(request: Idb_ListSettingRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ListSettingResponse { return try await proxy(request: request, context: context) } func launch(requestStream: GRPCAsyncRequestStream<Idb_LaunchRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_LaunchResponse>, context: GRPCAsyncServerCallContext) async throws { try await proxy(requestStream: requestStream, responseStream: responseStream, context: context) } func list_apps(request: Idb_ListAppsRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ListAppsResponse { return try await proxy(request: request, context: context) } func terminate(request: Idb_TerminateRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_TerminateResponse { return try await proxy(request: request, context: context) } func uninstall(request: Idb_UninstallRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_UninstallResponse { return try await proxy(request: request, context: context) } func add_media(requestStream: GRPCAsyncRequestStream<Idb_AddMediaRequest>, context: GRPCAsyncServerCallContext) async throws -> Idb_AddMediaResponse { return try await proxy(requestStream: requestStream, context: context) } func record(requestStream: GRPCAsyncRequestStream<Idb_RecordRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_RecordResponse>, context: GRPCAsyncServerCallContext) async throws { try await proxy(requestStream: requestStream, responseStream: responseStream, context: context) } func screenshot(request: Idb_ScreenshotRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ScreenshotResponse { return try await proxy(request: request, context: context) } func video_stream(requestStream: GRPCAsyncRequestStream<Idb_VideoStreamRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_VideoStreamResponse>, context: GRPCAsyncServerCallContext) async throws { return try await proxy(requestStream: requestStream, responseStream: responseStream, context: context) } func crash_delete(request: Idb_CrashLogQuery, context: GRPCAsyncServerCallContext) async throws -> Idb_CrashLogResponse { return try await proxy(request: request, context: context) } func crash_list(request: Idb_CrashLogQuery, context: GRPCAsyncServerCallContext) async throws -> Idb_CrashLogResponse { return try await proxy(request: request, context: context) } func crash_show(request: Idb_CrashShowRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_CrashShowResponse { return try await proxy(request: request, context: context) } func xctest_list_bundles(request: Idb_XctestListBundlesRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_XctestListBundlesResponse { return try await proxy(request: request, context: context) } func xctest_list_tests(request: Idb_XctestListTestsRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_XctestListTestsResponse { return try await proxy(request: request, context: context) } func xctest_run(request: Idb_XctestRunRequest, responseStream: GRPCAsyncResponseStreamWriter<Idb_XctestRunResponse>, context: GRPCAsyncServerCallContext) async throws { guard shouldHandleNatively(context: context) else { try await proxy(request: request, responseStream: responseStream, context: context) return } try await XCTestRunMethodHandler(target: target, commandExecutor: commandExecutor, reporter: reporter, targetLogger: targetLogger, logger: logger) .handle(request: request, responseStream: responseStream, context: context) } func ls(request: Idb_LsRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_LsResponse { return try await proxy(request: request, context: context) } func mkdir(request: Idb_MkdirRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_MkdirResponse { return try await proxy(request: request, context: context) } func mv(request: Idb_MvRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_MvResponse { return try await proxy(request: request, context: context) } func rm(request: Idb_RmRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_RmResponse { return try await proxy(request: request, context: context) } func pull(request: Idb_PullRequest, responseStream: GRPCAsyncResponseStreamWriter<Idb_PullResponse>, context: GRPCAsyncServerCallContext) async throws { try await proxy(request: request, responseStream: responseStream, context: context) } func push(requestStream: GRPCAsyncRequestStream<Idb_PushRequest>, context: GRPCAsyncServerCallContext) async throws -> Idb_PushResponse { return try await proxy(requestStream: requestStream, context: context) } func tail(requestStream: GRPCAsyncRequestStream<Idb_TailRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_TailResponse>, context: GRPCAsyncServerCallContext) async throws { try await proxy(requestStream: requestStream, responseStream: responseStream, context: context) } } extension CompanionServiceProvider { private func proxy<Request: Message, Response: Message>(request: Request, context: GRPCAsyncServerCallContext) async throws -> Response { let methodPath = try extractMethodPathAndLogProxyMessage(context: context) return try await internalCppClient.performAsyncUnaryCall(path: methodPath, request: request) } private func proxy<Request: Message, Response: Message>(request: Request, responseStream: GRPCAsyncResponseStreamWriter<Response>, context: GRPCAsyncServerCallContext) async throws { let methodPath = try extractMethodPathAndLogProxyMessage(context: context) let resultStream = internalCppClient.performAsyncServerStreamingCall(path: methodPath, request: request, responseType: Response.self) for try await response in resultStream { try await responseStream.send(response) } } private func proxy<Request: Message, Response: Message>(requestStream: GRPCAsyncRequestStream<Request>, context: GRPCAsyncServerCallContext) async throws -> Response { let methodPath = try extractMethodPathAndLogProxyMessage(context: context) return try await internalCppClient.performAsyncClientStreamingCall(path: methodPath, requests: requestStream) } private func proxy<Request: Message, Response: Message>(requestStream: GRPCAsyncRequestStream<Request>, responseStream: GRPCAsyncResponseStreamWriter<Response>, context: GRPCAsyncServerCallContext) async throws { let methodPath = try extractMethodPathAndLogProxyMessage(context: context) let resultStream = internalCppClient.performAsyncBidirectionalStreamingCall(path: methodPath, requests: requestStream, responseType: Response.self) for try await response in resultStream { try await responseStream.send(response) } } private func extractMethodPathAndLogProxyMessage(context: GRPCAsyncServerCallContext) throws -> String { guard let methodPath = context.userInfo[MethodPathKey.self] else { throw GRPCStatus(code: .internalError, message: "Method path not provided. Check idb_companion's grpc interceptor configuration") } logger.log("Proxying \(methodPath) to cpp server") return methodPath } }