/** @file

  Implements unit test for SDK APIs

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you 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 "Regression.h"
#include "api/ts/ts.h"

#include <arpa/inet.h>          /* For htonl */
#include "P_Net.h"
#include <records/I_RecHttp.h>

#define SDBG_TAG "SockServer"
#define CDBG_TAG "SockClient"

#define IP(a,b,c,d) htonl((a) << 24 | (b) << 16 | (c) << 8 | (d))

#define SET_TEST_HANDLER(_d, _s) {_d = _s;}

#define MAGIC_ALIVE 0xfeedbaba
#define MAGIC_DEAD  0xdeadbeef

#define SYNSERVER_LISTEN_PORT  3300

#define PROXY_CONFIG_NAME_HTTP_PORT "proxy.config.http.server_port"
#define PROXY_HTTP_DEFAULT_PORT 8080

#define REQUEST_MAX_SIZE  4095
#define RESPONSE_MAX_SIZE 4095

#define HTTP_REQUEST_END "\r\n\r\n"

// each request/response includes an identifier as a Mime field
#define X_REQUEST_ID  "X-Request-ID"
#define X_RESPONSE_ID "X-Response-ID"

#define ERROR_BODY "TESTING ERROR PAGE"
#define TRANSFORM_APPEND_STRING "This is a transformed response"

//////////////////////////////////////////////////////////////////////////////
// STRUCTURES
//////////////////////////////////////////////////////////////////////////////

typedef int (*TxnHandler) (TSCont contp, TSEvent event, void *data);

/* Server transaction structure */
typedef struct
{
  TSVConn vconn;

  TSVIO read_vio;
  TSIOBuffer req_buffer;
  TSIOBufferReader req_reader;

  TSVIO write_vio;
  TSIOBuffer resp_buffer;
  TSIOBufferReader resp_reader;

  char request[REQUEST_MAX_SIZE + 1];
  int request_len;

  TxnHandler current_handler;
  unsigned int magic;
} ServerTxn;

/* Server structure */
typedef struct
{
  int accept_port;
  TSAction accept_action;
  TSCont accept_cont;
  unsigned int magic;
} SocketServer;

typedef enum
{
  REQUEST_SUCCESS,
  REQUEST_INPROGRESS,
  REQUEST_FAILURE
} RequestStatus;

/* Client structure */
typedef struct
{
  TSVConn vconn;

  TSVIO read_vio;
  TSIOBuffer req_buffer;
  TSIOBufferReader req_reader;

  TSVIO write_vio;
  TSIOBuffer resp_buffer;
  TSIOBufferReader resp_reader;

  char *request;
  char response[RESPONSE_MAX_SIZE + 1];
  int response_len;

  RequestStatus status;

  int connect_port;
  int local_port;
  uint64_t connect_ip;
  TSAction connect_action;

  TxnHandler current_handler;

  unsigned int magic;
} ClientTxn;


//////////////////////////////////////////////////////////////////////////////
// DECLARATIONS
//////////////////////////////////////////////////////////////////////////////

/* utility */
static char *get_body_ptr(const char *request);
static char *generate_request(int test_case);
static char *generate_response(const char *request);
static int get_request_id(TSHttpTxn txnp);


/* client side */
static ClientTxn *synclient_txn_create(void);
static int synclient_txn_delete(ClientTxn * txn);
static int synclient_txn_close(TSCont contp);
static int synclient_txn_send_request(ClientTxn * txn, char *request);
static int synclient_txn_send_request_to_vc(ClientTxn * txn, char *request, TSVConn vc);
static int synclient_txn_read_response(TSCont contp);
static int synclient_txn_read_response_handler(TSCont contp, TSEvent event, void *data);
static int synclient_txn_write_request(TSCont contp);
static int synclient_txn_write_request_handler(TSCont contp, TSEvent event, void *data);
static int synclient_txn_connect_handler(TSCont contp, TSEvent event, void *data);
static int synclient_txn_main_handler(TSCont contp, TSEvent event, void *data);

