/*
 * 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.
 */

@testable import FBControlCore
import XCTest

class FBConcatedJsonParserTests: XCTestCase {

  func testPlainSmallJsonParse() throws {
    let json = """
    {"hello": "world"}
    """

    try XCTAssertEqual(parse(string: json), ["hello": "world"])
  }

  func testPlainDoubleJsonParse() throws {
    let json = """
    {"hello": "world"}
    {"second": "value"}
    """

    try XCTAssertEqual(parse(string: json), ["hello": "world", "second": "value"])
  }

  func testWithCurlyBracketsInString() throws {
    let json = """
    {"hello": "wor{ld"}
    {"second": "value"}
    """

    try XCTAssertEqual(parse(string: json), ["hello": "wor{ld", "second": "value"])
  }

  func testWithEscapedCharatersInString() throws {
    let json = """
    {"hello": "worl\\"d"}
    {"second": "value"}
    """

    try XCTAssertEqual(parse(string: json), ["hello": "worl\"d", "second": "value"])
  }

  func testThrowsIncorrectJson() throws {
    let json = """
    {"hello": 1"world"1}
    """
    XCTAssertThrowsError(try parse(string: json))
  }

  private func parse(string: String) throws -> [String: String] {
    guard let json = try FBConcatedJsonParser.parseConcatenatedJSON(from: string) as? [String: String] else {
      throw NSError(domain: "Json parsed with incorrect type", code: 0)
    }

    return json
  }
}


/*
 * 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.
 */

@testable import FBControlCore

import XCTest

final class FBCrashLogInfoTests: XCTestCase {

  func testAssetsdCustomSet() throws {
    let info = try FBCrashLogInfo.fromCrashLog(atPath: TestFixtures.assetsdCrashPathWithCustomDeviceSet)

    XCTAssertEqual(info.processIdentifier, 39942)
    XCTAssertEqual(info.parentProcessIdentifier, 39927)
    XCTAssertEqual(info.identifier, "assetsd")
    XCTAssertEqual(info.processName, "assetsd")
    XCTAssertEqual(info.parentProcessName, "launchd_sim")
    XCTAssertEqual(info.executablePath, "/Applications/xcode_7.2.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/AssetsLibrary.framework/Support/assetsd")
    XCTAssertEqual(info.date.timeIntervalSinceReferenceDate, 479723902, accuracy: 1)
    XCTAssertEqual(info.processType, .system)
  }

  func testAgentCustomSet() throws {
    let info = try FBCrashLogInfo.fromCrashLog(atPath: TestFixtures.agentCrashPathWithCustomDeviceSet)

    XCTAssertEqual(info.processIdentifier, 39655)
    XCTAssertEqual(info.parentProcessIdentifier, 39576)
    XCTAssertEqual(info.identifier, "WebDriverAgent")
    XCTAssertEqual(info.processName, "WebDriverAgent")
    XCTAssertEqual(info.parentProcessName, "launchd_sim")
    XCTAssertEqual(info.executablePath, "/Users/USER/*/WebDriverAgent")
    XCTAssertEqual(info.date.timeIntervalSinceReferenceDate, 479723798, accuracy: 1)
    XCTAssertEqual(info.processType, .custom)
  }

  func testAppDefaultSet() throws {
    let info = try FBCrashLogInfo.fromCrashLog(atPath: TestFixtures.appCrashPathWithDefaultDeviceSet)

    XCTAssertEqual(info.processIdentifier, 37083)
    XCTAssertEqual(info.parentProcessIdentifier, 37007)
    XCTAssertEqual(info.identifier, "TableSearch")
    XCTAssertEqual(info.processName, "TableSearch")
    XCTAssertEqual(info.parentProcessName, "launchd_sim")
    XCTAssertEqual(info.executablePath, "/Users/USER/Library/Developer/CoreSimulator/Devices/2FF8DD07-20B7-4D04-97F0-092DF61CD3C3/data/Containers/Bundle/Application/2BF2C731-1965-497D-B3E2-E347BD7BF464/TableSearch.app/TableSearch")
    XCTAssertEqual(info.date.timeIntervalSinceReferenceDate, 479723201, accuracy: 1)
    XCTAssertEqual(info.processType, .application)
  }

  func testAppCustomSet() throws {
    let info = try FBCrashLogInfo.fromCrashLog(atPath: TestFixtures.appCrashPathWithCustomDeviceSet)

    XCTAssertEqual(info.processIdentifier, 40119)
    XCTAssertEqual(info.parentProcessIdentifier, 39927)
    XCTAssertEqual(info.identifier, "TableSearch")
    XCTAssertEqual(info.processName, "TableSearch")
    XCTAssertEqual(info.parentProcessName, "launchd_sim")
    XCTAssertEqual(info.executablePath, "/private/var/folders/*/TableSearch.app/TableSearch")
    XCTAssertEqual(info.date.timeIntervalSinceReferenceDate, 479723902, accuracy: 1)
    XCTAssertEqual(info.processType, .application)
  }

  func testIdentifierPredicate() throws {
    try XCTAssertEqual(allCrashLogs.filtered(using: FBCrashLogInfo.predicate(forIdentifier: "assetsd")).count, 1)
  }

  func testNamePredicate() throws {
    try XCTAssertEqual(allCrashLogs.filtered(using: FBCrashLogInfo.predicate(forName: "assetsd_custom_set.crash")).count, 1)
  }

  func testJSONCrashLogFormat() throws {
    let info = try FBCrashLogInfo.fromCrashLog(atPath: TestFixtures.appCrashWithJSONFormat)

    XCTAssertEqual(info.processIdentifier, 82406)
    XCTAssertEqual(info.parentProcessIdentifier, 81861)
    XCTAssertEqual(info.identifier, "xctest3")
    XCTAssertEqual(info.processName, "xctest3")
    XCTAssertEqual(info.parentProcessName, "idb")
    XCTAssertEqual(info.executablePath, "/Applications/Xcode_13.3.0_fb.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest")
    XCTAssertEqual(info.date.timeIntervalSinceReferenceDate, 672154593, accuracy: 1)
    XCTAssertEqual(info.processType, .system)
  }

  private var allCrashLogs: NSArray {
    get throws {
      return try [
        FBCrashLogInfo.fromCrashLog(atPath: TestFixtures.assetsdCrashPathWithCustomDeviceSet),
        FBCrashLogInfo.fromCrashLog(atPath: TestFixtures.agentCrashPathWithCustomDeviceSet),
        FBCrashLogInfo.fromCrashLog(atPath: TestFixtures.appCrashPathWithDefaultDeviceSet),
        FBCrashLogInfo.fromCrashLog(atPath: TestFixtures.appCrashPathWithCustomDeviceSet),
      ]
    }
  }
}


