/*
 * Copyright (C) 2020 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.
 */

#include "chpp/clients/wifi.h"

#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include "chpp/app.h"
#include "chpp/clients.h"
#include "chpp/clients/discovery.h"
#ifdef CHPP_CLIENT_ENABLED_TIMESYNC
#include "chpp/clients/timesync.h"
#endif
#include "chpp/common/standard_uuids.h"
#include "chpp/common/wifi.h"
#include "chpp/common/wifi_types.h"
#include "chpp/common/wifi_utils.h"
#include "chpp/log.h"
#include "chpp/macros.h"
#include "chpp/memory.h"
#include "chre/pal/wifi.h"
#include "chre_api/chre/wifi.h"

#ifndef CHPP_WIFI_DISCOVERY_TIMEOUT_MS
#define CHPP_WIFI_DISCOVERY_TIMEOUT_MS CHPP_DISCOVERY_DEFAULT_TIMEOUT_MS
#endif

#ifndef CHPP_WIFI_MAX_TIMESYNC_AGE_NS
#define CHPP_WIFI_MAX_TIMESYNC_AGE_NS CHPP_TIMESYNC_DEFAULT_MAX_AGE_NS
#endif

/************************************************
 *  Prototypes
 ***********************************************/

static enum ChppAppErrorCode chppDispatchWifiResponse(void *clientContext,
                                                      uint8_t *buf, size_t len);
static enum ChppAppErrorCode chppDispatchWifiNotification(void *clientContext,
                                                          uint8_t *buf,
                                                          size_t len);
static bool chppWifiClientInit(void *clientContext, uint8_t handle,
                               struct ChppVersion serviceVersion);
static void chppWifiClientDeinit(void *clientContext);
static void chppWifiClientNotifyReset(void *clientContext);
static void chppWifiClientNotifyMatch(void *clientContext);

/************************************************
 *  Private Definitions
 ***********************************************/

/**
 * Structure to maintain state for the WiFi client and its Request/Response
 * (RR) functionality.
 */
struct ChppWifiClientState {
  struct ChppClientState client;     // WiFi client state
  const struct chrePalWifiApi *api;  // WiFi PAL API

  struct ChppRequestResponseState rRState[CHPP_WIFI_CLIENT_REQUEST_MAX + 1];

  uint32_t capabilities;            // Cached GetCapabilities result
  bool scanMonitorEnabled;          // Scan monitoring is enabled
  bool scanMonitorSilenceCallback;  // Silence callback during recovery from a
                                    // service reset
  bool capabilitiesValid;  // Flag to indicate if the capabilities result
                           // is valid
};

// Note: This global definition of gWifiClientContext supports only one
// instance of the CHPP WiFi client at a time.
struct ChppWifiClientState gWifiClientContext;
static const struct chrePalSystemApi *gSystemApi;
static const struct chrePalWifiCallbacks *gCallbacks;

/**
 * Configuration parameters for this client
 */
static const struct ChppClient kWifiClientConfig = {
    .descriptor.uuid = CHPP_UUID_WIFI_STANDARD,

    // Version
    .descriptor.version.major = 1,
    .descriptor.version.minor = 0,
    .descriptor.version.patch = 0,

    // Notifies client if CHPP is reset
    .resetNotifierFunctionPtr = &chppWifiClientNotifyReset,

    // Notifies client if they are matched to a service
    .matchNotifierFunctionPtr = &chppWifiClientNotifyMatch,

    // Service response dispatch function pointer
    .responseDispatchFunctionPtr = &chppDispatchWifiResponse,

    // Service notification dispatch function pointer
    .notificationDispatchFunctionPtr = &chppDispatchWifiNotification,

    // Service response dispatch function pointer
    .initFunctionPtr = &chppWifiClientInit,

    // Service notification dispatch function pointer
    .deinitFunctionPtr = &chppWifiClientDeinit,

    // Number of request-response states in the rRStates array.
    .rRStateCount = ARRAY_SIZE(gWifiClientContext.rRState),

    // Min length is the entire header
    .minLength = sizeof(struct ChppAppHeader),
};

/************************************************
 *  Prototypes
 ***********************************************/

static bool chppWifiClientOpen(const struct chrePalSystemApi *systemApi,
                               const struct chrePalWifiCallbacks *callbacks);
static void chppWifiClientClose(void);
static uint32_t chppWifiClientGetCapabilities(void);
static bool chppWifiClientConfigureScanMonitor(bool enable);
static bool chppWifiClientRequestScan(const struct chreWifiScanParams *params);
static void chppWifiClientReleaseScanEvent(struct chreWifiScanEvent *event);
static bool chppWifiClientRequestRanging(
    const struct chreWifiRangingParams *params);
static void chppWifiClientReleaseRangingEvent(
    struct chreWifiRangingEvent *event);

static void chppWiFiRecoverScanMonitor(
    struct ChppWifiClientState *clientContext);
static void chppWifiCloseResult(struct ChppWifiClientState *clientContext,
                                uint8_t *buf, size_t len);
static void chppWifiGetCapabilitiesResult(
    struct ChppWifiClientState *clientContext, uint8_t *buf, size_t len);
static void chppWifiConfigureScanMonitorResult(
    struct ChppWifiClientState *clientContext, uint8_t *buf, size_t len);