/* Server side */
SocketServer *synserver_create(int port);
static int synserver_start(SocketServer * s);
static int synserver_stop(SocketServer * s);
static int synserver_delete(SocketServer * s);
static int synserver_accept_handler(TSCont contp, TSEvent event, void *data);
static int synserver_txn_close(TSCont contp);
static int synserver_txn_write_response(TSCont contp);
static int synserver_txn_write_response_handler(TSCont contp, TSEvent event, void *data);
static int synserver_txn_read_request(TSCont contp);
static int synserver_txn_read_request_handler(TSCont contp, TSEvent event, void *data);
static int synserver_txn_main_handler(TSCont contp, TSEvent event, void *data);

//////////////////////////////////////////////////////////////////////////////
// REQUESTS/RESPONSES GENERATION
//////////////////////////////////////////////////////////////////////////////

static char *
get_body_ptr(const char *request)
{
  char *ptr = (char *) strstr((const char *) request, (const char *) "\r\n\r\n");
  return (ptr != NULL) ? (ptr + 4) : NULL;
}


/* Caller must free returned request */
static char *
generate_request(int test_case)
{

// We define request formats.
// Each format has an X-Request-ID field that contains the id of the testcase
#define HTTP_REQUEST_DEFAULT_FORMAT  "GET http://127.0.0.1:%d/default.html HTTP/1.0\r\n" \
                                     "X-Request-ID: %d\r\n" \
				     "\r\n"

#define HTTP_REQUEST_FORMAT1 "GET http://127.0.0.1:%d/format1.html HTTP/1.0\r\n" \
			     "X-Request-ID: %d\r\n" \
			     "\r\n"

#define HTTP_REQUEST_FORMAT2 "GET http://127.0.0.1:%d/format2.html HTTP/1.0\r\n" \
			     "X-Request-ID: %d\r\n" \
                             "Content-Type: text/html\r\n" \
			     "\r\n"
#define HTTP_REQUEST_FORMAT3 "GET http://127.0.0.1:%d/format3.html HTTP/1.0\r\n" \
			     "X-Request-ID: %d\r\n" \
			     "Response: Error\r\n" \
			     "\r\n"
#define HTTP_REQUEST_FORMAT4 "GET http://127.0.0.1:%d/format4.html HTTP/1.0\r\n" \
			     "X-Request-ID: %d\r\n" \
                             "Request:%d\r\n" \
                             "\r\n"
#define HTTP_REQUEST_FORMAT5 "GET http://127.0.0.1:%d/format5.html HTTP/1.0\r\n" \
			     "X-Request-ID: %d\r\n" \
                             "Request:%d\r\n" \
                             "\r\n"
#define HTTP_REQUEST_FORMAT6 "GET http://127.0.0.1:%d/format.html HTTP/1.0\r\n" \
			     "X-Request-ID: %d\r\n" \
                             "Accept-Language: English\r\n" \
                             "\r\n"
#define HTTP_REQUEST_FORMAT7 "GET http://127.0.0.1:%d/format.html HTTP/1.0\r\n" \
			     "X-Request-ID: %d\r\n" \
                             "Accept-Language: French\r\n" \
                             "\r\n"
#define HTTP_REQUEST_FORMAT8 "GET http://127.0.0.1:%d/format.html HTTP/1.0\r\n" \
			     "X-Request-ID: %d\r\n" \
                             "Accept-Language: English,French\r\n" \
                             "\r\n"
#define HTTP_REQUEST_FORMAT9 "GET http://trafficserver.apache.org/format9.html HTTP/1.0\r\n" \
			     "X-Request-ID: %d\r\n" \
                             "\r\n"
#define HTTP_REQUEST_FORMAT10 "GET http://trafficserver.apache.org/format10.html HTTP/1.0\r\n" \
			      "X-Request-ID: %d\r\n" \
                              "\r\n"

  char *request = (char *) TSmalloc(REQUEST_MAX_SIZE + 1);

  switch (test_case) {
  case 1:
    snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT1, SYNSERVER_LISTEN_PORT, test_case);
    break;
  case 2:
    snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT2, SYNSERVER_LISTEN_PORT, test_case);
    break;
  case 3:
    snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT3, SYNSERVER_LISTEN_PORT, test_case);
    break;
  case 4:
    snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT4, SYNSERVER_LISTEN_PORT, test_case, 1);
    break;
  case 5:
    snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT5, SYNSERVER_LISTEN_PORT, test_case, 2);
    break;
  case 6:
    snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT6, SYNSERVER_LISTEN_PORT, test_case);
    break;
  case 7:
    snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT7, SYNSERVER_LISTEN_PORT, test_case - 1);
    break;
  case 8:
    snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT8, SYNSERVER_LISTEN_PORT, test_case - 2);
    break;
  case 9:
    snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT9, test_case);
    break;
  case 10:
    snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT10, test_case);
    break;
  default:
    snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_DEFAULT_FORMAT, SYNSERVER_LISTEN_PORT, test_case);
    break;
  }

  return request;
}