/*
 * 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

@propertyWrapper
public final class Atomic<Value>: @unchecked Sendable {

  private var value: Value
  private let mutex: FBMutex

  public init(wrappedValue: Value) {
    self.mutex = FBMutex()
    self.value = wrappedValue
  }

  /// Custom setter for wrapped value is dangerous thing, because it may give false safety feeling
  ///
  /// For example
  /// ```
  /// @Atomic var counter = 0
  ///
  /// DispatchQueue.global().async { counter = 10 }
  /// ```
  /// is safe operation and perfectly valid.
  /// From the other side,
  /// ```
  /// DispatchQueue.global().async { counter += 10 }
  /// ```
  /// is unsafe, because consists of two operations: read current counter and save new value to the counter.
  /// For all mutation operations please use explicit synchronisation.
  public var wrappedValue: Value {
    mutex.sync(execute: { value })
  }

  /// Convenience plain setter.
  /// This produces exact same results:
  /// ```
  /// @Atomic var counter = 0
  ///
  /// $counter.set(1)
  /// $counter.sync { $0 = 1 }
  /// ```
  public func `set`(_ newValue: Value) {
    mutex.sync(execute: { value = newValue })
  }

  public func sync<R>(execute work: (inout Value) throws -> R) rethrows -> R {
    try mutex.sync(execute: { try work(&value) })
  }
}


/*
 * 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

struct CodeLocation: CustomStringConvertible {
  let function: String?
  let file: String
  let line: Int
  let column: Int

  var description: String {
    "Located at file: \(file), line: \(line), column: \(column)" + (function.map { ", function: " + $0 } ?? "")
  }
}


/*
 * 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

enum FBTeardownContextError: Error, Sendable {
  case emptyContext
  case cleanupAlreadyPerformed
}

private final class FBTeardownContextImpl: @unchecked Sendable {

  @Atomic private var cleanupList: [() async throws -> Void] = []
  @Atomic var cleanupPerformed = false

  func add(cleanup: @escaping () async throws -> Void) throws {
    guard !cleanupPerformed else {
      throw FBTeardownContextError.cleanupAlreadyPerformed
    }
    _cleanupList.sync { $0.append(cleanup) }
  }

  func performCleanup() async throws {
    let cleanupAlreadyPerformed = _cleanupPerformed.sync { cleanupPerformed -> Bool in
      defer { cleanupPerformed = true }
      return cleanupPerformed
    }
    guard !cleanupAlreadyPerformed else {
      throw FBTeardownContextError.cleanupAlreadyPerformed
    }
    for cleanup in cleanupList.reversed() {
      try await cleanup()
    }
  }
}

/// Use this class to coordinate cleanup of the tasks.
///
/// ```
///
/// func automaticCleanup() async throws -> Response {
///   return FBTeardownContext.withAutocleanup {
///     doSomeStuff()
///   }
/// }
///
/// func doSomeStuff() {
///   let tmpDir = createTemporaryDirectory()
///   FBTeardownContext.current.addCleanup {
///     FileManager.default.remove(tmpDir)
///   }
///   addFiles(to: tmpDir)
/// }
///
/// ```
public final class FBTeardownContext: Sendable {

  /// Current context that binded to swift concurrency Task. For more info read about `@TaskLocal`
  @TaskLocal public static var current: FBTeardownContext = .init(emptyContext: ())

  private let contextImpl: FBTeardownContextImpl?
  private let codeLocation: CodeLocation
  private let isAutocleanup: Bool

  private init(emptyContext: ()) {
    self.contextImpl = nil
    self.isAutocleanup = false
    self.codeLocation = .init(function: nil, file: "", line: 0, column: 0)
  }

  /// Initializer is private intentionally to restrict to `withAutocleanup` usage
  private init(isAutocleanup: Bool, function: String = #function, file: String = #file, line: Int = #line, column: Int = #column) {
    self.contextImpl = FBTeardownContextImpl()
    self.isAutocleanup = isAutocleanup
    self.codeLocation = .init(function: function, file: file, line: line, column: column)
  }

  /// Creates `FBContext` and executes operation with it
  /// - Parameter operation: Inside the operation you have `FBTeardownContext.current` available that will be cleaned up on scoping out
  /// - Returns: Operation result
  public static func withAutocleanup<T>(function: String = #function, file: String = #file, line: Int = #line, column: Int = #column, operation: @Sendable () async throws -> T) async throws -> T {
    let context = FBTeardownContext(isAutocleanup: true, function: function, file: file, line: line, column: column)
    let result = try await FBTeardownContext.$current.withValue(context, operation: operation)
    try await context.performCleanup()
    return result
  }

  /// Adds cleanup closure to the stack. All cleanup jobs will be called in LIFO order
  /// - Parameter cleanup: Task with cleanup job. There is no enforcement that job *should* throw an error on failure. This is optional.
  public func addCleanup(_ cleanup: @escaping () async throws -> Void) throws {
    guard let contextImpl else {
      throw FBTeardownContextError.emptyContext
    }
    try contextImpl.add(cleanup: cleanup)
  }

  /// This method should be called explicitly. Relying on deinit is programmer error.
  public func performCleanup() async throws {
    guard let contextImpl else {
      throw FBTeardownContextError.emptyContext
    }
    try await contextImpl.performCleanup()
  }

  deinit {
    if let contextImpl, contextImpl.cleanupPerformed == false {

      if !Task.isCancelled && !isAutocleanup {
        // Despite that we can cleanup automatically, this should be done explicitly

        // Note:
        // When current task is cancelled, we may not reach explicit cleanup.
        // Then cleanup in deinit is ok, bacause task cancellation means that we exceeded client
        // request timeout and error propagation is not required anymore.
        // But Task.isCancelled not always correctly represents cancellation in deinit (concurrency bug?)
        // so there are possibility of false-failure report.
        // To reduce false failures `isAutocleanup` introduced in contexts that used within 100% safe
        // env with automatic cleanup error propagation.
        assertionFailure("Context was not cleaned up explicitly. \(codeLocation)")
      }

      Task {
        try await contextImpl.performCleanup()
      }
    }
  }
}


/*
 * 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

public protocol AsyncStreamWriter {
  associatedtype Value: Sendable

  func send(_ value: Value) async throws
}

/// Wraps any async stream writer and bridges it to synchronous world
/// preserving FIFO order of elements.
public final class FIFOStreamWriter<StreamWriter: AsyncStreamWriter>: @unchecked Sendable {

  private let stream: StreamWriter

  private let semaphore = DispatchSemaphore(value: 0)

  public init(stream: StreamWriter) {
    self.stream = stream
  }

  // We need to please swift concurrency checker, because
  // interop between semaphores and swift concurrency is not compile-time safe
  private class ErrorWrapper: @unchecked Sendable {
    var error: Error?
  }

  /// This method should be called from GCD
  /// Never ever call that from swift concurrency cooperative pool thread, because it is unsafe
  /// and you will violate swift concurrency contract. Doing that may cause deadlock of whole concurrency runtime.
  public func send(_ value: StreamWriter.Value) throws {
    // Implementation is indentionally naive. "Clever" implementation is much harder to understand
    // and gives 0.01 better results on 1000 of elements. We can live pretty happily with
    // that relatively "slow" impl.
    // There is one downside in current implementation - it is blocking and we consume the thread.
    // So we assume that `stream.send` will not live for a long time.

    let errWrapper = ErrorWrapper()
    Task {
      do {
        try await stream.send(value)
      } catch {
        errWrapper.error = error
      }
      semaphore.signal()
    }
    semaphore.wait()

    if let err = errWrapper.error {
      throw err
    }
  }
}


/*
 * 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 Darwin
import Foundation

/// Helper wrapper around `pthread_mutex`
final class FBMutex: @unchecked Sendable {

  private var underlyingMutex = pthread_mutex_t()

  init() {
    var attr = pthread_mutexattr_t()
    guard pthread_mutexattr_init(&attr) == 0 else {
      preconditionFailure()
    }
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL)
    guard pthread_mutex_init(&underlyingMutex, &attr) == 0 else {
      preconditionFailure()
    }
    pthread_mutexattr_destroy(&attr)
  }

  func sync<R>(execute work: () throws -> R) rethrows -> R {
    pthread_mutex_lock(&underlyingMutex)
    defer { pthread_mutex_unlock(&underlyingMutex) }
    return try work()
  }

  deinit {
    pthread_mutex_destroy(&underlyingMutex)
  }
}


/*
 * 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

struct TaskSelectState<Success: Sendable, Failure: Error>: Sendable {
  var complete = false
  var tasks: [Task<Success, Failure>]? = []

  mutating func add(_ task: Task<Success, Failure>) -> Task<Success, Failure>? {
    if var tasks {
      tasks.append(task)
      self.tasks = tasks
      return nil
    } else {
      return task
    }
  }
}

extension Task {
  /// Determine the first task to complete of a sequence of tasks.
  ///
  /// - Parameters:
  ///   - tasks: The running tasks to obtain a result from
  /// - Returns: The first task to complete from the running tasks
  public static func select<Tasks: Sequence & Sendable>(
    _ tasks: Tasks
  ) async -> Task<Success, Failure>
  where Tasks.Element == Task<Success, Failure>, Success: Sendable {

    let state = Atomic<TaskSelectState<Success, Failure>>(wrappedValue: .init())
    return await withTaskCancellationHandler {
      await withUnsafeContinuation { continuation in
        for task in tasks {
          Task<Void, Never> {
            _ = await task.result
            let winner = state.sync { state -> Bool in
              defer { state.complete = true }
              return !state.complete
            }
            if winner {
              continuation.resume(returning: task)
            }
          }
          state.sync { state in
            state.add(task)
          }?.cancel()
        }
      }
    } onCancel: {

      let tasks = state.sync { state -> [Task<Success, Failure>] in
        defer { state.tasks = nil }
        return state.tasks ?? []
      }
      for task in tasks {
        task.cancel()
      }
    }
  }

  /// Determine the first task to complete of a list of tasks.
  ///
  /// - Parameters:
  ///   - tasks: The running tasks to obtain a result from
  /// - Returns: The first task to complete from the running tasks
  public static func select(
    _ tasks: Task<Success, Failure>...
  ) async -> Task<Success, Failure> where Success: Sendable {
    await select(tasks)
  }
}


/*
 * 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

struct TaskTimeoutError: Error, LocalizedError {
  let location: CodeLocation

  var errorDescription: String? {
    "Received timeout for task. \(location)"
  }
}

extension Task where Failure == Error {

  /// Awaits certain amount of time for a job throwing an error on timeouts
  /// - Parameters:
  ///   - nanoseconds: Amount of time to wait
  /// - Returns: Job result
  public static func timeout(nanoseconds: UInt64, function: String = #function, file: String = #file, line: Int = #line, column: Int = #column, job: @escaping @Sendable () async throws -> Success) async throws -> Success {
    let jobTask = Task<Success, Error> { try await job() }
    let result = await Task<Success, Error>.select(
      jobTask,
      Task<Success, Error> {
        try await Task<Never, Never>.sleep(nanoseconds: nanoseconds)
        throw TaskTimeoutError(location: .init(function: function, file: file, line: line, column: column))
      }
    )
    return try await result.value
  }
}


/*
 * 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.
 */

@testable import IDBCompanionUtilities
import XCTest

class AtomicTests: XCTestCase {

  func testAtomicSync() throws {
    @Atomic var counter = 0

    let iterationCount = 1000
    DispatchQueue.concurrentPerform(iterations: iterationCount) { _ in
      _counter.sync {
        $0 += 1
      }
    }

    XCTAssertEqual(counter, iterationCount, "Counters don't match. Caution: this may be flaky, because it tests possible race condition.")
  }

  func testConcurrentReadNoCrash() {
    @Atomic var counter = 10

    DispatchQueue.concurrentPerform(iterations: 1000) { _ in
      XCTAssertEqual(counter, 10, "This should never fail, we test for concurrent read crashes")
    }
  }

  func testAtomicSet() {
    @Atomic var counter = 0
    @Atomic var testableCounter = 0

    let iterationCount = 1000
    DispatchQueue.concurrentPerform(iterations: iterationCount) { _ in

      // We assume that "sync" works and use that as a reference to test "set"
      let etalonCounter = _counter.sync { c -> Int in
        c += 1
        return c
      }
      _testableCounter.set(etalonCounter)
    }

    XCTAssertEqual(counter, iterationCount, "Conters not match. Caution: this maby flacky, because tests possible race condition.")
  }
}


/*
 * 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.
 */

@testable import IDBCompanionUtilities
import XCTest

class FIFOStreamWriterTests: XCTestCase {

  private static let maxValue: Int = 1_000
  let sequentialValues = (0...maxValue).map({ $0 })
  private var mockWriter = MockStreamWriter(terminator: maxValue)

  override func setUp() {
    mockWriter = .init(terminator: Self.maxValue)
  }

