/*
 * test-bio.c - BIO layer for testing
 *
 * Copyright (c) 2012 The Chromium Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * This is a 'source/sink' BIO which supports synthetic inputs and outputs, and
 * can be used to drive filter BIOs through a state machine. It buffers all
 * output sent to it, which can be retrieved with BIO_test_get_output(), and
 * input sent to it, which is handed back in response to BIO_read() by the
 * filter BIO.
 */

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

#include "src/test-bio.h"
#include "src/util.h"

int verbose;
int verbose_debug;

static const unsigned int kMagic = 0x5f8d3f15;

struct test_ctx
{
  unsigned int magic;
  unsigned char *out;
  size_t outsz;
  unsigned char *in;
  size_t insz;
};

static struct test_ctx *bio_ctx (BIO *b)
{
  struct test_ctx *ctx = b->ptr;
  assert (ctx->magic == kMagic);
  return ctx;
}

static size_t buf_drain (unsigned char **buf, size_t *bufsz,
                         unsigned char *out, size_t outsz)
{
  if (*bufsz < outsz)
    outsz = *bufsz;
  memcpy (out, *buf, outsz);
  if (*bufsz > outsz)
    memmove (*buf, *buf + outsz, *bufsz - outsz);
  *bufsz -= outsz;
  *buf = realloc (*buf, *bufsz);
  return outsz;
}

static void buf_fill (unsigned char **buf, size_t *bufsz,
                      const unsigned char *in, size_t insz)
{
  *buf = realloc (*buf, *bufsz + insz);
  memcpy (*buf + *bufsz, in, insz);
  *bufsz += insz;
}

int test_new (BIO *b)
{
  struct test_ctx *ctx = malloc (sizeof *ctx);
  if (!ctx)
    return 0;
  ctx->magic = kMagic;
  ctx->in = NULL;
  ctx->insz = 0;
  ctx->out = NULL;
  ctx->outsz = 0;
  b->init = 1;
  b->flags = 0;
  b->ptr = ctx;
  return 1;
}

int test_free (BIO *b)
{
  struct test_ctx *ctx;
  if (!b || !b->ptr)
    return 1;
  ctx = bio_ctx (b);
  free (ctx->in);
  free (ctx->out);
  return 1;
}

int test_write (BIO *b, const char *buf, int sz)
{
  struct test_ctx *ctx = bio_ctx (b);
  if (sz <= 0)
    return 0;
  buf_fill (&ctx->out, &ctx->outsz, (unsigned char *) buf, sz);
  return sz;
}

int test_read (BIO *b, char *buf, int sz)
{
  struct test_ctx *ctx = bio_ctx (b);
  if (sz <= 0)
    return 0;
  return buf_drain (&ctx->in, &ctx->insz, (unsigned char *) buf, sz);
}

long test_ctrl (BIO *b, int cmd, long num, void *ptr)
{
  return 0;
}

long test_callback_ctrl (BIO *b, int cmd, bio_info_cb fp)
{
  return 0;
}

BIO_METHOD test_methods =
{
  BIO_TYPE_SOCKET,
  "test",
  test_write,
  test_read,
  NULL,
  NULL,
  test_ctrl,
  test_new,
  test_free,
  test_callback_ctrl,
};

BIO_METHOD *BIO_s_test()
{
  return &test_methods;
}

BIO API *BIO_new_test()
{
  return BIO_new (BIO_s_test());
}

size_t API BIO_test_output_left (BIO *b)
{
  return bio_ctx (b)->outsz;
}

size_t API BIO_test_get_output (BIO *b, unsigned char *buf, size_t bufsz)
{
  struct test_ctx *c = bio_ctx (b);
  return buf_drain (&c->out, &c->outsz, buf, bufsz);
}

void API BIO_test_add_input (BIO *b, const unsigned char *buf, size_t bufsz)
{
  struct test_ctx *c = bio_ctx (b);
  return buf_fill (&c->in, &c->insz, buf, bufsz);
}