/* Caller must free returned response */
static char *
generate_response(const char *request)
{
// define format for response
// Each response contains a field X-Response-ID that contains the id of the testcase
#define HTTP_REQUEST_TESTCASE_FORMAT "GET %1024s HTTP/1.%d\r\n" \
                                     "X-Request-ID: %d\r\n"

#define HTTP_RESPONSE_DEFAULT_FORMAT "HTTP/1.0 200 OK\r\n" \
			      "X-Response-ID: %d\r\n" \
                              "Cache-Control: max-age=86400\r\n" \
                              "Content-Type: text/html\r\n" \
			      "\r\n" \
			      "Default body"

#define HTTP_RESPONSE_FORMAT1 "HTTP/1.0 200 OK\r\n" \
			      "X-Response-ID: %d\r\n" \
                              "Content-Type: text/html\r\n" \
			      "Cache-Control: no-cache\r\n" \
			      "\r\n" \
			      "Body for response 1"

#define HTTP_RESPONSE_FORMAT2 "HTTP/1.0 200 OK\r\n" \
			      "X-Response-ID: %d\r\n" \
                              "Cache-Control: max-age=86400\r\n" \
                              "Content-Type: text/html\r\n" \
			      "\r\n" \
			      "Body for response 2"
#define HTTP_RESPONSE_FORMAT4 "HTTP/1.0 200 OK\r\n" \
			      "X-Response-ID: %d\r\n" \
                              "Cache-Control: max-age=86400\r\n" \
                              "Content-Type: text/html\r\n" \
                              "\r\n" \
                              "Body for response 4"
#define HTTP_RESPONSE_FORMAT5 "HTTP/1.0 200 OK\r\n" \
			      "X-Response-ID: %d\r\n" \
                              "Content-Type: text/html\r\n" \
                              "\r\n" \
                              "Body for response 5"
#define HTTP_RESPONSE_FORMAT6 "HTTP/1.0 200 OK\r\n" \
			      "X-Response-ID: %d\r\n" \
                              "Cache-Control: max-age=86400\r\n" \
                              "Content-Language: English\r\n" \
                              "\r\n" \
                              "Body for response 6"
#define HTTP_RESPONSE_FORMAT7 "HTTP/1.0 200 OK\r\n" \
			      "X-Response-ID: %d\r\n" \
                              "Cache-Control: max-age=86400\r\n" \
                              "Content-Language: French\r\n" \
                              "\r\n" \
                              "Body for response 7"

#define HTTP_RESPONSE_FORMAT8 "HTTP/1.0 200 OK\r\n" \
			      "X-Response-ID: %d\r\n" \
                              "Cache-Control: max-age=86400\r\n" \
                              "Content-Language: French, English\r\n" \
                              "\r\n" \
                              "Body for response 8"

#define HTTP_RESPONSE_FORMAT9 "HTTP/1.0 200 OK\r\n" \
                              "Cache-Control: max-age=86400\r\n" \
			      "X-Response-ID: %d\r\n" \
                              "\r\n" \
                              "Body for response 9"

#define HTTP_RESPONSE_FORMAT10 "HTTP/1.0 200 OK\r\n" \
                              "Cache-Control: max-age=86400\r\n" \
			      "X-Response-ID: %d\r\n" \
                              "\r\n" \
                              "Body for response 10"


  int test_case, match, http_version;

  char *response = (char *) TSmalloc(RESPONSE_MAX_SIZE + 1);
  char url[1025];

  // coverity[secure_coding]
  match = sscanf(request, HTTP_REQUEST_TESTCASE_FORMAT, url, &http_version, &test_case);
  if (match == 3) {
    switch (test_case) {
    case 1:
      snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT1, test_case);
      break;
    case 2:
      snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT2, test_case);
      break;
    case 4:
      snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT4, test_case);
      break;
    case 5:
      snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT5, test_case);
      break;
    case 6:
      snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT6, test_case);
      break;
    case 7:
      snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT7, test_case);
      break;
    case 8:
      snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT8, test_case);
      break;
    case 9:
      snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT9, test_case);
      break;
    case 10:
      snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT10, test_case);
      break;
    default:
      snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_DEFAULT_FORMAT, test_case);
      break;
    }
  } else {
    /* Didin't recognize a testcase request. send the default response */
    snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_DEFAULT_FORMAT, test_case);
  }

  return response;
}


