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

#import <idbGRPC/idb.grpc.pb.h>
#import <idbGRPC/idb.pb.h>
#import <grpcpp/grpcpp.h>
#import <FBSimulatorControl/FBSimulatorControl.h>

#import "FBCodeCoverageRequest.h"
#import "FBDataDownloadInput.h"
#import "FBIDBCommandExecutor.h"
#import "FBIDBError.h"
#import "FBIDBPortsConfiguration.h"
#import "FBIDBServiceHandler.h"
#import "FBIDBStorageManager.h"
#import "FBIDBTestOperation.h"
#import "FBIDBXCTestReporter.h"
#import "FBXCTestRunRequest.h"
#import <FBControlCore/FBFuture.h>
#import "FBXCTestDescriptor.h"
#import "FBDsymInstallLinkToBundle.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;

#pragma mark Private Functions

static NSString *nsstring_from_c_string(const ::std::string& string)
{
  return [NSString stringWithUTF8String:string.c_str()];
}

static int BufferOutputSize = 16384; //  # 16Kb

static FBCompressionFormat read_compression_format(const idb::Payload_Compression comp)
{
  switch (comp) {
    case idb::Payload_Compression::Payload_Compression_GZIP:
      return FBCompressionFormatGZIP;
    case idb::Payload_Compression::Payload_Compression_ZSTD:
      return FBCompressionFormatZSTD;
    default:
      return FBCompressionFormatGZIP;
  }
}

template <class T>
static FBFuture<NSNull *> * resolve_next_read(grpc::internal::ReaderInterface<T> *reader)
{
  FBMutableFuture<NSNull *> *future = FBMutableFuture.future;
  dispatch_queue_t queue = dispatch_queue_create("com.facebook.idb.grpc.reader_wait", DISPATCH_QUEUE_SERIAL);
  dispatch_async(queue, ^{
    T stop;
    reader->Read(&stop);
    [future resolveWithResult:NSNull.null];
  });
  return future;
}

template <class T>
static id<FBDataConsumer> drain_consumer(grpc::internal::WriterInterface<T> *writer, FBFuture<NSNull *> *done)
{
  return [FBBlockDataConsumer asynchronousDataConsumerWithBlock:^(NSData *data) {
    if (done.hasCompleted) {
      return;
    }
    T response;
    idb::Payload *payload = response.mutable_payload();
    payload->set_data(data.bytes, data.length);
    writer->Write(response);
  }];
}

template <class Write, class Read>
static id<FBDataConsumer> consumer_from_request(grpc::ServerReaderWriter<Write, Read> *stream, Read& request, FBFuture<NSNull *> *done, NSError **error)
{
  Read initial;
  stream->Read(&initial);
  request = initial;
  const std::string requestedFilePath = initial.start().file_path();
  if (requestedFilePath.length() > 0) {
    return [FBFileWriter syncWriterForFilePath:nsstring_from_c_string(requestedFilePath.c_str()) error:error];
  }
  return drain_consumer(stream, done);
}

template <class T>
static Status drain_writer(FBFuture<FBProcess<NSNull *, NSInputStream *, id> *> *taskFuture, grpc::internal::WriterInterface<T> *stream)
{
  NSError *error = nil;
  FBProcess<NSNull *, NSInputStream *, id> *task = [taskFuture block:&error];
  if (!task) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  NSInputStream *inputStream = task.stdOut;
  [inputStream open];
  while (true) {
    uintptr_t buffer[BufferOutputSize];
    NSInteger size = [inputStream read:(uint8_t *)buffer maxLength:BufferOutputSize];
    if (size == 0) {
      break;
    }
    if (size < 0) {
      return Status::OK;
    }
    T response;
    idb::Payload *payload = response.mutable_payload();
    payload->set_data(reinterpret_cast<void *>(buffer), size);
    stream->Write(response);
  }
  [inputStream close];
  NSNumber *exitCode = [task.exitCode block:&error];
  if (exitCode.integerValue != 0) {
    NSString *errorString = [NSString stringWithFormat:@"Draining operation failed with exit code %ld", (long)exitCode.integerValue];
    return Status(grpc::StatusCode::INTERNAL, errorString.UTF8String);
  }
  return Status::OK;
}

template <class T>
static Status respond_file_path(NSURL *source, NSString *destination, grpc::internal::WriterInterface<T> *stream)
{
  if (source) {
    NSError *error = nil;
    if (![NSFileManager.defaultManager moveItemAtPath:source.path toPath:destination error:&error]) {
      return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
    }
  }
  T response;
  idb::Payload payload = response.payload();
  payload.set_file_path(destination.UTF8String);
  stream->Write(response);
  return Status::OK;
}

template <class T>
static FBProcessInput<NSOutputStream *> *pipe_to_input(const idb::Payload initial, grpc::ServerReader<T> *reader)
{
  const std::string initialData = initial.data();
  FBProcessInput<NSOutputStream *> *input = [FBProcessInput inputFromStream];
  NSOutputStream *stream = input.contents;
  dispatch_queue_t queue = dispatch_queue_create("com.facebook.idb.processinput", DISPATCH_QUEUE_SERIAL);
  dispatch_async(queue, ^{
    T request;
    [stream open];
    [stream write:(const uint8_t *)initialData.c_str() maxLength:initialData.length()];
    while (reader->Read(&request)) {
      const auto tarData = request.payload().data();
      [stream write:(const uint8_t *)tarData.c_str() maxLength:tarData.length()];
    }
    [stream close];
  });
  return input;
}

static FBProcessInput<NSOutputStream *> *pipe_to_input_output(const idb::Payload initial, grpc::ServerReaderWriter<idb::InstallResponse, idb::InstallRequest> *stream)
{
  const std::string initialData = initial.data();
  FBProcessInput<NSOutputStream *> *input = [FBProcessInput inputFromStream];
  NSOutputStream *appStream = input.contents;
  dispatch_queue_t queue = dispatch_queue_create("com.facebook.idb.processinput", DISPATCH_QUEUE_SERIAL);
  dispatch_async(queue, ^{
    idb::InstallRequest request;
    [appStream open];
    [appStream write:(const uint8_t *)initialData.c_str() maxLength:initialData.length()];
    while (stream->Read(&request)) {
      const auto tarData = request.payload().data();
      [appStream write:(const uint8_t *)tarData.c_str() maxLength:tarData.length()];
    }
    [appStream close];
  });
return input;
}

static id<FBDataConsumer, FBDataConsumerLifecycle> pipe_output(const idb::ProcessOutput_Interface interface, dispatch_queue_t queue, grpc::ServerReaderWriter<idb::LaunchResponse, idb::LaunchRequest> *stream)
{
  id<FBDataConsumer, FBDataConsumerLifecycle> consumer = [FBBlockDataConsumer asynchronousDataConsumerOnQueue:queue consumer:^(NSData *data) {
    idb::LaunchResponse response;
    idb::ProcessOutput *output = response.mutable_output();
    output->set_data(data.bytes, data.length);
    output->set_interface(interface);
    stream->Write(response);
  }];
  return consumer;
}

template <class T>
static FBFuture<NSArray<NSURL *> *> *filepaths_from_stream(const idb::Payload initial, grpc::ServerReader<T> *reader)
{
  NSMutableArray<NSURL *> *filePaths = NSMutableArray.array;
  FBMutableFuture<NSArray<NSURL *> *> *future = FBMutableFuture.future;
  dispatch_queue_t queue = dispatch_queue_create("com.facebook.idb.processinput", DISPATCH_QUEUE_SERIAL);
  dispatch_async(queue, ^{
    T request;
    [filePaths addObject:[NSURL fileURLWithPath:nsstring_from_c_string(initial.file_path())]];
    while (reader->Read(&request)) {
      [filePaths addObject:[NSURL fileURLWithPath:nsstring_from_c_string(request.payload().file_path())]];
    }
    [future resolveWithResult:filePaths];
  });
  return future;
}

static FBFutureContext<NSArray<NSURL *> *> *filepaths_from_tar(FBTemporaryDirectory *temporaryDirectory, FBProcessInput<NSOutputStream *> *input, bool extract_from_subdir, FBCompressionFormat compression, id<FBControlCoreLogger> logger)
{
  dispatch_queue_t queue = dispatch_queue_create("com.facebook.idb.processinput", DISPATCH_QUEUE_SERIAL);
  FBFutureContext<NSURL *> *tarContext = [temporaryDirectory withArchiveExtractedFromStream:input compression:compression];
  if (extract_from_subdir) {
    // Extract from subdirectories
    return [temporaryDirectory filesFromSubdirs:tarContext];
  } else {
    // Extract from the top level
    return [tarContext onQueue:queue pend:^FBFuture<NSArray<NSURL *> *> *(NSURL *extractionDir) {
      NSError *error;
      NSArray<NSURL *> *paths = [NSFileManager.defaultManager contentsOfDirectoryAtURL:extractionDir includingPropertiesForKeys:@[NSURLIsDirectoryKey] options:0 error:&error];
      if (!paths) {
        return [FBFuture futureWithError:error];
      }
      return [FBFuture futureWithResult:paths];
    }];
  }
}