static void chppWifiRequestScanResult(struct ChppWifiClientState *clientContext,
                                      uint8_t *buf, size_t len);
static void chppWifiRequestRangingResult(
    struct ChppWifiClientState *clientContext, uint8_t *buf, size_t len);
static void chppWifiRequestNanSubscribeResult(uint8_t *buf, size_t len);

static void chppWifiScanEventNotification(
    struct ChppWifiClientState *clientContext, uint8_t *buf, size_t len);
static void chppWifiRangingEventNotification(
    struct ChppWifiClientState *clientContext, uint8_t *buf, size_t len);
static void chppWifiDiscoveryEventNotification(uint8_t *buf, size_t len);
static void chppWifiNanServiceLostEventNotification(uint8_t *buf, size_t len);
static void chppWifiNanServiceTerminatedEventNotification(uint8_t *buf,
                                                          size_t len);
static void chppWifiRequestNanSubscribeNotification(uint8_t *buf, size_t len);
static void chppWifiNanSubscriptionCanceledNotification(uint8_t *buf,
                                                        size_t len);
static void chppWifiNanSubscriptionCanceledResult(uint8_t *buf, size_t len);

/************************************************
 *  Private Functions
 ***********************************************/

/**
 * Dispatches a service response from the transport layer that is determined to
 * be for the WiFi client.
 *
 * This function is called from the app layer using its function pointer given
 * during client registration.
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 *
 * @return Indicates the result of this function call.
 */
static enum ChppAppErrorCode chppDispatchWifiResponse(void *clientContext,
                                                      uint8_t *buf,
                                                      size_t len) {
  struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
  struct ChppWifiClientState *wifiClientContext =
      (struct ChppWifiClientState *)clientContext;
  enum ChppAppErrorCode error = CHPP_APP_ERROR_NONE;

  if (rxHeader->command > CHPP_WIFI_CLIENT_REQUEST_MAX) {
    error = CHPP_APP_ERROR_INVALID_COMMAND;

  } else if (!chppClientTimestampResponse(
                 &wifiClientContext->client,
                 &wifiClientContext->rRState[rxHeader->command], rxHeader)) {
    error = CHPP_APP_ERROR_UNEXPECTED_RESPONSE;

  } else {
    switch (rxHeader->command) {
      case CHPP_WIFI_OPEN: {
        chppClientProcessOpenResponse(&wifiClientContext->client, buf, len);
        chppWiFiRecoverScanMonitor(wifiClientContext);
        break;
      }

      case CHPP_WIFI_CLOSE: {
        chppWifiCloseResult(wifiClientContext, buf, len);
        break;
      }

      case CHPP_WIFI_GET_CAPABILITIES: {
        chppWifiGetCapabilitiesResult(wifiClientContext, buf, len);
        break;
      }

      case CHPP_WIFI_CONFIGURE_SCAN_MONITOR_ASYNC: {
        chppWifiConfigureScanMonitorResult(wifiClientContext, buf, len);
        break;
      }

      case CHPP_WIFI_REQUEST_SCAN_ASYNC: {
        chppWifiRequestScanResult(wifiClientContext, buf, len);
        break;
      }

      case CHPP_WIFI_REQUEST_RANGING_ASYNC:
      case CHPP_WIFI_REQUEST_NAN_RANGING_ASYNC: {
        chppWifiRequestRangingResult(wifiClientContext, buf, len);
        break;
      }

      case CHPP_WIFI_REQUEST_NAN_SUB: {
        chppWifiRequestNanSubscribeResult(buf, len);
        break;
      }

      case CHPP_WIFI_REQUEST_NAN_SUB_CANCEL: {
        chppWifiNanSubscriptionCanceledResult(buf, len);
        break;
      }

      default: {
        error = CHPP_APP_ERROR_INVALID_COMMAND;
        break;
      }
    }
  }

  return error;
}

/**
 * Dispatches a service notification from the transport layer that is determined
 * to be for the WiFi client.
 *
 * This function is called from the app layer using its function pointer given
 * during client registration.
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 *
 * @return Indicates the result of this function call.
 */
static enum ChppAppErrorCode chppDispatchWifiNotification(void *clientContext,
                                                          uint8_t *buf,
                                                          size_t len) {
  struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
  struct ChppWifiClientState *wifiClientContext =
      (struct ChppWifiClientState *)clientContext;
  enum ChppAppErrorCode error = CHPP_APP_ERROR_NONE;

  switch (rxHeader->command) {
    case CHPP_WIFI_REQUEST_SCAN_ASYNC: {
      chppWifiScanEventNotification(wifiClientContext, buf, len);
      break;
    }

    case CHPP_WIFI_REQUEST_RANGING_ASYNC:
    case CHPP_WIFI_REQUEST_NAN_RANGING_ASYNC: {
      chppWifiRangingEventNotification(wifiClientContext, buf, len);
      break;
    }

    case CHPP_WIFI_NOTIFICATION_NAN_SERVICE_DISCOVERY: {
      chppWifiDiscoveryEventNotification(buf, len);
      break;
    }

    case CHPP_WIFI_NOTIFICATION_NAN_SERVICE_LOST: {
      chppWifiNanServiceLostEventNotification(buf, len);
      break;
    }

    case CHPP_WIFI_NOTIFICATION_NAN_SERVICE_TERMINATED: {
      chppWifiNanServiceTerminatedEventNotification(buf, len);
      break;
    }

    case CHPP_WIFI_REQUEST_NAN_SUB: {
      chppWifiRequestNanSubscribeNotification(buf, len);
      break;
    }

    case CHPP_WIFI_REQUEST_NAN_SUB_CANCEL: {
      chppWifiNanSubscriptionCanceledNotification(buf, len);
      break;
    }

    default: {
      error = CHPP_APP_ERROR_INVALID_COMMAND;
      break;
    }
  }

  return error;
}