// This routine can be called by tests, from the READ_REQUEST_HDR_HOOK
// to figure out the id of a test message
// Returns id/-1 in case of error
static int
get_request_id(TSHttpTxn txnp)
{
  TSMBuffer bufp;
  TSMLoc hdr_loc, id_loc;
  int id = -1;

  if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
    return -1;
  }

  id_loc = TSMimeHdrFieldFind(bufp, hdr_loc, X_REQUEST_ID, -1);
  if (id_loc == TS_NULL_MLOC) {
    TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
    return -1;
  }

  id = TSMimeHdrFieldValueIntGet(bufp, hdr_loc, id_loc, 0);

  TSHandleMLocRelease(bufp, hdr_loc, id_loc);
  TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
  return id;
}



//////////////////////////////////////////////////////////////////////////////
// SOCKET CLIENT
//////////////////////////////////////////////////////////////////////////////

static ClientTxn *
synclient_txn_create(void)
{
  HttpProxyPort* proxy_port;

  ClientTxn *txn = (ClientTxn *) TSmalloc(sizeof(ClientTxn));
  if (0 == (proxy_port = HttpProxyPort::findHttp(AF_INET)))
    txn->connect_port = PROXY_HTTP_DEFAULT_PORT;
  else
    txn->connect_port = proxy_port->m_port;

  txn->local_port = (int) 0;
  txn->connect_ip = IP(127, 0, 0, 1);
  txn->status = REQUEST_INPROGRESS;
  txn->request = NULL;
  txn->vconn = NULL;
  txn->req_buffer = NULL;
  txn->req_reader = NULL;
  txn->resp_buffer = NULL;
  txn->resp_reader = NULL;
  txn->magic = MAGIC_ALIVE;
  txn->connect_action = NULL;

  TSDebug(CDBG_TAG, "Connecting to proxy 127.0.0.1 on port %d", txn->connect_port);
  return txn;
}

static int
synclient_txn_delete(ClientTxn * txn)
{
  TSAssert(txn->magic == MAGIC_ALIVE);
  if (txn->connect_action && !TSActionDone(txn->connect_action)) {
    TSActionCancel(txn->connect_action);
    txn->connect_action = NULL;
  }

  ats_free(txn->request);
  txn->magic = MAGIC_DEAD;
  TSfree(txn);
  return 1;
}

