/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef FARF_MEDIUM
#define FARF_MEDIUM 1
#endif

#include "HAP_farf.h"
#include "timer.h"

#include "chre/core/event_loop_manager.h"
#include "chre/core/host_comms_manager.h"
#include "chre/core/settings.h"
#include "chre/platform/fatal_error.h"
#include "chre/platform/log.h"
#include "chre/platform/memory.h"
#include "chre/platform/shared/host_protocol_chre.h"
#include "chre/platform/shared/nanoapp_load_manager.h"
#ifdef CHRE_USE_BUFFERED_LOGGING
#include "chre/platform/shared/log_buffer_manager.h"
#endif
#include "chre/platform/slpi/fastrpc.h"
#include "chre/platform/slpi/power_control_util.h"
#include "chre/platform/system_time.h"
#include "chre/platform/system_timer.h"
#include "chre/util/fixed_size_blocking_queue.h"
#include "chre/util/flatbuffers/helpers.h"
#include "chre/util/macros.h"
#include "chre/util/nested_data_ptr.h"
#include "chre/util/unique_ptr.h"
#include "chre_api/chre/version.h"

#include <inttypes.h>
#include <limits.h>

namespace chre {

namespace {

constexpr size_t kOutboundQueueSize = 32;

//! The last time a time sync request message has been sent.
//! TODO: Make this a member of HostLinkBase
Nanoseconds gLastTimeSyncRequestNanos(0);

struct NanoappListData {
  ChreFlatBufferBuilder *builder;
  DynamicVector<NanoappListEntryOffset> nanoappEntries;
  uint16_t hostClientId;
};

enum class PendingMessageType {
  Shutdown,
  NanoappMessageToHost,
  HubInfoResponse,
  NanoappListResponse,
  LoadNanoappResponse,
  UnloadNanoappResponse,
  DebugDumpData,
  DebugDumpResponse,
  TimeSyncRequest,
  LowPowerMicAccessRequest,
  LowPowerMicAccessRelease,
  EncodedLogMessage,
  SelfTestResponse,
  MetricLog,
  NanConfigurationRequest,
};

struct PendingMessage {
  PendingMessage(PendingMessageType msgType, uint16_t hostClientId) {
    type = msgType;
    data.hostClientId = hostClientId;
  }

  PendingMessage(PendingMessageType msgType,
                 const MessageToHost *msgToHost = nullptr) {
    type = msgType;
    data.msgToHost = msgToHost;
  }

  PendingMessage(PendingMessageType msgType, ChreFlatBufferBuilder *builder) {
    type = msgType;
    data.builder = builder;
  }