/**
 * Initializes the client and provides its handle number and the version of the
 * matched service when/if it the client is matched with a service during
 * discovery.
 *
 * @param clientContext Maintains status for each client instance.
 * @param handle Handle number for this client.
 * @param serviceVersion Version of the matched service.
 *
 * @return True if client is compatible and successfully initialized.
 */
static bool chppWifiClientInit(void *clientContext, uint8_t handle,
                               struct ChppVersion serviceVersion) {
  UNUSED_VAR(serviceVersion);

  struct ChppWifiClientState *wifiClientContext =
      (struct ChppWifiClientState *)clientContext;
  chppClientInit(&wifiClientContext->client, handle);

  return true;
}

/**
 * Deinitializes the client.
 *
 * @param clientContext Maintains status for each client instance.
 */
static void chppWifiClientDeinit(void *clientContext) {
  struct ChppWifiClientState *wifiClientContext =
      (struct ChppWifiClientState *)clientContext;
  chppClientDeinit(&wifiClientContext->client);
}

/**
 * Notifies the client of an incoming reset.
 *
 * @param clientContext Maintains status for each client instance.
 */
static void chppWifiClientNotifyReset(void *clientContext) {
  struct ChppWifiClientState *wifiClientContext =
      (struct ChppWifiClientState *)clientContext;

  chppClientCloseOpenRequests(&wifiClientContext->client, &kWifiClientConfig,
                              false /* clearOnly */);
  chppCheckWifiScanEventNotificationReset();

  if (wifiClientContext->client.openState != CHPP_OPEN_STATE_OPENED &&
      !wifiClientContext->client.pseudoOpen) {
    CHPP_LOGW("WiFi client reset but wasn't open");
  } else {
    CHPP_LOGI("WiFi client reopening from state=%" PRIu8,
              wifiClientContext->client.openState);
    chppClientSendOpenRequest(&wifiClientContext->client,
                              &wifiClientContext->rRState[CHPP_WIFI_OPEN],
                              CHPP_WIFI_OPEN,
                              /*blocking=*/false);
  }
}

/**
 * Notifies the client of being matched to a service.
 *
 * @param clientContext Maintains status for each client instance.
 */
static void chppWifiClientNotifyMatch(void *clientContext) {
  struct ChppWifiClientState *wifiClientContext =
      (struct ChppWifiClientState *)clientContext;

  if (wifiClientContext->client.pseudoOpen) {
    CHPP_LOGD("Pseudo-open WiFi client opening");
    chppClientSendOpenRequest(&wifiClientContext->client,
                              &wifiClientContext->rRState[CHPP_WIFI_OPEN],
                              CHPP_WIFI_OPEN,
                              /*blocking=*/false);
  }
}

/**
 * Restores the state of scan monitoring after an incoming reset.
 *
 * @param clientContext Maintains status for each client instance.
 */
static void chppWiFiRecoverScanMonitor(
    struct ChppWifiClientState *clientContext) {
  if (clientContext->scanMonitorEnabled) {
    CHPP_LOGI("Re-enabling WiFi scan monitoring after reset");
    clientContext->scanMonitorEnabled = false;
    clientContext->scanMonitorSilenceCallback = true;

    if (!chppWifiClientConfigureScanMonitor(true)) {
      clientContext->scanMonitorSilenceCallback = false;
      CHPP_DEBUG_ASSERT_LOG(false, "Failed to re-enable WiFi scan monitoring");
    }
  }
}

