/*

  silcbuffer.h

  Author: Pekka Riikonen <priikone@silcnet.org>

  Copyright (C) 1998 - 2007 Pekka Riikonen

  The contents of this file are subject to one of the Licenses specified 
  in the COPYING file;  You may not use this file except in compliance 
  with the License.

  The software distributed under the License is distributed on an "AS IS"
  basis, in the hope that it will be useful, but WITHOUT WARRANTY OF ANY
  KIND, either expressed or implied.  See the COPYING file for more
  information.

*/
/* $Id: silcbuffer.h,v 1.34.2.1 2007/06/17 10:16:39 priikone Exp $ */

/****h* silcutil/SILC Buffer Interface
 *
 * DESCRIPTION
 *
 * SilcBuffer is very simple and easy to use, yet you can do to the
 * buffer almost anything you want with its method functions. The buffer
 * is constructed of four different data sections that in whole creates
 * the allocated data area.
 *
 ***/

#ifndef SILCBUFFER_H
#define SILCBUFFER_H

/****s* silcutil/SilcBufferAPI/SilcBuffer
 *
 * NAME
 *
 *    typedef struct { ... } *SilcBuffer, SilcBufferStruct;
 *
 * DESCRIPTION
 *
 *    SILC Buffer object. Following short description of the fields
 *    of the buffer.
 *
 * EXAMPLE
 *
 *    unsiged char *head;
 *
 *        Head of the allocated buffer. This is the start of the allocated
 *        data area and remains as same throughout the lifetime of the buffer.
 *        However, the end of the head area or the start of the currently valid
 *        data area is variable.
 *
 *        --------------------------------
 *        | head  | data         | tail  |
 *        --------------------------------
 *        ^       ^
 *
 *        Current head section in the buffer is sb->data - sb->head.
 *
 *    unsigned char *data;
 *
 *        Currently valid data area. This is the start of the currently valid
 *        main data area. The data area is variable in all directions.
 *
 *        --------------------------------
 *        | head  | data         | tail  |
 *        --------------------------------
 *                ^              ^
 *
 *        Current valid data area in the buffer is sb->tail - sb->data.
 *
 *     unsigned char *tail;
 *
 *        Tail of the buffer. This is the end of the currently valid data area
 *        or start of the tail area. The start of the tail area is variable.
 *
 *        --------------------------------
 *        | head  | data         | tail  |
 *        --------------------------------
 *                               ^       ^
 *
 *        Current tail section in the buffer is sb->end - sb->tail.
 *
 *    unsigned char *end;
 *
 *        End of the allocated buffer. This is the end of the allocated data
 *        area and remains as same throughout the lifetime of the buffer.
 *        Usually this field is not needed except when checking the size
 *        of the buffer.
 *
 *        --------------------------------
 *        | head  | data         | tail  |
 *        --------------------------------
 *                                       ^
 *
 *        Length of the entire buffer is (ie. truelen) sb->end - sb->head.
 *
 *     Currently valid data area is considered to be the main data area in
 *     the buffer. However, the entire buffer is of course valid data and can
 *     be used as such. Usually head section of the buffer includes different
 *     kind of headers or similar. Data section includes the main data of
 *     the buffer. Tail section can be seen as a reserve space of the data
 *     section. Tail section can be pulled towards end, and thus the data
 *     section becomes larger.
 *
 * SILC Buffer is not thread-safe.  If the same SilcBuffer context must be
 * used in multithreaded environment concurrency control must be employed.
 *
 * SOURCE
 */
typedef struct SilcBufferObject {
  unsigned char *head;
  unsigned char *data;
  unsigned char *tail;
  unsigned char *end;
} *SilcBuffer, SilcBufferStruct;
/***/

/* Macros */

/****f* silcutil/SilcBufferAPI/silc_buffer_data
 *
 * NAME
 *
 *    unsigned char *silc_buffer_data(SilcBuffer sb)
 *
 * DESCRIPTION
 *
 *    Returns pointer to the data area of the buffer.
 *
 * SOURCE
 */
#define silc_buffer_data(x) (x)->data
/***/

