/**
 * Copyright 2010 Christian Liesch
 *
 * 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.
 */

/**
 * @file
 *
 * @Author christian liesch <liesch@gmx.ch>
 *
 * Implementation of the HTTP Test Tool binary module 
 */

/************************************************************************
 * Includes
 ***********************************************************************/
#include "module.h"

/************************************************************************
 * Definitions 
 ***********************************************************************/

/************************************************************************
 * Commands
 ***********************************************************************/
/**
 * SEND binary data
 *
 * @param worker IN
 * @param parent IN
 *
 * @return APR_SUCCESS or an APR error
 */
static apr_status_t block_BINARY_SEND(worker_t * worker, worker_t *parent, apr_pool_t *ptmp) {
  apr_status_t status = APR_SUCCESS;
  char *copy;
  apr_size_t i;

  if (!worker->socket || !worker->socket->socket) {
    return APR_ENOSOCKET;
  }
    
  for (i = 1; i < store_get_size(worker->params); i++) {
    int unresolved; 

    copy = store_get_copy(worker->params, worker->pcache, apr_itoa(ptmp, i));
    if (!copy) {
      continue;
    }
    copy = worker_replace_vars(worker, copy, &unresolved, ptmp);

    if (unresolved) {
      apr_table_addn(worker->cache, 
		     apr_pstrdup(worker->pcache, "BINARY;resolve"), copy);
    }
    else {
      apr_table_addn(worker->cache, 
		     apr_pstrdup(worker->pcache, "BINARY"), copy);
    }
  }

  return status;
}

/**
 * RECV binary data and makes a hex dump
 *
 * @param worker IN 
 * @param parent IN
 *
 * @return APR_SUCCESS or an APR error
 */
static apr_status_t block_BINARY_RECV(worker_t * worker, worker_t *parent, apr_pool_t *ptmp) {
  apr_pool_t *pool;
  apr_status_t status;
  apr_size_t recv_len;
  apr_size_t peeklen;
  sockreader_t *sockreader;
  char *buf;
  const char *val;

  int poll = 0;

  val = store_get(worker->params, "1");
  /* must be a number */
  recv_len = apr_atoi64(val);

  HT_POOL_CREATE(&pool);

  if (worker->socket->sockreader == NULL) {
    peeklen = worker->socket->peeklen;
    worker->socket->peeklen = 0;
    if ((status = sockreader_new(&worker->socket->sockreader, 
                                 worker->socket->transport,
				 worker->socket->peek, peeklen)) 
        != APR_SUCCESS) {
      goto out_err;
    }
  }
  sockreader = worker->socket->sockreader;

  if ((status = content_length_reader(sockreader, &buf, &recv_len, "")) != APR_SUCCESS) {
    if (poll && APR_STATUS_IS_INCOMPLETE(status)) {
      status = APR_SUCCESS;
    }
    else {
      goto out_err;
    }
  }

  worker->flags |= FLAGS_PRINT_HEX;
  if ((status = worker_handle_buf(worker, pool, buf, recv_len)) 
      != APR_SUCCESS) {
    goto out_err;
  }

out_err:
  status = worker_assert(worker, status);
  apr_pool_destroy(pool);

  return status;
}

/**
 * Do hex to bin transformation with this hook, this is called
 * after late variable replacement.
 *
 * @param worker IN worker context
 * @param line IN line informations
 */
static apr_status_t binary_line_get_length(worker_t *worker, line_t *line) {
  apr_size_t len;

  /* lets see if we do have work */
  if (strncmp(line->info, "BINARY", 6) != 0) {
    return APR_SUCCESS;
  }

  apr_collapse_spaces(line->buf, line->buf);
  /* callculate buf len */
  len = strlen(line->buf);
  if (len && len%2 != 1) {
    len /= 2;
  }
  else {
    worker_log(worker, LOG_ERR, "Binary data must have an equal number of digits");
    return APR_EINVAL;
  }
  line->info = apr_psprintf(worker->pcache, "NOCRLF:%"APR_SIZE_T_FMT, len);
  line->len = len;

  return APR_SUCCESS;
}

/**
 * Do hex to bin transformation with this hook, this is called
 * after late variable replacement.
 *
 * @param worker IN worker context
 * @param line IN line informations
 */
static apr_status_t binary_line_flush(worker_t *worker, line_t *line) {
  apr_status_t status;
  char *buf;
  apr_size_t i;

  /* lets see if we do have work */
  if (strncmp(line->info, "BINARY", 6) != 0) {
    return APR_SUCCESS;
  }

  if ((status = binary_line_get_length(worker, line)) != APR_SUCCESS) {
    return status;
  }

  buf = apr_pcalloc(worker->pcache, line->len);

  for (i = 0; i < line->len; i++) {
    char hex[3];
    hex[0] = line->buf[i * 2];
    hex[1] = line->buf[i * 2 + 1];
    hex[2] = 0;
    buf[i] = (char )apr_strtoi64(hex, NULL, 16);
  }
  line->buf = buf;

  return APR_SUCCESS;
}

/************************************************************************
 * Module
 ***********************************************************************/
apr_status_t binary_module_init(global_t *global) {
  apr_status_t status;
  if ((status = module_command_new(global, "BINARY", "_SEND",
	                           "<hex-digits>*",
	                           "send hex digits as binary data",
	                           block_BINARY_SEND)) != APR_SUCCESS) {
    return status;
  }
  if ((status = module_command_new(global, "BINARY", "_RECV",
	                           "<number-of-bytes>",
	                           "prints received data as hex digit",
	                           block_BINARY_RECV)) != APR_SUCCESS) {
    return status;
  }
  htt_hook_line_flush(binary_line_flush, NULL, NULL, 0);
  htt_hook_line_get_length(binary_line_get_length, NULL, NULL, 0);
  return APR_SUCCESS;
}