  PendingMessageType type;
  union {
    const MessageToHost *msgToHost;
    uint16_t hostClientId;
    ChreFlatBufferBuilder *builder;
  } data;
};

struct UnloadNanoappCallbackData {
  uint64_t appId;
  uint32_t transactionId;
  uint16_t hostClientId;
  bool allowSystemNanoappUnload;
};

/**
 * @see buildAndEnqueueMessage()
 */
typedef void(MessageBuilderFunction)(ChreFlatBufferBuilder &builder,
                                     void *cookie);

FixedSizeBlockingQueue<PendingMessage, kOutboundQueueSize> gOutboundQueue;

int copyToHostBuffer(const ChreFlatBufferBuilder &builder,
                     unsigned char *buffer, size_t bufferSize,
                     unsigned int *messageLen) {
  uint8_t *data = builder.GetBufferPointer();
  size_t size = builder.GetSize();
  int result;

  if (size > bufferSize) {
    LOGE("Encoded structure size %zu too big for host buffer %zu; dropping",
         size, bufferSize);
    result = CHRE_FASTRPC_ERROR;
  } else {
    memcpy(buffer, data, size);
    *messageLen = size;
    result = CHRE_FASTRPC_SUCCESS;
  }

  return result;
}

/**
 * Wrapper function to enqueue a message on the outbound message queue. All
 * outgoing message to the host must be called through this function.
 *
 * @param message The message to send to host.
 *
 * @return true if the message was successfully added to the queue.
 */
bool enqueueMessage(PendingMessage message) {
  // Vote for big image temporarily when waking up the main thread waiting for
  // the message
  bool voteSuccess = slpiForceBigImage();
  bool success = gOutboundQueue.push(message);

  // Remove the vote only if we successfully made a big image transition
  if (voteSuccess) {
    slpiRemoveBigImageVote();
  }

  return success;
}

/**
 * Helper function that takes care of the boilerplate for allocating a
 * ChreFlatBufferBuilder on the heap and adding it to the outbound message
 * queue.
 *
 * @param msgType Identifies the message while in the outboud queue
 * @param initialBufferSize Number of bytes to reserve when first allocating the
 *        ChreFlatBufferBuilder
 * @param buildMsgFunc Synchronous callback used to encode the FlatBuffer
 *        message. Will not be invoked if allocation fails.
 * @param cookie Opaque pointer that will be passed through to buildMsgFunc
 *
 * @return true if the message was successfully added to the queue
 */
bool buildAndEnqueueMessage(PendingMessageType msgType,
                            size_t initialBufferSize,
                            MessageBuilderFunction *msgBuilder, void *cookie) {
  bool pushed = false;

  auto builder = MakeUnique<ChreFlatBufferBuilder>(initialBufferSize);
  if (builder.isNull()) {
    LOGE("Couldn't allocate memory for message type %d",
         static_cast<int>(msgType));
  } else {
    msgBuilder(*builder, cookie);

    // TODO: if this fails, ideally we should block for some timeout until
    // there's space in the queue
    if (!enqueueMessage(PendingMessage(msgType, builder.get()))) {
      LOGE("Couldn't push message type %d to outbound queue",
           static_cast<int>(msgType));
    } else {
      builder.release();
      pushed = true;
    }
  }

  return pushed;
}

/**
 * FlatBuffer message builder callback used with handleNanoappListRequest()
 */
void buildNanoappListResponse(ChreFlatBufferBuilder &builder, void *cookie) {
  auto nanoappAdderCallback = [](const Nanoapp *nanoapp, void *data) {
    auto *cbData = static_cast<NanoappListData *>(data);
    HostProtocolChre::addNanoappListEntry(
        *(cbData->builder), cbData->nanoappEntries, nanoapp->getAppId(),
        nanoapp->getAppVersion(), true /*enabled*/, nanoapp->isSystemNanoapp(),
        nanoapp->getAppPermissions(), nanoapp->getRpcServices());
  };

  // Add a NanoappListEntry to the FlatBuffer for each nanoapp
  auto *cbData = static_cast<NanoappListData *>(cookie);
  cbData->builder = &builder;
  EventLoop &eventLoop = EventLoopManagerSingleton::get()->getEventLoop();
  eventLoop.forEachNanoapp(nanoappAdderCallback, cbData);
  HostProtocolChre::finishNanoappListResponse(builder, cbData->nanoappEntries,
                                              cbData->hostClientId);
}

void handleUnloadNanoappCallback(SystemCallbackType /*type*/,
                                 UniquePtr<UnloadNanoappCallbackData> &&data) {
  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    auto *cbData = static_cast<UnloadNanoappCallbackData *>(cookie);

    bool success = false;
    uint16_t instanceId;
    EventLoop &eventLoop = EventLoopManagerSingleton::get()->getEventLoop();
    if (!eventLoop.findNanoappInstanceIdByAppId(cbData->appId, &instanceId)) {
      LOGE("Couldn't unload app ID 0x%016" PRIx64 ": not found", cbData->appId);
    } else {
      success =
          eventLoop.unloadNanoapp(instanceId, cbData->allowSystemNanoappUnload);
    }

    HostProtocolChre::encodeUnloadNanoappResponse(
        builder, cbData->hostClientId, cbData->transactionId, success);
  };