template<class T>
static FBFutureContext<NSArray<NSURL *> *> *filepaths_from_reader(FBTemporaryDirectory *temporaryDirectory, grpc::ServerReader<T> *reader, bool extract_from_subdir, id<FBControlCoreLogger> logger)
{
  T request;
  reader->Read(&request);
  idb::Payload firstPayload = request.payload();

  // The first item in the payload stream may be the compression format, if it's not assume the default.
  FBCompressionFormat compression = FBCompressionFormatGZIP;
  if (firstPayload.source_case() == idb::Payload::kCompression) {
    compression = read_compression_format(firstPayload.compression());
    reader->Read(&request);
    firstPayload = request.payload();
  }

  switch (firstPayload.source_case()) {
    case idb::Payload::kData: {
      FBProcessInput<NSOutputStream *> *input = pipe_to_input(firstPayload, reader);
      return filepaths_from_tar(temporaryDirectory, input, extract_from_subdir, compression, logger);
    }
    case idb::Payload::kFilePath: {
      return [FBFutureContext futureContextWithFuture:filepaths_from_stream(firstPayload, reader)];
    }
    default: {
      return [FBFutureContext futureContextWithError:[FBControlCoreError errorForFormat:@"Unrecognised initial payload type %u", firstPayload.source_case()]];
    }
  }
}

static NSDictionary<NSString *, NSString *> *extract_str_dict(const ::google::protobuf::Map<::std::string, ::std::string >& iterator)
{
  NSMutableDictionary<NSString *, NSString *> *environment = NSMutableDictionary.dictionary;
  for (auto item : iterator) {
    environment[nsstring_from_c_string(item.first)] = nsstring_from_c_string(item.second);
  }
  return environment;
}

template <class T>
static NSArray<NSString *> *extract_string_array(T &input)
{
  NSMutableArray<NSString *> *arguments = NSMutableArray.array;
  for (auto value : input) {
    [arguments addObject:nsstring_from_c_string(value)];
  }
  return arguments;
}

static FBCodeCoverageRequest *extract_code_coverage(const idb::XctestRunRequest *request) {
  if (request->has_code_coverage()) {
    const idb::XctestRunRequest::CodeCoverage codeCoverage = request->code_coverage();
    FBCodeCoverageFormat format = FBCodeCoverageExported;
    switch (codeCoverage.format()) {
      case idb::XctestRunRequest_CodeCoverage_Format::XctestRunRequest_CodeCoverage_Format_RAW:
        format = FBCodeCoverageRaw;
        break;
      case idb::XctestRunRequest_CodeCoverage_Format::XctestRunRequest_CodeCoverage_Format_EXPORTED:
      default:
        format = FBCodeCoverageExported;
        break;
    }
    return [[FBCodeCoverageRequest alloc] initWithCollect:codeCoverage.collect() format:format];
  } else {
    // fallback to deprecated request field for backwards compatibility
    return [[FBCodeCoverageRequest alloc] initWithCollect:request->collect_coverage() format:FBCodeCoverageExported];
  }
}

static FBXCTestRunRequest *convert_xctest_request(const idb::XctestRunRequest *request)
{
  NSNumber *testTimeout = @(request->timeout());
  NSArray<NSString *> *arguments = extract_string_array(request->arguments());
  NSDictionary<NSString *, NSString *> *environment = extract_str_dict(request->environment());
  NSMutableSet<NSString *> *testsToRun = nil;
  NSMutableSet<NSString *> *testsToSkip = NSMutableSet.set;
  NSString *testBundleID = nsstring_from_c_string(request->test_bundle_id());
  BOOL reportActivities = request->report_activities();
  BOOL collectLogs = request->collect_logs();
  BOOL waitForDebugger = request->wait_for_debugger();
  BOOL reportAttachments = request->report_attachments();
  FBCodeCoverageRequest *coverage = extract_code_coverage(request);

  if (request->tests_to_run_size() > 0) {
    testsToRun = NSMutableSet.set;
    for (int i = 0; i < request->tests_to_run_size(); i++) {
      const std::string value = request->tests_to_run(i);
      [testsToRun addObject:nsstring_from_c_string(value)];
    }
  }
  for (int i = 0; i < request->tests_to_skip_size(); i++) {
    const std::string value = request->tests_to_skip(i);
    [testsToSkip addObject:nsstring_from_c_string(value)];
  }


  switch (request->mode().mode_case()) {
    case idb::XctestRunRequest_Mode::kLogic: {
      return [FBXCTestRunRequest logicTestWithTestBundleID:testBundleID environment:environment arguments:arguments testsToRun:testsToRun testsToSkip:testsToSkip testTimeout:testTimeout reportActivities:reportActivities reportAttachments:reportAttachments coverageRequest:coverage collectLogs:collectLogs waitForDebugger:waitForDebugger];
    }
    case idb::XctestRunRequest_Mode::kApplication: {
      const idb::XctestRunRequest::Application application = request->mode().application();
      NSString *appBundleID = nsstring_from_c_string(application.app_bundle_id());
      return [FBXCTestRunRequest applicationTestWithTestBundleID:testBundleID appBundleID:appBundleID environment:environment arguments:arguments testsToRun:testsToRun testsToSkip:testsToSkip testTimeout:testTimeout reportActivities:reportActivities reportAttachments:reportAttachments coverageRequest:coverage collectLogs:collectLogs waitForDebugger:waitForDebugger];
    }
    case idb::XctestRunRequest_Mode::kUi: {
      const idb::XctestRunRequest::UI ui = request->mode().ui();
      NSString *appBundleID = nsstring_from_c_string(ui.app_bundle_id());
      NSString *testHostAppBundleID = nsstring_from_c_string(ui.test_host_app_bundle_id());
      return [FBXCTestRunRequest uiTestWithTestBundleID:testBundleID appBundleID:appBundleID testHostAppBundleID:testHostAppBundleID environment:environment arguments:arguments testsToRun:testsToRun testsToSkip:testsToSkip testTimeout:testTimeout reportActivities:reportActivities reportAttachments:reportAttachments coverageRequest:coverage collectLogs:collectLogs];
    }
    default:
      return nil;
  }
}

static NSPredicate *nspredicate_from_crash_log_query(const idb::CrashLogQuery *request)
{
  NSMutableArray<NSPredicate *> *subpredicates = [NSMutableArray array];
  if (request->since()) {
    [subpredicates addObject:[FBCrashLogInfo predicateNewerThanDate:[NSDate dateWithTimeIntervalSince1970:request->since()]]];
  }
  if (request->before()) {
    [subpredicates addObject:[FBCrashLogInfo predicateOlderThanDate:[NSDate dateWithTimeIntervalSince1970:request->before()]]];
  }
  NSString *bundle_id = nsstring_from_c_string(request->bundle_id());
  if (bundle_id.length) {
    [subpredicates addObject:[FBCrashLogInfo predicateForIdentifier:bundle_id]];
  }
  NSString *name = nsstring_from_c_string(request->name());
  if (name.length) {
    [subpredicates addObject:[FBCrashLogInfo predicateForName:name]];
  }
  if (subpredicates.count == 0) {
    return [NSPredicate predicateWithValue:YES];
  }
  return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
}

static void fill_crash_log_info(idb::CrashLogInfo *info, const FBCrashLogInfo *crash)
{
  info->set_name(crash.name.UTF8String);
  info->set_process_name(crash.processName.UTF8String);
  info->set_parent_process_name(crash.parentProcessName.UTF8String);
  info->set_process_identifier(crash.processIdentifier);
  info->set_parent_process_identifier(crash.parentProcessIdentifier);
  info->set_timestamp(crash.date.timeIntervalSince1970);
}

static void fill_crash_log_response(idb::CrashLogResponse *response, const NSArray<FBCrashLogInfo *> *crashes)
{
  for (FBCrashLogInfo *crash in crashes) {
    idb::CrashLogInfo *info = response->add_list();
    fill_crash_log_info(info, crash);
  }
}

static FBSimulatorHIDEvent *translate_event(idb::HIDEvent &event, NSError **error)
{
  if (event.has_press()) {
    idb::HIDEvent_HIDDirection direction = event.press().direction();
    idb::HIDEvent_HIDPressAction action = event.press().action();
    if (action.has_key()) {
      int keycode = (int)action.key().keycode();
      if (direction == idb::HIDEvent_HIDDirection_DOWN) {
        return [FBSimulatorHIDEvent keyDown:keycode];
      } else if (direction == idb::HIDEvent_HIDDirection_UP) {
        return [FBSimulatorHIDEvent keyUp:keycode];
      }
    } else if (action.has_button()) {
      // Need to convert between the objc enum that starts at 1 and the grpc enum that starts at 0
      FBSimulatorHIDButton button = (FBSimulatorHIDButton)(action.button().button() + 1);
      if (direction == idb::HIDEvent_HIDDirection_DOWN) {
        return [FBSimulatorHIDEvent buttonDown:button];
      } else if (direction == idb::HIDEvent_HIDDirection_UP) {
        return [FBSimulatorHIDEvent buttonUp:button];
      }
    } else if (action.has_touch()) {
      int x = action.touch().point().x();
      int y = action.touch().point().y();
      if (direction == idb::HIDEvent_HIDDirection_DOWN) {
        return [FBSimulatorHIDEvent touchDownAtX:x y:y];
      } else if (direction == idb::HIDEvent_HIDDirection_UP) {
        return [FBSimulatorHIDEvent touchUpAtX:x y:y];
      }
    }
  } else if (event.has_swipe()) {
    return [FBSimulatorHIDEvent swipe:event.swipe().start().x() yStart:event.swipe().start().y() xEnd:event.swipe().end().x() yEnd:event.swipe().end().y() delta:event.swipe().delta() duration:event.swipe().duration()];
  } else if (event.has_delay()) {
    return [FBSimulatorHIDEvent delay:event.delay().duration()];
  }
  if (error) {
    *error = [FBControlCoreError errorForDescription:@"Can't decode event"];
  }
  return nil;
}

