/*
* buffer.c -- generic memory buffer .
*
* Copyright (c) 2001-2008, NLnet Labs. All rights reserved.
*
* See LICENSE for the license.
*
*/
/**
* \file
*
* This file contains the definition of sldns_buffer, and functions to manipulate those.
*/
#include "config.h"
#include "sldns/sbuffer.h"
#include <stdarg.h>

sldns_buffer *
sldns_buffer_new(size_t capacity)
{
       sldns_buffer *buffer = (sldns_buffer*)malloc(sizeof(sldns_buffer));

       if (!buffer) {
               return NULL;
       }

       buffer->_data = (uint8_t *) malloc(capacity);
       if (!buffer->_data) {
               free(buffer);
               return NULL;
       }

       buffer->_position = 0;
       buffer->_limit = buffer->_capacity = capacity;
       buffer->_fixed = 0;
       buffer->_status_err = 0;

       sldns_buffer_invariant(buffer);

       return buffer;
}

void
sldns_buffer_new_frm_data(sldns_buffer *buffer, void *data, size_t size)
{
       assert(data != NULL);

       buffer->_position = 0;
       buffer->_limit = buffer->_capacity = size;
       buffer->_fixed = 0;
       if (!buffer->_fixed && buffer->_data)
               free(buffer->_data);
       buffer->_data = malloc(size);
       if(!buffer->_data) {
               buffer->_status_err = 1;
               return;
       }
       memcpy(buffer->_data, data, size);
       buffer->_status_err = 0;

       sldns_buffer_invariant(buffer);
}

void
sldns_buffer_init_frm_data(sldns_buffer *buffer, void *data, size_t size)
{
       memset(buffer, 0, sizeof(*buffer));
       buffer->_data = data;
       buffer->_capacity = buffer->_limit = size;
       buffer->_fixed = 1;
}

int
sldns_buffer_set_capacity(sldns_buffer *buffer, size_t capacity)
{
       void *data;

       sldns_buffer_invariant(buffer);
       assert(buffer->_position <= capacity && !buffer->_fixed);

       data = (uint8_t *) realloc(buffer->_data, capacity);
       if (!data) {
               buffer->_status_err = 1;
               return 0;
       } else {
               buffer->_data = data;
               buffer->_limit = buffer->_capacity = capacity;
               return 1;
       }
}

int
sldns_buffer_reserve(sldns_buffer *buffer, size_t amount)
{
       sldns_buffer_invariant(buffer);
       assert(!buffer->_fixed);
       if (buffer->_capacity < buffer->_position + amount) {
               size_t new_capacity = buffer->_capacity * 3 / 2;

               if (new_capacity < buffer->_position + amount) {
                       new_capacity = buffer->_position + amount;
               }
               if (!sldns_buffer_set_capacity(buffer, new_capacity)) {
                       buffer->_status_err = 1;
                       return 0;
               }
       }
       buffer->_limit = buffer->_capacity;
       return 1;
}

int
sldns_buffer_printf(sldns_buffer *buffer, const char *format, ...)
{
       va_list args;
       int written = 0;
       size_t remaining;

       if (sldns_buffer_status_ok(buffer)) {
               sldns_buffer_invariant(buffer);
               assert(buffer->_limit == buffer->_capacity);

               remaining = sldns_buffer_remaining(buffer);
               va_start(args, format);
               written = vsnprintf((char *) sldns_buffer_current(buffer), remaining,
                                   format, args);
               va_end(args);
               if (written == -1) {
                       buffer->_status_err = 1;
                       return -1;
               }
               buffer->_position += written;
       }
       return written;
}

void
sldns_buffer_free(sldns_buffer *buffer)
{
       if (!buffer) {
               return;
       }

       if (!buffer->_fixed)
               free(buffer->_data);

       free(buffer);
}

void
sldns_buffer_copy(sldns_buffer* result, sldns_buffer* from)
{
       size_t tocopy = sldns_buffer_limit(from);

       if(tocopy > sldns_buffer_capacity(result))
               tocopy = sldns_buffer_capacity(result);
       sldns_buffer_clear(result);
       sldns_buffer_write(result, sldns_buffer_begin(from), tocopy);
       sldns_buffer_flip(result);
}