  constexpr size_t kInitialBufferSize = 52;
  buildAndEnqueueMessage(PendingMessageType::UnloadNanoappResponse,
                         kInitialBufferSize, msgBuilder, data.get());
}

int generateMessageToHost(const MessageToHost *msgToHost, unsigned char *buffer,
                          size_t bufferSize, unsigned int *messageLen) {
  // TODO: ideally we'd construct our flatbuffer directly in the
  // host-supplied buffer
  constexpr size_t kFixedSizePortion = 88;
  ChreFlatBufferBuilder builder(msgToHost->message.size() + kFixedSizePortion);
  HostProtocolChre::encodeNanoappMessage(
      builder, msgToHost->appId, msgToHost->toHostData.messageType,
      msgToHost->toHostData.hostEndpoint, msgToHost->message.data(),
      msgToHost->message.size(), msgToHost->toHostData.appPermissions,
      msgToHost->toHostData.messagePermissions, msgToHost->toHostData.wokeHost);

  int result = copyToHostBuffer(builder, buffer, bufferSize, messageLen);

  auto &hostCommsManager =
      EventLoopManagerSingleton::get()->getHostCommsManager();
  hostCommsManager.onMessageToHostComplete(msgToHost);

  return result;
}

int generateHubInfoResponse(uint16_t hostClientId, unsigned char *buffer,
                            size_t bufferSize, unsigned int *messageLen) {
  constexpr size_t kInitialBufferSize = 192;

  constexpr char kHubName[] = "CHRE on SLPI";
  constexpr char kVendor[] = "Google";
  constexpr char kToolchain[] =
      "Hexagon Tools 8.x (clang " STRINGIFY(__clang_major__) "." STRINGIFY(
          __clang_minor__) "." STRINGIFY(__clang_patchlevel__) ")";
  constexpr uint32_t kLegacyPlatformVersion = 0;
  constexpr uint32_t kLegacyToolchainVersion =
      ((__clang_major__ & 0xFF) << 24) | ((__clang_minor__ & 0xFF) << 16) |
      (__clang_patchlevel__ & 0xFFFF);
  constexpr float kPeakMips = 350;
  constexpr float kStoppedPower = 0;
  constexpr float kSleepPower = 1;
  constexpr float kPeakPower = 15;

  // Note that this may execute prior to EventLoopManager::lateInit() completing
  ChreFlatBufferBuilder builder(kInitialBufferSize);
  HostProtocolChre::encodeHubInfoResponse(
      builder, kHubName, kVendor, kToolchain, kLegacyPlatformVersion,
      kLegacyToolchainVersion, kPeakMips, kStoppedPower, kSleepPower,
      kPeakPower, chreGetMessageToHostMaxSize(), chreGetPlatformId(),
      chreGetVersion(), hostClientId, /* supportsReliableMessage= */ false);

  return copyToHostBuffer(builder, buffer, bufferSize, messageLen);
}

int generateMessageFromBuilder(ChreFlatBufferBuilder *builder,
                               unsigned char *buffer, size_t bufferSize,
                               unsigned int *messageLen,
                               bool isEncodedLogMessage) {
  CHRE_ASSERT(builder != nullptr);
  int result = copyToHostBuffer(*builder, buffer, bufferSize, messageLen);

#ifdef CHRE_USE_BUFFERED_LOGGING
  if (isEncodedLogMessage && LogBufferManagerSingleton::isInitialized()) {
    LogBufferManagerSingleton::get()->onLogsSentToHost();
  }
#else
  UNUSED_VAR(isEncodedLogMessage);
#endif

  builder->~ChreFlatBufferBuilder();
  memoryFree(builder);
  return result;
}

void sendDebugDumpData(uint16_t hostClientId, const char *debugStr,
                       size_t debugStrSize) {
  struct DebugDumpMessageData {
    uint16_t hostClientId;
    const char *debugStr;
    size_t debugStrSize;
  };

  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    const auto *data = static_cast<const DebugDumpMessageData *>(cookie);
    HostProtocolChre::encodeDebugDumpData(builder, data->hostClientId,
                                          data->debugStr, data->debugStrSize);
  };

  constexpr size_t kFixedSizePortion = 52;
  DebugDumpMessageData data;
  data.hostClientId = hostClientId;
  data.debugStr = debugStr;
  data.debugStrSize = debugStrSize;
  buildAndEnqueueMessage(PendingMessageType::DebugDumpData,
                         kFixedSizePortion + debugStrSize, msgBuilder, &data);
}

void sendDebugDumpResponse(uint16_t hostClientId, bool success,
                           uint32_t dataCount) {
  struct DebugDumpResponseData {
    uint16_t hostClientId;
    bool success;
    uint32_t dataCount;
  };

  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    const auto *data = static_cast<const DebugDumpResponseData *>(cookie);
    HostProtocolChre::encodeDebugDumpResponse(builder, data->hostClientId,
                                              data->success, data->dataCount);
  };

  constexpr size_t kInitialSize = 52;
  DebugDumpResponseData data;
  data.hostClientId = hostClientId;
  data.success = success;
  data.dataCount = dataCount;
  buildAndEnqueueMessage(PendingMessageType::DebugDumpResponse, kInitialSize,
                         msgBuilder, &data);
}