/****f* silcutil/SilcBufferAPI/silc_buffer_datalen
 *
 * NAME
 *
 *    #define silc_buffer_datalen ...
 *
 * DESCRIPTION
 *
 *    Macro that can be used in function argument list to give the data
 *    pointer and the data length, instead of calling both silc_buffer_data
 *    and silc_buffer_len separately.
 *
 * EXAMPLE
 *
 *    // Following are the same thing
 *    silc_foo_function(foo, silc_buffer_datalen(buf));
 *    silc_foo_function(foo, silc_buffer_data(buf), silc_buffer_len(buf));
 *
 * SOURCE
 */
#define silc_buffer_datalen(x) (x) ? silc_buffer_data((x)) : NULL, \
  (x) ? silc_buffer_len((x)) : 0
/***/

/* Inline functions */

/****d* silcutil/SilcBufferAPI/silc_buffer_truelen
 *
 * NAME
 *
 *    SilcUInt32 silc_buffer_truelen(SilcBuffer sb)
 *
 * DESCRIPTION
 *
 *    Returns the true length of the buffer.
 *
 ***/
static inline
SilcUInt32 silc_buffer_truelen(SilcBuffer x)
{
  return (SilcUInt32)(x->end - x->head);
}

/****d* silcutil/SilcBufferAPI/silc_buffer_len
 *
 * NAME
 *
 *    SilcUInt32 silc_buffer_len(SilcBuffer sb)
 *
 * DESCRIPTION
 *
 *    Returns the current length of the data area of the buffer.
 *
 ***/
static inline
SilcUInt32 silc_buffer_len(SilcBuffer x)
{
  return (SilcUInt32)(x->tail - x->data);
}

/****d* silcutil/SilcBufferAPI/silc_buffer_headlen
 *
 * NAME
 *
 *    SilcUInt32 silc_buffer_headlen(SilcBuffer sb)
 *
 * DESCRIPTION
 *
 *    Returns the current length of the head data area of the buffer.
 *
 ***/
static inline
SilcUInt32 silc_buffer_headlen(SilcBuffer x)
{
  return (SilcUInt32)(x->data - x->head);
}

/****d* silcutil/SilcBufferAPI/silc_buffer_taillen
 *
 * NAME
 *
 *    SilcUInt32 silc_buffer_taillen(SilcBuffer sb)
 *
 * DESCRIPTION
 *
 *    Returns the current length of the tail data area of the buffer.
 *
 ***/
static inline
SilcUInt32 silc_buffer_taillen(SilcBuffer x)
{
  return (SilcUInt32)(x->end - x->tail);
}

/****f* silcutil/SilcBufferAPI/silc_buffer_alloc
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_alloc(SilcUInt32 len);
 *
 * DESCRIPTION
 *
 *    Allocates new SilcBuffer and returns it.  Returns NULL on error.
 *
 ***/

