/*
 * Copyright (C) 2016 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.
 *
 * Implement a simple T=1 echo endpoint.
 */

#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "../libese-teq1/include/ese/teq1.h"
#include "../libese/include/ese/ese.h"
#include "../libese/include/ese/log.h"

struct EchoState {
  struct Teq1Frame frame;
  uint8_t *rx_fill;
  uint8_t *tx_sent;
  int recvd;
};

#define ECHO_STATE(ese) (*(struct EchoState **)(&ese->pad[1]))

static int echo_open(struct EseInterface *ese, void *hw_opts) {
  struct EchoState *es = hw_opts; /* shorter than __attribute */
  struct EchoState **es_ptr;
  if (sizeof(ese->pad) < sizeof(struct EchoState *)) {
    /* This is a compile-time correctable error only. */
    ALOGE("Pad size too small to use Echo HW (%zu < %zu)", sizeof(ese->pad),
          sizeof(struct EchoState *));
    return -1;
  }
  es_ptr = &ECHO_STATE(ese);
  *es_ptr = malloc(sizeof(struct EchoState));
  if (!*es_ptr) {
    return -1;
  }
  es = ECHO_STATE(ese);
  es->rx_fill = &es->frame.header.NAD;
  es->tx_sent = es->rx_fill;
  es->recvd = 0;
  return 0;
}

static void echo_close(struct EseInterface *ese) {
  struct EchoState *es;
  es = ECHO_STATE(ese);
  if (!es) {
    return;
  }
  free(es);
  es = NULL;
}

static uint32_t echo_receive(struct EseInterface *ese, uint8_t *buf,
                             uint32_t len, int complete) {
  struct EchoState *es = ECHO_STATE(ese);
  ALOGV("interface attempting to read data");
  if (!es->recvd) {
    return 0;
  }

  if (len > sizeof(es->frame) - (es->tx_sent - &es->frame.header.NAD)) {
    return 0;
  }

  /* NAD was polled for so skip it. */
  memcpy(buf, es->tx_sent, len);
  es->tx_sent += len;
  if (complete) {
    es->tx_sent = &es->frame.header.NAD;
    es->recvd = 0;
    ALOGV("card sent a frame");
  }
  return sizeof(es->frame.header) + es->frame.header.LEN;
}

static uint32_t echo_transmit(struct EseInterface *ese, const uint8_t *buf,
                              uint32_t len, int complete) {
  struct EchoState *es = ECHO_STATE(ese);
  ALOGV("interface transmitting data");
  if (len > sizeof(es->frame) - (es->rx_fill - &es->frame.header.NAD)) {
    return 0;
  }
  memcpy(es->rx_fill, buf, len);
  es->rx_fill += len;
  es->recvd = complete;
  if (complete) {
    es->frame.header.NAD = 0x00;
    if (teq1_compute_LRC(&es->frame) != es->frame.INF[es->frame.header.LEN]) {
      ALOGV("card received frame with bad LRC");
      return 0;
    }
    ALOGV("card received valid frame");
    es->rx_fill = &es->frame.header.NAD;
  }
  return len;
}

static int echo_poll(struct EseInterface *ese, uint8_t poll_for, float timeout,
                     int complete) {
  struct EchoState *es = ECHO_STATE(ese);
  const struct Teq1ProtocolOptions *opts = ese->ops->opts;
  ALOGV("interface polling for start of frame/host node address: %x", poll_for);
  /* In reality, we should be polling at intervals up to the timeout. */
  if (timeout > 0.0) {
    usleep(timeout * 1000);
  }
  if (poll_for == opts->host_address) {
    ALOGV("interface received NAD");
    if (!complete) {
      es->tx_sent++; /* Consume the polled byte: NAD */
    }
    return 1;
  }
  return -1;
}

int echo_preprocess(const struct Teq1ProtocolOptions *const opts,
                    struct Teq1Frame *frame, int tx) {
  if (tx) {
    /* Recompute the LRC with the NAD of 0x00 */
    frame->header.NAD = 0x00;
    frame->INF[frame->header.LEN] = teq1_compute_LRC(frame);
    frame->header.NAD = opts->node_address;
    ALOGV("interface is preprocessing outbound frame");
  } else {
    /* Replace the NAD with 0x00 so the LRC check passes. */
    frame->header.NAD = 0x00;
    ALOGV("interface is preprocessing inbound frame");
  }
  return 0;
}

static const struct Teq1ProtocolOptions kTeq1Options = {
    .host_address = 0xAA,
    .node_address = 0xBB,
    .bwt = 3.14152f,
    .etu = 1.0f,
    .preprocess = &echo_preprocess,
};

uint32_t echo_transceive(struct EseInterface *ese,
                         const struct EseSgBuffer *tx_buf, uint32_t tx_len,
                         struct EseSgBuffer *rx_buf, uint32_t rx_len) {
  return teq1_transceive(ese, &kTeq1Options, tx_buf, tx_len, rx_buf, rx_len);
}

static const char *kErrorMessages[] = {
    "T=1 hard failure.",        /* TEQ1_ERROR_HARD_FAIL */
    "T=1 abort.",               /* TEQ1_ERROR_ABORT */
    "T=1 device reset failed.", /* TEQ1_ERROR_DEVICE_ABORT */
};

static const struct EseOperations ops = {
    .name = "eSE Echo Hardware (fake)",
    .open = &echo_open,
    .hw_receive = &echo_receive,
    .hw_transmit = &echo_transmit,
    .transceive = &echo_transceive,
    .poll = &echo_poll,
    .close = &echo_close,
    .opts = &kTeq1Options,
    .errors = kErrorMessages,
    .errors_count = sizeof(kErrorMessages),
};
ESE_DEFINE_HW_OPS(ESE_HW_ECHO, ops);