void sendSelfTestResponse(uint16_t hostClientId, bool success) {
  struct SelfTestResponseData {
    uint16_t hostClientId;
    bool success;
  };

  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    const auto *data = static_cast<const SelfTestResponseData *>(cookie);
    HostProtocolChre::encodeSelfTestResponse(builder, data->hostClientId,
                                             data->success);
  };

  constexpr size_t kInitialSize = 52;
  SelfTestResponseData data;
  data.hostClientId = hostClientId;
  data.success = success;
  buildAndEnqueueMessage(PendingMessageType::SelfTestResponse, kInitialSize,
                         msgBuilder, &data);
}

/**
 * Sends a request to the host for a time sync message.
 */
void sendTimeSyncRequest() {
  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    HostProtocolChre::encodeTimeSyncRequest(builder);
  };

  constexpr size_t kInitialSize = 52;
  buildAndEnqueueMessage(PendingMessageType::TimeSyncRequest, kInitialSize,
                         msgBuilder, nullptr);

  gLastTimeSyncRequestNanos = SystemTime::getMonotonicTime();
}

void setTimeSyncRequestTimer(Nanoseconds delay) {
  static SystemTimer sTimeSyncRequestTimer;
  static bool sTimeSyncRequestTimerInitialized = false;

  // Check for timer init since this method might be called before CHRE
  // init is called.
  if (!sTimeSyncRequestTimerInitialized) {
    if (!sTimeSyncRequestTimer.init()) {
      FATAL_ERROR("Failed to initialize time sync request timer.");
    } else {
      sTimeSyncRequestTimerInitialized = true;
    }
  }
  if (sTimeSyncRequestTimer.isActive()) {
    sTimeSyncRequestTimer.cancel();
  }
  auto callback = [](void * /* data */) { sendTimeSyncRequest(); };
  if (!sTimeSyncRequestTimer.set(callback, nullptr /* data */, delay)) {
    LOGE("Failed to set time sync request timer.");
  }
}

/**
 * Helper function that prepares a nanoapp that can be loaded into the system
 * from a file stored on disk.
 *
 * @param hostClientId the ID of client that originated this transaction
 * @param transactionId the ID of the transaction
 * @param appId the ID of the app to load
 * @param appVersion the version of the app to load
 * @param targetApiVersion the API version this nanoapp is targeted for
 * @param appFilename Null-terminated ASCII string containing the file name that
 *     contains the app binary to be loaded.
 *
 * @return A valid pointer to a nanoapp that can be loaded into the system. A
 *     nullptr if the preparation process fails.
 */
UniquePtr<Nanoapp> handleLoadNanoappFile(uint16_t hostClientId,
                                         uint32_t transactionId, uint64_t appId,
                                         uint32_t appVersion,
                                         uint32_t targetApiVersion,
                                         const char *appFilename) {
  LOGD("Load nanoapp request for app ID 0x%016" PRIx64 " ver 0x%" PRIx32
       " target API 0x%08" PRIx32 " (txnId %" PRIu32 " client %" PRIu16 ")",
       appId, appVersion, targetApiVersion, transactionId, hostClientId);

  auto nanoapp = MakeUnique<Nanoapp>();

  if (nanoapp.isNull()) {
    LOG_OOM();
  } else if (!nanoapp->setAppInfo(appId, appVersion, appFilename,
                                  targetApiVersion) ||
             !nanoapp->isLoaded()) {
    nanoapp.reset(nullptr);
  }

  return nanoapp;
}

/**
 * FastRPC method invoked by the host to block on messages
 *
 * @param buffer Output buffer to populate with message data
 * @param bufferLen Size of the buffer, in bytes
 * @param messageLen Output parameter to populate with the size of the message
 *        in bytes upon success
 *
 * @return 0 on success, nonzero on failure
 */