/**
 * Handles the service response for the close client request.
 *
 * This function is called from chppDispatchWifiResponse().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiCloseResult(struct ChppWifiClientState *clientContext,
                                uint8_t *buf, size_t len) {
  // TODO
  UNUSED_VAR(clientContext);
  UNUSED_VAR(buf);
  UNUSED_VAR(len);
}

/**
 * Handles the service response for the get capabilities client request.
 *
 * This function is called from chppDispatchWifiResponse().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiGetCapabilitiesResult(
    struct ChppWifiClientState *clientContext, uint8_t *buf, size_t len) {
  if (len < sizeof(struct ChppWifiGetCapabilitiesResponse)) {
    CHPP_LOGE("Bad WiFi capabilities len=%" PRIuSIZE, len);

  } else {
    struct ChppWifiGetCapabilitiesParameters *result =
        &((struct ChppWifiGetCapabilitiesResponse *)buf)->params;

    CHPP_LOGD("chppWifiGetCapabilitiesResult received capabilities=0x%" PRIx32,
              result->capabilities);

    CHPP_ASSERT((result->capabilities & CHPP_WIFI_DEFAULT_CAPABILITIES) ==
                CHPP_WIFI_DEFAULT_CAPABILITIES);
    if (result->capabilities != CHPP_WIFI_DEFAULT_CAPABILITIES) {
      CHPP_LOGE("WiFi capabilities 0x%" PRIx32 " != 0x%" PRIx32,
                result->capabilities, CHPP_WIFI_DEFAULT_CAPABILITIES);
    }

    clientContext->capabilitiesValid = true;
    clientContext->capabilities = result->capabilities;
  }
}

/**
 * Handles the service response for the Configure Scan Monitor client request.
 *
 * This function is called from chppDispatchWifiResponse().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiConfigureScanMonitorResult(
    struct ChppWifiClientState *clientContext, uint8_t *buf, size_t len) {
  UNUSED_VAR(clientContext);

  if (len < sizeof(struct ChppWifiConfigureScanMonitorAsyncResponse)) {
    // Short response length indicates an error
    gCallbacks->scanMonitorStatusChangeCallback(
        false, chppAppShortResponseErrorHandler(buf, len, "ScanMonitor"));

  } else {
    struct ChppWifiConfigureScanMonitorAsyncResponseParameters *result =
        &((struct ChppWifiConfigureScanMonitorAsyncResponse *)buf)->params;

    gWifiClientContext.scanMonitorEnabled = result->enabled;
    CHPP_LOGD(
        "chppWifiConfigureScanMonitorResult received enable=%d, "
        "errorCode=%" PRIu8,
        result->enabled, result->errorCode);

    if (!gWifiClientContext.scanMonitorSilenceCallback) {
      // Per the scanMonitorStatusChangeCallback API contract, unsolicited
      // calls to scanMonitorStatusChangeCallback must not be made, and it
      // should only be invoked as the direct result of an earlier call to
      // configureScanMonitor.
      gCallbacks->scanMonitorStatusChangeCallback(result->enabled,
                                                  result->errorCode);
    }  // Else, the WiFi subsystem has been reset and we are required to
       // silently reenable the scan monitor.

    gWifiClientContext.scanMonitorSilenceCallback = false;
  }
}

/**
 * Handles the service response for the Request Scan Result client request.
 *
 * This function is called from chppDispatchWifiResponse().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiRequestScanResult(struct ChppWifiClientState *clientContext,
                                      uint8_t *buf, size_t len) {
  UNUSED_VAR(clientContext);

  if (len < sizeof(struct ChppWifiRequestScanResponse)) {
    // Short response length indicates an error
    gCallbacks->scanResponseCallback(
        false, chppAppShortResponseErrorHandler(buf, len, "ScanRequest"));

  } else {
    struct ChppWifiRequestScanResponseParameters *result =
        &((struct ChppWifiRequestScanResponse *)buf)->params;
    CHPP_LOGI("Scan request success=%d at service", result->pending);
    gCallbacks->scanResponseCallback(result->pending, result->errorCode);
  }
}

/**
 * Handles the service response for the Request Ranging Result client request.
 *
 * This function is called from chppDispatchWifiResponse().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiRequestRangingResult(
    struct ChppWifiClientState *clientContext, uint8_t *buf, size_t len) {
  UNUSED_VAR(clientContext);
  UNUSED_VAR(len);

  struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;

  if (rxHeader->error != CHPP_APP_ERROR_NONE) {
    CHPP_LOGE("Ranging failed at service" PRIu8);
    gCallbacks->rangingEventCallback(chppAppErrorToChreError(rxHeader->error),
                                     NULL);

  } else {
    CHPP_LOGD("Ranging request accepted at service");
  }
}

/**
 * Handles the service response for the NAN subscribe client request.
 *
 * This function is called from chppDispatchWifiResponse().
 *
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiRequestNanSubscribeResult(uint8_t *buf, size_t len) {
  UNUSED_VAR(len);

  struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;

  if (rxHeader->error != CHPP_APP_ERROR_NONE) {
    gCallbacks->nanServiceIdentifierCallback(
        chppAppErrorToChreError(rxHeader->error), 0 /* subscriptionId */);

  } else {
    CHPP_LOGD("NAN sub accepted at service");
  }
}

/**
 * Handles the service response for the NAN subscription cancel client request.
 *
 * This function is called from chppDispatchWifiResponse().
 *
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiNanSubscriptionCanceledResult(uint8_t *buf, size_t len) {
  UNUSED_VAR(len);

  struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;

  if (rxHeader->error != CHPP_APP_ERROR_NONE) {
    gCallbacks->nanSubscriptionCanceledCallback(
        chppAppErrorToChreError(rxHeader->error), 0 /* subscriptionId */);

  } else {
    CHPP_LOGD("NAN sub cancel accepted at service");
  }
}