static int
synclient_txn_close(TSCont contp)
{
  ClientTxn *txn = (ClientTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  if (txn->vconn != NULL) {
    TSVConnClose(txn->vconn);
  }
  if (txn->req_buffer != NULL) {
    TSIOBufferDestroy(txn->req_buffer);
  }
  if (txn->resp_buffer != NULL) {
    TSIOBufferDestroy(txn->resp_buffer);
  }

  TSContDestroy(contp);

  TSDebug(CDBG_TAG, "Client Txn destroyed");
  return TS_EVENT_IMMEDIATE;
}

static int
synclient_txn_send_request(ClientTxn * txn, char *request)
{
  TSCont cont;
  sockaddr_in addr;

  TSAssert(txn->magic == MAGIC_ALIVE);
  txn->request = ats_strdup(request);
  SET_TEST_HANDLER(txn->current_handler, synclient_txn_connect_handler);

  cont = TSContCreate(synclient_txn_main_handler, TSMutexCreate());
  TSContDataSet(cont, txn);

  ats_ip4_set(&addr, txn->connect_ip, htons(txn->connect_port));
  TSNetConnect(cont, ats_ip_sa_cast(&addr));
  return 1;
}

/* This can be used to send a request to a specific VC */
static int
synclient_txn_send_request_to_vc(ClientTxn * txn, char *request, TSVConn vc)
{
  TSCont cont;
  TSAssert(txn->magic == MAGIC_ALIVE);
  txn->request = ats_strdup(request);
  SET_TEST_HANDLER(txn->current_handler, synclient_txn_connect_handler);

  cont = TSContCreate(synclient_txn_main_handler, TSMutexCreate());
  TSContDataSet(cont, txn);

  TSContCall(cont, TS_EVENT_NET_CONNECT, vc);
  return 1;
}


static int
synclient_txn_read_response(TSCont contp)
{
  ClientTxn *txn = (ClientTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  TSIOBufferBlock block = TSIOBufferReaderStart(txn->resp_reader);
  while (block != NULL) {
    int64_t blocklen;
    const char *blockptr = TSIOBufferBlockReadStart(block, txn->resp_reader, &blocklen);

    if (txn->response_len+blocklen <= RESPONSE_MAX_SIZE) {
      memcpy((char *) (txn->response + txn->response_len), blockptr, blocklen);
      txn->response_len += blocklen;
    } else {
      TSError("Error: Response length %" PRId64" > response buffer size %d", txn->response_len+blocklen, RESPONSE_MAX_SIZE);
    }

    block = TSIOBufferBlockNext(block);
  }

  txn->response[txn->response_len + 1] = '\0';
  TSDebug(CDBG_TAG, "Response = |%s|, req len = %d", txn->response, txn->response_len);

  return 1;
}

static int
synclient_txn_read_response_handler(TSCont contp, TSEvent event, void * /* data ATS_UNUSED */)
{
  ClientTxn *txn = (ClientTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  int64_t avail;

  switch (event) {
  case TS_EVENT_VCONN_READ_READY:
  case TS_EVENT_VCONN_READ_COMPLETE:
    if (event == TS_EVENT_VCONN_READ_READY) {
      TSDebug(CDBG_TAG, "READ_READY");
    } else {
      TSDebug(CDBG_TAG, "READ_COMPLETE");
    }

    avail = TSIOBufferReaderAvail(txn->resp_reader);
    TSDebug(CDBG_TAG, "%" PRId64" bytes available in buffer", avail);

    if (avail > 0) {
      synclient_txn_read_response(contp);
      TSIOBufferReaderConsume(txn->resp_reader, avail);
    }

    TSVIOReenable(txn->read_vio);
    break;

  case TS_EVENT_VCONN_EOS:
    TSDebug(CDBG_TAG, "READ_EOS");
    // Connection closed. In HTTP/1.0 it means we're done for this request.
    txn->status = REQUEST_SUCCESS;
    return synclient_txn_close(contp);
    break;

  case TS_EVENT_ERROR:
    TSDebug(CDBG_TAG, "READ_ERROR");
    txn->status = REQUEST_FAILURE;
    return synclient_txn_close(contp);
    break;

  default:
    TSAssert(!"Invalid event");
    break;
  }
  return 1;
}


static int
synclient_txn_write_request(TSCont contp)
{
  ClientTxn *txn = (ClientTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  TSIOBufferBlock block;
  char *ptr_block;
  int64_t len, ndone, ntodo, towrite, avail;

  len = strlen(txn->request);

  ndone = 0;
  ntodo = len;
  while (ntodo > 0) {
    block = TSIOBufferStart(txn->req_buffer);
    ptr_block = TSIOBufferBlockWriteStart(block, &avail);
    towrite = MIN(ntodo, avail);
    memcpy(ptr_block, txn->request + ndone, towrite);
    TSIOBufferProduce(txn->req_buffer, towrite);
    ntodo -= towrite;
    ndone += towrite;
  }

  /* Start writing the response */
  TSDebug(CDBG_TAG, "Writing |%s| (%" PRId64") bytes", txn->request, len);
  txn->write_vio = TSVConnWrite(txn->vconn, contp, txn->req_reader, len);

  return 1;
}

static int
synclient_txn_write_request_handler(TSCont contp, TSEvent event, void * /* data ATS_UNUSED */)
{
  ClientTxn *txn = (ClientTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  switch (event) {
  case TS_EVENT_VCONN_WRITE_READY:
    TSDebug(CDBG_TAG, "WRITE_READY");
    TSVIOReenable(txn->write_vio);
    break;

  case TS_EVENT_VCONN_WRITE_COMPLETE:
    TSDebug(CDBG_TAG, "WRITE_COMPLETE");
    // Weird: synclient should not close the write part of vconn.
    // Otherwise some strangeness...

    /* Start reading */
    SET_TEST_HANDLER(txn->current_handler, synclient_txn_read_response_handler);
    txn->read_vio = TSVConnRead(txn->vconn, contp, txn->resp_buffer, INT64_MAX);
    break;

  case TS_EVENT_VCONN_EOS:
    TSDebug(CDBG_TAG, "WRITE_EOS");
    txn->status = REQUEST_FAILURE;
    return synclient_txn_close(contp);
    break;

  case TS_EVENT_ERROR:
    TSDebug(CDBG_TAG, "WRITE_ERROR");
    txn->status = REQUEST_FAILURE;
    return synclient_txn_close(contp);
    break;

  default:
    TSAssert(!"Invalid event");
    break;
  }
  return TS_EVENT_IMMEDIATE;
}


static int
synclient_txn_connect_handler(TSCont contp, TSEvent event, void *data)
{
  TSAssert((event == TS_EVENT_NET_CONNECT) || (event == TS_EVENT_NET_CONNECT_FAILED));

  ClientTxn *txn = (ClientTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  if (event == TS_EVENT_NET_CONNECT) {
    TSDebug(CDBG_TAG, "NET_CONNECT");

    txn->req_buffer = TSIOBufferCreate();
    txn->req_reader = TSIOBufferReaderAlloc(txn->req_buffer);
    txn->resp_buffer = TSIOBufferCreate();
    txn->resp_reader = TSIOBufferReaderAlloc(txn->resp_buffer);

    txn->response[0] = '\0';
    txn->response_len = 0;

    txn->vconn = (TSVConn) data;
    txn->local_port = (int) ((NetVConnection *) data)->get_local_port();

    txn->write_vio = NULL;
    txn->read_vio = NULL;

    /* start writing */
    SET_TEST_HANDLER(txn->current_handler, synclient_txn_write_request_handler);
    synclient_txn_write_request(contp);

    return TS_EVENT_IMMEDIATE;
  } else {
    TSDebug(CDBG_TAG, "NET_CONNECT_FAILED");
    txn->status = REQUEST_FAILURE;
    synclient_txn_close(contp);
  }

  return TS_EVENT_IMMEDIATE;
}


static int
synclient_txn_main_handler(TSCont contp, TSEvent event, void *data)
{
  ClientTxn *txn = (ClientTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  TxnHandler handler = txn->current_handler;
  return (*handler) (contp, event, data);
}


//////////////////////////////////////////////////////////////////////////////
// SOCKET SERVER
//////////////////////////////////////////////////////////////////////////////

SocketServer *
synserver_create(int port)
{
  SocketServer *s = (SocketServer *) TSmalloc(sizeof(SocketServer));
  s->magic = MAGIC_ALIVE;
  s->accept_port = port;
  s->accept_action = NULL;
  s->accept_cont = TSContCreate(synserver_accept_handler, TSMutexCreate());
  TSContDataSet(s->accept_cont, s);
  return s;
}

static int
synserver_start(SocketServer * s)
{
  TSAssert(s->magic == MAGIC_ALIVE);
  s->accept_action = TSNetAccept(s->accept_cont, s->accept_port, -1, 0);
  return 1;
}

static int
synserver_stop(SocketServer * s)
{
  TSAssert(s->magic == MAGIC_ALIVE);
  if (s->accept_action && !TSActionDone(s->accept_action)) {
    TSActionCancel(s->accept_action);
    s->accept_action = NULL;
    TSDebug(SDBG_TAG, "Had to cancel action");
  }
  TSDebug(SDBG_TAG, "stopped");
  return 1;
}

static int
synserver_delete(SocketServer * s)
{
  TSAssert(s->magic == MAGIC_ALIVE);
  synserver_stop(s);

  if (s->accept_cont) {
    TSContDestroy(s->accept_cont);
    s->accept_cont = NULL;
    TSDebug(SDBG_TAG, "destroyed accept cont");
  }
  s->magic = MAGIC_DEAD;
  TSfree(s);
  TSDebug(SDBG_TAG, "deleted server");
  return 1;
}

static int
synserver_accept_handler(TSCont contp, TSEvent event, void *data)
{
  TSAssert((event == TS_EVENT_NET_ACCEPT) || (event == TS_EVENT_NET_ACCEPT_FAILED));

  SocketServer *s = (SocketServer *) TSContDataGet(contp);
  TSAssert(s->magic == MAGIC_ALIVE);

  if (event == TS_EVENT_NET_ACCEPT_FAILED) {
    Warning("Synserver failed to bind to port %d.", ntohs(s->accept_port));
    ink_release_assert(!"Synserver must be able to bind to a port, check system netstat");
    TSDebug(SDBG_TAG, "NET_ACCEPT_FAILED");
    return TS_EVENT_IMMEDIATE;
  }

  TSDebug(SDBG_TAG, "NET_ACCEPT");

  /* Create a new transaction */
  ServerTxn *txn = (ServerTxn *) TSmalloc(sizeof(ServerTxn));
  txn->magic = MAGIC_ALIVE;

  SET_TEST_HANDLER(txn->current_handler, synserver_txn_read_request_handler);

  TSCont txn_cont = TSContCreate(synserver_txn_main_handler, TSMutexCreate());
  TSContDataSet(txn_cont, txn);

  txn->req_buffer = TSIOBufferCreate();
  txn->req_reader = TSIOBufferReaderAlloc(txn->req_buffer);

  txn->resp_buffer = TSIOBufferCreate();
  txn->resp_reader = TSIOBufferReaderAlloc(txn->resp_buffer);

  txn->request[0] = '\0';
  txn->request_len = 0;

  txn->vconn = (TSVConn) data;

  txn->write_vio = NULL;

  /* start reading */
  txn->read_vio = TSVConnRead(txn->vconn, txn_cont, txn->req_buffer, INT64_MAX);

  return TS_EVENT_IMMEDIATE;
}


static int
synserver_txn_close(TSCont contp)
{
  ServerTxn *txn = (ServerTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  if (txn->vconn != NULL) {
    TSVConnClose(txn->vconn);
  }
  if (txn->req_buffer) {
    TSIOBufferDestroy(txn->req_buffer);
  }
  if (txn->resp_buffer) {
    TSIOBufferDestroy(txn->resp_buffer);
  }

  txn->magic = MAGIC_DEAD;
  TSfree(txn);
  TSContDestroy(contp);

  TSDebug(SDBG_TAG, "Server Txn destroyed");
  return TS_EVENT_IMMEDIATE;
}


static int
synserver_txn_write_response(TSCont contp)
{
  ServerTxn *txn = (ServerTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  SET_TEST_HANDLER(txn->current_handler, synserver_txn_write_response_handler);

  TSIOBufferBlock block;
  char *ptr_block;
  int64_t len, ndone, ntodo, towrite, avail;
  char *response;

  response = generate_response(txn->request);
  len = strlen(response);

  ndone = 0;
  ntodo = len;
  while (ntodo > 0) {
    block = TSIOBufferStart(txn->resp_buffer);
    ptr_block = TSIOBufferBlockWriteStart(block, &avail);
    towrite = MIN(ntodo, avail);
    memcpy(ptr_block, response + ndone, towrite);
    TSIOBufferProduce(txn->resp_buffer, towrite);
    ntodo -= towrite;
    ndone += towrite;
  }

  /* Start writing the response */
  TSDebug(SDBG_TAG, "Writing response: |%s| (%" PRId64") bytes)", response, len);
  txn->write_vio = TSVConnWrite(txn->vconn, contp, txn->resp_reader, len);

  /* Now that response is in IOBuffer, free up response */
  TSfree(response);

  return TS_EVENT_IMMEDIATE;
}


static int
synserver_txn_write_response_handler(TSCont contp, TSEvent event, void * /* data ATS_UNUSED */)
{
  ServerTxn *txn = (ServerTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  switch (event) {
  case TS_EVENT_VCONN_WRITE_READY:
    TSDebug(SDBG_TAG, "WRITE_READY");
    TSVIOReenable(txn->write_vio);
    break;

  case TS_EVENT_VCONN_WRITE_COMPLETE:
    TSDebug(SDBG_TAG, "WRITE_COMPLETE");
    TSVConnShutdown(txn->vconn, 0, 1);
    return synserver_txn_close(contp);
    break;

  case TS_EVENT_VCONN_EOS:
    TSDebug(SDBG_TAG, "WRITE_EOS");
    return synserver_txn_close(contp);
    break;

  case TS_EVENT_ERROR:
    TSDebug(SDBG_TAG, "WRITE_ERROR");
    return synserver_txn_close(contp);
    break;

  default:
    TSAssert(!"Invalid event");
    break;
  }
  return TS_EVENT_IMMEDIATE;
}


static int
synserver_txn_read_request(TSCont contp)
{
  ServerTxn *txn = (ServerTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  int end;
  TSIOBufferBlock block = TSIOBufferReaderStart(txn->req_reader);

  while (block != NULL) {
    int64_t blocklen;
    const char *blockptr = TSIOBufferBlockReadStart(block, txn->req_reader, &blocklen);

    if (txn->request_len+blocklen <= REQUEST_MAX_SIZE) {
      memcpy((char *) (txn->request + txn->request_len), blockptr, blocklen);
      txn->request_len += blocklen;
    } else {
      TSError("Error: Request length %" PRId64" > request buffer size %d", txn->request_len+blocklen, REQUEST_MAX_SIZE);
    }

    block = TSIOBufferBlockNext(block);
  }

  txn->request[txn->request_len] = '\0';
  TSDebug(SDBG_TAG, "Request = |%s|, req len = %d", txn->request, txn->request_len);

  end = (strstr(txn->request, HTTP_REQUEST_END) != NULL);
  TSDebug(SDBG_TAG, "End of request = %d", end);

  return end;
}

static int
synserver_txn_read_request_handler(TSCont contp, TSEvent event, void * /* data ATS_UNUSED */)
{
  ServerTxn *txn = (ServerTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  int64_t avail;
  int end_of_request;

  switch (event) {
  case TS_EVENT_VCONN_READ_READY:
  case TS_EVENT_VCONN_READ_COMPLETE:
    TSDebug(SDBG_TAG, (event == TS_EVENT_VCONN_READ_READY) ? "READ_READY" : "READ_COMPLETE");
    avail = TSIOBufferReaderAvail(txn->req_reader);
    TSDebug(SDBG_TAG, "%" PRId64" bytes available in buffer", avail);

    if (avail > 0) {
      end_of_request = synserver_txn_read_request(contp);
      TSIOBufferReaderConsume(txn->req_reader, avail);

      if (end_of_request) {
        TSVConnShutdown(txn->vconn, 1, 0);
        return synserver_txn_write_response(contp);
      }
    }

    TSVIOReenable(txn->read_vio);
    break;

  case TS_EVENT_VCONN_EOS:
    TSDebug(SDBG_TAG, "READ_EOS");
    return synserver_txn_close(contp);
    break;

  case TS_EVENT_ERROR:
    TSDebug(SDBG_TAG, "READ_ERROR");
    return synserver_txn_close(contp);
    break;

  default:
    TSAssert(!"Invalid event");
    break;
  }
  return TS_EVENT_IMMEDIATE;
}


static int
synserver_txn_main_handler(TSCont contp, TSEvent event, void *data)
{
  ServerTxn *txn = (ServerTxn *) TSContDataGet(contp);
  TSAssert(txn->magic == MAGIC_ALIVE);

  TxnHandler handler = txn->current_handler;
  return (*handler) (contp, event, data);
}