extern "C" int chre_slpi_get_message_to_host(unsigned char *buffer,
                                             int bufferLen,
                                             unsigned int *messageLen) {
  CHRE_ASSERT(buffer != nullptr);
  CHRE_ASSERT(bufferLen > 0);
  CHRE_ASSERT(messageLen != nullptr);
  int result = CHRE_FASTRPC_ERROR;

  if (bufferLen <= 0 || buffer == nullptr || messageLen == nullptr) {
    // Note that we can't use regular logs here as they can result in sending
    // a message, leading to an infinite loop if the error is persistent
    FARF(FATAL, "Invalid buffer size %d or bad pointers (buf %d len %d)",
         bufferLen, (buffer == nullptr), (messageLen == nullptr));
  } else {
    size_t bufferSize = static_cast<size_t>(bufferLen);
    PendingMessage pendingMsg = gOutboundQueue.pop();

    switch (pendingMsg.type) {
      case PendingMessageType::Shutdown:
        result = CHRE_FASTRPC_ERROR_SHUTTING_DOWN;
        break;

      case PendingMessageType::NanoappMessageToHost:
        result = generateMessageToHost(pendingMsg.data.msgToHost, buffer,
                                       bufferSize, messageLen);
        break;

      case PendingMessageType::HubInfoResponse:
        result = generateHubInfoResponse(pendingMsg.data.hostClientId, buffer,
                                         bufferSize, messageLen);
        break;

      case PendingMessageType::NanoappListResponse:
      case PendingMessageType::LoadNanoappResponse:
      case PendingMessageType::UnloadNanoappResponse:
      case PendingMessageType::DebugDumpData:
      case PendingMessageType::DebugDumpResponse:
      case PendingMessageType::TimeSyncRequest:
      case PendingMessageType::LowPowerMicAccessRequest:
      case PendingMessageType::LowPowerMicAccessRelease:
      case PendingMessageType::EncodedLogMessage:
      case PendingMessageType::SelfTestResponse:
      case PendingMessageType::MetricLog:
      case PendingMessageType::NanConfigurationRequest:
        result = generateMessageFromBuilder(
            pendingMsg.data.builder, buffer, bufferSize, messageLen,
            pendingMsg.type == PendingMessageType::EncodedLogMessage);
        break;

      default:
        CHRE_ASSERT_LOG(false, "Unexpected pending message type");
    }
  }

  // Opportunistically send a time sync message (1 hour period threshold)
  constexpr Seconds kOpportunisticTimeSyncPeriod = Seconds(60 * 60 * 1);
  if (SystemTime::getMonotonicTime() >
      gLastTimeSyncRequestNanos + kOpportunisticTimeSyncPeriod) {
    sendTimeSyncRequest();
  }

  return result;
}

/**
 * FastRPC method invoked by the host to send a message to the system
 *
 * @param buffer
 * @param size
 *
 * @return 0 on success, nonzero on failure
 */
extern "C" int chre_slpi_deliver_message_from_host(const unsigned char *message,
                                                   int messageLen) {
  CHRE_ASSERT(message != nullptr);
  CHRE_ASSERT(messageLen > 0);
  int result = CHRE_FASTRPC_ERROR;

  if (message == nullptr || messageLen <= 0) {
    LOGE("Got null or invalid size (%d) message from host", messageLen);
  } else if (!HostProtocolChre::decodeMessageFromHost(
                 message, static_cast<size_t>(messageLen))) {
    LOGE("Failed to decode/handle message");
  } else {
    result = CHRE_FASTRPC_SUCCESS;
  }

  return result;
}

}  // anonymous namespace

void sendDebugDumpResultToHost(uint16_t hostClientId, const char *debugStr,
                               size_t debugStrSize, bool complete,
                               uint32_t dataCount) {
  if (debugStrSize > 0) {
    sendDebugDumpData(hostClientId, debugStr, debugStrSize);
  }

  if (complete) {
    sendDebugDumpResponse(hostClientId, true /*success*/, dataCount);
  }
}

void HostLink::flushMessagesSentByNanoapp(uint64_t /*appId*/) {
  // TODO: this is not completely safe since it's timer-based, but should work
  // well enough for the initial implementation. To be fully safe, we'd need
  // some synchronization with the thread that runs
  // chre_slpi_get_message_to_host(), e.g. a mutex that is held by that thread
  // prior to calling pop() and only released after onMessageToHostComplete
  // would've been called. If we acquire that mutex here, and hold it while
  // purging any messages sent by the nanoapp in the queue, we can be certain
  // that onMessageToHostComplete will not be called after this function returns
  // for messages sent by that nanoapp
  flushOutboundQueue();

  // One extra sleep to try to ensure that any messages popped just before
  // checking empty() are fully processed before we return
  constexpr time_timetick_type kFinalDelayUsec = 10000;
  timer_sleep(kFinalDelayUsec, T_USEC, true /* non_deferrable */);
}