/**
 * Handles the WiFi scan event service notification.
 *
 * This function is called from chppDispatchWifiNotification().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiScanEventNotification(
    struct ChppWifiClientState *clientContext, uint8_t *buf, size_t len) {
  UNUSED_VAR(clientContext);
  CHPP_LOGD("chppWifiScanEventNotification received data len=%" PRIuSIZE, len);

  buf += sizeof(struct ChppAppHeader);
  len -= sizeof(struct ChppAppHeader);

  struct chreWifiScanEvent *chre =
      chppWifiScanEventToChre((struct ChppWifiScanEvent *)buf, len);

  if (chre == NULL) {
    CHPP_LOGE("Scan event conversion failed len=%" PRIuSIZE, len);
  } else {
#ifdef CHPP_CLIENT_ENABLED_TIMESYNC
    uint64_t correctedTime =
        chre->referenceTime -
        (uint64_t)chppTimesyncGetOffset(gWifiClientContext.client.appContext,
                                        CHPP_WIFI_MAX_TIMESYNC_AGE_NS);
    CHPP_LOGD("WiFi scan time corrected from %" PRIu64 "to %" PRIu64,
              chre->referenceTime / CHPP_NSEC_PER_MSEC,
              correctedTime / CHPP_NSEC_PER_MSEC);
    chre->referenceTime = correctedTime;
#endif

    CHPP_DEBUG_ASSERT(chppCheckWifiScanEventNotification(chre));

    gCallbacks->scanEventCallback(chre);
  }
}

/**
 * Handles the WiFi ranging event service notification.
 *
 * This function is called from chppDispatchWifiNotification().
 *
 * @param clientContext Maintains status for each client instance.
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiRangingEventNotification(
    struct ChppWifiClientState *clientContext, uint8_t *buf, size_t len) {
  UNUSED_VAR(clientContext);

  CHPP_LOGD("chppWifiRangingEventNotification received data len=%" PRIuSIZE,
            len);

  buf += sizeof(struct ChppAppHeader);
  len -= sizeof(struct ChppAppHeader);

  // Timestamp correction prior to conversion to avoid const casting issues.
#ifdef CHPP_CLIENT_ENABLED_TIMESYNC
  struct ChppWifiRangingEvent *event = (struct ChppWifiRangingEvent *)buf;

  for (size_t i = 0; i < event->resultCount; i++) {
    struct ChppWifiRangingResult *results =
        (struct ChppWifiRangingResult *)&buf[event->results.offset];

    uint64_t correctedTime =
        results[i].timestamp -
        (uint64_t)chppTimesyncGetOffset(gWifiClientContext.client.appContext,
                                        CHPP_WIFI_MAX_TIMESYNC_AGE_NS);
    CHPP_LOGD("WiFi ranging result time corrected from %" PRIu64 "to %" PRIu64,
              results[i].timestamp / CHPP_NSEC_PER_MSEC,
              correctedTime / CHPP_NSEC_PER_MSEC);
    results[i].timestamp = correctedTime;
  }
#endif

  struct chreWifiRangingEvent *chre =
      chppWifiRangingEventToChre((struct ChppWifiRangingEvent *)buf, len);

  uint8_t error = CHRE_ERROR_NONE;
  if (chre == NULL) {
    error = CHRE_ERROR;
    CHPP_LOGE("Ranging event conversion failed len=%" PRIuSIZE, len);
  }

  gCallbacks->rangingEventCallback(error, chre);
}

/**
 * Handles the NAN discovery event service notification.
 *
 * This function is called from chppDispatchWifiNotification().
 *
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiDiscoveryEventNotification(uint8_t *buf, size_t len) {
  CHPP_LOGD("chppWifiDiscoveryEventNotification data len=%" PRIuSIZE, len);

  buf += sizeof(struct ChppAppHeader);
  len -= sizeof(struct ChppAppHeader);

  struct ChppWifiNanDiscoveryEvent *chppEvent =
      (struct ChppWifiNanDiscoveryEvent *)buf;
  struct chreWifiNanDiscoveryEvent *event =
      chppWifiNanDiscoveryEventToChre(chppEvent, len);

  if (event == NULL) {
    CHPP_LOGE("Discovery event CHPP -> CHRE conversion failed");
  } else {
    gCallbacks->nanServiceDiscoveryCallback(event);
  }
}

/**
 * Handles the NAN connection lost event service notification.
 *
 * This function is called from chppDispatchWifiNotification().
 *
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiNanServiceLostEventNotification(uint8_t *buf, size_t len) {
  buf += sizeof(struct ChppAppHeader);
  len -= sizeof(struct ChppAppHeader);

  struct ChppWifiNanSessionLostEvent *chppEvent =
      (struct ChppWifiNanSessionLostEvent *)buf;
  struct chreWifiNanSessionLostEvent *event =
      chppWifiNanSessionLostEventToChre(chppEvent, len);

  if (event == NULL) {
    CHPP_LOGE("Session lost event CHPP -> CHRE conversion failed");
  } else {
    gCallbacks->nanServiceLostCallback(event->id, event->peerId);
  }
}

/**
 * Handles the NAN subscription termination event service notification.
 *
 * This function is called from chppDispatchWifiNotification().
 *
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiNanServiceTerminatedEventNotification(uint8_t *buf,
                                                          size_t len) {
  buf += sizeof(struct ChppAppHeader);
  len -= sizeof(struct ChppAppHeader);

  struct ChppWifiNanSessionTerminatedEvent *chppEvent =
      (struct ChppWifiNanSessionTerminatedEvent *)buf;
  struct chreWifiNanSessionTerminatedEvent *event =
      chppWifiNanSessionTerminatedEventToChre(chppEvent, len);

  if (event == NULL) {
    CHPP_LOGE("Session terminated event CHPP -> CHRE conversion failed");
  } else {
    gCallbacks->nanServiceTerminatedCallback(event->reason, event->id);
  }
}

/**
 * Handles the service response for the NAN subscribe client request.
 *
 * This function is called from chppDispatchWifiNotification().
 *
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiRequestNanSubscribeNotification(uint8_t *buf, size_t len) {
  uint8_t errorCode = CHRE_ERROR_NONE;
  uint32_t subscriptionId = 0;

  if (len < sizeof(struct ChppWifiNanServiceIdentifier)) {
    errorCode = CHRE_ERROR;
  } else {
    struct ChppWifiNanServiceIdentifier *id =
        (struct ChppWifiNanServiceIdentifier *)buf;
    errorCode = id->errorCode;
    subscriptionId = id->subscriptionId;
  }
  gCallbacks->nanServiceIdentifierCallback(errorCode, subscriptionId);
}

/**
 * Handles the service response for the NAN subscription cancel client request.
 *
 * This function is called from chppDispatchWifiNotification().
 *
 * @param buf Input data. Cannot be null.
 * @param len Length of input data in bytes.
 */