static FBInstrumentsConfiguration *translate_instruments_configuration(idb::InstrumentsRunRequest_Start request, FBIDBStorageManager *storageManager)
{
  return [FBInstrumentsConfiguration
    configurationWithTemplateName:nsstring_from_c_string(request.template_name())
    targetApplication:nsstring_from_c_string(request.app_bundle_id())
    appEnvironment:extract_str_dict(request.environment())
    appArguments:extract_string_array(request.arguments())
    toolArguments:[storageManager interpolateArgumentReplacements:extract_string_array(request.tool_arguments())]
    timings:[FBInstrumentsTimings
      timingsWithTerminateTimeout:request.timings().terminate_timeout() ?: DefaultInstrumentsTerminateTimeout
      launchRetryTimeout:request.timings().launch_retry_timeout() ?: DefaultInstrumentsLaunchRetryTimeout
      launchErrorTimeout:request.timings().launch_error_timeout() ?: DefaultInstrumentsLaunchErrorTimeout
      operationDuration:request.timings().operation_duration() ?: DefaultInstrumentsOperationDuration
      ]
    ];
}

static FBXCTraceRecordConfiguration *translate_xctrace_record_configuration(idb::XctraceRecordRequest_Start request)
{
  return [FBXCTraceRecordConfiguration
    RecordWithTemplateName:nsstring_from_c_string(request.template_name())
    timeLimit:request.time_limit() ?: DefaultXCTraceRecordOperationTimeLimit
    package:nsstring_from_c_string(request.package())
    allProcesses:request.target().all_processes()
    processToAttach:nsstring_from_c_string(request.target().process_to_attach())
    processToLaunch:nsstring_from_c_string(request.target().launch_process().process_to_launch())
    launchArgs:extract_string_array(request.target().launch_process().launch_args())
    targetStdin:nsstring_from_c_string(request.target().launch_process().target_stdin())
    targetStdout:nsstring_from_c_string(request.target().launch_process().target_stdout())
    processEnv:extract_str_dict(request.target().launch_process().process_env())
    shim:nil
  ];
}

static idb::DebugServerResponse translate_debugserver_status(id<FBDebugServer> debugServer)
{
  idb::DebugServerResponse response;
  idb::DebugServerResponse::Status *status = response.mutable_status();
  if (debugServer) {
    for (NSString *command in debugServer.lldbBootstrapCommands) {
      status->add_lldb_bootstrap_commands(command.UTF8String);
    }
  }
  return response;
}

static NSString *file_container(idb::FileContainer container)
{
  switch (container.kind()) {
    case idb::FileContainer_Kind_ROOT:
      return FBFileContainerKindRoot;
    case idb::FileContainer_Kind_MEDIA:
      return FBFileContainerKindMedia;
    case idb::FileContainer_Kind_CRASHES:
      return FBFileContainerKindCrashes;
    case idb::FileContainer_Kind_PROVISIONING_PROFILES:
      return FBFileContainerKindProvisioningProfiles;
    case idb::FileContainer_Kind_MDM_PROFILES:
      return FBFileContainerKindMDMProfiles;
    case idb::FileContainer_Kind_SPRINGBOARD_ICONS:
      return FBFileContainerKindSpringboardIcons;
    case idb::FileContainer_Kind_WALLPAPER:
      return FBFileContainerKindWallpaper;
    case idb::FileContainer_Kind_DISK_IMAGES:
      return FBFileContainerKindDiskImages;
    case idb::FileContainer_Kind_GROUP_CONTAINER:
      return FBFileContainerKindGroup;
    case idb::FileContainer_Kind_APPLICATION_CONTAINER:
      return FBFileContainerKindApplication;
    case idb::FileContainer_Kind_AUXILLARY:
      return FBFileContainerKindAuxillary;
    case idb::FileContainer_Kind_XCTEST:
      return FBFileContainerKindXctest;
    case idb::FileContainer_Kind_DYLIB:
      return FBFileContainerKindDylib;
    case idb::FileContainer_Kind_DSYM:
      return FBFileContainerKindDsym;
    case idb::FileContainer_Kind_FRAMEWORK:
      return FBFileContainerKindFramework;
    case idb::FileContainer_Kind_SYMBOLS:
      return FBFileContainerKindSymbols;
    case idb::FileContainer_Kind_APPLICATION:
    default:
      return nsstring_from_c_string(container.bundle_id());
  }
}

static FBDsymBundleType bundle_type_link_to_dsym(idb::InstallRequest_LinkDsymToBundle_BundleType bundleType)
{
  switch (bundleType) {
    case idb::InstallRequest_LinkDsymToBundle_BundleType_XCTEST:
      return FBDsymBundleTypeXCTest;
    case idb::InstallRequest_LinkDsymToBundle_BundleType_APP:
      return FBDsymBundleTypeApp;
    default:
      return FBDsymBundleTypeApp;
  }
}

static void populate_companion_info(idb::CompanionInfo *info, id<FBEventReporter> reporter, id<FBiOSTarget> target)
{
  info->set_udid(target.udid.UTF8String);
  NSDictionary<NSString *, NSString *> *metadata = reporter.metadata ?: @{};
  NSError *error = nil;
  NSData *data = [NSJSONSerialization dataWithJSONObject:metadata options:0 error:&error];
  if (data) {
    info->set_metadata(data.bytes, data.length);
  }
}

#pragma mark Constructors

FBIDBServiceHandler::FBIDBServiceHandler(FBIDBCommandExecutor *commandExecutor, id<FBiOSTarget> target, id<FBEventReporter> eventReporter)
{
  _commandExecutor = commandExecutor;
  _target = target;
  _eventReporter = eventReporter;
}

FBIDBServiceHandler::FBIDBServiceHandler(const FBIDBServiceHandler &c)
{
  _commandExecutor = c._commandExecutor;
  _target = c._target;
  _eventReporter = c._eventReporter;
}

#pragma mark Handled Methods

FBFuture<FBInstalledArtifact *> *FBIDBServiceHandler::install_future(const idb::InstallRequest_Destination destination, grpc::ServerReaderWriter<idb::InstallResponse, idb::InstallRequest> *stream)
{@autoreleasepool{
  // Read the initial request
  idb::InstallRequest request;
  stream->Read(&request);

  // The name hint may be provided, if it is not then default to some UUID, then advance the stream.
  NSString *name = NSUUID.UUID.UUIDString;
  if (request.value_case() == idb::InstallRequest::ValueCase::kNameHint) {
    name = nsstring_from_c_string(request.name_hint());
    stream->Read(&request);
  }

  // A debuggable flag may be provided, if it is, then obtain that value, then advance that stream.
  BOOL makeDebuggable = NO;
  if (request.value_case() == idb::InstallRequest::ValueCase::kMakeDebuggable) {
    makeDebuggable = (request.make_debuggable() == true);
    stream->Read(&request);
  }
  
  FBDsymInstallLinkToBundle *linkToBundle = nil;
  //(2022-03-02) REMOVE! Keeping only for retrocompatibility
  // A bundle id might be provided, if it is, then obtain the installed app if exists, then advance that stream.
  // It can be used to determine where debug symbols should be linked
  if (request.value_case() == idb::InstallRequest::ValueCase::kBundleId) {
    NSString *bundleID = nsstring_from_c_string(request.bundle_id());
    linkToBundle = [[FBDsymInstallLinkToBundle alloc] initWith:bundleID bundle_type:FBDsymBundleTypeApp];
    stream->Read(&request);
  }

  if (request.value_case() == idb::InstallRequest::ValueCase::kLinkDsymToBundle) {
    idb::InstallRequest_LinkDsymToBundle link_to_bundle = request.link_dsym_to_bundle();
    FBDsymBundleType bundleType = bundle_type_link_to_dsym(link_to_bundle.bundle_type());
    NSString *bundleID = nsstring_from_c_string(link_to_bundle.bundle_id());
    linkToBundle = [[FBDsymInstallLinkToBundle alloc] initWith:bundleID bundle_type:bundleType];
    stream->Read(&request);
  }
  
  // Now that we've read the header, the next item in the stream must be the payload.
  if (request.value_case() != idb::InstallRequest::ValueCase::kPayload) {
    return [[FBIDBError
      describeFormat:@"Expected the next item in the stream to be a payload"]
      failFuture];
  }

  // The first item in the payload stream may be the compression format, if it's not assume the default.
  FBCompressionFormat compression = FBCompressionFormatGZIP;
  idb::Payload payload = request.payload();
  if (payload.source_case() == idb::Payload::kCompression) {
    compression = read_compression_format(payload.compression());
    stream->Read(&request);
    payload = request.payload();
  }

  switch (payload.source_case()) {
    case idb::Payload::kData: {
      FBProcessInput<NSOutputStream *> *dataStream = pipe_to_input_output(payload, stream);
      switch (destination) {
        case idb::InstallRequest_Destination::InstallRequest_Destination_APP:
          return [_commandExecutor install_app_stream:dataStream compression:compression make_debuggable:makeDebuggable];
        case idb::InstallRequest_Destination::InstallRequest_Destination_XCTEST:
          return [_commandExecutor install_xctest_app_stream:dataStream];
        case idb::InstallRequest_Destination::InstallRequest_Destination_DSYM:
          return [_commandExecutor install_dsym_stream:dataStream compression:compression linkTo:linkToBundle];
        case idb::InstallRequest_Destination::InstallRequest_Destination_DYLIB:
          return [_commandExecutor install_dylib_stream:dataStream name:name];
        case idb::InstallRequest_Destination::InstallRequest_Destination_FRAMEWORK:
          return [_commandExecutor install_framework_stream:dataStream];
        default:
          return nil;
      }
    }
    case idb::Payload::kUrl: {
      NSURL *url = [NSURL URLWithString:[NSString stringWithCString:payload.url().c_str() encoding:NSUTF8StringEncoding]];
      FBDataDownloadInput *download = [FBDataDownloadInput dataDownloadWithURL:url logger:_target.logger];
      switch (destination) {
        case idb::InstallRequest_Destination::InstallRequest_Destination_APP:
          return [_commandExecutor install_app_stream:download.input compression:compression make_debuggable:makeDebuggable];
        case idb::InstallRequest_Destination::InstallRequest_Destination_XCTEST:
          return [_commandExecutor install_xctest_app_stream:download.input];
        case idb::InstallRequest_Destination::InstallRequest_Destination_DSYM:
          return [_commandExecutor install_dsym_stream:download.input compression:compression linkTo:linkToBundle];
        case idb::InstallRequest_Destination::InstallRequest_Destination_DYLIB:
          return [_commandExecutor install_dylib_stream:download.input name:name];
        case idb::InstallRequest_Destination::InstallRequest_Destination_FRAMEWORK:
          return [_commandExecutor install_framework_stream:download.input];
        default:
          return nil;
      }
    }
    case idb::Payload::kFilePath: {
      NSString *filePath = nsstring_from_c_string(payload.file_path());
      switch (destination) {
        case idb::InstallRequest_Destination::InstallRequest_Destination_APP:
          return [_commandExecutor install_app_file_path:filePath make_debuggable:makeDebuggable];
        case idb::InstallRequest_Destination::InstallRequest_Destination_XCTEST:
          return [_commandExecutor install_xctest_app_file_path:filePath];
        case idb::InstallRequest_Destination::InstallRequest_Destination_DSYM:
          return [_commandExecutor install_dsym_file_path:filePath linkTo:linkToBundle];
        case idb::InstallRequest_Destination::InstallRequest_Destination_DYLIB:
          return [_commandExecutor install_dylib_file_path:filePath];
        case idb::InstallRequest_Destination::InstallRequest_Destination_FRAMEWORK:
          return [_commandExecutor install_framework_file_path:filePath];
        default:
          return nil;
      }
    }
    default:
      return nil;
  }
}}

