/******************************************************************************
 *
 *  Copyright (C) 2014 Google, Inc.
 *
 *  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.
 *
 ******************************************************************************/

#define LOG_TAG "bt_hci_inject"

#include "hci_inject.h"

#include <assert.h>
#include <errno.h>
#include <string.h>

#include "bt_types.h"
#include "buffer_allocator.h"
#include "hci_layer.h"
#include "osi/include/allocator.h"
#include "osi/include/list.h"
#include "osi/include/log.h"
#include "osi/include/osi.h"
#include "osi/include/socket.h"
#include "osi/include/thread.h"

typedef enum {
  HCI_PACKET_COMMAND  = 1,
  HCI_PACKET_ACL_DATA = 2,
  HCI_PACKET_SCO_DATA = 3,
  HCI_PACKET_EVENT    = 4,
} hci_packet_t;

typedef struct {
  socket_t *socket;
  uint8_t buffer[65536 + 3];  // 2 bytes length prefix, 1 byte type prefix.
  size_t buffer_size;
} client_t;

static const port_t LISTEN_PORT = 8873;

static const hci_inject_t interface;
static const hci_t *hci;
static const allocator_t *buffer_allocator;
static socket_t *listen_socket;
static thread_t *thread;
static list_t *clients;

static int hci_packet_to_event(hci_packet_t packet);
static void accept_ready(socket_t *socket, void *context);
static void read_ready(socket_t *socket, void *context);
static void client_free(void *ptr);

bool hci_inject_open(const hci_t *hci_interface) {
#if (!defined(BT_NET_DEBUG) || (BT_NET_DEBUG != TRUE))
  return true;          // Disable using network sockets for security reasons
#endif

  assert(listen_socket == NULL);
  assert(thread == NULL);
  assert(clients == NULL);
  assert(hci_interface != NULL);

  hci = hci_interface;

  thread = thread_new("hci_inject");
  if (!thread)
    goto error;

  clients = list_new(client_free);
  if (!clients)
    goto error;

  listen_socket = socket_new();
  if (!listen_socket)
    goto error;

  if (!socket_listen(listen_socket, LISTEN_PORT))
    goto error;

  socket_register(listen_socket, thread_get_reactor(thread), NULL, accept_ready, NULL);
  return true;

error:;
  interface.close();
  return false;
}

void hci_inject_close(void) {
#if (!defined(BT_NET_DEBUG) || (BT_NET_DEBUG != TRUE))
  return;               // Disable using network sockets for security reasons
#endif

  socket_free(listen_socket);
  list_free(clients);
  thread_free(thread);

  listen_socket = NULL;
  thread = NULL;
  clients = NULL;
}

static int hci_packet_to_event(hci_packet_t packet) {
  switch (packet) {
    case HCI_PACKET_COMMAND:
      return MSG_STACK_TO_HC_HCI_CMD;
    case HCI_PACKET_ACL_DATA:
      return MSG_STACK_TO_HC_HCI_ACL;
    case HCI_PACKET_SCO_DATA:
      return MSG_STACK_TO_HC_HCI_SCO;
    default:
      LOG_ERROR(LOG_TAG, "%s unsupported packet type: %d", __func__, packet);
      return -1;
  }
}

static void accept_ready(socket_t *socket, UNUSED_ATTR void *context) {
  assert(socket != NULL);
  assert(socket == listen_socket);

  socket = socket_accept(socket);
  if (!socket)
    return;

  client_t *client = (client_t *)osi_calloc(sizeof(client_t));

  client->socket = socket;

  if (!list_append(clients, client)) {
    LOG_ERROR(LOG_TAG, "%s unable to add client to list.", __func__);
    client_free(client);
    return;
  }

  socket_register(socket, thread_get_reactor(thread), client, read_ready, NULL);
}

static void read_ready(UNUSED_ATTR socket_t *socket, void *context) {
  assert(socket != NULL);
  assert(context != NULL);

  client_t *client = (client_t *)context;

  ssize_t ret = socket_read(client->socket, client->buffer + client->buffer_size, sizeof(client->buffer) - client->buffer_size);
  if (ret == 0 || (ret == -1 && ret != EWOULDBLOCK && ret != EAGAIN)) {
    list_remove(clients, client);
    return;
  }
  client->buffer_size += ret;

  while (client->buffer_size > 3) {
    uint8_t *buffer = client->buffer;
    hci_packet_t packet_type = (hci_packet_t)buffer[0];
    size_t packet_len = (buffer[2] << 8) | buffer[1];
    size_t frame_len = 3 + packet_len;

    if (client->buffer_size < frame_len)
      break;

    // TODO(sharvil): validate incoming HCI messages.
    // TODO(sharvil): once we have an HCI parser, we can eliminate
    //   the 2-byte size field since it will be contained in the packet.

    BT_HDR *buf = (BT_HDR *)buffer_allocator->alloc(BT_HDR_SIZE + packet_len);
    if (buf) {
      buf->event = hci_packet_to_event(packet_type);
      buf->offset = 0;
      buf->layer_specific = 0;
      buf->len = packet_len;
      memcpy(buf->data, buffer + 3, packet_len);
      hci->transmit_downward(buf->event, buf);
    } else {
      LOG_ERROR(LOG_TAG, "%s dropping injected packet of length %zu", __func__, packet_len);
    }

    size_t remainder = client->buffer_size - frame_len;
    memmove(buffer, buffer + frame_len, remainder);
    client->buffer_size -= frame_len;
  }
}

static void client_free(void *ptr) {
  if (!ptr)
    return;

  client_t *client = (client_t *)ptr;
  socket_free(client->socket);
  osi_free(client);
}

static const hci_inject_t interface = {
  hci_inject_open,
  hci_inject_close
};

const hci_inject_t *hci_inject_get_interface() {
  buffer_allocator = buffer_allocator_get_interface();
  return &interface;
}