static void chppWifiNanSubscriptionCanceledNotification(uint8_t *buf,
                                                        size_t len) {
  uint8_t errorCode = CHRE_ERROR_NONE;
  uint32_t subscriptionId = 0;
  if (len < (sizeof(struct ChppWifiNanSubscriptionCanceledResponse))) {
    errorCode = CHRE_ERROR;
  } else {
    struct ChppWifiNanSubscriptionCanceledResponse *chppNotif =
        (struct ChppWifiNanSubscriptionCanceledResponse *)buf;
    errorCode = chppNotif->errorCode;
    subscriptionId = chppNotif->subscriptionId;
  }
  gCallbacks->nanSubscriptionCanceledCallback(errorCode, subscriptionId);
}

/**
 * Initializes the WiFi client upon an open request from CHRE and responds
 * with the result.
 *
 * @param systemApi CHRE system function pointers.
 * @param callbacks CHRE entry points.
 *
 * @return True if successful. False otherwise.
 */
static bool chppWifiClientOpen(const struct chrePalSystemApi *systemApi,
                               const struct chrePalWifiCallbacks *callbacks) {
  CHPP_DEBUG_ASSERT(systemApi != NULL);
  CHPP_DEBUG_ASSERT(callbacks != NULL);

  bool result = false;
  gSystemApi = systemApi;
  gCallbacks = callbacks;

  CHPP_LOGD("WiFi client opening");
  if (gWifiClientContext.client.appContext == NULL) {
    CHPP_LOGE("WiFi client app is null");
  } else {
    if (chppWaitForDiscoveryComplete(gWifiClientContext.client.appContext,
                                     CHPP_WIFI_DISCOVERY_TIMEOUT_MS)) {
      result = chppClientSendOpenRequest(
          &gWifiClientContext.client,
          &gWifiClientContext.rRState[CHPP_WIFI_OPEN], CHPP_WIFI_OPEN,
          /*blocking=*/true);
    }

    // Since CHPP_WIFI_DEFAULT_CAPABILITIES is mandatory, we can always
    // pseudo-open and return true. Otherwise, these should have been gated.
    chppClientPseudoOpen(&gWifiClientContext.client);
    result = true;
  }

  return result;
}

/**
 * Deinitializes the WiFi client.
 */
static void chppWifiClientClose(void) {
  // Remote
  struct ChppAppHeader *request = chppAllocClientRequestCommand(
      &gWifiClientContext.client, CHPP_WIFI_CLOSE);

  if (request == NULL) {
    CHPP_LOG_OOM();
  } else if (chppSendTimestampedRequestAndWait(
                 &gWifiClientContext.client,
                 &gWifiClientContext.rRState[CHPP_WIFI_CLOSE], request,
                 sizeof(*request))) {
    gWifiClientContext.client.openState = CHPP_OPEN_STATE_CLOSED;
    gWifiClientContext.capabilities = CHRE_WIFI_CAPABILITIES_NONE;
    gWifiClientContext.capabilitiesValid = false;
    chppClientCloseOpenRequests(&gWifiClientContext.client, &kWifiClientConfig,
                                true /* clearOnly */);
  }
}

/**
 * Retrieves a set of flags indicating the WiFi features supported by the
 * current implementation.
 *
 * @return Capabilities flags.
 */
static uint32_t chppWifiClientGetCapabilities(void) {
  uint32_t capabilities = CHPP_WIFI_DEFAULT_CAPABILITIES;

  if (gWifiClientContext.capabilitiesValid) {
    // Result already cached
    capabilities = gWifiClientContext.capabilities;

  } else {
    struct ChppAppHeader *request = chppAllocClientRequestCommand(
        &gWifiClientContext.client, CHPP_WIFI_GET_CAPABILITIES);

    if (request == NULL) {
      CHPP_LOG_OOM();
    } else {
      if (chppSendTimestampedRequestAndWait(
              &gWifiClientContext.client,
              &gWifiClientContext.rRState[CHPP_WIFI_GET_CAPABILITIES], request,
              sizeof(*request))) {
        // Success. gWifiClientContext.capabilities is now populated
        if (gWifiClientContext.capabilitiesValid) {
          capabilities = gWifiClientContext.capabilities;
        }
      }
    }
  }

  return capabilities;
}

/**
 * Enables/disables receiving unsolicited scan results (scan monitoring).
 *
 * @param enable True to enable.
 *
 * @return True indicates the request was sent off to the service.
 */