static inline
SilcBuffer silc_buffer_alloc(SilcUInt32 len)
{
  SilcBuffer sb;

  /* Allocate new SilcBuffer */
  sb = (SilcBuffer)silc_calloc(1, sizeof(*sb));
  if (silc_unlikely(!sb))
    return NULL;

  if (silc_likely(len)) {
    /* Allocate the actual data area */
    sb->head = (unsigned char *)silc_calloc(len, sizeof(*sb->head));
    if (silc_unlikely(!sb->head))
      return NULL;

    /* Set pointers to the new buffer */
    sb->data = sb->head;
    sb->tail = sb->head;
    sb->end = sb->head + len;
  }

  return sb;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_free
 *
 * SYNOPSIS
 *
 *    static inline
 *    void silc_buffer_free(SilcBuffer sb);
 *
 * DESCRIPTION
 *
 *    Frees SilcBuffer.  Can be called safely `sb' as NULL.
 *
 * NOTES
 *
 *    Must not be called for buffers allocated with silc_buffer_salloc,
 *    silc_buffer_salloc_size, silc_buffer_scopy and silc_buffer_sclone.
 *
 ***/

static inline
void silc_buffer_free(SilcBuffer sb)
{
  if (sb) {
#if defined(SILC_DEBUG)
    if (sb->head)
      memset(sb->head, 'F', silc_buffer_truelen(sb));
#endif
    silc_free(sb->head);
    silc_free(sb);
  }
}

/****f* silcutil/SilcBufferAPI/silc_buffer_steal
 *
 * SYNOPSIS
 *
 *    static inline
 *    unsigned char *silc_buffer_steal(SilcBuffer sb, SilcUInt32 *data_len);
 *
 * DESCRIPTION
 *
 *    Steals the data from the buffer `sb'.  This returns pointer to the
 *    start of the buffer and the true length of that buffer.  The `sb'
 *    cannot be used anymore after calling this function because the
 *    data buffer was stolen.  The `sb' must be freed with silc_buffer_free.
 *    The caller is responsible of freeing the stolen data buffer with
 *    silc_free.
 *
 ***/

static inline
unsigned char *silc_buffer_steal(SilcBuffer sb, SilcUInt32 *data_len)
{
  unsigned char *buf = sb->head;
  if (data_len)
    *data_len = silc_buffer_truelen(sb);
  sb->head = sb->data = sb->tail = sb->end = NULL;
  return buf;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_purge
 *
 * SYNOPSIS
 *
 *    static inline
 *    void silc_buffer_purge(SilcBuffer sb);
 *
 * DESCRIPTION
 *
 *    Same as silc_buffer_free but free's only the contents of the buffer
 *    not the buffer itself.  The `sb' remains intact, data is freed.  Buffer
 *    is ready for re-use after calling this function.
 *
 * NOTES
 *
 *    Must not be called for buffers allocated with silc_buffer_salloc,
 *    silc_buffer_salloc_size, silc_buffer_scopy and silc_buffer_sclone.
 *
 ***/

static inline
void silc_buffer_purge(SilcBuffer sb)
{
  silc_free(silc_buffer_steal(sb, NULL));
}

/****f* silcutil/SilcBufferAPI/silc_buffer_set
 *
 * SYNOPSIS
 *
 *    static inline
 *    void silc_buffer_set(SilcBuffer sb,
 *			   unsigned char *data,
 *                         SilcUInt32 data_len);
 *
 * DESCRIPTION
 *
 *    Sets the `data' and `data_len' to the buffer pointer sent as argument.
 *    The data area is automatically set to the `data_len'. This function
 *    can be used to set the data to static buffer without needing any
 *    memory allocations. The `data' will not be copied to the buffer.
 *
 * EXAMPLE
 *
 *    SilcBufferStruct buf;
 *    silc_buffer_set(&buf, data, data_len);
 *
 ***/

static inline
void silc_buffer_set(SilcBuffer sb, unsigned char *data, SilcUInt32 data_len)
{
  sb->data = sb->head = data;
  sb->tail = sb->end = data + data_len;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_pull
 *
 * SYNOPSIS
 *
 *    static inline
 *    unsigned char *silc_buffer_pull(SilcBuffer sb, SilcUInt32 len);
 *
 * DESCRIPTION
 *
 *    Pulls current data area towards end. The length of the currently
 *    valid data area is also decremented. Returns pointer to the data
 *    area before pulling. Returns NULL on error.
 *
 * EXAMPLE
 *
 *    ---------------------------------
 *    | head  | data       | tail     |
 *    ---------------------------------
 *            ^
 *            Pulls the start of the data area.
 *
 *    ---------------------------------
 *    | head     | data    | tail     |
 *    ---------------------------------
 *            ^
 *
 *    silc_buffer_pull(sb, 20);
 *
 ***/

static inline
unsigned char *silc_buffer_pull(SilcBuffer sb, SilcUInt32 len)
{
  unsigned char *old_data = sb->data;
#if defined(SILC_DEBUG)
  SILC_ASSERT(len <= silc_buffer_len(sb));
#else
  if (silc_unlikely(len > silc_buffer_len(sb)))
    return NULL;
#endif
  sb->data += len;
  return old_data;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_push
 *
 * SYNOPSIS
 *
 *    static inline
 *    unsigned char *silc_buffer_push(SilcBuffer sb, SilcUInt32 len);
 *
 * DESCRIPTION
 *
 *    Pushes current data area towards beginning. Length of the currently
 *    valid data area is also incremented. Returns a pointer to the
 *    data area before pushing. Returns NULL on error.
 *
 * EXAMPLE
 *
 *    ---------------------------------
 *    | head     | data    | tail     |
 *    ---------------------------------
 *               ^
 *               Pushes the start of the data area.
 *
 *    ---------------------------------
 *    | head  | data       | tail     |
 *    ---------------------------------
 *               ^
 *
 *    silc_buffer_push(sb, 20);
 *
 ***/

static inline
unsigned char *silc_buffer_push(SilcBuffer sb, SilcUInt32 len)
{
  unsigned char *old_data = sb->data;
#if defined(SILC_DEBUG)
  SILC_ASSERT((sb->data - len) >= sb->head);
#else
  if (silc_unlikely((sb->data - len) < sb->head))
    return NULL;
#endif
  sb->data -= len;
  return old_data;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_pull_tail
 *
 * SYNOPSIS
 *
 *    static inline
 *    unsigned char *silc_buffer_pull_tail(SilcBuffer sb, SilcUInt32 len);
 *
 * DESCRIPTION
 *
 *    Pulls current tail section towards end. Length of the current valid
 *    data area is also incremented. Returns a pointer to the data area
 *    before pulling. Returns NULL on error.
 *
 * EXAMPLE
 *
 *    ---------------------------------
 *    | head  | data       | tail     |
 *    ---------------------------------
 *                         ^
 *                         Pulls the start of the tail section.
 *
 *    ---------------------------------
 *    | head  | data           | tail |
 *    ---------------------------------
 *                         ^
 *
 *    silc_buffer_pull_tail(sb, 23);
 *
 ***/

static inline
unsigned char *silc_buffer_pull_tail(SilcBuffer sb, SilcUInt32 len)
{
  unsigned char *old_tail = sb->tail;
#if defined(SILC_DEBUG)
  SILC_ASSERT(len <= silc_buffer_taillen(sb));
#else
  if (silc_unlikely(len > silc_buffer_taillen(sb)))
    return NULL;
#endif
  sb->tail += len;
  return old_tail;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_push_tail
 *
 * SYNOPSIS
 *
 *    static inline
 *    unsigned char *silc_buffer_push_tail(SilcBuffer sb, SilcUInt32 len);
 *
 * DESCRIPTION
 *
 *    Pushes current tail section towards beginning. Length of the current
 *    valid data area is also decremented. Returns a pointer to the
 *    tail section before pushing. Returns NULL on error.
 *
 * EXAMPLE
 *
 *    ---------------------------------
 *    | head  | data           | tail |
 *    ---------------------------------
 *                             ^
 *                             Pushes the start of the tail section.
 *
 *    ---------------------------------
 *    | head  | data       | tail     |
 *    ---------------------------------
 *                             ^
 *
 *    silc_buffer_push_tail(sb, 23);
 *
 ***/

static inline
unsigned char *silc_buffer_push_tail(SilcBuffer sb, SilcUInt32 len)
{
  unsigned char *old_tail = sb->tail;
#if defined(SILC_DEBUG)
  SILC_ASSERT((sb->tail - len) >= sb->data);
#else
  if (silc_unlikely((sb->tail - len) < sb->data))
    return NULL;
#endif
  sb->tail -= len;
  return old_tail;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_put_head
 *
 * SYNOPSIS
 *
 *    static inline
 *    unsigned char *silc_buffer_put_head(SilcBuffer sb,
 *					  const unsigned char *data,
 *					  SilcUInt32 len);
 *
 * DESCRIPTION
 *
 *    Puts data at the head of the buffer. Returns pointer to the copied
 *    data area. Returns NULL on error.
 *
 * EXAMPLE
 *
 *    ---------------------------------
 *    | head  | data       | tail     |
 *    ---------------------------------
 *    ^
 *    Puts data to the head section.
 *
 *    silc_buffer_put_head(sb, data, data_len);
 *
 ***/

static inline
unsigned char *silc_buffer_put_head(SilcBuffer sb,
				    const unsigned char *data,
				    SilcUInt32 len)
{
#if defined(SILC_DEBUG)
  SILC_ASSERT(len <= silc_buffer_headlen(sb));
#else
  if (silc_unlikely(len > silc_buffer_headlen(sb)))
    return NULL;
#endif
  return (unsigned char *)memcpy(sb->head, data, len);
}

/****f* silcutil/SilcBufferAPI/silc_buffer_put
 *
 * SYNOPSIS
 *
 *    static inline
 *    unsigned char *silc_buffer_put(SilcBuffer sb,
 *				     const unsigned char *data,
 *				     SilcUInt32 len);
 *
 * DESCRIPTION
 *
 *    Puts data at the start of the valid data area. Returns a pointer
 *    to the copied data area.  Returns NULL on error.
 *
 * EXAMPLE
 *
 *    ---------------------------------
 *    | head  | data       | tail     |
 *    ---------------------------------
 *            ^
 *            Puts data to the data section.
 *
 *    silc_buffer_put(sb, data, data_len);
 *
 ***/

static inline
unsigned char *silc_buffer_put(SilcBuffer sb,
			       const unsigned char *data,
			       SilcUInt32 len)
{
#if defined(SILC_DEBUG)
  SILC_ASSERT(len <= silc_buffer_len(sb));
#else
  if (silc_unlikely(len > silc_buffer_len(sb)))
    return NULL;
#endif
  return (unsigned char *)memcpy(sb->data, data, len);
}

/****f* silcutil/SilcBufferAPI/silc_buffer_put_tail
 *
 * SYNOPSIS
 *
 *    static inline
 *    unsigned char *silc_buffer_put_tail(SilcBuffer sb,
 *					  const unsigned char *data,
 *					  SilcUInt32 len);
 *
 * DESCRIPTION
 *
 *    Puts data at the tail of the buffer. Returns pointer to the copied
 *    data area.  Returns NULL on error.
 *
 * EXAMPLE
 *
 *    ---------------------------------
 *    | head  | data           | tail |
 *    ---------------------------------
 *                             ^
 * 			       Puts data to the tail section.
 *
 *    silc_buffer_put_tail(sb, data, data_len);
 *
 ***/

static inline
unsigned char *silc_buffer_put_tail(SilcBuffer sb,
				    const unsigned char *data,
				    SilcUInt32 len)
{
#if defined(SILC_DEBUG)
  SILC_ASSERT(len <= silc_buffer_taillen(sb));
#else
  if (silc_unlikely(len > silc_buffer_taillen(sb)))
    return NULL;
#endif
  return (unsigned char *)memcpy(sb->tail, data, len);
}

/****f* silcutil/SilcBufferAPI/silc_buffer_alloc_size
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_alloc_size(SilcUInt32 len);
 *
 * DESCRIPTION
 *
 *    Allocates `len' bytes size buffer and moves the tail area automatically
 *    `len' bytes so that the buffer is ready to use without calling the
 *    silc_buffer_pull_tail.  Returns NULL on error.
 *
 ***/

static inline
SilcBuffer silc_buffer_alloc_size(SilcUInt32 len)
{
  SilcBuffer sb = silc_buffer_alloc(len);
  if (silc_unlikely(!sb))
    return NULL;
  silc_buffer_pull_tail(sb, len);
  return sb;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_reset
 *
 * SYNOPSIS
 *
 *    static inline
 *    void silc_buffer_reset(SilcBuffer sb);
 *
 * DESCRIPTION
 *
 *    Resets the buffer to the state as if it was just allocated by
 *    silc_buffer_alloc.  This does not clear the data area.  Use
 *    silc_buffer_clear if you also want to clear the data area.
 *
 ***/

static inline
void silc_buffer_reset(SilcBuffer sb)
{
  sb->data = sb->tail = sb->head;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_clear
 *
 * SYNOPSIS
 *
 *    static inline
 *    void silc_buffer_clear(SilcBuffer sb);
 *
 * DESCRIPTION
 *
 *    Clears and initialiazes the buffer to the state as if it was just
 *    allocated by silc_buffer_alloc.
 *
 ***/

static inline
void silc_buffer_clear(SilcBuffer sb)
{
  memset(sb->head, 0, silc_buffer_truelen(sb));
  silc_buffer_reset(sb);
}

/****f* silcutil/SilcBufferAPI/silc_buffer_start
 *
 * SYNOPSIS
 *
 *    static inline
 *    void silc_buffer_start(SilcBuffer sb);
 *
 * DESCRIPTION
 *
 *    Moves the data area at the start of the buffer.  The tail area remains
 *    as is.
 *
 ***/

static inline
void silc_buffer_start(SilcBuffer sb)
{
  sb->data = sb->head;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_end
 *
 * SYNOPSIS
 *
 *    static inline
 *    void silc_buffer_end(SilcBuffer sb);
 *
 * DESCRIPTION
 *
 *    Moves the end of the data area to the end of the buffer.  The start
 *    of the data area remains same.  If the start of data area is at the
 *    start of the buffer, after this function returns the buffer's data
 *    area length is the length of the entire buffer.
 *
 ***/

static inline
void silc_buffer_end(SilcBuffer sb)
{
  sb->tail = sb->end;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_copy
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_copy(SilcBuffer sb);
 *
 * DESCRIPTION
 *
 *    Generates copy of a SilcBuffer. This copies everything inside the
 *    currently valid data area, nothing more. Use silc_buffer_clone to
 *    copy entire buffer.  Returns NULL on error.
 *
 ***/

static inline
SilcBuffer silc_buffer_copy(SilcBuffer sb)
{
  SilcBuffer sb_new;

  sb_new = silc_buffer_alloc_size(silc_buffer_len(sb));
  if (silc_unlikely(!sb_new))
    return NULL;
  silc_buffer_put(sb_new, sb->data, silc_buffer_len(sb));

  return sb_new;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_clone
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_clone(SilcBuffer sb);
 *
 * DESCRIPTION
 *
 *    Clones SilcBuffer. This generates new SilcBuffer and copies
 *    everything from the source buffer. The result is exact clone of
 *    the original buffer.  Returns NULL on error.
 *
 ***/

static inline
SilcBuffer silc_buffer_clone(SilcBuffer sb)
{
  SilcBuffer sb_new;

  sb_new = silc_buffer_alloc_size(silc_buffer_truelen(sb));
  if (silc_unlikely(!sb_new))
    return NULL;
  silc_buffer_put(sb_new, sb->head, silc_buffer_truelen(sb));
  sb_new->data = sb_new->head + silc_buffer_headlen(sb);
  sb_new->tail = sb_new->data + silc_buffer_len(sb);

  return sb_new;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_realloc
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_realloc(SilcBuffer sb, SilcUInt32 newsize);
 *
 * DESCRIPTION
 *
 *    Reallocates buffer. Old data is saved into the new buffer. The buffer
 *    is exact clone of the old one except that there is now more space
 *    at the end of buffer.  This always returns the same `sb' unless `sb'
 *    was NULL. Returns NULL on error.
 *
 ***/

static inline
SilcBuffer silc_buffer_realloc(SilcBuffer sb, SilcUInt32 newsize)
{
  SilcUInt32 hlen, dlen;
  unsigned char *h;

  if (!sb)
    return silc_buffer_alloc(newsize);

  if (silc_unlikely(newsize <= silc_buffer_truelen(sb)))
    return sb;

  hlen = silc_buffer_headlen(sb);
  dlen = silc_buffer_len(sb);
  h = (unsigned char *)silc_realloc(sb->head, newsize);
  if (silc_unlikely(!h))
    return NULL;
  sb->head = h;
  sb->data = sb->head + hlen;
  sb->tail = sb->data + dlen;
  sb->end = sb->head + newsize;

  return sb;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_realloc_size
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_realloc_size(SilcBuffer sb, SilcUInt32 newsize);
 *
 * DESCRIPTION
 *
 *    Same as silc_buffer_realloc but moves moves the tail area
 *    automatically so that the buffer is ready to use without calling the
 *    silc_buffer_pull_tail.  Returns NULL on error.
 *
 ***/

static inline
SilcBuffer silc_buffer_realloc_size(SilcBuffer sb, SilcUInt32 newsize)
{
  sb = silc_buffer_realloc(sb, newsize);
  if (silc_unlikely(!sb))
    return NULL;
  silc_buffer_pull_tail(sb, silc_buffer_taillen(sb));
  return sb;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_enlarge
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_enlarge(SilcBuffer sb, SilcUInt32 size);
 *
 * DESCRIPTION
 *
 *    Enlarges the buffer by the amount of `size' if it doesn't have that
 *    must space in the data area and in the tail area.  Moves the tail
 *    area automatically after enlarging so that the current data area
 *    is at least the size of `size'.  If there is more space than `size'
 *    in the data area this does not do anything.  If there is enough
 *    space in the tail area this merely moves the tail area to reveal
 *    the extra space.  Returns FALSE on error.
 *
 ***/

static inline
SilcBool silc_buffer_enlarge(SilcBuffer sb, SilcUInt32 size)
{
  if (size > silc_buffer_len(sb)) {
    if (size > silc_buffer_taillen(sb) + silc_buffer_len(sb))
      if (silc_unlikely(!silc_buffer_realloc(sb, silc_buffer_truelen(sb) +
					     (size - silc_buffer_taillen(sb) -
					      silc_buffer_len(sb)))))
	return FALSE;
    silc_buffer_pull_tail(sb, size - silc_buffer_len(sb));
  }
  return TRUE;
}


/* SilcStack aware SilcBuffer routines */

/****f* silcutil/SilcBufferAPI/silc_buffer_salloc
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_salloc(SilcStack stack, SilcUInt32 len);
 *
 * DESCRIPTION
 *
 *    Allocates new SilcBuffer and returns it.
 *
 *    This routine use SilcStack are memory source.  If `stack' is NULL
 *    reverts back to normal allocating routine.
 *
 ***/

static inline
SilcBuffer silc_buffer_salloc(SilcStack stack, SilcUInt32 len)
{
  SilcBuffer sb;

  if (!stack)
    return silc_buffer_alloc(len);

  /* Allocate new SilcBuffer */
  sb = (SilcBuffer)silc_scalloc(stack, 1, sizeof(*sb));
  if (silc_unlikely(!sb))
    return NULL;

  /* Allocate the actual data area */
  sb->head = (unsigned char *)silc_smalloc(stack, len);
  if (silc_unlikely(!sb->head))
    return NULL;

  /* Set pointers to the new buffer */
  sb->data = sb->head;
  sb->tail = sb->head;
  sb->end = sb->head + len;

  return sb;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_salloc_size
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_salloc_size(SilcStack stack, SilcUInt32 len);
 *
 * DESCRIPTION
 *
 *    Allocates `len' bytes size buffer and moves the tail area automatically
 *    `len' bytes so that the buffer is ready to use without calling the
 *    silc_buffer_pull_tail.
 *
 *    This routine use SilcStack are memory source.  If `stack' is NULL
 *    reverts back to normal allocating routine.
 *
 ***/

static inline
SilcBuffer silc_buffer_salloc_size(SilcStack stack, SilcUInt32 len)
{
  SilcBuffer sb = silc_buffer_salloc(stack, len);
  if (silc_unlikely(!sb))
    return NULL;
  silc_buffer_pull_tail(sb, len);
  return sb;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_srealloc
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_srealloc(SilcStack stack,
 *                                    SilcBuffer sb, SilcUInt32 newsize);
 *
 * DESCRIPTION
 *
 *    Reallocates buffer. Old data is saved into the new buffer. The buffer
 *    is exact clone of the old one except that there is now more space
 *    at the end of buffer.
 *
 *    This routine use SilcStack are memory source.  If `stack' is NULL
 *    reverts back to normal allocating routine.
 *
 ***/

static inline
SilcBuffer silc_buffer_srealloc(SilcStack stack,
				SilcBuffer sb, SilcUInt32 newsize)
{
  SilcUInt32 hlen, dlen;
  unsigned char *h;

  if (!stack)
    return silc_buffer_realloc(sb, newsize);

  if (!sb)
    return silc_buffer_salloc(stack, newsize);

  if (newsize <= silc_buffer_truelen(sb))
    return sb;

  hlen = silc_buffer_headlen(sb);
  dlen = silc_buffer_len(sb);
  h = (unsigned char *)silc_srealloc(stack, silc_buffer_truelen(sb),
				     sb->head, newsize);
  if (!h) {
    /* Do slow and stack wasting realloc.  The old sb->head is lost and
       is freed eventually. */
    h = (unsigned char *)silc_smalloc(stack, newsize);
    if (silc_unlikely(!h))
      return NULL;
    memcpy(h, sb->head, silc_buffer_truelen(sb));
  }

  sb->head = h;
  sb->data = sb->head + hlen;
  sb->tail = sb->data + dlen;
  sb->end = sb->head + newsize;

  return sb;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_srealloc_size
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_srealloc_size(SilcStack stack,
 *                                         SilcBuffer sb, SilcUInt32 newsize);
 *
 * DESCRIPTION
 *
 *    Same as silc_buffer_srealloc but moves moves the tail area
 *    automatically so that the buffer is ready to use without calling the
 *    silc_buffer_pull_tail.
 *
 *    This routine use SilcStack are memory source.  If `stack' is NULL
 *    reverts back to normal allocating routine.
 *
 ***/

static inline
SilcBuffer silc_buffer_srealloc_size(SilcStack stack,
				     SilcBuffer sb, SilcUInt32 newsize)
{
  sb = silc_buffer_srealloc(stack, sb, newsize);
  if (silc_unlikely(!sb))
    return NULL;
  silc_buffer_pull_tail(sb, silc_buffer_taillen(sb));
  return sb;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_senlarge
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_senlarge(SilcStack stack, SilcBuffer sb,
 *                                    SilcUInt32 size);
 *
 * DESCRIPTION
 *
 *    Enlarges the buffer by the amount of `size' if it doesn't have that
 *    must space in the data area and in the tail area.  Moves the tail
 *    area automatically after enlarging so that the current data area
 *    is at least the size of `size'.  If there is more space than `size'
 *    in the data area this does not do anything.  If there is enough
 *    space in the tail area this merely moves the tail area to reveal
 *    the extra space.  Returns FALSE on error.
 *
 *    This routine use SilcStack are memory source.  If `stack' is NULL
 *    reverts back to normal allocating routine.
 *
 ***/

static inline
SilcBool silc_buffer_senlarge(SilcStack stack, SilcBuffer sb, SilcUInt32 size)
{
  if (size > silc_buffer_len(sb)) {
    if (size > silc_buffer_taillen(sb) + silc_buffer_len(sb))
      if (silc_unlikely(!silc_buffer_srealloc(stack, sb,
					      silc_buffer_truelen(sb) +
					      (size - silc_buffer_taillen(sb) -
					       silc_buffer_len(sb)))))
	return FALSE;
    silc_buffer_pull_tail(sb, size - silc_buffer_len(sb));
  }
  return TRUE;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_scopy
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_scopy(SilcStack stack, SilcBuffer sb);
 *
 * DESCRIPTION
 *
 *    Generates copy of a SilcBuffer. This copies everything inside the
 *    currently valid data area, nothing more. Use silc_buffer_clone to
 *    copy entire buffer.
 *
 *    This routine use SilcStack are memory source.  If `stack' is NULL
 *    reverts back to normal allocating routine.
 *
 ***/

static inline
SilcBuffer silc_buffer_scopy(SilcStack stack, SilcBuffer sb)
{
  SilcBuffer sb_new;

  sb_new = silc_buffer_salloc_size(stack, silc_buffer_len(sb));
  if (silc_unlikely(!sb_new))
    return NULL;
  silc_buffer_put(sb_new, sb->data, silc_buffer_len(sb));

  return sb_new;
}

/****f* silcutil/SilcBufferAPI/silc_buffer_sclone
 *
 * SYNOPSIS
 *
 *    static inline
 *    SilcBuffer silc_buffer_sclone(SilcStack stack, SilcBuffer sb);
 *
 * DESCRIPTION
 *
 *    Clones SilcBuffer. This generates new SilcBuffer and copies
 *    everything from the source buffer. The result is exact clone of
 *    the original buffer.
 *
 *    This routine use SilcStack are memory source.  If `stack' is NULL
 *    reverts back to normal allocating routine.
 *
 ***/

static inline
SilcBuffer silc_buffer_sclone(SilcStack stack, SilcBuffer sb)
{
  SilcBuffer sb_new;

  sb_new = silc_buffer_salloc_size(stack, silc_buffer_truelen(sb));
  if (silc_unlikely(!sb_new))
    return NULL;
  silc_buffer_put(sb_new, sb->head, silc_buffer_truelen(sb));
  sb_new->data = sb_new->head + silc_buffer_headlen(sb);
  sb_new->tail = sb_new->data + silc_buffer_len(sb);

  return sb_new;
}

#endif /* SILCBUFFER_H */