bool HostLink::sendMessage(const MessageToHost *message) {
  return enqueueMessage(
      PendingMessage(PendingMessageType::NanoappMessageToHost, message));
}

bool HostLink::sendMessageDeliveryStatus(uint32_t /* messageSequenceNumber */,
                                         uint8_t /* errorCode */) {
  return false;
}

bool HostLink::sendMetricLog(uint32_t metricId, const uint8_t *encodedMetric,
                             size_t encodedMetricLen) {
  struct MetricLogData {
    uint32_t metricId;
    const uint8_t *encodedMetric;
    size_t encodedMetricLen;
  };

  MetricLogData data;
  data.metricId = metricId;
  data.encodedMetric = encodedMetric;
  data.encodedMetricLen = encodedMetricLen;

  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    const auto *data = static_cast<const MetricLogData *>(cookie);
    HostProtocolChre::encodeMetricLog(
        builder, data->metricId, data->encodedMetric, data->encodedMetricLen);
  };

  constexpr size_t kInitialSize = 52;
  buildAndEnqueueMessage(PendingMessageType::MetricLog, kInitialSize,
                         msgBuilder, &data);
  return true;
}

bool HostLinkBase::flushOutboundQueue() {
  int waitCount = 5;

  FARF(MEDIUM, "Draining message queue");
  while (!gOutboundQueue.empty() && waitCount-- > 0) {
    timer_sleep(kPollingIntervalUsec, T_USEC, true /* non_deferrable */);
  }

  return (waitCount >= 0);
}

void HostLinkBase::sendLogMessage(const uint8_t *logMessage,
                                  size_t logMessageSize) {
  struct LogMessageData {
    const uint8_t *logMsg;
    size_t logMsgSize;
  };

  LogMessageData logMessageData;

  logMessageData.logMsg = logMessage;
  logMessageData.logMsgSize = logMessageSize;

  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    const auto *data = static_cast<const LogMessageData *>(cookie);
    HostProtocolChre::encodeLogMessages(builder, data->logMsg,
                                        data->logMsgSize);
  };

  constexpr size_t kInitialSize = 128;
  buildAndEnqueueMessage(PendingMessageType::EncodedLogMessage, kInitialSize,
                         msgBuilder, &logMessageData);
}

void HostLinkBase::sendLogMessageV2(const uint8_t *logMessage,
                                    size_t logMessageSize,
                                    uint32_t numLogsDropped) {
  struct LogMessageData {
    const uint8_t *logMsg;
    size_t logMsgSize;
    uint32_t numLogsDropped;
  };

  LogMessageData logMessageData{logMessage, logMessageSize, numLogsDropped};

  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    const auto *data = static_cast<const LogMessageData *>(cookie);
    HostProtocolChre::encodeLogMessagesV2(
        builder, data->logMsg, data->logMsgSize, data->numLogsDropped);
  };

  constexpr size_t kInitialSize = 128;
  buildAndEnqueueMessage(PendingMessageType::EncodedLogMessage, kInitialSize,
                         msgBuilder, &logMessageData);
}

void HostLinkBase::sendNanConfiguration(bool enable) {
  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    const auto *data = static_cast<const bool *>(cookie);
    HostProtocolChre::encodeNanConfigurationRequest(builder, *data);
  };

  constexpr size_t kInitialSize = 48;
  buildAndEnqueueMessage(PendingMessageType::NanConfigurationRequest,
                         kInitialSize, msgBuilder, &enable);
}

void HostLinkBase::shutdown() {
  // Push a null message so the blocking call in chre_slpi_get_message_to_host()
  // returns and the host can exit cleanly. If the queue is full, try again to
  // avoid getting stuck (no other new messages should be entering the queue at
  // this time). Don't wait too long as the host-side binary may have died in
  // a state where it's not blocked in chre_slpi_get_message_to_host().
  int retryCount = 5;
  FARF(MEDIUM, "Shutting down host link");
  while (!enqueueMessage(PendingMessage(PendingMessageType::Shutdown)) &&
         --retryCount > 0) {
    timer_sleep(kPollingIntervalUsec, T_USEC, true /* non_deferrable */);
  }

  if (retryCount <= 0) {
    // Don't use LOGE, as it may involve trying to send a message
    FARF(ERROR,
         "No room in outbound queue for shutdown message and host not "
         "draining queue!");
  } else {
    // We were able to push the shutdown message. Wait for the queue to
    // completely flush before returning.
    if (!flushOutboundQueue()) {
      FARF(ERROR, "Host took too long to drain outbound queue; exiting anyway");
    } else {
      FARF(MEDIUM, "Finished draining queue");
    }
  }
}