static bool chppWifiClientConfigureScanMonitor(bool enable) {
  bool result = false;

  struct ChppWifiConfigureScanMonitorAsyncRequest *request =
      chppAllocClientRequestFixed(
          &gWifiClientContext.client,
          struct ChppWifiConfigureScanMonitorAsyncRequest);

  if (request == NULL) {
    CHPP_LOG_OOM();
  } else {
    request->header.command = CHPP_WIFI_CONFIGURE_SCAN_MONITOR_ASYNC;
    request->params.enable = enable;
    request->params.cookie =
        &gWifiClientContext.rRState[CHPP_WIFI_CONFIGURE_SCAN_MONITOR_ASYNC];

    result = chppSendTimestampedRequestOrFail(
        &gWifiClientContext.client,
        &gWifiClientContext.rRState[CHPP_WIFI_CONFIGURE_SCAN_MONITOR_ASYNC],
        request, sizeof(*request), CHPP_CLIENT_REQUEST_TIMEOUT_DEFAULT);
  }

  return result;
}

/**
 * Request that the WiFi chipset perform a scan or deliver results from its
 * cache.
 *
 * @param params See chreWifiRequestScanAsync().
 *
 * @return True indicates the request was sent off to the service.
 */
static bool chppWifiClientRequestScan(const struct chreWifiScanParams *params) {
  struct ChppWifiScanParamsWithHeader *request;
  size_t requestLen;

  bool result = chppWifiScanParamsFromChre(params, &request, &requestLen);

  if (!result) {
    CHPP_LOG_OOM();
  } else {
    request->header.handle = gWifiClientContext.client.handle;
    request->header.type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST;
    request->header.transaction = gWifiClientContext.client.transaction++;
    request->header.error = CHPP_APP_ERROR_NONE;
    request->header.command = CHPP_WIFI_REQUEST_SCAN_ASYNC;

    result = chppSendTimestampedRequestOrFail(
        &gWifiClientContext.client,
        &gWifiClientContext.rRState[CHPP_WIFI_REQUEST_SCAN_ASYNC], request,
        requestLen, CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS);
  }

  return result;
}

/**
 * Releases the memory held for the scan event callback.
 *
 * @param event Location event to be released.
 */
static void chppWifiClientReleaseScanEvent(struct chreWifiScanEvent *event) {
  if (event->scannedFreqListLen > 0) {
    void *scannedFreqList = CHPP_CONST_CAST_POINTER(event->scannedFreqList);
    CHPP_FREE_AND_NULLIFY(scannedFreqList);
  }

  if (event->resultCount > 0) {
    void *results = CHPP_CONST_CAST_POINTER(event->results);
    CHPP_FREE_AND_NULLIFY(results);
  }

  CHPP_FREE_AND_NULLIFY(event);
}

/**
 * Request that the WiFi chipset perform RTT ranging.
 *
 * @param params See chreWifiRequestRangingAsync().
 *
 * @return True indicates the request was sent off to the service.
 */
static bool chppWifiClientRequestRanging(
    const struct chreWifiRangingParams *params) {
  struct ChppWifiRangingParamsWithHeader *request;
  size_t requestLen;

  bool result = chppWifiRangingParamsFromChre(params, &request, &requestLen);

  if (!result) {
    CHPP_LOG_OOM();
  } else {
    request->header.handle = gWifiClientContext.client.handle;
    request->header.type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST;
    request->header.transaction = gWifiClientContext.client.transaction++;
    request->header.error = CHPP_APP_ERROR_NONE;
    request->header.command = CHPP_WIFI_REQUEST_RANGING_ASYNC;

    result = chppSendTimestampedRequestOrFail(
        &gWifiClientContext.client,
        &gWifiClientContext.rRState[CHPP_WIFI_REQUEST_RANGING_ASYNC], request,
        requestLen, CHRE_WIFI_RANGING_RESULT_TIMEOUT_NS);
  }

  return result;
}

/**
 * Releases the memory held for the RTT ranging event callback.
 *
 * @param event Location event to be released.
 */
static void chppWifiClientReleaseRangingEvent(
    struct chreWifiRangingEvent *event) {
  if (event->resultCount > 0) {
    void *results = CHPP_CONST_CAST_POINTER(event->results);
    CHPP_FREE_AND_NULLIFY(results);
  }

  CHPP_FREE_AND_NULLIFY(event);
}

/**
 * Request that the WiFi chipset perform a NAN subscription.
 * @see chreWifiNanSubscribe for more information.
 *
 * @param config NAN service subscription configuration.
 * @return true if subscribe request was successful, false otherwise.
 */
static bool chppWifiClientNanSubscribe(
    const struct chreWifiNanSubscribeConfig *config) {
  struct ChppWifiNanSubscribeConfigWithHeader *request;
  size_t requestLen;

  bool result =
      chppWifiNanSubscribeConfigFromChre(config, &request, &requestLen);

  if (!result) {
    CHPP_LOG_OOM();
  } else {
    request->header.handle = gWifiClientContext.client.handle;
    request->header.type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST;
    request->header.transaction = gWifiClientContext.client.transaction++;
    request->header.error = CHPP_APP_ERROR_NONE;
    request->header.command = CHPP_WIFI_REQUEST_NAN_SUB;

    result = chppSendTimestampedRequestOrFail(
        &gWifiClientContext.client,
        &gWifiClientContext.rRState[CHPP_WIFI_REQUEST_NAN_SUB], request,
        requestLen, CHRE_ASYNC_RESULT_TIMEOUT_NS);
  }
  return result;
}