Status FBIDBServiceHandler::list_apps(ServerContext *context, const idb::ListAppsRequest *request, idb::ListAppsResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  NSSet<NSString *> *persistedBundleIDs = _commandExecutor.storageManager.application.persistedBundleIDs;
  BOOL fetchAppProcessState = request->suppress_process_state() == false;
  NSDictionary<FBInstalledApplication *, id> *apps = [[_commandExecutor list_apps:fetchAppProcessState] block:&error];
  if (!apps) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  for (FBInstalledApplication *app in apps.allKeys) {
    idb::InstalledAppInfo *appInfo = response->add_apps();
    appInfo->set_bundle_id(app.bundle.identifier.UTF8String ?: "");
    appInfo->set_name(app.bundle.name.UTF8String ?: "");
    appInfo->set_install_type(app.installTypeString.UTF8String);
    for (NSString *architecture in app.bundle.binary.architectures) {
      appInfo->add_architectures(architecture.UTF8String);
    }
    id processState = apps[app];
    if ([processState isKindOfClass:NSNumber.class]) {
      appInfo->set_process_state(idb::InstalledAppInfo_AppProcessState_RUNNING);
      appInfo->set_process_identifier([processState unsignedIntegerValue]);
    } else {
      appInfo->set_process_state(idb::InstalledAppInfo_AppProcessState_UNKNOWN);
    }
    appInfo->set_debuggable(app.installType == FBApplicationInstallTypeUserDevelopment && [persistedBundleIDs containsObject:app.bundle.identifier]);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::open_url(ServerContext *context, const idb::OpenUrlRequest *request, idb::OpenUrlRequest *response)
{@autoreleasepool{
  NSError *error = nil;
  [[_commandExecutor open_url:nsstring_from_c_string(request->url())] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, [error.localizedDescription UTF8String]);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::install(ServerContext *context, grpc::ServerReaderWriter<idb::InstallResponse, idb::InstallRequest> *stream)
{@autoreleasepool{
  idb::InstallRequest request;
  stream->Read(&request);
  idb::InstallRequest_Destination destination = request.destination();

  NSError *error = nil;
  FBInstalledArtifact *artifact = [install_future(destination, stream) block:&error];
  if (!artifact) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String ?: "An internal error occured when installing");
  }
  idb::InstallResponse response;
  response.set_name(artifact.name.UTF8String);
  response.set_uuid(artifact.uuid.UUIDString.UTF8String ?: "");
  stream->Write(response);
  return Status::OK;
}}

Status FBIDBServiceHandler::screenshot(ServerContext *context, const idb::ScreenshotRequest *request, idb::ScreenshotResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  NSData *screenshot = [[_commandExecutor take_screenshot:FBScreenshotFormatPNG] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  response->set_image_data(screenshot.bytes, screenshot.length);
  return Status::OK;
}}

Status FBIDBServiceHandler::focus(ServerContext *context, const idb::FocusRequest *request, idb::FocusResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  [[_commandExecutor focus] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, [error.localizedDescription UTF8String]);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::accessibility_info(ServerContext *context, const idb::AccessibilityInfoRequest *request, idb::AccessibilityInfoResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  NSValue *point = nil;
  if (request->has_point()) {
    point = [NSValue valueWithPoint:CGPointMake(request->point().x(), request->point().y())];
  }
  BOOL nestedFormat = request->format() == idb::AccessibilityInfoRequest_Format::AccessibilityInfoRequest_Format_NESTED;
  NSArray<NSDictionary<NSString *, id> *> *info = [[_commandExecutor accessibility_info_at_point:point nestedFormat:nestedFormat] block:&error];

  if (!info) {
    return Status(grpc::StatusCode::INTERNAL, [error.localizedDescription UTF8String]);
  }

  NSData *data = [NSJSONSerialization dataWithJSONObject:info options:0 error:&error];
  if (!data) {
    return Status(grpc::StatusCode::INTERNAL, [error.localizedDescription UTF8String]);
  }

  NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  response->set_json([json UTF8String]);
  return Status::OK;
}}

Status FBIDBServiceHandler::uninstall(ServerContext *context, const idb::UninstallRequest *request, idb::UninstallResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  [[_commandExecutor uninstall_application:nsstring_from_c_string(request->bundle_id())] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, [error.localizedDescription UTF8String]);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::mkdir(grpc::ServerContext *context, const idb::MkdirRequest *request, idb::MkdirResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  [[_commandExecutor create_directory:nsstring_from_c_string(request->path()) containerType:file_container(request->container())] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::mv(grpc::ServerContext *context, const idb::MvRequest *request, idb::MvResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  NSMutableArray<NSString *> *originalPaths = NSMutableArray.array;
  for (int j = 0; j < request->src_paths_size(); j++) {
    [originalPaths addObject:nsstring_from_c_string(request->src_paths(j))];
  }
  [[_commandExecutor move_paths:originalPaths to_path:nsstring_from_c_string(request->dst_path()) containerType:file_container(request->container())] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::rm(grpc::ServerContext *context, const idb::RmRequest *request, idb::RmResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  NSMutableArray<NSString *> *paths = NSMutableArray.array;
  for (int j = 0; j < request->paths_size(); j++) {
    [paths addObject:nsstring_from_c_string(request->paths(j))];
  }
  [[_commandExecutor remove_paths:paths containerType:file_container(request->container())] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::ls(grpc::ServerContext *context, const idb::LsRequest *request, idb::LsResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  if (request->paths_size() > 0) {
    NSArray<NSString *> *inputPaths = extract_string_array(request->paths());
    NSDictionary<NSString *, NSArray<NSString *> *> *pathsToPaths = [[_commandExecutor list_paths:inputPaths containerType:file_container(request->container())] block:&error];
    if (error) {
      return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
    }

    for (NSString *containerPath in pathsToPaths.allKeys) {
      NSArray<NSString *> *paths = pathsToPaths[containerPath];
      idb::FileListing *listing = response->add_listings();
      idb::FileInfo *parent = listing->mutable_parent();
      parent->set_path(containerPath.UTF8String);
      for (NSString *path in paths) {
        idb::FileInfo *info = listing->add_files();
        info->set_path(path.UTF8String);
      }
    }
  } else {
    // Back-compat with single paths
    NSArray<NSString *> *paths = [[_commandExecutor list_path:nsstring_from_c_string(request->path()) containerType:file_container(request->container())] block:&error];
    if (error) {
      return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
    }

    for (NSString *path in paths) {
      idb::FileInfo *info = response->add_files();
      info->set_path(path.UTF8String);
    }
  }

  return Status::OK;
}}

Status FBIDBServiceHandler::approve(ServerContext *context, const idb::ApproveRequest *request, idb::ApproveResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  NSDictionary<NSNumber *, FBSettingsApprovalService> *mapping = @{
    @((int)idb::ApproveRequest_Permission::ApproveRequest_Permission_MICROPHONE): FBSettingsApprovalServiceMicrophone,
    @((int)idb::ApproveRequest_Permission::ApproveRequest_Permission_PHOTOS): FBSettingsApprovalServicePhotos,
    @((int)idb::ApproveRequest_Permission::ApproveRequest_Permission_CAMERA): FBSettingsApprovalServiceCamera,
    @((int)idb::ApproveRequest_Permission::ApproveRequest_Permission_CONTACTS): FBSettingsApprovalServiceContacts,
    @((int)idb::ApproveRequest_Permission::ApproveRequest_Permission_URL): FBSettingsApprovalServiceUrl,
    @((int)idb::ApproveRequest_Permission::ApproveRequest_Permission_LOCATION): FBSettingsApprovalServiceLocation,
    @((int)idb::ApproveRequest_Permission::ApproveRequest_Permission_NOTIFICATION): FBSettingsApprovalServiceNotification,
  };
  NSMutableSet<FBSettingsApprovalService> *services = NSMutableSet.set;
  for (int j = 0; j < request->permissions_size(); j++) {
    idb::ApproveRequest_Permission permission = request->permissions(j);
    [services addObject:mapping[@(permission)]];
  }
  if ([services containsObject:FBSettingsApprovalServiceUrl]) {
    [services removeObject:FBSettingsApprovalServiceUrl];
    [[_commandExecutor approve_deeplink:nsstring_from_c_string(request->scheme())
                        for_application:nsstring_from_c_string(request->bundle_id())] block:&error];
  }
  if ([services count] > 0 && !error) {
    [[_commandExecutor approve:services for_application:nsstring_from_c_string(request->bundle_id())] block:&error];
  }
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::clear_keychain(ServerContext *context, const idb::ClearKeychainRequest *request, idb::ClearKeychainResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  [[_commandExecutor clear_keychain] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, [error.localizedDescription UTF8String]);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::terminate(ServerContext *context, const idb::TerminateRequest *request, idb::TerminateResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  [[_commandExecutor kill_application:nsstring_from_c_string(request->bundle_id())] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::hid(grpc::ServerContext *context, grpc::ServerReader<idb::HIDEvent> *reader, idb::HIDResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  idb::HIDEvent grpcEvent;
  while (reader->Read(&grpcEvent)) {
    FBSimulatorHIDEvent *event = translate_event(grpcEvent, &error);
    if (error) {
      return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
    }
    [[_commandExecutor hid:event] block:&error];
    if (error) {
      return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
    }
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::set_location(ServerContext *context, const idb::SetLocationRequest *request, idb::SetLocationResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  [[_commandExecutor set_location:request->location().latitude() longitude:request->location().longitude()] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, [error.localizedDescription UTF8String]);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::setting(ServerContext* context, const idb::SettingRequest* request, idb::SettingResponse* response)
{@autoreleasepool{
  switch (request->setting_case()) {
    case idb::SettingRequest::SettingCase::kHardwareKeyboard: {
      NSError *error = nil;
      NSNull *result = [[_commandExecutor set_hardware_keyboard_enabled:request->hardwarekeyboard().enabled()] await:&error];
      if (!result) {
        return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
      }
      return Status::OK;
    }
    case idb::SettingRequest::SettingCase::kStringSetting: {
      idb::SettingRequest::StringSetting stringSetting = request->stringsetting();
      switch (stringSetting.setting()) {
        case idb::Setting::LOCALE: {
          NSError *error = nil;
          NSNull *result = [[_commandExecutor set_locale_with_identifier:nsstring_from_c_string(stringSetting.value().c_str())] await:&error];
          if (!result) {
            return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
          }
          return Status::OK;
        }
        case idb::Setting::ANY: {
          NSError *error = nil;
          NSString *name = nsstring_from_c_string(stringSetting.name().c_str());
          NSString *value = nsstring_from_c_string(stringSetting.value().c_str());
          NSString *type = nil;
          if (stringSetting.value_type().length() > 0) {
            type = nsstring_from_c_string(stringSetting.value_type().c_str());
          }
          NSString *domain = nil;
          if (stringSetting.domain().length() > 0) {
            domain = nsstring_from_c_string(stringSetting.domain().c_str());
          }
          NSNull *result = [[_commandExecutor set_preference:name value:value type: type domain:domain] await:&error];
          if (!result) {
            return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
          }
          return Status::OK;
        }
        default:
          return Status(grpc::StatusCode::INTERNAL, "Unknown setting case");
      }
    }
    default:
      return Status(grpc::StatusCode::INTERNAL, "Unknown setting case");
  }
}}

Status FBIDBServiceHandler::get_setting(ServerContext* context, const idb::GetSettingRequest* request, idb::GetSettingResponse* response)
{@autoreleasepool{
  switch (request->setting()) {
    case idb::Setting::LOCALE: {
      NSError *error = nil;
      NSString *localeIdentifier = [[_commandExecutor get_current_locale_identifier] await:&error];
      if (error) {
        return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
      }
      response->set_value(localeIdentifier.UTF8String);
      return Status::OK;
    }
    case idb::Setting::ANY: {
      NSError *error = nil;
      NSString *name = nsstring_from_c_string(request->name().c_str());
      NSString *domain = nil;
      if (request->domain().length() > 0) {
        domain = nsstring_from_c_string(request->domain().c_str());
      }
      NSString *value = [[_commandExecutor get_preference:name domain:domain] await:&error];
      if (error) {
        return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
      }
      response->set_value(value.UTF8String);
      return Status::OK;
    }
    default:
      return Status(grpc::StatusCode::INTERNAL, "Unknown setting case");
  }
}}

Status FBIDBServiceHandler::list_settings(ServerContext* context, const idb::ListSettingRequest* request, idb::ListSettingResponse* response)
{@autoreleasepool{
  switch (request->setting()) {
    case idb::Setting::LOCALE: {
      NSArray<NSString *> *localeIdentifiers = _commandExecutor.list_locale_identifiers;
      auto values = response->mutable_values();
      for (NSString *localeIdentifier in localeIdentifiers) {
        values->Add(localeIdentifier.UTF8String);
      }
      return Status::OK;
    }
    default:
      return Status(grpc::StatusCode::INTERNAL, "Unknown setting case");
  }
}}

Status FBIDBServiceHandler::contacts_update(ServerContext *context, const idb::ContactsUpdateRequest *request, idb::ContactsUpdateResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  std::string data = request->payload().data();
  [[_commandExecutor update_contacts:[NSData dataWithBytes:data.c_str() length:data.length()]] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, [error.localizedDescription UTF8String]);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::launch(grpc::ServerContext *context, grpc::ServerReaderWriter<idb::LaunchResponse, idb::LaunchRequest> *stream)
{@autoreleasepool{
  idb::LaunchRequest request;
  stream->Read(&request);
  idb::LaunchRequest_Start start = request.start();
  NSError *error = nil;
  FBProcessOutput *stdOut = FBProcessOutput.outputForNullDevice;
  FBProcessOutput *stdErr = FBProcessOutput.outputForNullDevice;
  NSMutableArray<FBFuture *> *completions = NSMutableArray.array;
  if (start.wait_for()) {
    dispatch_queue_t writeQueue = dispatch_queue_create("com.facebook.idb.launch.write", DISPATCH_QUEUE_SERIAL);
    id<FBDataConsumer, FBDataConsumerLifecycle> consumer = pipe_output(idb::ProcessOutput_Interface_STDOUT, writeQueue, stream);
    [completions addObject:consumer.finishedConsuming];
    stdOut = [FBProcessOutput outputForDataConsumer:consumer];
    consumer = pipe_output(idb::ProcessOutput_Interface_STDERR, writeQueue, stream);
    [completions addObject:consumer.finishedConsuming];
    stdErr = [FBProcessOutput outputForDataConsumer:consumer];
  }
  FBProcessIO *io = [[FBProcessIO alloc]
    initWithStdIn:nil
    stdOut:stdOut
    stdErr:stdErr];
  FBApplicationLaunchConfiguration *configuration = [[FBApplicationLaunchConfiguration alloc]
    initWithBundleID:nsstring_from_c_string(start.bundle_id())
    bundleName:nil
    arguments:extract_string_array(start.app_args())
    environment:extract_str_dict(start.env())
    waitForDebugger:(start.wait_for_debugger() ? YES : NO)
    io:io
    launchMode:start.foreground_if_running() ? FBApplicationLaunchModeForegroundIfRunning : FBApplicationLaunchModeFailIfRunning];
  id<FBLaunchedApplication> launchedApp = [[_commandExecutor launch_app:configuration] block:&error];
  if (!launchedApp) {
    if (error.code != 0) {
      return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
    } else {
      return Status(grpc::StatusCode::FAILED_PRECONDITION, error.localizedDescription.UTF8String);
    }
  }
  // Respond with the pid of the launched process
  idb::LaunchResponse response;
  idb::DebuggerInfo *debugger_info = response.mutable_debugger();
  debugger_info->set_pid(launchedApp.processIdentifier);
  stream->Write(response);
  // Return early if not waiting for output
  if (!start.wait_for()) {
    return Status::OK;
  }
  // Otherwise wait for the client to hang up.
  stream->Read(&request);
  [[launchedApp.applicationTerminated cancel] block:nil];
  [[FBFuture futureWithFutures:completions] block:nil];
  return Status::OK;
}}

Status FBIDBServiceHandler::crash_list(ServerContext *context, const idb::CrashLogQuery *request, idb::CrashLogResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  NSPredicate *predicate = nspredicate_from_crash_log_query(request);
  NSArray<FBCrashLogInfo *> *crashes = [[_commandExecutor crash_list:predicate] block:&error];
  if (error) {
     return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  fill_crash_log_response(response, crashes);
  return Status::OK;
}}

Status FBIDBServiceHandler::crash_show(ServerContext *context, const idb::CrashShowRequest *request, idb::CrashShowResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  NSString *name = nsstring_from_c_string(request->name());
  if (!name){
      return Status(grpc::StatusCode::INTERNAL, @"Missing crash name".UTF8String);
  }
  NSPredicate *predicate = [FBCrashLogInfo predicateForName:name];
  FBCrashLog *crash = [[_commandExecutor crash_show:predicate] block:&error];
  if (error) {
      return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  idb::CrashLogInfo *info = response->mutable_info();
  fill_crash_log_info(info, crash.info);
  response->set_contents(crash.contents.UTF8String);
  return Status::OK;
}}

Status FBIDBServiceHandler::crash_delete(ServerContext *context, const idb::CrashLogQuery *request, idb::CrashLogResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  NSPredicate *predicate = nspredicate_from_crash_log_query(request);
  NSArray<FBCrashLogInfo *> *crashes = [[_commandExecutor crash_delete:predicate] block:&error];
  if (error) {
      return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  fill_crash_log_response(response, crashes);
  return Status::OK;
}}

Status FBIDBServiceHandler::xctest_list_bundles(ServerContext *context, const idb::XctestListBundlesRequest *request, idb::XctestListBundlesResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  NSSet<id<FBXCTestDescriptor>> *descriptors = [[_commandExecutor list_test_bundles] block:&error];
  if (!descriptors) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  for (id<FBXCTestDescriptor> descriptor in descriptors) {
    idb::XctestListBundlesResponse_Bundles *bundle = response->add_bundles();
    bundle->set_name(descriptor.name.UTF8String ?: "");
    bundle->set_bundle_id(descriptor.testBundleID.UTF8String ?: "");
    for (NSString *architecture in descriptor.architectures) {
      bundle->add_architectures(architecture.UTF8String);
    }
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::xctest_list_tests(ServerContext *context, const idb::XctestListTestsRequest *request, idb::XctestListTestsResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  NSArray<NSString *> *tests = [[_commandExecutor list_tests_in_bundle:nsstring_from_c_string(request->bundle_name()) with_app:nsstring_from_c_string(request->app_path())] block:&error];
  if (!tests) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  for (NSString *test in tests) {
    response->add_names(test.UTF8String);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::xctest_run(ServerContext *context, const idb::XctestRunRequest *request, grpc::ServerWriter<idb::XctestRunResponse> *response)
{@autoreleasepool{
  FBXCTestRunRequest *xctestRunRequest = convert_xctest_request(request);
  if (xctestRunRequest == nil) {
    return Status(grpc::StatusCode::INTERNAL, "Failed to convert xctest request");
  }
  // Once the reporter is created, only it will perform writing to the writer.
  NSError *error = nil;
  FBIDBXCTestReporter *reporter = [[FBIDBXCTestReporter alloc] initWithResponseWriter:response queue:_target.workQueue logger:_target.logger];
  FBIDBTestOperation *operation = [[_commandExecutor xctest_run:xctestRunRequest reporter:reporter logger:[FBControlCoreLoggerFactory loggerToConsumer:reporter]] block:&error];
  if (!operation) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  reporter.configuration = operation.reporterConfiguration;

  // First wait for the test operation to finish
  [operation.completed block:&error];
  // Then make sure we've reported everything, otherwise we could write in the background (use-after-free)
  [reporter.reportingTerminated block:&error];
  return Status::OK;
}}

Status FBIDBServiceHandler::log(ServerContext *context, const idb::LogRequest *request, grpc::ServerWriter<idb::LogResponse> *response)
{@autoreleasepool{
  // In the background, write out the log data. Prevent future writes if the client write fails.
  // This will happen asynchronously with the server thread.
  FBMutableFuture<NSNull *> *writingDone = FBMutableFuture.future;
  id<FBDataConsumer, FBDataConsumerLifecycle, FBDataConsumerSync> consumer = [FBBlockDataConsumer synchronousDataConsumerWithBlock:^(NSData *data) {
    idb::LogResponse item;
    item.set_output(data.bytes, data.length);
    if (writingDone.hasCompleted) {
      return;
    }
    bool success = response->Write(item);
    if (success) {
      return;
    }
    // The client write failed, the client has gone so don't write again.
    [writingDone resolveWithResult:NSNull.null];
  }];

  // Setup the log operation.
  NSError *error = nil;
  BOOL logFromCompanion = request->source() == idb::LogRequest::Source::LogRequest_Source_COMPANION;
  NSArray<NSString *> *arguments = extract_string_array(request->arguments());
  id<FBLogOperation> operation = [(logFromCompanion ? [_commandExecutor tail_companion_logs:consumer] : [_target tailLog:arguments consumer:consumer]) block:&error];
  if (!operation) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }

  // Poll on completion (logging happens asynchronously). This occurs when the stream is cancelled, the log operation is done, or the last write failed.
  FBFuture<NSNull *> *completed = [FBFuture race:@[writingDone, operation.completed]];
  while (completed.hasCompleted == NO && context->IsCancelled() == false) {
    // Sleep for 200ms before polling again.
    usleep(1000 * 200);
  }

  // Signal that we're done writing due to the operation completion or the client going away.
  // This will also prevent the polling of the ClientContext.
  [writingDone resolveWithResult:NSNull.null];

  // Teardown the log operation now that we're done with it
  FBFuture<NSNull *> *teardown = [operation.completed cancel];
  [teardown block:nil];

  return Status::OK;
}}

Status FBIDBServiceHandler::record(grpc::ServerContext *context, grpc::ServerReaderWriter<idb::RecordResponse, idb::RecordRequest> *stream)
{@autoreleasepool{
  idb::RecordRequest initial;
  stream->Read(&initial);
  NSError *error = nil;
  const std::string requestedFilePath = initial.start().file_path();
  NSString *filePath = requestedFilePath.length() > 0 ? nsstring_from_c_string(requestedFilePath.c_str()) : [[_target.auxillaryDirectory stringByAppendingPathComponent:@"idb_encode"] stringByAppendingPathExtension:@"mp4"];
  id<FBiOSTargetOperation> operation = [[_target startRecordingToFile:filePath] block:&error];
  if (!operation) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  idb::RecordRequest stop;
  stream->Read(&stop);
  if (![[_target stopRecording] succeeds:&error]) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  if (requestedFilePath.length() > 0) {
    return respond_file_path(nil, filePath, stream);
  } else {
    return drain_writer([FBArchiveOperations createGzipForPath:filePath queue:dispatch_queue_create("com.facebook.idb.record", DISPATCH_QUEUE_SERIAL) logger:_target.logger], stream);
  }
}}

Status FBIDBServiceHandler::video_stream(ServerContext* context, grpc::ServerReaderWriter<idb::VideoStreamResponse, idb::VideoStreamRequest>* stream)
{@autoreleasepool{
  NSError *error = nil;
  idb::VideoStreamRequest request;
  FBMutableFuture<NSNull *> *done = FBMutableFuture.future;
  id<FBDataConsumer> consumer = consumer_from_request(stream, request, done, &error);
  if (!consumer) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  idb::VideoStreamRequest_Start start = request.start();
  NSNumber *framesPerSecond = start.fps() > 0 ? @(start.fps()) : nil;
  FBVideoStreamEncoding encoding = @"";
  switch (start.format()) {
    case idb::VideoStreamRequest_Format_RBGA:
      encoding = FBVideoStreamEncodingBGRA;
      break;
    case idb::VideoStreamRequest_Format_H264:
      encoding = FBVideoStreamEncodingH264;
      break;
    case idb::VideoStreamRequest_Format_MJPEG:
      encoding = FBVideoStreamEncodingMJPEG;
      break;
    case idb::VideoStreamRequest_Format_MINICAP:
      encoding = FBVideoStreamEncodingMinicap;
      break;
    default:
      return Status(grpc::StatusCode::INTERNAL, "Invalid Video format provided");
  }
  NSNumber *compressionQuality = @(start.compression_quality());
  NSNumber *scaleFactor = @(start.scale_factor());
  NSNumber *avgBitrate = start.avg_bitrate() > 0 ? @(start.avg_bitrate()) : nil;
  FBVideoStreamConfiguration *configuration = [[FBVideoStreamConfiguration alloc] initWithEncoding:encoding framesPerSecond:framesPerSecond compressionQuality:compressionQuality scaleFactor:scaleFactor avgBitrate:avgBitrate];
  id<FBVideoStream> videoStream = [[_target createStreamWithConfiguration:configuration] block:&error];
  if (!stream) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  BOOL success = [[videoStream startStreaming:consumer] block:&error] != nil;
  if (success == NO) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }

  // Wait for the client to hangup or stream to stop
  FBFuture<NSNull *> *clientStopped = resolve_next_read(stream);
  [[FBFuture race:@[clientStopped, videoStream.completed]] block:nil];

  // Signal that we're done so we don't write to a dangling pointer.
  [done resolveWithResult:NSNull.null];
  // Stop the streaming for real. It may have stopped already in which case this returns instantly.
  success = [[videoStream stopStreaming] block:&error] != nil;
  [_target.logger logFormat:@"The video stream is terminated"];
  if (success == NO) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::push(grpc::ServerContext *context, grpc::ServerReader<idb::PushRequest> *reader, idb::PushResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  idb::PushRequest request;
  reader->Read(&request);
  if (request.value_case() != idb::PushRequest::kInner) {
    return Status(grpc::StatusCode::INTERNAL, "First message must contain the commands information");
  }
  const idb::PushRequest_Inner inner = request.inner();

  [[filepaths_from_reader(_commandExecutor.temporaryDirectory, reader, false, _target.logger) onQueue:_target.asyncQueue pop:^FBFuture<NSNull *> *(NSArray<NSURL *> *files) {
    return [_commandExecutor push_files:files to_path:nsstring_from_c_string(inner.dst_path()) containerType:file_container(inner.container())];
  }] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::pull(ServerContext *context, const ::idb::PullRequest *request, grpc::ServerWriter<::idb::PullResponse> *stream)
{@autoreleasepool{
  NSString *path = nsstring_from_c_string(request->src_path());
  NSError *error = nil;
  if (request->dst_path().length() > 0) {
    NSString *filePath = [[_commandExecutor pull_file_path:path destination_path:nsstring_from_c_string(request->dst_path()) containerType:file_container(request->container())] block:&error];
    if (error) {
      return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
    }
    return respond_file_path(nil, filePath, stream);
  } else {
    NSURL *url = [_commandExecutor.temporaryDirectory temporaryDirectory];
    NSString *tempPath = [url.path stringByAppendingPathComponent:path.lastPathComponent];
    NSString *filePath = [[_commandExecutor pull_file_path:path destination_path:tempPath containerType:file_container(request->container())] block:&error];
    if (error) {
      return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
    }
    return drain_writer([FBArchiveOperations
                         createGzippedTarForPath:filePath
                         queue:dispatch_queue_create("com.facebook.idb.pull", DISPATCH_QUEUE_SERIAL)
                         logger:_target.logger],
                        stream);
  }
}}

Status FBIDBServiceHandler::tail(ServerContext* context, grpc::ServerReaderWriter<idb::TailResponse, idb::TailRequest>* stream)
{@autoreleasepool{
  idb::TailRequest request;
  stream->Read(&request);
  idb::TailRequest_Start start = request.start();
  NSString *path = nsstring_from_c_string(start.path());
  NSString *container = file_container(start.container());

  FBMutableFuture<NSNull *> *finished = FBMutableFuture.future;
  id<FBDataConsumer, FBDataConsumerSync> consumer = [FBBlockDataConsumer synchronousDataConsumerWithBlock:^(NSData *data) {
    if (finished.hasCompleted) {
      return;
    }
    idb::TailResponse response;
    response.set_data(data.bytes, data.length);
    stream->Write(response);
  }];

  NSError *error = nil;
  FBFuture<NSNull *> *tailOperation = [[_commandExecutor tail:path to_consumer:consumer in_container:container] block:&error];
  if (!tailOperation) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }

  stream->Read(&request);
  [[tailOperation cancel] block:nil];
  [finished resolveWithResult:NSNull.null];

  return Status::OK;
}}

Status FBIDBServiceHandler::describe(ServerContext *context, const idb::TargetDescriptionRequest *request, idb::TargetDescriptionResponse *response)
{@autoreleasepool{
  // Populate the default values
  idb::TargetDescription *description = response->mutable_target_description();
  FBiOSTargetScreenInfo *screenInfo = _target.screenInfo;
  if (screenInfo) {
    idb::ScreenDimensions *dimensions = description->mutable_screen_dimensions();
    dimensions->set_width(screenInfo.widthPixels);
    dimensions->set_height(screenInfo.heightPixels);
    dimensions->set_height_points(screenInfo.heightPixels/screenInfo.scale);
    dimensions->set_width_points(screenInfo.widthPixels/screenInfo.scale);
    dimensions->set_density(screenInfo.scale);
  }
  description->set_udid(_target.udid.UTF8String);
  description->set_name(_target.name.UTF8String);
  description->set_state(FBiOSTargetStateStringFromState(_target.state).UTF8String);
  description->set_target_type(FBiOSTargetTypeStringFromTargetType(_target.targetType).lowercaseString.UTF8String);
  description->set_os_version(_target.osVersion.name.UTF8String);
  description->set_architecture(_target.architecture.UTF8String);

  // Add extended information
  NSDictionary<NSString *, id> *extendedInformation = _target.extendedInformation;
  NSError *error = nil;
  NSData *data = [NSJSONSerialization dataWithJSONObject:extendedInformation options:0 error:&error];
  if (!data) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  description->set_extended(data.bytes, data.length);

  // Also attach the companion metadata
  populate_companion_info(response->mutable_companion(), _eventReporter, _target);

  // Only fetch diagnostic information when requested.
  if (!request->fetch_diagnostics()) {
    return Status::OK;
  }
  NSDictionary<NSString *, id> *diagnosticInformation = [[_commandExecutor diagnostic_information] block:&error];
  if (!diagnosticInformation) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  data = [NSJSONSerialization dataWithJSONObject:diagnosticInformation options:0 error:&error];
  if (!data) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  description->set_diagnostics(data.bytes, data.length);

  return Status::OK;
}}

Status FBIDBServiceHandler::add_media(grpc::ServerContext *context, grpc::ServerReader<idb::AddMediaRequest> *reader, idb::AddMediaResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  [[filepaths_from_reader(_commandExecutor.temporaryDirectory, reader, true, _target.logger)
    onQueue:_target.asyncQueue pop:^(NSArray<NSURL *> *files) {
      return [_commandExecutor add_media:files];
    }]
    block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::instruments_run(grpc::ServerContext *context, grpc::ServerReaderWriter<idb::InstrumentsRunResponse, idb::InstrumentsRunRequest> *stream)
{@autoreleasepool{
  __block idb::InstrumentsRunRequest startRunRequest;
  __block pthread_mutex_t mutex;
  pthread_mutex_init(&mutex, NULL);
  __block bool finished_writing = NO;
  dispatch_queue_t queue = dispatch_queue_create("com.facebook.idb.instruments.server", DISPATCH_QUEUE_SERIAL);
  dispatch_sync(queue, ^{
    idb::InstrumentsRunRequest request;
    stream->Read(&request);
    startRunRequest = request;
  });

  FBInstrumentsConfiguration *configuration = translate_instruments_configuration(startRunRequest.start(), _commandExecutor.storageManager);

  NSError *error = nil;
  id<FBDataConsumer> consumer = [FBBlockDataConsumer asynchronousDataConsumerOnQueue:queue consumer:^(NSData *data) {
    idb::InstrumentsRunResponse response;
    response.set_log_output(data.bytes, data.length);
    pthread_mutex_lock(&mutex);
    if (!finished_writing) {
      stream->Write(response);
    }
    pthread_mutex_unlock(&mutex);

  }];
  id<FBControlCoreLogger> logger = [FBControlCoreLoggerFactory compositeLoggerWithLoggers:@[
    [FBControlCoreLoggerFactory loggerToConsumer:consumer],
    _target.logger,
  ]];
  FBInstrumentsOperation *operation = [[_target startInstruments:configuration logger:logger] block:&error];
  if (!operation) {
    pthread_mutex_lock(&mutex);
    finished_writing = YES;
    pthread_mutex_unlock(&mutex);
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  __block idb::InstrumentsRunRequest stopRunRequest;
  dispatch_sync(queue, ^{
    idb::InstrumentsRunResponse response;
    response.set_state(idb::InstrumentsRunResponse::State::InstrumentsRunResponse_State_RUNNING_INSTRUMENTS);
    stream->Write(response);
    idb::InstrumentsRunRequest request;
    stream->Read(&request);
    stopRunRequest = request;
  });
  if (![operation.stop succeeds:&error]) {
    pthread_mutex_lock(&mutex);
    finished_writing = YES;
    pthread_mutex_unlock(&mutex);
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  NSArray<NSString *> *postProcessArguments = [_commandExecutor.storageManager interpolateArgumentReplacements:extract_string_array(stopRunRequest.stop().post_process_arguments())];
  dispatch_sync(queue, ^{
    idb::InstrumentsRunResponse response;
    response.set_state(idb::InstrumentsRunResponse::State::InstrumentsRunResponse_State_POST_PROCESSING);
    stream->Write(response);
  });
  NSURL *processed = [[FBInstrumentsOperation postProcess:postProcessArguments traceDir:operation.traceDir queue:queue logger:logger] block:&error];
  pthread_mutex_lock(&mutex);
  finished_writing = YES;
  pthread_mutex_unlock(&mutex);
  if (!processed) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  return drain_writer([FBArchiveOperations createGzippedTarForPath:processed.path queue:queue logger:_target.logger], stream);
}}

Status FBIDBServiceHandler::debugserver(grpc::ServerContext *context, grpc::ServerReaderWriter<idb::DebugServerResponse, idb::DebugServerRequest> *stream)
{@autoreleasepool{
  idb::DebugServerRequest request;
  stream->Read(&request);

  NSError *error = nil;
  switch (request.control_case()) {
    case idb::DebugServerRequest::ControlCase::kStart: {
      idb::DebugServerRequest::Start start = request.start();
      id<FBDebugServer> debugServer = [[_commandExecutor debugserver_start:nsstring_from_c_string(start.bundle_id())] block:&error];
      if (!debugServer) {
        return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
      }
      stream->Write(translate_debugserver_status(debugServer));
      return Status::OK;
    }
    case idb::DebugServerRequest::ControlCase::kStatus: {
      id<FBDebugServer> debugServer = [[_commandExecutor debugserver_status] block:&error];
      stream->Write(translate_debugserver_status(debugServer));
      return Status::OK;
    }
    case idb::DebugServerRequest::ControlCase::kStop: {
      id<FBDebugServer> debugServer = [[_commandExecutor debugserver_stop] block:&error];
      if (!debugServer) {
        return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
      }
      stream->Write(translate_debugserver_status(debugServer));
      return Status::OK;
    }
    default: {
      return Status(grpc::StatusCode::UNIMPLEMENTED, NULL);
    }
  }
}}

Status FBIDBServiceHandler::dap(grpc::ServerContext *context, grpc::ServerReaderWriter<idb::DapResponse, idb::DapRequest> *stream)
{@autoreleasepool{
  idb::DapRequest initial_request;
  stream->Read(&initial_request);
  if (initial_request.control_case() != idb::DapRequest::ControlCase::kStart) {
    return Status(grpc::StatusCode::FAILED_PRECONDITION, "Dap command expected a Start messaged in the beginning of the Stream");
  }
  idb::DapRequest_Start start = initial_request.start();
  NSString *pkg_id = nsstring_from_c_string(start.debugger_pkg_id());
  NSString *lldb_vscode = [@"dap" stringByAppendingPathComponent:[pkg_id stringByAppendingPathComponent: @"usr/bin/lldb-vscode"]];

  id<FBDataConsumer> reader = [FBBlockDataConsumer synchronousDataConsumerWithBlock:^(NSData *data) {
    idb::DapResponse response;
    idb::DapResponse_Pipe *stdout = response.mutable_stdout();
    stdout->set_data(data.bytes, data.length);
    stream->Write(response);
    [_target.logger.debug logFormat:@"Dap server stdout consumer: sent %lu bytes.", data.length];
  }];


  [_target.logger.debug logFormat:@"Starting dap server with path %@", lldb_vscode];
  NSError *error = nil;
  FBProcessInput<id<FBDataConsumer>> *writer = [FBProcessInput inputFromConsumer];
  FBProcess *process = [[_commandExecutor dapServerWithPath:lldb_vscode stdIn:writer stdOut:reader] awaitWithTimeout:600 error:&error];
  if (error){
    NSString *errorMsg = [NSString stringWithFormat:@"Failed to spaw DAP server. Error: %@", error.localizedDescription];
    return Status(grpc::StatusCode::INTERNAL, errorMsg.UTF8String);
  }
  [_target.logger.debug logFormat:@"Dap server spawn with PID: %d", process.processIdentifier];
  idb::DapResponse response;
  response.mutable_started();
  stream->Write(response);

  dispatch_queue_t write_queue = dispatch_queue_create("com.facebook.idb.dap.write", DISPATCH_QUEUE_SERIAL);
  auto writeFuture = [FBFuture onQueue:write_queue resolveWhen:^BOOL {
    idb::DapRequest request;
    stream->Read(&request);
    if (request.control_case() == idb::DapRequest::ControlCase::kStop){
      [_target.logger.debug logFormat:@"Received stop from Dap Request"];
      [_target.logger.debug logFormat:@"Dap server with pid %d. Stderr: %@", process.processIdentifier, process.stdErr];
      return YES;
    }

    idb::DapRequest_Pipe pipe = request.pipe();
    auto raw_data = pipe.data();
    NSData *data = [NSData dataWithBytes:raw_data.c_str() length:raw_data.length()];
    if (data.length == 0) {
      [_target.logger.debug logFormat:@"Dap Request. Receiving empty messages. Transmission finished."];
      return YES;
    }
    [_target.logger.debug logFormat:@"Dap Request. Received %lu bytes from client", data.length];
    [writer.contents consumeData:data];
    return NO;
  }];

  //  Debug session shouln't be longer than 10hours
  [writeFuture awaitWithTimeout:36000 error:&error];
  if (error){
    NSString *errorMsg = [NSString stringWithFormat:@"Error in writting to dap server stdout: %@", error.localizedDescription];
    return Status(grpc::StatusCode::INTERNAL, errorMsg.UTF8String);
  }

  idb::DapResponse_Event *stopped = response.mutable_stopped();
  stopped->set_desc(@"Dap server stopped.".UTF8String);
  stream->Write(response);

  return Status::OK;
}}




Status FBIDBServiceHandler::connect(grpc::ServerContext *context, const idb::ConnectRequest *request, idb::ConnectResponse *response)
{@autoreleasepool{
  // Add Meta to Reporter
  [_eventReporter addMetadata:extract_str_dict(request->metadata())];

  // Get the local state
  BOOL isLocal = [NSFileManager.defaultManager fileExistsAtPath:nsstring_from_c_string(request->local_file_path())];
  idb::CompanionInfo *info = response->mutable_companion();
  info->set_is_local(isLocal);

  // Populate the other values.
  populate_companion_info(info, _eventReporter, _target);

  return Status::OK;
}}

Status FBIDBServiceHandler::xctrace_record(ServerContext *context,grpc::ServerReaderWriter<idb::XctraceRecordResponse, idb::XctraceRecordRequest> *stream)
{@autoreleasepool{
  __block idb::XctraceRecordRequest recordRequest;
  __block pthread_mutex_t mutex;
  pthread_mutex_init(&mutex, NULL);
  __block bool finished_writing = NO;
  dispatch_queue_t queue = dispatch_queue_create("com.facebook.idb.xctrace.record", DISPATCH_QUEUE_SERIAL);
  // @lint-ignore FBOBJCDISCOURAGEDFUNCTION
  dispatch_sync(queue, ^{
    idb::XctraceRecordRequest request;
    stream->Read(&request);
    recordRequest = request;
  });

  FBXCTraceRecordConfiguration *configuration = translate_xctrace_record_configuration(recordRequest.start());

  NSError *error = nil;
  id<FBDataConsumer> consumer = [FBBlockDataConsumer asynchronousDataConsumerOnQueue:queue consumer:^(NSData *data) {
    idb::XctraceRecordResponse response;
    response.set_log(data.bytes, data.length);
    pthread_mutex_lock(&mutex);
    if (!finished_writing) {
      stream->Write(response);
    }
    pthread_mutex_unlock(&mutex);
  }];
  id<FBControlCoreLogger> logger = [FBControlCoreLoggerFactory compositeLoggerWithLoggers:@[
    [FBControlCoreLoggerFactory loggerToConsumer:consumer],
    _target.logger,
  ]];

  FBXCTraceRecordOperation *operation = [[_target startXctraceRecord:configuration logger:logger] block:&error];
  if (!operation) {
    pthread_mutex_lock(&mutex);
    finished_writing = YES;
    pthread_mutex_unlock(&mutex);
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  // @lint-ignore FBOBJCDISCOURAGEDFUNCTION
  dispatch_sync(queue, ^{
    idb::XctraceRecordResponse response;
    response.set_state(idb::XctraceRecordResponse::State::XctraceRecordResponse_State_RUNNING);
    stream->Write(response);
    idb::XctraceRecordRequest request;
    stream->Read(&request);
    recordRequest = request;
  });
  NSTimeInterval stopTimeout = recordRequest.stop().timeout() ?: DefaultXCTraceRecordStopTimeout;
  if (![[operation stopWithTimeout:stopTimeout] succeeds:&error]) {
    pthread_mutex_lock(&mutex);
    finished_writing = YES;
    pthread_mutex_unlock(&mutex);
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  NSArray<NSString *> *postProcessArgs = extract_string_array(recordRequest.stop().args());
  // @lint-ignore FBOBJCDISCOURAGEDFUNCTION
  dispatch_sync(queue, ^{
    idb::XctraceRecordResponse response;
    response.set_state(idb::XctraceRecordResponse::State::XctraceRecordResponse_State_PROCESSING);
    stream->Write(response);
  });
  NSURL *processed = [[FBInstrumentsOperation postProcess:postProcessArgs traceDir:operation.traceDir queue:queue logger:logger] block:&error];
  pthread_mutex_lock(&mutex);
  finished_writing = YES;
  pthread_mutex_unlock(&mutex);
  if (!processed) {
    return Status(grpc::StatusCode::INTERNAL, error.localizedDescription.UTF8String);
  }
  return drain_writer([FBArchiveOperations createGzippedTarForPath:processed.path queue:queue logger:_target.logger], stream);
}}

Status FBIDBServiceHandler::send_notification(grpc::ServerContext *context, const idb::SendNotificationRequest *request, idb::SendNotificationResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  [[_commandExecutor sendPushNotificationForBundleID:nsstring_from_c_string(request->bundle_id()) jsonPayload:nsstring_from_c_string(request->json_payload())] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, [error.localizedDescription UTF8String]);
  }
  return Status::OK;
}}

Status FBIDBServiceHandler::simulate_memory_warning(grpc::ServerContext *context, const idb::SimulateMemoryWarningRequest *request, idb::SimulateMemoryWarningResponse *response)
{@autoreleasepool{
  NSError *error = nil;
  [[_commandExecutor simulateMemoryWarning] block:&error];
  if (error) {
    return Status(grpc::StatusCode::INTERNAL, [error.localizedDescription UTF8String]);
  }
  return Status::OK;
}}