void sendAudioRequest() {
  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    HostProtocolChre::encodeLowPowerMicAccessRequest(builder);
  };

  constexpr size_t kInitialSize = 32;
  buildAndEnqueueMessage(PendingMessageType::LowPowerMicAccessRequest,
                         kInitialSize, msgBuilder, nullptr);
}

void sendAudioRelease() {
  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    HostProtocolChre::encodeLowPowerMicAccessRelease(builder);
  };

  constexpr size_t kInitialSize = 32;
  buildAndEnqueueMessage(PendingMessageType::LowPowerMicAccessRelease,
                         kInitialSize, msgBuilder, nullptr);
}

void HostMessageHandlers::sendFragmentResponse(uint16_t hostClientId,
                                               uint32_t transactionId,
                                               uint32_t fragmentId,
                                               bool success) {
  struct FragmentedLoadInfoResponse {
    uint16_t hostClientId;
    uint32_t transactionId;
    uint32_t fragmentId;
    bool success;
  };

  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
    auto *cbData = static_cast<FragmentedLoadInfoResponse *>(cookie);
    HostProtocolChre::encodeLoadNanoappResponse(
        builder, cbData->hostClientId, cbData->transactionId, cbData->success,
        cbData->fragmentId);
  };

  FragmentedLoadInfoResponse response = {
      .hostClientId = hostClientId,
      .transactionId = transactionId,
      .fragmentId = fragmentId,
      .success = success,
  };
  constexpr size_t kInitialBufferSize = 48;
  buildAndEnqueueMessage(PendingMessageType::LoadNanoappResponse,
                         kInitialBufferSize, msgBuilder, &response);
}

void HostMessageHandlers::handleNanoappMessage(
    uint64_t appId, uint32_t messageType, uint16_t hostEndpoint,
    const void *messageData, size_t messageDataLen, bool isReliable,
    uint32_t messageSequenceNumber) {
  LOGD("Parsed nanoapp message from host: app ID 0x%016" PRIx64
       ", endpoint "
       "0x%" PRIx16 ", msgType %" PRIu32 ", payload size %zu",
       appId, hostEndpoint, messageType, messageDataLen);

  HostCommsManager &manager =
      EventLoopManagerSingleton::get()->getHostCommsManager();
  manager.sendMessageToNanoappFromHost(appId, messageType, hostEndpoint,
                                       messageData, messageDataLen, isReliable,
                                       messageSequenceNumber);
}

void HostMessageHandlers::handleMessageDeliveryStatus(
    uint32_t /* messageSequenceNumber */, uint8_t /* errorCode */) {}

void HostMessageHandlers::handleHubInfoRequest(uint16_t hostClientId) {
  // We generate the response in the context of chre_slpi_get_message_to_host
  LOGD("Hub info request from client ID %" PRIu16, hostClientId);
  enqueueMessage(
      PendingMessage(PendingMessageType::HubInfoResponse, hostClientId));
}

void HostMessageHandlers::handleNanoappListRequest(uint16_t hostClientId) {
  auto callback = [](uint16_t /*type*/, void *data, void * /*extraData*/) {
    uint16_t cbHostClientId = NestedDataPtr<uint16_t>(data);

    NanoappListData cbData = {};
    cbData.hostClientId = cbHostClientId;

    size_t expectedNanoappCount =
        EventLoopManagerSingleton::get()->getEventLoop().getNanoappCount();
    if (!cbData.nanoappEntries.reserve(expectedNanoappCount)) {
      LOG_OOM();
    } else {
      constexpr size_t kFixedOverhead = 48;
      constexpr size_t kPerNanoappSize = 32;
      size_t initialBufferSize =
          (kFixedOverhead + expectedNanoappCount * kPerNanoappSize);

      buildAndEnqueueMessage(PendingMessageType::NanoappListResponse,
                             initialBufferSize, buildNanoappListResponse,
                             &cbData);
    }
  };

  LOGD("Nanoapp list request from client ID %" PRIu16, hostClientId);
  EventLoopManagerSingleton::get()->deferCallback(
      SystemCallbackType::NanoappListResponse,
      NestedDataPtr<uint16_t>(hostClientId), callback);
}