/**
 * Request the WiFi chipset to cancel a NAN subscription.
 * @param subscriptionId Identifier assigned by the NAN engine for a service
 *        subscription.
 * @return true if cancelation request was successfully dispatched, false
 *         otherwise.
 */
static bool chppWifiClientNanSubscribeCancel(uint32_t subscriptionId) {
  bool result = false;
  struct ChppWifiNanSubscribeCancelRequest *request =
      chppAllocClientRequestFixed(&gWifiClientContext.client,
                                  struct ChppWifiNanSubscribeCancelRequest);

  if (request == NULL) {
    CHPP_LOG_OOM();
  } else {
    request->header.handle = gWifiClientContext.client.handle;
    request->header.command = CHPP_WIFI_REQUEST_NAN_SUB_CANCEL;
    request->header.type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST;
    request->header.transaction = gWifiClientContext.client.transaction++;
    request->header.error = CHPP_APP_ERROR_NONE;
    request->subscriptionId = subscriptionId;

    result = chppSendTimestampedRequestAndWait(
        &gWifiClientContext.client,
        &gWifiClientContext.rRState[CHPP_WIFI_REQUEST_NAN_SUB_CANCEL], request,
        sizeof(*request));
  }
  return result;
}

/**
 * Release the memory held for the NAN service discovery callback.
 *
 * @param event Discovery event to be freed.
 */
static void chppWifiClientNanReleaseDiscoveryEvent(
    struct chreWifiNanDiscoveryEvent *event) {
  if (event != NULL) {
    if (event->serviceSpecificInfo != NULL) {
      void *info = CHPP_CONST_CAST_POINTER(event->serviceSpecificInfo);
      CHPP_FREE_AND_NULLIFY(info);
    }
    CHPP_FREE_AND_NULLIFY(event);
  }
}

/**
 * Request that the WiFi chipset perform NAN ranging.
 *
 * @param params WiFi NAN ranging parameters.
 * @return true if the ranging request was successfully dispatched, false
 *         otherwise.
 */
static bool chppWifiClientNanRequestNanRanging(
    const struct chreWifiNanRangingParams *params) {
  struct ChppWifiNanRangingParamsWithHeader *request;
  size_t requestLen;
  bool result = chppWifiNanRangingParamsFromChre(params, &request, &requestLen);

  if (!result) {
    CHPP_LOG_OOM();
  } else {
    request->header.handle = gWifiClientContext.client.handle;
    request->header.command = CHPP_WIFI_REQUEST_NAN_RANGING_ASYNC;
    request->header.type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST;
    request->header.transaction = gWifiClientContext.client.transaction++;
    request->header.error = CHPP_APP_ERROR_NONE;

    result = chppSendTimestampedRequestOrFail(
        &gWifiClientContext.client,
        &gWifiClientContext.rRState[CHPP_WIFI_REQUEST_NAN_RANGING_ASYNC],
        request, requestLen, CHRE_ASYNC_RESULT_TIMEOUT_NS);
  }
  return result;
}

/************************************************
 *  Public Functions
 ***********************************************/

void chppRegisterWifiClient(struct ChppAppState *appContext) {
  chppRegisterClient(appContext, (void *)&gWifiClientContext,
                     &gWifiClientContext.client, gWifiClientContext.rRState,
                     &kWifiClientConfig);
}

void chppDeregisterWifiClient(struct ChppAppState *appContext) {
  // TODO

  UNUSED_VAR(appContext);
}

struct ChppClientState *getChppWifiClientState(void) {
  return &gWifiClientContext.client;
}

#ifdef CHPP_CLIENT_ENABLED_WIFI

#ifdef CHPP_CLIENT_ENABLED_CHRE_WIFI
const struct chrePalWifiApi *chrePalWifiGetApi(uint32_t requestedApiVersion) {
#else
const struct chrePalWifiApi *chppPalWifiGetApi(uint32_t requestedApiVersion) {
#endif

  static const struct chrePalWifiApi api = {
      .moduleVersion = CHPP_PAL_WIFI_API_VERSION,
      .open = chppWifiClientOpen,
      .close = chppWifiClientClose,
      .getCapabilities = chppWifiClientGetCapabilities,
      .configureScanMonitor = chppWifiClientConfigureScanMonitor,
      .requestScan = chppWifiClientRequestScan,
      .releaseScanEvent = chppWifiClientReleaseScanEvent,
      .requestRanging = chppWifiClientRequestRanging,
      .releaseRangingEvent = chppWifiClientReleaseRangingEvent,
      .nanSubscribe = chppWifiClientNanSubscribe,
      .nanSubscribeCancel = chppWifiClientNanSubscribeCancel,
      .releaseNanDiscoveryEvent = chppWifiClientNanReleaseDiscoveryEvent,
      .requestNanRanging = chppWifiClientNanRequestNanRanging,
  };

  CHPP_STATIC_ASSERT(
      CHRE_PAL_WIFI_API_CURRENT_VERSION == CHPP_PAL_WIFI_API_VERSION,
      "A newer CHRE PAL API version is available. Please update.");

  if (!CHRE_PAL_VERSIONS_ARE_COMPATIBLE(api.moduleVersion,
                                        requestedApiVersion)) {
    return NULL;
  } else {
    return &api;
  }
}

#endif