  func testFIFOOrder() throws {
    let expectation = expectation(description: #function)
    let fifoWrapper = FIFOStreamWriter(stream: mockWriter)
    mockWriter.completion = { expectation.fulfill() }

    for value in sequentialValues {
      try fifoWrapper.send(value)
    }

    wait(for: [expectation], timeout: 2)

    XCTAssertEqual(sequentialValues, mockWriter.storage)
  }
}


/*
 * 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 IDBCompanionUtilities

enum MockStreamError: Error {
  case completionIsNil
}

class MockStreamWriter: AsyncStreamWriter {

  let terminator: Int

  @Atomic var storage: [Int] = []

  var completion: (() -> Void)?

  init(terminator: Int) {
    self.terminator = terminator
  }

  func send(_ value: Int) async throws {
    _storage.sync { $0.append(value) }

    if value == terminator {
      guard let completion else {
        throw MockStreamError.completionIsNil
      }
      completion()
    }
  }
}


/*
 * 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 FBSimulatorControl
import Foundation

/// Mock class for OSS implementation. It is easier to have mock than to use optional everywhere.
@objc final class EmptyEventReporter: NSObject, FBEventReporter {

  @objc static let shared = EmptyEventReporter()

  var metadata: [String: String] = [:]

  func report(_ subject: FBEventReporterSubject) {}
  func addMetadata(_ metadata: [String: String]) {}
}


/*
 * 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

class IDBConfiguration: NSObject {

  @objc static var eventReporter: FBEventReporter = EmptyEventReporter.shared
  @objc static var swiftEventReporter: FBEventReporter = EmptyEventReporter.shared

  static var idbKillswitch: IDBKillswitch = EmptyIDBKillswitch()

  @objc static func getIDBKillswitch() -> IDBKillswitchBridge {
    return idbKillswitch.objcBridged
  }
}


/*
 * 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

enum IDBFeature {
  case grpcEndpoint
}

protocol IDBKillswitch {
  func disabled(_ killswitch: IDBFeature) async -> Bool
}

extension IDBKillswitch {
  var objcBridged: IDBKillswitchBridge {
    .init(idbKillswitch: self)
  }
}

final class EmptyIDBKillswitch: IDBKillswitch {
  func disabled(_ killswitch: IDBFeature) async -> Bool { true }
}


/*
 * 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 FBSimulatorControl
import Foundation

/// Bridge for killswitch from objc world. Should be subset of `IDBFeature`. Use from objc
@objc(FBIDBFeatureKey) enum IDBFeatureBridge: Int {
  case grpcEndpoint

  fileprivate var nativeValue: IDBFeature {
    switch self {
    case .grpcEndpoint:
      return .grpcEndpoint
    }
  }
}

@objc(FBIDBKillswitch) class IDBKillswitchBridge: NSObject {

  private let idbKillswitch: IDBKillswitch

  init(idbKillswitch: IDBKillswitch) {
    self.idbKillswitch = idbKillswitch
  }

  /// - Returns: NSNumber initialized with boolean value
  @objc func disabledWith(_ feature: IDBFeatureBridge) -> FBFuture<NSNumber> {
    return Task.fbFuture {
      await NSNumber(value: self.idbKillswitch.disabled(feature.nativeValue))
    }
  }
}


/*
 * 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 GRPC
import NIO

enum GRPCConnectionTarget: CustomStringConvertible {
  enum ExtractionError: Error {
    case socketAddressIsEmpty
    case failedToExtractAssociatedInfo
  }

  case tcpPort(port: Int)
  case unixDomainSocket(String)

  var grpcConnection: ConnectionTarget {
    switch self {
    case let .tcpPort(port):
      return .hostAndPort("::", port)

    case let .unixDomainSocket(path):
      return .unixDomainSocket(path)
    }
  }

  func outputDescription(for socketAddress: SocketAddress?) throws -> [String: Any] {
    guard let socketAddress else {
      throw ExtractionError.socketAddressIsEmpty
    }
    switch self {
    case .tcpPort:
      guard let port = socketAddress.port else {
        throw ExtractionError.failedToExtractAssociatedInfo
      }
      return ["grpc_swift_port": port,
              "grpc_port": port]

    case .unixDomainSocket:
      guard let path = socketAddress.pathname else {
        throw ExtractionError.failedToExtractAssociatedInfo
      }
      return ["grpc_path": path]
    }
  }

  var description: String {
    switch self {
    case let .tcpPort(port):
      return "tcp port \(port)"
    case let .unixDomainSocket(path):
      return "unix socket \(path)"
    }
  }

  var supportsTLSCert: Bool {
    switch self {
    case .unixDomainSocket:
      return false
    case .tcpPort:
      return true
    }
  }
}

@objc final class IDBPortsConfiguration: NSObject {

  private enum Key {
    static let debugPort = "-debug-port"
    static let grpcDomainSock = "-grpc-domain-sock"
    static let tlsCertPath = "-tls-cert-path"
    static let grpcPort = "-grpc-port"
  }

  /// The GRPC Unix Domain Socket Path
  private let grpcDomainSocket: String?

  /// The GRPC TCP Port.
  private let grpcPort: Int

  /// The debugserver port
  @objc let debugserverPort: Int

  /// The TLS server cert path. If not specified grpcPort will be listening on unencrypted socket
  let tlsCertPath: String?

  var swiftServerTarget: GRPCConnectionTarget {
    if let grpcDomainSocket, !grpcDomainSocket.isEmpty {
      return .unixDomainSocket(grpcDomainSocket)
    } else {
      return .tcpPort(port: grpcPort)
    }
  }

  /// Construct a ports object.
  @objc init(arguments: UserDefaults) {
    self.debugserverPort = arguments.string(forKey: Key.debugPort).flatMap(Int.init) ?? 10881
    self.grpcPort = arguments.string(forKey: Key.grpcPort).flatMap(Int.init) ?? 10882
    self.grpcDomainSocket = arguments.string(forKey: Key.grpcDomainSock)
    self.tlsCertPath = arguments.string(forKey: Key.tlsCertPath)
  }
}


/*
 * 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 GRPC
import IDBCompanionUtilities
import IDBGRPCSwift
import NIOHPACK
import SwiftProtobuf
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 interceptorFactory: Idb_CompanionServiceServerInterceptorFactoryProtocol

  init(target: FBiOSTarget,
       commandExecutor: FBIDBCommandExecutor,
       reporter: FBEventReporter,
       logger: FBIDBLogger,
       interceptors: Idb_CompanionServiceServerInterceptorFactoryProtocol) {
    self.target = target
    self.commandExecutor = commandExecutor
    self.reporter = reporter
    self.logger = logger
    self.interceptorFactory = interceptors
  }

  var interceptors: Idb_CompanionServiceServerInterceptorFactoryProtocol? { interceptorFactory }

  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 {
    return try await FBTeardownContext.withAutocleanup {
      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 {
    return try await FBTeardownContext.withAutocleanup {
      try await DebugserverMethodHandler(commandExecutor: commandExecutor)
        .handle(requestStream: requestStream, responseStream: responseStream, context: context)
    }
  }

  func dap(requestStream: GRPCAsyncRequestStream<Idb_DapRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_DapResponse>, context: GRPCAsyncServerCallContext) async throws {
    return try await FBTeardownContext.withAutocleanup {
      try await DapMethodHandler(commandExecutor: commandExecutor, targetLogger: targetLogger)
        .handle(requestStream: requestStream, responseStream: responseStream, context: context)
    }
  }

  func describe(request: Idb_TargetDescriptionRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_TargetDescriptionResponse {
    return try await FBTeardownContext.withAutocleanup {
      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 {
    return try await FBTeardownContext.withAutocleanup {
      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 {
    return try await FBTeardownContext.withAutocleanup {
      try await InstrumentsRunMethodHandler(target: target, targetLogger: targetLogger, commandExecutor: commandExecutor, logger: logger)
        .handle(requestStream: requestStream, responseStream: responseStream, context: context)
    }
  }

  func log(request: Idb_LogRequest, responseStream: GRPCAsyncResponseStreamWriter<Idb_LogResponse>, context: GRPCAsyncServerCallContext) async throws {
    return try await FBTeardownContext.withAutocleanup {
      try await LogMethodHandler(target: target, commandExecutor: commandExecutor)
        .handle(request: request, responseStream: responseStream, context: context)
    }
  }

  func xctrace_record(requestStream: GRPCAsyncRequestStream<Idb_XctraceRecordRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_XctraceRecordResponse>, context: GRPCAsyncServerCallContext) async throws {
    return try await FBTeardownContext.withAutocleanup {
      try await XctraceRecordMethodHandler(logger: logger, targetLogger: targetLogger, target: target)
        .handle(requestStream: requestStream, responseStream: responseStream, context: context)
    }
  }

  func accessibility_info(request: Idb_AccessibilityInfoRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_AccessibilityInfoResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await AccessibilityInfoMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func focus(request: Idb_FocusRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_FocusResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await FocusMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func hid(requestStream: GRPCAsyncRequestStream<Idb_HIDEvent>, context: GRPCAsyncServerCallContext) async throws -> Idb_HIDResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await HidMethodHandler(commandExecutor: commandExecutor)
        .handle(requestStream: requestStream, context: context)
    }
  }

  func open_url(request: Idb_OpenUrlRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_OpenUrlRequest {
    return try await FBTeardownContext.withAutocleanup {
      try await OpenUrlMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func set_location(request: Idb_SetLocationRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SetLocationResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await SetLocationMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func send_notification(request: Idb_SendNotificationRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SendNotificationResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await SendNotificationMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func simulate_memory_warning(request: Idb_SimulateMemoryWarningRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SimulateMemoryWarningResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await SimulateMemoryWarningMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func approve(request: Idb_ApproveRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ApproveResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await ApproveMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func revoke(request: Idb_RevokeRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_RevokeResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await RevokeMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func clear_keychain(request: Idb_ClearKeychainRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ClearKeychainResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await ClearKeychainMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func contacts_update(request: Idb_ContactsUpdateRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ContactsUpdateResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await ContactsUpdateMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func setting(request: Idb_SettingRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_SettingResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await SettingMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func get_setting(request: Idb_GetSettingRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_GetSettingResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await GetSettingMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func list_settings(request: Idb_ListSettingRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ListSettingResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await ListSettingsMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func launch(requestStream: GRPCAsyncRequestStream<Idb_LaunchRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_LaunchResponse>, context: GRPCAsyncServerCallContext) async throws {
    return try await FBTeardownContext.withAutocleanup {
      try await LaunchMethodHandler(commandExecutor: commandExecutor)
        .handle(requestStream: requestStream, responseStream: responseStream, context: context)
    }
  }

  func list_apps(request: Idb_ListAppsRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ListAppsResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await ListAppsMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func terminate(request: Idb_TerminateRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_TerminateResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await TerminateMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func uninstall(request: Idb_UninstallRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_UninstallResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await UninstallMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func add_media(requestStream: GRPCAsyncRequestStream<Idb_AddMediaRequest>, context: GRPCAsyncServerCallContext) async throws -> Idb_AddMediaResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await AddMediaMethodHandler(commandExecutor: commandExecutor)
        .handle(requestStream: requestStream, context: context)
    }
  }

  func record(requestStream: GRPCAsyncRequestStream<Idb_RecordRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_RecordResponse>, context: GRPCAsyncServerCallContext) async throws {
    return try await FBTeardownContext.withAutocleanup {
      try await RecordMethodHandler(target: target, targetLogger: targetLogger)
        .handle(requestStream: requestStream, responseStream: responseStream, context: context)
    }
  }

  func screenshot(request: Idb_ScreenshotRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ScreenshotResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await ScreenshotMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func video_stream(requestStream: GRPCAsyncRequestStream<Idb_VideoStreamRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_VideoStreamResponse>, context: GRPCAsyncServerCallContext) async throws {
    return try await FBTeardownContext.withAutocleanup {
      try await VideoStreamMethodHandler(target: target, targetLogger: targetLogger, commandExecutor: commandExecutor)
        .handle(requestStream: requestStream, responseStream: responseStream, context: context)
    }
  }

  func crash_delete(request: Idb_CrashLogQuery, context: GRPCAsyncServerCallContext) async throws -> Idb_CrashLogResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await CrashDeleteMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func crash_list(request: Idb_CrashLogQuery, context: GRPCAsyncServerCallContext) async throws -> Idb_CrashLogResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await CrashListMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func crash_show(request: Idb_CrashShowRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_CrashShowResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await CrashShowMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func xctest_list_bundles(request: Idb_XctestListBundlesRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_XctestListBundlesResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await XCTestListBundlesMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func xctest_list_tests(request: Idb_XctestListTestsRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_XctestListTestsResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await XCTestListTestsMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func xctest_run(request: Idb_XctestRunRequest, responseStream: GRPCAsyncResponseStreamWriter<Idb_XctestRunResponse>, context: GRPCAsyncServerCallContext) async throws {
    return try await FBTeardownContext.withAutocleanup {
      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 FBTeardownContext.withAutocleanup {
      try await LsMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func mkdir(request: Idb_MkdirRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_MkdirResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await MkdirMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func mv(request: Idb_MvRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_MvResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await MvMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func rm(request: Idb_RmRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_RmResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await RmMethodHandler(commandExecutor: commandExecutor)
        .handle(request: request, context: context)
    }
  }

  func pull(request: Idb_PullRequest, responseStream: GRPCAsyncResponseStreamWriter<Idb_PullResponse>, context: GRPCAsyncServerCallContext) async throws {
    return try await FBTeardownContext.withAutocleanup {
      try await PullMethodHandler(target: target, commandExecutor: commandExecutor)
        .handle(request: request, responseStream: responseStream, context: context)
    }
  }

  func push(requestStream: GRPCAsyncRequestStream<Idb_PushRequest>, context: GRPCAsyncServerCallContext) async throws -> Idb_PushResponse {
    return try await FBTeardownContext.withAutocleanup {
      try await PushMethodHandler(target: target, commandExecutor: commandExecutor)
        .handle(requestStream: requestStream, context: context)
    }
  }

  func tail(requestStream: GRPCAsyncRequestStream<Idb_TailRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_TailResponse>, context: GRPCAsyncServerCallContext) async throws {
    return try await FBTeardownContext.withAutocleanup {
      try await TailMethodHandler(commandExecutor: commandExecutor)
        .handle(requestStream: requestStream, responseStream: responseStream, context: context)
    }
  }
}


/*
 * 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 FBControlCore
import Foundation
import GRPC
import IDBGRPCSwift
import NIOCore
import NIOPosix
import NIOSSL

struct IDBUnixDomainSocketPathWrongType: Error {}

@objc
final class GRPCSwiftServer: NSObject {

  private struct TLSCertificates {
    let certificates: [NIOSSLCertificateSource]
    let privateKey: NIOSSLPrivateKeySource
  }

  private var server: EventLoopFuture<Server>?
  private let provider: CallHandlerProvider
  private let logger: FBIDBLogger

  private let serverConfig: Server.Configuration
  private let ports: IDBPortsConfiguration

  @objc
  let completed: FBMutableFuture<NSNull>

  @objc
  init(target: FBiOSTarget,
       commandExecutor: FBIDBCommandExecutor,
       reporter: FBEventReporter,
       logger: FBIDBLogger,
       ports: IDBPortsConfiguration) throws {

    let group = MultiThreadedEventLoopGroup(numberOfThreads: 4)
    let tlsCerts = Self.loadCertificates(tlsCertPath: ports.tlsCertPath, logger: logger)

    let interceptors = CompanionServiceInterceptors(logger: logger, reporter: reporter)

    self.provider = CompanionServiceProvider(target: target,
                                             commandExecutor: commandExecutor,
                                             reporter: reporter,
                                             logger: logger,
                                             interceptors: interceptors)

    var serverConfiguration = Server.Configuration.default(target: ports.swiftServerTarget.grpcConnection,
                                                           eventLoopGroup: group,
                                                           serviceProviders: [provider])
    serverConfiguration.maximumReceiveMessageLength = 16777216

    if ports.swiftServerTarget.supportsTLSCert {
      serverConfiguration.tlsConfiguration = tlsCerts.map {
        GRPCTLSConfiguration.makeServerConfigurationBackedByNIOSSL(certificateChain: $0.certificates, privateKey: $0.privateKey)
      }
    }

    serverConfiguration.errorDelegate = GRPCSwiftServerErrorDelegate()
    self.serverConfig = serverConfiguration
    self.ports = ports

    self.completed = FBMutableFuture<NSNull>()
    self.logger = logger

    super.init()
  }

  @objc func start() -> FBMutableFuture<NSDictionary> {
    // Start the server and print its address once it has started.
    let future = FBMutableFuture<NSDictionary>()

    if case .unixDomainSocket(let path) = ports.swiftServerTarget {
      do {
        try cleanupUnixDomainSocket(path: path)
      } catch {
        self.logger.error().log("\(error)")
        future.resolveWithError(error)
        return future
      }
    }

    let server = Server.start(configuration: serverConfig)
    self.server = server

    logger.info().log("Starting swift server on \(ports.swiftServerTarget)")
    if let tlsPath = ports.tlsCertPath, !tlsPath.isEmpty {
      logger.info().log("Starting swift server with TLS path \(tlsPath)")
    }

    server.map(\.channel.localAddress).whenComplete { [weak self, ports] result in
      do {
        let address = try result.get()
        self?.logServerStartup(address: address)
        try future.resolve(withResult: ports.swiftServerTarget.outputDescription(for: address) as NSDictionary)
      } catch {
        self?.logger.error().log("\(error)")
        future.resolveWithError(error)
      }
    }

    server.flatMap(\.onClose).whenCompleteBlocking(onto: .main) { [completed] _ in
      self.logger.info().log("Server closed")
      completed.resolve(withResult: NSNull())
    }

    return future
  }

  private func cleanupUnixDomainSocket(path: String) throws {
    do {
      self.logger.info().log("Cleaning up UDS if exists")
      var sb: stat = stat()
      try withUnsafeMutablePointer(to: &sb) { sbPtr in
        try syscall {
          stat(path, sbPtr)
        }
      }

      // Only unlink the existing file if it is a socket
      if sb.st_mode & S_IFSOCK == S_IFSOCK {
        self.logger.info().log("Existed UDS socket found, unlinking")
        try syscall {
          unlink(path)
        }
        self.logger.info().log("UDS socket cleaned up")
      } else {
        throw IDBUnixDomainSocketPathWrongType()
      }
    } catch let err as IOError {
      // If the filepath did not exist, we consider it cleaned up
      if err.errnoCode == ENOENT {
        return
      }
      throw err
    }
  }

  private func syscall(function: String = #function, _ body: () throws -> Int32) throws {
    while true {
      let res = try body()
      if res == -1 {
        let err = errno
        switch err {
        case EINTR:
          continue
        default:
          throw IOError(errnoCode: err, reason: function)
        }
      }
      return
    }
  }

  private func logServerStartup(address: SocketAddress?) {
    let message = "Swift server started on "
    if let address {
      logger.info().log(message + address.description)
    } else {
      logger.error().log(message + " unknown address")
    }
  }

  private static func loadCertificates(tlsCertPath: String?, logger: FBIDBLogger) -> TLSCertificates? {
    guard let tlsPath = tlsCertPath,
          !tlsPath.isEmpty
    else { return nil }

    let tlsURL = URL(fileURLWithPath: tlsPath)
    do {
      let rawCert = try Data(contentsOf: tlsURL)

      let certificate = try NIOSSLCertificateSource.certificate(.init(bytes: [UInt8](rawCert), format: .pem))
      let privateKey = try NIOSSLPrivateKeySource.privateKey(.init(bytes: [UInt8](rawCert), format: .pem))

      return TLSCertificates(
        certificates: [certificate],
        privateKey: privateKey
      )
    } catch {
      logger.error().log("Unable to load tls certificate. Error: \(error)")
      fatalError("Unable to load tls certificate. Error: \(error)")
    }
  }
}


/*
 * 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 GRPC
import NIOHPACK

final class GRPCSwiftServerErrorDelegate: ServerErrorDelegate {

  func transformRequestHandlerError(_ error: Error, headers: HPACKHeaders) -> GRPCStatusAndTrailers? {
    if error is GRPCStatus || error is GRPCStatusTransformable {
      // Use default error propagation transformation
      return nil
    }

    var message = error.localizedDescription
    if type(of: error) == NSError.self {
      // Legacy NSError from objc, we should unwrap it for more expressive error handling.
      // Don't use `is NSError` check because all swift errors bridges to NSError successfully and this check passed

      message = extractMessage(fromLegacyNSError: error as NSError)
    }
    return GRPCStatusAndTrailers(status: GRPCStatus(code: .internalError, message: message))
  }

  private func extractMessage(fromLegacyNSError error: NSError) -> String {
    var userInfo = error.userInfo

    var message: String
    if let localizedDescription = userInfo.removeValue(forKey: NSLocalizedDescriptionKey) as? String {
      message = localizedDescription
    } else {
      message = error.description
    }
    if !userInfo.isEmpty {
      message += "\nInfo: \(userInfo)"
    }
    return message
  }
}


/*
 * 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 GRPC
import IDBGRPCSwift

// By design in grpc-swift we should provide interceptor for each method separately.
// This gives us ability to precicely control which interceptors will be used for concrete method from one side,
// but make it too explicit from other side.
final class CompanionServiceInterceptors: Idb_CompanionServiceServerInterceptorFactoryProtocol {

  private let logger: FBIDBLogger
  private let reporter: FBEventReporter

  init(logger: FBIDBLogger, reporter: FBEventReporter) {
    self.logger = logger
    self.reporter = reporter
  }

  private func commonInterceptors<Request, Response>() -> [ServerInterceptor<Request, Response>] {
    [MethodInfoSetterInterceptor(),
     LoggingInterceptor(logger: logger, reporter: reporter)]
  }

  func makeconnectInterceptors() -> [ServerInterceptor<Idb_ConnectRequest, Idb_ConnectResponse>] {
    commonInterceptors()
  }

  func makedebugserverInterceptors() -> [ServerInterceptor<Idb_DebugServerRequest, Idb_DebugServerResponse>] {
    commonInterceptors()
  }

  func makedapInterceptors() -> [ServerInterceptor<Idb_DapRequest, Idb_DapResponse>] {
    commonInterceptors()
  }

  func makedescribeInterceptors() -> [ServerInterceptor<Idb_TargetDescriptionRequest, Idb_TargetDescriptionResponse>] {
    commonInterceptors()
  }

  func makeinstallInterceptors() -> [ServerInterceptor<Idb_InstallRequest, Idb_InstallResponse>] {
    commonInterceptors()
  }

  func makeinstruments_runInterceptors() -> [ServerInterceptor<Idb_InstrumentsRunRequest, Idb_InstrumentsRunResponse>] {
    commonInterceptors()
  }

  func makelogInterceptors() -> [ServerInterceptor<Idb_LogRequest, Idb_LogResponse>] {
    commonInterceptors()
  }

  func makexctrace_recordInterceptors() -> [ServerInterceptor<Idb_XctraceRecordRequest, Idb_XctraceRecordResponse>] {
    commonInterceptors()
  }

  func makeaccessibility_infoInterceptors() -> [ServerInterceptor<Idb_AccessibilityInfoRequest, Idb_AccessibilityInfoResponse>] {
    commonInterceptors()
  }

  func makefocusInterceptors() -> [ServerInterceptor<Idb_FocusRequest, Idb_FocusResponse>] {
    commonInterceptors()
  }

  func makehidInterceptors() -> [ServerInterceptor<Idb_HIDEvent, Idb_HIDResponse>] {
    commonInterceptors()
  }

  func makeopen_urlInterceptors() -> [ServerInterceptor<Idb_OpenUrlRequest, Idb_OpenUrlRequest>] {
    commonInterceptors()
  }

  func makeset_locationInterceptors() -> [ServerInterceptor<Idb_SetLocationRequest, Idb_SetLocationResponse>] {
    commonInterceptors()
  }

  func makesend_notificationInterceptors() -> [ServerInterceptor<Idb_SendNotificationRequest, Idb_SendNotificationResponse>] {
    commonInterceptors()
  }

  func makesimulate_memory_warningInterceptors() -> [ServerInterceptor<Idb_SimulateMemoryWarningRequest, Idb_SimulateMemoryWarningResponse>] {
    commonInterceptors()
  }

  func makeapproveInterceptors() -> [ServerInterceptor<Idb_ApproveRequest, Idb_ApproveResponse>] {
    commonInterceptors()
  }

  func makerevokeInterceptors() -> [ServerInterceptor<Idb_RevokeRequest, Idb_RevokeResponse>] {
    commonInterceptors()
  }

  func makeclear_keychainInterceptors() -> [ServerInterceptor<Idb_ClearKeychainRequest, Idb_ClearKeychainResponse>] {
    commonInterceptors()
  }

  func makecontacts_updateInterceptors() -> [ServerInterceptor<Idb_ContactsUpdateRequest, Idb_ContactsUpdateResponse>] {
    commonInterceptors()
  }

  func makesettingInterceptors() -> [ServerInterceptor<Idb_SettingRequest, Idb_SettingResponse>] {
    commonInterceptors()
  }

  func makeget_settingInterceptors() -> [ServerInterceptor<Idb_GetSettingRequest, Idb_GetSettingResponse>] {
    commonInterceptors()
  }

  func makelist_settingsInterceptors() -> [ServerInterceptor<Idb_ListSettingRequest, Idb_ListSettingResponse>] {
    commonInterceptors()
  }

  func makelaunchInterceptors() -> [ServerInterceptor<Idb_LaunchRequest, Idb_LaunchResponse>] {
    commonInterceptors()
  }

  func makelist_appsInterceptors() -> [ServerInterceptor<Idb_ListAppsRequest, Idb_ListAppsResponse>] {
    commonInterceptors()
  }

  func maketerminateInterceptors() -> [ServerInterceptor<Idb_TerminateRequest, Idb_TerminateResponse>] {
    commonInterceptors()
  }

  func makeuninstallInterceptors() -> [ServerInterceptor<Idb_UninstallRequest, Idb_UninstallResponse>] {
    commonInterceptors()
  }

  func makeadd_mediaInterceptors() -> [ServerInterceptor<Idb_AddMediaRequest, Idb_AddMediaResponse>] {
    commonInterceptors()
  }

  func makerecordInterceptors() -> [ServerInterceptor<Idb_RecordRequest, Idb_RecordResponse>] {
    commonInterceptors()
  }

  func makescreenshotInterceptors() -> [ServerInterceptor<Idb_ScreenshotRequest, Idb_ScreenshotResponse>] {
    commonInterceptors()
  }

  func makevideo_streamInterceptors() -> [ServerInterceptor<Idb_VideoStreamRequest, Idb_VideoStreamResponse>] {
    commonInterceptors()
  }

  func makecrash_deleteInterceptors() -> [ServerInterceptor<Idb_CrashLogQuery, Idb_CrashLogResponse>] {
    commonInterceptors()
  }

  func makecrash_listInterceptors() -> [ServerInterceptor<Idb_CrashLogQuery, Idb_CrashLogResponse>] {
    commonInterceptors()
  }

  func makecrash_showInterceptors() -> [ServerInterceptor<Idb_CrashShowRequest, Idb_CrashShowResponse>] {
    commonInterceptors()
  }

  func makexctest_list_bundlesInterceptors() -> [ServerInterceptor<Idb_XctestListBundlesRequest, Idb_XctestListBundlesResponse>] {
    commonInterceptors()
  }

  func makexctest_list_testsInterceptors() -> [ServerInterceptor<Idb_XctestListTestsRequest, Idb_XctestListTestsResponse>] {
    commonInterceptors()
  }

  func makexctest_runInterceptors() -> [ServerInterceptor<Idb_XctestRunRequest, Idb_XctestRunResponse>] {
    commonInterceptors()
  }

  func makelsInterceptors() -> [ServerInterceptor<Idb_LsRequest, Idb_LsResponse>] {
    commonInterceptors()
  }

  func makemkdirInterceptors() -> [ServerInterceptor<Idb_MkdirRequest, Idb_MkdirResponse>] {
    commonInterceptors()
  }

  func makemvInterceptors() -> [ServerInterceptor<Idb_MvRequest, Idb_MvResponse>] {
    commonInterceptors()
  }

  func makermInterceptors() -> [ServerInterceptor<Idb_RmRequest, Idb_RmResponse>] {
    commonInterceptors()
  }

  func makepullInterceptors() -> [ServerInterceptor<Idb_PullRequest, Idb_PullResponse>] {
    commonInterceptors()
  }

  func makepushInterceptors() -> [ServerInterceptor<Idb_PushRequest, Idb_PushResponse>] {
    commonInterceptors()
  }

  func maketailInterceptors() -> [ServerInterceptor<Idb_TailRequest, Idb_TailResponse>] {
    commonInterceptors()
  }
}


/*
 * 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 GRPC
import NIO

private enum MethodStartKey: UserInfo.Key {
  typealias Value = Date
}

final class LoggingInterceptor<Request, Response>: ServerInterceptor<Request, Response> {

  private let logger: FBIDBLogger
  private let reporter: FBEventReporter

  init(logger: FBIDBLogger, reporter: FBEventReporter) {
    self.logger = logger
    self.reporter = reporter
  }

  // MARK: Request start + incoming frames

  override func receive(_ part: GRPCServerRequestPart<Request>, context: ServerInterceptorContext<Request, Response>) {
    guard let methodInfo = context.userInfo[MethodInfoKey.self] else {
      assertionFailure("MethodInfoKey is empty, you have incorrect interceptor order")
      super.receive(part, context: context)
      return
    }

    switch part {
    case .metadata:
      saveStartDate(in: context)
      reportMethodStart(methodName: methodInfo.name, in: context)

    case .message where methodInfo.callType == .clientStreaming || methodInfo.callType == .bidirectionalStreaming:
      logger.debug().log("Receive frame of \(methodInfo.name)")

    case .end where methodInfo.callType == .clientStreaming || methodInfo.callType == .bidirectionalStreaming:
      logger.debug().log("Close client stream of \(methodInfo.name)")

    default:
      break
    }

    super.receive(part, context: context)
  }

  private func saveStartDate(in context: ServerInterceptorContext<Request, Response>) {
    context.userInfo[MethodStartKey.self] = Date()
  }

  private func reportMethodStart(methodName: String, in context: ServerInterceptorContext<Request, Response>) {
    logger.info().log("Start of \(methodName)")
    let subject = FBEventReporterSubject(forStartedCall: methodName, arguments: [], reportNativeSwiftMethodCall: true)
    reporter.report(subject)
  }

  // MARK: Request end + outgoing frames

  override func send(_ part: GRPCServerResponsePart<Response>, promise: EventLoopPromise<Void>?, context: ServerInterceptorContext<Request, Response>) {
    guard let methodInfo = context.userInfo[MethodInfoKey.self] else {
      assertionFailure("MethodInfoKey is empty, you have incorrect interceptor order")
      super.send(part, promise: promise, context: context)
      return
    }

    switch part {
    case let .end(status, _):
      reportMethodEnd(methodName: methodInfo.name, status: status, context: context)

    default:
      break
    }

    super.send(part, promise: promise, context: context)
  }

  private func reportMethodEnd(methodName: String, status: GRPCStatus, context: ServerInterceptorContext<Request, Response>) {
    let duration = getMethodDuration(context: context)

    let subject: FBEventReporterSubject
    if status.isOk {
      logger.debug().log("Success of \(methodName)")
      subject = FBEventReporterSubject(forSuccessfulCall: methodName, duration: duration, size: nil, arguments: [], reportNativeSwiftMethodCall: true)
    } else {
      logger.info().log("Failure of \(methodName), \(status)")
      subject = FBEventReporterSubject(forFailingCall: methodName, duration: duration, message: status.message ?? "Unknown error with code \(status.code)", size: nil, arguments: [], reportNativeSwiftMethodCall: true)
    }

    reporter.report(subject)
  }

  private func getMethodDuration(context: ServerInterceptorContext<Request, Response>) -> TimeInterval {
    guard let methodStartDate = context.userInfo[MethodStartKey.self] else {
      assertionFailure("\(MethodStartKey.self) is not configured on request start")
      return 0
    }
    return Date().timeIntervalSince(methodStartDate)
  }
}


/*
 * 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 GRPC
import IDBCompanionUtilities
import IDBGRPCSwift

enum MethodInfoKey: UserInfo.Key {
  typealias Value = GRPCMethodInfo
}

struct GRPCMethodInfo {
  let name: String
  let path: String
  let callType: GRPCCallType
}

final class MethodInfoSetterInterceptor<Request, Response>: ServerInterceptor<Request, Response> {

  @Atomic var methodDescriptors: [String: GRPCMethodDescriptor] = Idb_CompanionServiceServerMetadata
    .serviceDescriptor
    .methods
    .reduce(into: [:]) { $0[$1.path] = $1 }

  override func receive(_ part: GRPCServerRequestPart<Request>, context: ServerInterceptorContext<Request, Response>) {
    switch part {
    case .metadata:
      let methodInfo: GRPCMethodInfo
      if let methodDescriptor = methodDescriptors[context.path] {
        methodInfo = GRPCMethodInfo(name: methodDescriptor.name,
                                    path: methodDescriptor.path,
                                    callType: methodDescriptor.type)
      } else {
        assertionFailure("Method not found in descriptors list. If this is client and companion version mismatch, ignore that error")
        // context.callType is not reported correctly in ServerInterceptorContext and always return .bidirectionalStreaming
        methodInfo = GRPCMethodInfo(name: String(extractMethodName(path: context.path)),
                                    path: context.path,
                                    callType: context.type)
      }
      context.userInfo[MethodInfoKey.self] = methodInfo

    default:
      break
    }

    super.receive(part, context: context)
  }

  private func extractMethodName(path: String) -> Substring {
    path
      .suffix(from: path.lastIndex(of: "/")!)
      .dropFirst()
  }
}


/*
 * 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 FBControlCore
import Foundation
import GRPC
import IDBGRPCSwift

struct AccessibilityInfoMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_AccessibilityInfoRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_AccessibilityInfoResponse {
    var point: NSValue?
    if request.hasPoint {
      point = NSValue(point: .init(x: request.point.x, y: request.point.y))
    }
    let nested = request.format == .nested
    let info = try await BridgeFuture.value(commandExecutor.accessibility_info_(at_point: point, nestedFormat: nested))
    let jsonData = try JSONSerialization.data(withJSONObject: info)
    return .with {
      $0.json = String(data: jsonData, encoding: .utf8) ?? ""
    }
  }
}


/*
 * 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 GRPC
import IDBGRPCSwift

struct AddMediaMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(requestStream: GRPCAsyncRequestStream<Idb_AddMediaRequest>, context: GRPCAsyncServerCallContext) async throws -> Idb_AddMediaResponse {
    let extractedFileURLs = try await MultisourceFileReader
      .filePathURLs(from: requestStream, temporaryDirectory: commandExecutor.temporaryDirectory, extractFromSubdir: true)

    try await BridgeFuture.await(commandExecutor.add_media(extractedFileURLs))
    return .init()
  }
}


/*
 * 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 GRPC
import IDBGRPCSwift

struct ApproveMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_ApproveRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ApproveResponse {

    // Swift implements custom bridging logic for NSNotificationName and this causes ALL string enums ended with "Notification"
    // suffix translates in this special way
    let notificationApprovalService = FBTargetSettingsService.FBTargetSettingsService

    let mapping: [Idb_ApproveRequest.Permission: FBTargetSettingsService] = [
      .microphone: .microphone,
      .photos: .photos,
      .camera: .camera,
      .contacts: .contacts,
      .url: .url,
      .location: .location,
      .notification: notificationApprovalService,
    ]

    var services = try Set(
      request.permissions.map { permission -> FBTargetSettingsService in
        guard let service = mapping[permission] else {
          throw GRPCStatus(code: .invalidArgument, message: "Unrecognized permission \(permission)")
        }
        return service
      }
    )
    if services.contains(.url) {
      services.remove(.url)
      try await BridgeFuture.await(commandExecutor.approve_deeplink(request.scheme, for_application: request.bundleID))
    }

    if !services.isEmpty {
      try await BridgeFuture.await(commandExecutor.approve(services, for_application: request.bundleID))
    }
    return .init()
  }
}


/*
 * 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 GRPC
import IDBGRPCSwift

struct ClearKeychainMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_ClearKeychainRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ClearKeychainResponse {
    try await BridgeFuture.await(commandExecutor.clear_keychain())
    return .init()
  }
}


/*
 * 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 GRPC
import IDBGRPCSwift

struct ConnectMethodHandler {

  let reporter: FBEventReporter
  let logger: FBIDBLogger
  let target: FBiOSTarget

  func handle(request: Idb_ConnectRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ConnectResponse {
    self.reporter.addMetadata(request.metadata)
    let isLocal = FileManager.default.fileExists(atPath: request.localFilePath)

    return Idb_ConnectResponse.with {
      $0.companion = .with {
        $0.udid = target.udid
        $0.isLocal = isLocal

        do {
          $0.metadata = try JSONSerialization.data(withJSONObject: reporter.metadata, options: [])
        } catch {
          logger.error().log("Error while serializing metadata \(error.localizedDescription)")
        }
      }
    }
  }
}


/*
 * 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 GRPC
import IDBGRPCSwift

struct ContactsUpdateMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_ContactsUpdateRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_ContactsUpdateResponse {
    try await BridgeFuture.await(commandExecutor.update_contacts(request.payload.data))
    return .init()
  }
}


/*
 * 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 GRPC
import IDBGRPCSwift

struct CrashDeleteMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_CrashLogQuery, context: GRPCAsyncServerCallContext) async throws -> Idb_CrashLogResponse {
    let predicate = CrashLogQueryValueTransformer.predicate(from: request)
    let crashes: [FBCrashLogInfo] = try await BridgeFuture.value(commandExecutor.crash_delete(predicate))
    return .with {
      $0.list = crashes.map(CrashLogInfoValueTransformer.responseCrashLogInfo(from:))
    }
  }
}


/*
 * 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 GRPC
import IDBGRPCSwift

struct CrashListMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_CrashLogQuery, context: GRPCAsyncServerCallContext) async throws -> Idb_CrashLogResponse {
    let predicate = CrashLogQueryValueTransformer.predicate(from: request)
    let crashes: [FBCrashLogInfo] = try await BridgeFuture.value(commandExecutor.crash_list(predicate))
    return Idb_CrashLogResponse.with {
      $0.list = crashes.map(CrashLogInfoValueTransformer.responseCrashLogInfo(from:))
    }
  }
}


/*
 * 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 GRPC
import IDBGRPCSwift

struct CrashShowMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_CrashShowRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_CrashShowResponse {
    guard !request.name.isEmpty else {
      throw GRPCStatus(code: .invalidArgument, message: "Missing crash name")
    }

    let predicate = FBCrashLogInfo.predicate(forName: request.name)
    let crash = try await BridgeFuture.value(commandExecutor.crash_show(predicate))
    return .with {
      $0.info = CrashLogInfoValueTransformer.responseCrashLogInfo(from: crash.info)
      $0.contents = crash.contents
    }
  }
}


/*
 * 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 FBControlCore
import FBSimulatorControl
import GRPC
import IDBCompanionUtilities
import IDBGRPCSwift

struct DapMethodHandler {

  let commandExecutor: FBIDBCommandExecutor
  let targetLogger: FBControlCoreLogger

  func handle(requestStream: GRPCAsyncRequestStream<Idb_DapRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_DapResponse>, context: GRPCAsyncServerCallContext) async throws {
    guard case let .start(start) = try await requestStream.requiredNext.control
    else { throw GRPCStatus(code: .failedPrecondition, message: "Dap command expected a Start messaged in the beginning of the Stream") }

    let writer = FBProcessInput<FBDataConsumer>.fromConsumer() as! FBProcessInput<AnyObject>
    let dapProcess = try await startDapServer(startRequest: start, processInput: writer, responseStream: responseStream)

    let tenHours: UInt64 = 36000 * 1000000000
    try await Task.timeout(nanoseconds: tenHours) {
      try await consumeElements(from: requestStream, to: writer, dapProcess: dapProcess)
    }

    let stoppedResponse = Idb_DapResponse.with {
      $0.event = .stopped(
        .with { $0.desc = "Dap server stopped" }
      )
    }
    try await responseStream.send(stoppedResponse)
  }

  private func startDapServer(startRequest: Idb_DapRequest.Start, processInput: FBProcessInput<AnyObject>, responseStream: GRPCAsyncResponseStreamWriter<Idb_DapResponse>) async throws -> FBProcess<AnyObject, FBDataConsumer, NSString> {

    let lldbVSCode = "dap/\(startRequest.debuggerPkgID)/usr/bin/lldb-vscode"

    let stdOutConsumer = createDataConsumer(to: responseStream)
    targetLogger.debug().log("Starting dap server with path \(lldbVSCode)")

    let tenMinutes: UInt64 = 600 * 1000000000
    let process = try await Task.timeout(nanoseconds: tenMinutes) {
      try await BridgeFuture.value(commandExecutor.dapServer(withPath: lldbVSCode, stdIn: processInput, stdOut: stdOutConsumer))
    }

    targetLogger.debug().log("Dap server spawn with PID: \(process.processIdentifier)")
    let serverStartedResponse = Idb_DapResponse.with {
      $0.event = .started(.init())
    }
    try await responseStream.send(serverStartedResponse)

    return process
  }

  private func consumeElements(from requestStream: GRPCAsyncRequestStream<Idb_DapRequest>, to writer: FBProcessInput<AnyObject>, dapProcess: FBProcess<AnyObject, FBDataConsumer, NSString>) async throws {
    for try await request in requestStream {
      switch request.control {
      case .start:
        throw GRPCStatus(code: .failedPrecondition, message: "DAP server already started")

      case .none:
        throw GRPCStatus(code: .invalidArgument, message: "Empty control in request")

      case let .pipe(pipe):
        guard !pipe.data.isEmpty else {
          targetLogger.debug().log("Dap request received empty message. Transmission finished")
          return
        }
        targetLogger.debug().log("Dap Request. Received \(pipe.data.count) bytes from client")
        writer.contents.consumeData(pipe.data)

      case .stop:
        targetLogger.debug().log("Received stop from Dap Request")
        targetLogger.debug().log("Stopping dap server with pid \(dapProcess.processIdentifier). Stderr: \(dapProcess.stdErr ?? "Empty")")
        return
      }
    }
  }

  private func createDataConsumer(to responseStream: GRPCAsyncResponseStreamWriter<Idb_DapResponse>) -> FBDataConsumer {
    let responseWriter = FIFOStreamWriter(stream: responseStream)

    return FBBlockDataConsumer.synchronousDataConsumer { data in
      let response = Idb_DapResponse.with {
        $0.event = .stdout(
          .with { $0.data = data }
        )
      }
      do {
        try responseWriter.send(response)
        targetLogger.debug().log("Dap server stdout consumer: sent \(data.count) bytes.")
      } catch {
        targetLogger.debug().log("Dap server stdout consumer: error \(error) when tried to send bytes.")
      }
    }
  }
}


/*
 * 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 FBControlCore
import GRPC
import IDBGRPCSwift

struct DebugserverMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(requestStream: GRPCAsyncRequestStream<Idb_DebugServerRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_DebugServerResponse>, context: GRPCAsyncServerCallContext) async throws {

    for try await request in requestStream {
      switch request.control {
      case let .start(start):
        let debugServer = try await BridgeFuture.value(commandExecutor.debugserver_start(start.bundleID))
        try await responseStream.send(debugserverStatusToProto(debugServer: debugServer))
        return

      case .status:
        // Replicates old cpp server behaviour. We should return `0` exit code if server not started
        if let debugServer = try? await BridgeFuture.value(commandExecutor.debugserver_status()) {
          try await responseStream.send(debugserverStatusToProto(debugServer: debugServer))
        } else {
          try await responseStream.send(.init())
        }
        return

      case .stop:
        let debugServer = try await BridgeFuture.value(commandExecutor.debugserver_stop())
        try await responseStream.send(debugserverStatusToProto(debugServer: debugServer))
        return

      case .pipe:
        throw GRPCStatus(code: .unimplemented)

      case .none:
        throw GRPCStatus(code: .invalidArgument, message: "Received empty control")
      }
    }
  }

  private func debugserverStatusToProto(debugServer: FBDebugServer) -> Idb_DebugServerResponse {
    return .with {
      $0.status = .with {
        $0.lldbBootstrapCommands = debugServer.lldbBootstrapCommands
      }
    }
  }
}


/*
 * 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 FBControlCore
import Foundation
import GRPC
import IDBGRPCSwift

struct DescribeMethodHandler {

  let reporter: FBEventReporter
  let logger: FBIDBLogger
  let target: FBiOSTarget
  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_TargetDescriptionRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_TargetDescriptionResponse {
    var response = Idb_TargetDescriptionResponse.with {
      $0.targetDescription = .with {
        $0.udid = target.udid
        $0.name = target.name
        $0.state = FBiOSTargetStateStringFromState(target.state).rawValue
        $0.targetType = FBiOSTargetTypeStringFromTargetType(target.targetType).lowercased()
        $0.osVersion = target.osVersion.name.rawValue
        if let screenInfo = target.screenInfo {
          $0.screenDimensions = .with {
            $0.width = UInt64(screenInfo.widthPixels)
            $0.widthPoints = $0.width / UInt64(screenInfo.scale)
            $0.height = UInt64(screenInfo.heightPixels)
            $0.heightPoints = $0.height / UInt64(screenInfo.scale)
            $0.density = Double(screenInfo.scale)
          }
        }
        if let extData = try? JSONSerialization.data(withJSONObject: target.extendedInformation) {
          $0.extended = extData
        }
      }
      $0.companion = Idb_CompanionInfo.with {
        $0.udid = target.udid
        if let metadata = try? JSONSerialization.data(withJSONObject: reporter.metadata) {
          $0.metadata = metadata
        }
      }
    }

    guard request.fetchDiagnostics else {
      return response
    }

    let diagnosticInformation = try await BridgeFuture.value(commandExecutor.diagnostic_information())
    let diagnosticInfoData = try JSONSerialization.data(withJSONObject: diagnosticInformation)
    response.targetDescription.diagnostics = diagnosticInfoData

    return response
  }

  private func populateCompanionInfo(info: inout Idb_CompanionInfo) throws {
    info.udid = target.udid
    let data = try JSONSerialization.data(withJSONObject: reporter.metadata, options: [])
    info.metadata = data
  }
}


/*
 * 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 GRPC
import IDBGRPCSwift

struct FocusMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_FocusRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_FocusResponse {
    try await BridgeFuture.await(commandExecutor.focus())
    return .init()
  }
}


/*
 * 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 GRPC
import IDBGRPCSwift

struct GetSettingMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(request: Idb_GetSettingRequest, context: GRPCAsyncServerCallContext) async throws -> Idb_GetSettingResponse {
    switch request.setting {
    case .locale:
      let localeIdentifier = try await BridgeFuture.value(commandExecutor.get_current_locale_identifier())
      return .with {
        $0.value = localeIdentifier as String
      }
    case .any:
      let domain = request.domain.isEmpty ? nil : request.domain
      let value = try await BridgeFuture.value(commandExecutor.get_preference(request.name, domain: domain))
      return .with {
        $0.value = value as String
      }
    case .UNRECOGNIZED:
      throw GRPCStatus(code: .invalidArgument, message: "Unknown setting case")
    }
  }
}


/*
 * 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 FBSimulatorControl
import GRPC
import IDBGRPCSwift

struct HidMethodHandler {

  let commandExecutor: FBIDBCommandExecutor

  func handle(requestStream: GRPCAsyncRequestStream<Idb_HIDEvent>, context: GRPCAsyncServerCallContext) async throws -> Idb_HIDResponse {
    for try await request in requestStream {
      let event = try fbSimulatorHIDEvent(from: request)
      try await BridgeFuture.await(commandExecutor.hid(event))
    }
    return .init()
  }

  private func fbSimulatorHIDEvent(from request: Idb_HIDEvent) throws -> FBSimulatorHIDEvent {
    switch request.event {
    case let .press(press):
      switch press.action.action {
      case let .key(key):
        switch press.direction {
        case .up:
          return .keyUp(UInt32(key.keycode))
        case .down:
          return .keyDown(UInt32(key.keycode))
        case .UNRECOGNIZED:
          throw GRPCStatus(code: .invalidArgument, message: "Unrecognized press.direction")
        }

      case let .button(button):
        guard let hidButton = fbSimulatorHIDButton(from: button.button) else {
          throw GRPCStatus(code: .invalidArgument, message: "Unrecognized hid button type")
        }
        switch press.direction {
        case .up:
          return .buttonUp(hidButton)
        case .down:
          return .buttonDown(hidButton)
        case .UNRECOGNIZED:
          throw GRPCStatus(code: .invalidArgument, message: "Unrecognized press.direction")
        }

      case let .touch(touch):
        switch press.direction {
        case .up:
          return .touchUpAt(x: touch.point.x, y: touch.point.y)
        case .down:
          return .touchDownAt(x: touch.point.x, y: touch.point.y)
        case .UNRECOGNIZED:
          throw GRPCStatus(code: .invalidArgument, message: "Unrecognized press.direction")
        }

      case .none:
        throw GRPCStatus(code: .invalidArgument, message: "Unrecognized press.action")
      }

    case let .swipe(swipe):
      return .swipe(swipe.start.x,
                    yStart: swipe.start.y,
                    xEnd: swipe.end.x,
                    yEnd: swipe.end.y,
                    delta: swipe.delta,
                    duration: swipe.duration)

    case let .delay(delay):
      return .delay(delay.duration)

    case .none:
      throw GRPCStatus(code: .invalidArgument, message: "Unrecognized request.event")
    }
  }

  private func fbSimulatorHIDButton(from request: Idb_HIDEvent.HIDButtonType) -> FBSimulatorHIDButton? {
    switch request {
    case .applePay:
      return .applePay
    case .home:
      return .homeButton
    case .lock:
      return .lock
    case .sideButton:
      return .sideButton
    case .siri:
      return .siri
    case .UNRECOGNIZED:
      return nil
    }
  }
}


/*
 * 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 FBControlCore
import Foundation
import GRPC
import IDBGRPCSwift

struct InstallMethodHandler {

  let commandExecutor: FBIDBCommandExecutor
  let targetLogger: FBControlCoreLogger

  func handle(requestStream: GRPCAsyncRequestStream<Idb_InstallRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_InstallResponse>, context: GRPCAsyncServerCallContext) async throws {

    let artifact = try await install(requestStream: requestStream, responseStream: responseStream)

    let response = Idb_InstallResponse.with {
      $0.name = artifact.name
      $0.uuid = artifact.uuid?.uuidString ?? ""
    }
    try await responseStream.send(response)
  }

  private func install(requestStream: GRPCAsyncRequestStream<Idb_InstallRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_InstallResponse>) async throws -> FBInstalledArtifact {

    func extractPayloadFromRequest() throws -> Idb_Payload {
      guard let payload = request.extractPayload() else {
        throw GRPCStatus(code: .invalidArgument, message: "Expected the next item in the stream to be a payload")
      }
      return payload
    }

    var request = try await requestStream.requiredNext

    guard case let .destination(destination) = request.value else {
      throw GRPCStatus(code: .failedPrecondition, message: "Expected destination as first request in stream")
    }
    request = try await requestStream.requiredNext

    var name = UUID().uuidString
    if case let .nameHint(nameHint) = request.value {
      name = nameHint
      request = try await requestStream.requiredNext
    }

    var makeDebuggable = false
    if case let .makeDebuggable(debuggable) = request.value {
      makeDebuggable = debuggable
      request = try await requestStream.requiredNext
    }
    var overrideModificationTime = false
    if case let .overrideModificationTime(omtime) = request.value {
      overrideModificationTime = omtime
      request = try await requestStream.requiredNext
    }

    var skipSigningBundles = false
    if case let .skipSigningBundles(skip) = request.value {
      skipSigningBundles = skip
      request = try await requestStream.requiredNext
    }

    var linkToBundle: FBDsymInstallLinkToBundle?

    // (2022-03-02) REMOVE! Keeping only for retrocompatibility
    if case let .bundleID(id) = request.value {
      linkToBundle = .init(id, bundle_type: .app)
      request = try await requestStream.requiredNext
    }

    if case let .linkDsymToBundle(link) = request.value {
      linkToBundle = readLinkBundleToDsym(from: link)
      request = try await requestStream.requiredNext
    }

    var payload = try extractPayloadFromRequest()

    var compression = FBCompressionFormat.GZIP
    if case let .compression(format) = payload.source {
      compression = readCompressionFormat(from: format)
      request = try await requestStream.requiredNext
      payload = try extractPayloadFromRequest()
    }

    return try await installData(from: payload.source,
                                 to: destination,
                                 requestStream: requestStream,
                                 name: name,
                                 makeDebuggable: makeDebuggable,
                                 linkToBundle: linkToBundle,
                                 compression: compression,
                                 overrideModificationTime: overrideModificationTime,
                                 skipSigningBundles: skipSigningBundles)
  }

  private func installData(from source: Idb_Payload.OneOf_Source?,
                           to destination: Idb_InstallRequest.Destination,
                           requestStream: GRPCAsyncRequestStream<Idb_InstallRequest>,
                           name: String,
                           makeDebuggable: Bool,
                           linkToBundle: FBDsymInstallLinkToBundle?,
                           compression: FBCompressionFormat,
                           overrideModificationTime: Bool,
                           skipSigningBundles: Bool) async throws -> FBInstalledArtifact {

    func installSource(dataStream: FBProcessInput<AnyObject>, skipSigningBundles: Bool) async throws -> FBInstalledArtifact {
      switch destination {
      case .app:
        return try await BridgeFuture.value(
          commandExecutor.install_app_stream(dataStream, compression: compression, make_debuggable: makeDebuggable, override_modification_time: overrideModificationTime)
        )
      case .xctest:
        return try await BridgeFuture.value(
          commandExecutor.install_xctest_app_stream(dataStream, skipSigningBundles: skipSigningBundles)
        )
      case .dsym:
        return try await BridgeFuture.value(
          commandExecutor.install_dsym_stream(dataStream, compression: compression, linkTo: linkToBundle)
        )
      case .dylib:
        return try await BridgeFuture.value(
          commandExecutor.install_dylib_stream(dataStream, name: name)
        )
      case .framework:
        return try await BridgeFuture.value(
          commandExecutor.install_framework_stream(dataStream)
        )
      case .UNRECOGNIZED:
        throw GRPCStatus(code: .invalidArgument, message: "Unrecognized destination")
      }
    }

    switch source {
    case let .data(data):
      let dataStream = pipeToInputOutput(initial: data, requestStream: requestStream) as! FBProcessInput<AnyObject>
      return try await installSource(dataStream: dataStream, skipSigningBundles: skipSigningBundles)

    case let .url(urlString):
      guard let url = URL(string: urlString) else {
        throw GRPCStatus(code: .invalidArgument, message: "Invalid url source")
      }
      let download = FBDataDownloadInput.dataDownload(with: url, logger: targetLogger)
      let input = download.input as! FBProcessInput<AnyObject>

      return try await installSource(dataStream: input, skipSigningBundles: skipSigningBundles)

    case let .filePath(filePath):
      switch destination {
      case .app:
        return try await BridgeFuture.value(
          commandExecutor.install_app_file_path(filePath, make_debuggable: makeDebuggable, override_modification_time: overrideModificationTime)
        )
      case .xctest:
        return try await BridgeFuture.value(
          commandExecutor.install_xctest_app_file_path(filePath, skipSigningBundles: skipSigningBundles)
        )
      case .dsym:
        return try await BridgeFuture.value(
          commandExecutor.install_dsym_file_path(filePath, linkTo: linkToBundle)
        )
      case .dylib:
        return try await BridgeFuture.value(
          commandExecutor.install_dylib_file_path(filePath)
        )
      case .framework:
        return try await BridgeFuture.value(
          commandExecutor.install_framework_file_path(filePath)
        )
      case .UNRECOGNIZED:
        throw GRPCStatus(code: .invalidArgument, message: "Unrecognized destination")
      }

    default:
      throw GRPCStatus(code: .invalidArgument, message: "Incorrect payload source")
    }
  }

  private func pipeToInputOutput(initial: Data, requestStream: GRPCAsyncRequestStream<Idb_InstallRequest>) -> FBProcessInput<OutputStream> {
    let input = FBProcessInput<OutputStream>.fromStream()
    let appStream = input.contents
    Task {
      appStream.open()
      defer { appStream.close() }

      var buffer = [UInt8](initial)
      appStream.write(&buffer, maxLength: buffer.count)

      for try await request in requestStream {
        guard let data = request.extractDataFrame() else {
          continue
        }

        var buffer = [UInt8](data)
        appStream.write(&buffer, maxLength: buffer.count)
      }
    }

    return input
  }

  private func readLinkBundleToDsym(from link: Idb_InstallRequest.LinkDsymToBundle) -> FBDsymInstallLinkToBundle {
    return .init(link.bundleID,
                 bundle_type: readDsymBundleType(from: link.bundleType))
  }

  private func readDsymBundleType(from bundleType: Idb_InstallRequest.LinkDsymToBundle.BundleType) -> FBDsymBundleType {
    switch bundleType {
    case .app:
      return .app
    case .xctest:
      return .xcTest
    case .UNRECOGNIZED:
      return .app
    }
  }

  private func readCompressionFormat(from compression: Idb_Payload.Compression) -> FBCompressionFormat {
    switch compression {
    case .gzip, .UNRECOGNIZED:
      return .GZIP
    case .zstd:
      return .ZSTD
    }
  }
}


/*
 * 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 FBSimulatorControl
import GRPC
import IDBCompanionUtilities
import IDBGRPCSwift

struct InstrumentsRunMethodHandler {

  let target: FBiOSTarget
  let targetLogger: FBControlCoreLogger
  let commandExecutor: FBIDBCommandExecutor
  let logger: FBControlCoreLogger

  func handle(requestStream: GRPCAsyncRequestStream<Idb_InstrumentsRunRequest>, responseStream: GRPCAsyncResponseStreamWriter<Idb_InstrumentsRunResponse>, context: GRPCAsyncServerCallContext) async throws {
    @Atomic var finishedWriting = false

    guard case let .start(start) = try await requestStream.requiredNext.control
    else { throw GRPCStatus(code: .failedPrecondition, message: "Expected start control") }

    let operation = try await startInstrumentsOperation(request: start, responseStream: responseStream, finishedWriting: _finishedWriting)

    guard case let .stop(stop) = try await requestStream.requiredNext.control
    else { throw GRPCStatus(code: .failedPrecondition, message: "Expected end control") }

    try await stopInstruments(operation: operation, request: stop, responseStream: responseStream, finishedWriting: _finishedWriting)
  }

  private func startInstrumentsOperation(request: Idb_InstrumentsRunRequest.Start, responseStream: GRPCAsyncResponseStreamWriter<Idb_InstrumentsRunResponse>, finishedWriting: Atomic<Bool>) async throws -> FBInstrumentsOperation {
    let configuration = instrumentsConfiguration(from: request, storageManager: commandExecutor.storageManager)

    let responseWriter = FIFOStreamWriter(stream: responseStream)
    let consumer = FBBlockDataConsumer.asynchronousDataConsumer { data in
      guard !finishedWriting.wrappedValue else { return }

      do {
        let response = Idb_InstrumentsRunResponse.with {
          $0.logOutput = data
        }
        try responseWriter.send(response)
      } catch {
        finishedWriting.set(true)
      }
    }
    let logger = FBControlCoreLoggerFactory.compositeLogger(with: [
      FBControlCoreLoggerFactory.logger(to: consumer),
      targetLogger,
    ].compactMap { $0 })

    let operation = try await BridgeFuture.value(target.startInstruments(configuration, logger: logger))

    let runningStateResponse = Idb_InstrumentsRunResponse.with {
      $0.output = .state(.runningInstruments)
    }
    try await responseStream.send(runningStateResponse)

    return operation
  }

  private func stopInstruments(operation: FBInstrumentsOperation, request: Idb_InstrumentsRunRequest.Stop, responseStream: GRPCAsyncResponseStreamWriter<Idb_InstrumentsRunResponse>, finishedWriting: Atomic<Bool>) async throws {
    _ = try await BridgeFuture.value(operation.stop())
    let response = Idb_InstrumentsRunResponse.with {
      $0.state = .postProcessing
    }
    try await responseStream.send(response)

    let postProcessArguments = commandExecutor.storageManager.interpolateArgumentReplacements(request.postProcessArguments)
    let processed = try await BridgeFuture.value(FBInstrumentsOperation.postProcess(postProcessArguments,
                                                                                traceDir: operation.traceDir,
                                                                                queue: BridgeQueues.futureSerialFullfillmentQueue,
                                                                                logger: logger))
    guard let processedPath = processed.path else {
      throw GRPCStatus(code: .internalError, message: "Unable to get post process file path")
    }
    finishedWriting.set(true)

    let archiveOperation = FBArchiveOperations.createGzippedTar(forPath: processedPath, logger: logger)

    try await FileDrainWriter.performDrain(taskFuture: archiveOperation) { data in
      let response = Idb_InstrumentsRunResponse.with {
        $0.payload = .with {
          $0.data = data
        }
      }
      try await responseStream.send(response)
    }
  }

  private func instrumentsConfiguration(from request: Idb_InstrumentsRunRequest.Start, storageManager: FBIDBStorageManager) -> FBInstrumentsConfiguration {
    func withDefaultTimeout(_ initial: Double, _ default: Double) -> Double {
      initial != 0 ? initial : `default`
    }
    return .init(templateName: request.templateName,
                 targetApplication: request.appBundleID,
                 appEnvironment: request.environment,
                 appArguments: request.arguments,
                 toolArguments: storageManager.interpolateArgumentReplacements(request.toolArguments) ?? [],
                 timings: .init(terminateTimeout: withDefaultTimeout(request.timings.terminateTimeout, DefaultInstrumentsTerminateTimeout),
                                launchRetryTimeout: withDefaultTimeout(request.timings.launchRetryTimeout, DefaultInstrumentsLaunchRetryTimeout),
                                launchErrorTimeout: withDefaultTimeout(request.timings.launchErrorTimeout, DefaultInstrumentsLaunchErrorTimeout),
                                operationDuration: withDefaultTimeout(request.timings.operationDuration, DefaultInstrumentsOperationDuration)))
  }
}