void HostMessageHandlers::handleLoadNanoappRequest(
    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
    uint32_t appVersion, uint32_t appFlags, uint32_t targetApiVersion,
    const void *buffer, size_t bufferLen, const char *appFileName,
    uint32_t fragmentId, size_t appBinaryLen, bool respondBeforeStart) {
  if (appFileName == nullptr) {
    loadNanoappData(hostClientId, transactionId, appId, appVersion, appFlags,
                    targetApiVersion, buffer, bufferLen, fragmentId,
                    appBinaryLen, respondBeforeStart);
    return;
  }

  UniquePtr<Nanoapp> pendingNanoapp =
      handleLoadNanoappFile(hostClientId, transactionId, appId, appVersion,
                            targetApiVersion, appFileName);

  if (!pendingNanoapp.isNull()) {
    auto cbData = MakeUnique<LoadNanoappCallbackData>();
    if (cbData.isNull()) {
      LOG_OOM();
    } else {
      cbData->transactionId = transactionId;
      cbData->hostClientId = hostClientId;
      cbData->appId = appId;
      cbData->fragmentId = fragmentId;
      cbData->nanoapp = std::move(pendingNanoapp);

      // Note that if this fails, we'll generate the error response in
      // the normal deferred callback
      EventLoopManagerSingleton::get()->deferCallback(
          SystemCallbackType::FinishLoadingNanoapp, std::move(cbData),
          finishLoadingNanoappCallback);
    }
  }
}

void HostMessageHandlers::handleUnloadNanoappRequest(
    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
    bool allowSystemNanoappUnload) {
  LOGD("Unload nanoapp request (txnID %" PRIu32 ") for appId 0x%016" PRIx64
       " system %d",
       transactionId, appId, allowSystemNanoappUnload);
  auto cbData = MakeUnique<UnloadNanoappCallbackData>();
  if (cbData == nullptr) {
    LOG_OOM();
  } else {
    cbData->appId = appId;
    cbData->transactionId = transactionId;
    cbData->hostClientId = hostClientId;
    cbData->allowSystemNanoappUnload = allowSystemNanoappUnload;

    EventLoopManagerSingleton::get()->deferCallback(
        SystemCallbackType::HandleUnloadNanoapp, std::move(cbData),
        handleUnloadNanoappCallback);
  }
}

void HostMessageHandlers::handleTimeSyncMessage(int64_t offset) {
  SystemTime::setEstimatedHostTimeOffset(offset);

  // Schedule a time sync request since offset may drift
  constexpr Seconds kClockDriftTimeSyncPeriod =
      Seconds(60 * 60 * 6);  // 6 hours
  setTimeSyncRequestTimer(kClockDriftTimeSyncPeriod);
}

void HostMessageHandlers::handleDebugDumpRequest(uint16_t hostClientId) {
  if (!chre::EventLoopManagerSingleton::get()
           ->getDebugDumpManager()
           .onDebugDumpRequested(hostClientId)) {
    LOGE("Couldn't trigger debug dump process");
    sendDebugDumpResponse(hostClientId, false /*success*/, 0 /*dataCount*/);
  }
}

void HostMessageHandlers::handleSettingChangeMessage(fbs::Setting setting,
                                                     fbs::SettingState state) {
  Setting chreSetting;
  bool chreSettingEnabled;
  if (HostProtocolChre::getSettingFromFbs(setting, &chreSetting) &&
      HostProtocolChre::getSettingEnabledFromFbs(state, &chreSettingEnabled)) {
    EventLoopManagerSingleton::get()->getSettingManager().postSettingChange(
        chreSetting, chreSettingEnabled);
  }
}

void HostMessageHandlers::handleSelfTestRequest(uint16_t hostClientId) {
  // TODO(b/182201569): Run test
  bool success = true;
  sendSelfTestResponse(hostClientId, success);
}

void HostMessageHandlers::handlePulseRequest() {}

void HostMessageHandlers::handleNanConfigurationUpdate(bool enabled) {
#ifdef CHRE_WIFI_NAN_SUPPORT_ENABLED
  EventLoopManagerSingleton::get()
      ->getWifiRequestManager()
      .updateNanAvailability(enabled);
#else
  UNUSED_VAR(enabled);
#endif  // CHRE_WIFI_NAN_SUPPORT_ENABLED
}

}  // namespace chre
