/******************************************************************************
*******************************************************************************
**
**  Copyright (C) Sistina Software, Inc.  2002-2003  All rights reserved.
**  Copyright (C) 2004 Red Hat, Inc.  All rights reserved.
**
**  This copyrighted material is made available to anyone wishing to use,
**  modify, copy, or redistribute it subject to the terms and conditions
**  of the GNU General Public License v.2.
**
*******************************************************************************
******************************************************************************/
/*
 * This is a bit of an abstraction layer to get this working in both kernel
 * and userspace.
 */
#define MIN(a,b) ((a<b)?a:b)

#ifdef __KERNEL__

#include "osi.h"

#ifdef __linux__
#include <linux/kernel.h>
#include <linux/sched.h>
#define __KERNEL_SYSCALLS__
#include <linux/unistd.h>
#endif /*__linux__*/

#define xdr_malloc(y) kmalloc(y, GFP_KERNEL)
#define xdr_free(x,y) kfree(x)
#define xdr_memcpy(x,y,l) memcpy(x,y,l)

/**
 * xdr_realloc - a realloc for kernel space.
 * @a: < pointer to realloc
 * @nl: < desired new size
 * @ol: < current old size
 * 
 * Not as good as the real realloc, since it always moves memory.  But good
 * enough for as little as it will get used here.
 *
 * XXX this is broken.
 * 
 * Returns: void*
 */
static void *xdr_realloc(void *a, size_t nl, size_t ol)
{
   if( nl == ol ) {
      return a;
   }else
   if( nl == 0 ) {
      xdr_free(a, ol);
      return NULL;
   }else
   if( a == NULL && nl > 0 ) {
      return xdr_malloc(nl);
   }else
   {
      void *tmp;
      tmp = xdr_malloc(nl);
      if( tmp == NULL ) return NULL;
      xdr_memcpy(tmp, a, MIN(nl,ol));
      xdr_free(a,ol);
      return tmp;
   }
}

#else /*__KERNEL__*/

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <sys/uio.h>

#include "osi_endian.h"

#define xdr_malloc(y) malloc(y)
#define xdr_free(x,y) free(x)
#define xdr_realloc(p,n,o) realloc(p,n)
#define xdr_memcpy(x,y,l) memcpy(x,y,l)

#endif /*__KERNEL__*/

#include "xdr.h"

typedef enum {xdr_enc, xdr_dec} xdr_type;

/* encoders have this sorta non-blocking, growing buffering stunt.
 * makes them a bit different from the decoders now.
 */
struct xdr_enc_s {
   size_t default_buf_size;
   xdr_socket fd;
   xdr_type type;
   size_t length;
   size_t curloc;
   uint8_t *stream;
};

/* decoders only pull a single item off of the socket at a time.
 * so this is all they need.
 */
struct xdr_dec_s {
   size_t length; /* total byte length of the stream */
   size_t curloc; /* current byte offset from start */
   uint8_t *stream; /* start of the encoded stream. */
   xdr_socket fd;
   xdr_type type;
};

/* the types of data we support. */

#define XDR_NULL          0x00 /* NOT A VALID TAG!!! used in dec code. */
#define XDR_LIST_START    0x01
#define XDR_LIST_STOP     0x02
/* list is a variable length device.  It is a start tag, some number of
 * xdr_enc_*, then an stop tag.  It's main purpose is to provide a method
 * of encasing data.
 * */
#define XDR_STRING        0x04
/* string tag is followed by a uint16 which is the byte length */
#define XDR_RAW           0x05
/* raw tag is followed by a uint16 which is the byte length
 * if 65535 bytes isn't enough, split your data and put multiples of these
 * back to back.  (idea of xdr is to avoid this twit.)
 * */

/* note, if the size of these should variate, I'm screwed.  Should consider
 * changing this all to the bit shift and array access to be more concrete.
 * later.
 */
#define XDR_UINT64        0x06
#define XDR_UINT32        0x07
#define XDR_UINT16        0x08
#define XDR_UINT8         0x09
/* should add signed ints */

#define XDR_IPv6          0x0a /* 16 bytes, IPv6 address */

/* any other base types?
 */

#define XDR_DEFAULT_BUFFER_SIZE 4096
/*****************************************************************************/

/**
 * xdr_enc_init - 
 * @fd: 
 * @buffer_size: 
 * 
 * 
 * Returns: xdr_enc_t*
 */
xdr_enc_t* xdr_enc_init(xdr_socket fd, int buffer_size)
{
   xdr_enc_t *xdr;

   if( buffer_size <=0 ) buffer_size = XDR_DEFAULT_BUFFER_SIZE;

   xdr = xdr_malloc(sizeof(xdr_enc_t));
   if( xdr == NULL ) return NULL;
   xdr->stream = xdr_malloc(buffer_size);
   if( xdr->stream == NULL ) {
      xdr_free(xdr, sizeof(xdr_enc_t));
      return NULL;
   }
   xdr->fd = fd;
   xdr->type = xdr_enc;
   xdr->default_buf_size = buffer_size;
   xdr->length = buffer_size;
   xdr->curloc = 0;

   return xdr;
}

/**
 * xdr_dec_init - 
 * @fd: 
 * @buffer_size: 
 * 
 * 
 * Returns: xdr_dec_t*
 */
xdr_dec_t* xdr_dec_init(xdr_socket fd, int buffer_size)
{
   xdr_dec_t *xdr;

   if( buffer_size <=0 ) buffer_size = XDR_DEFAULT_BUFFER_SIZE;

   xdr = xdr_malloc(sizeof(xdr_dec_t));
   if( xdr == NULL ) return NULL;
   xdr->length = buffer_size;
   xdr->curloc = 0;
   xdr->stream = xdr_malloc(buffer_size);
   xdr->fd = fd;
   xdr->type = xdr_dec;
   if( xdr->stream == NULL ) {
      xdr_free(xdr, sizeof(xdr_dec_t));
      return NULL;
   }
   *(xdr->stream) = XDR_NULL; /* so the first dec_call will call get_next */
   return xdr;
}

/*****************************************************************************/
/**
 * xdr_enc_flush - 
 * @xdr: 
 * 
 * Returns: int
 */
int xdr_enc_flush(xdr_enc_t *xdr)
{
   int err;
   if( xdr == NULL ) return -EINVAL;
   if( xdr->type != xdr_enc) return -EINVAL;
   if( xdr->curloc == 0 ) return 0;

   err = xdr_send(xdr->fd, xdr->stream, xdr->curloc);
   if( err < 0 ) return err;
   if( err == 0 ) return -EPROTO; /* why? */
   xdr->curloc = 0;

   return 0;
}

/**
 * xdr_release - 
 * @xdr: 
 *
 * Free the memory, losing whatever may be there.
 */
void xdr_dec_release(xdr_dec_t *xdr)
{
   if( xdr == NULL ) return;
   xdr_free(xdr->stream, xdr->length);
   xdr_free(xdr, sizeof(xdr_dec_t));
}

/**
 * xdr_enc_force_release - 
 * @xdr: 
 * 
 * Free the memory, losing whatever may be there.
 */
void xdr_enc_force_release(xdr_enc_t *xdr)
{
   if( xdr == NULL ) return;
   if( xdr->stream != NULL ) xdr_free(xdr->stream, xdr->length);
   xdr_free(xdr, sizeof(xdr_enc_t));
}

/**
 * xdr_enc_release - 
 * @xdr: 
 * 
 * Free things up, trying to send any possible leftover data first.
 * 
 * Returns: int
 */
int xdr_enc_release(xdr_enc_t *xdr)
{
   int e;
   if( xdr == NULL ) return -EINVAL;
   if((e=xdr_enc_flush(xdr)) != 0 ) return e;
   xdr_enc_force_release(xdr);
   return 0;
}

/*****************************************************************************/
/**
 * grow_stream - 
 * @xdr: 
 * @len: 
 * 
 * each single encoded call needs to fit within a buffer.  So we make sure
 * the buffer is big enough.
 *
 * If the buffer is big enough, but just doesn't have room, we send the
 * data in the buffer, emptying it, first.
 * 
 * Returns: int
 */
static int grow_stream(xdr_enc_t *enc, size_t len)
{
   int err;
   uint8_t *c;

   /* buffer must be big enough for one type entry. */
   if( len > enc->length ) {
      c = xdr_realloc(enc->stream, len, enc->length);
      if( c == NULL ) return -ENOMEM;
      enc->stream = c;
      enc->length = len;
   }

   /* if there isn't room on the end of this chunk,
    * try sending what we've got.
    */
   if( enc->curloc + len > enc->length ) {
      err = xdr_enc_flush(enc);
      if(err != 0 ) {
         /* error, better pass this up. */
         return err;
      }
   }

   return 0;
}


/**
 * append_bytes - 
 * @xdr: 
 * @xdr_type: 
 * @bytes: 
 * @len: 
 * 
 * 
 * Returns: int
 */
static int append_bytes(xdr_enc_t *xdr, uint8_t xdr_type, void *bytes, size_t len)
{
   int e;
   if( xdr == NULL ) return -EINVAL;
   if( xdr->type != xdr_enc ) return -EINVAL;

   /* len + 1; need the one byte for the type code. */
   if((e=grow_stream(xdr, len + 1)) != 0) return e;
   *(xdr->stream + xdr->curloc) = xdr_type;
   xdr->curloc += 1;
   xdr_memcpy((xdr->stream + xdr->curloc), bytes, len);
   xdr->curloc += len;

   return 0;
}

int __inline__ xdr_enc_uint64(xdr_enc_t *xdr, uint64_t i)
{
   uint64_t b = osi_cpu_to_be64(i);
   return append_bytes(xdr, XDR_UINT64, &b, sizeof(uint64_t));
}

int __inline__ xdr_enc_uint32(xdr_enc_t *xdr, uint32_t i)
{
   uint32_t b = osi_cpu_to_be32(i);
   return append_bytes(xdr, XDR_UINT32, &b, sizeof(uint32_t));
}

int __inline__ xdr_enc_uint16(xdr_enc_t *xdr, uint16_t i)
{
   uint16_t b = osi_cpu_to_be16(i);
   return append_bytes(xdr, XDR_UINT16, &b, sizeof(uint16_t));
}

int __inline__ xdr_enc_uint8(xdr_enc_t *xdr, uint8_t i)
{
   return append_bytes(xdr, XDR_UINT8, &i, sizeof(uint8_t));
}

/* or should i add the headers, and pass this a struct in6_addr? */
int __inline__ xdr_enc_ipv6(xdr_enc_t *xdr, struct in6_addr *ip)
{ /* bytes should already be in the right order. */
   return append_bytes(xdr, XDR_IPv6, ip->s6_addr, 16);
}

int xdr_enc_raw(xdr_enc_t *xdr, void *p, uint16_t len)
{
   int e;
   uint16_t temp;
   if( xdr == NULL ) return -EINVAL;
   if((e=grow_stream(xdr, len + 3)) != 0) return e;
   *(xdr->stream + xdr->curloc) = XDR_RAW;
   xdr->curloc +=1;
   temp = osi_cpu_to_be16(len);
   xdr_memcpy((xdr->stream + xdr->curloc), &temp, 2);
   xdr->curloc += 2;
   xdr_memcpy((xdr->stream + xdr->curloc), p, len);
   xdr->curloc += len;
   return 0;
}

int xdr_enc_raw_iov(xdr_enc_t *xdr, int count, struct iovec *iov)
{
   size_t total=0;
   int i,err;
   uint16_t temp;
   if( xdr == NULL || count < 1 || iov == NULL ) return -EINVAL;
   for(i=0; i<count; i++) total += iov[i].iov_len;
   /* make sure it fits in a uint16_t */
   if( total > 0xffff ) return -EFBIG;
   /* grow to fit */
   if((err = grow_stream(xdr, total + 3)) != 0 ) return err;
   /* copy in header and size */
   *(xdr->stream + xdr->curloc) = XDR_RAW;
   xdr->curloc +=1;
   temp = osi_cpu_to_be16(total);
   xdr_memcpy((xdr->stream + xdr->curloc), &temp, 2);
   xdr->curloc += 2;
   /* copy in all iovbufs */
   for(i=0; i < count; i++) {
      if(iov[i].iov_base == NULL ) continue;
      xdr_memcpy((xdr->stream + xdr->curloc), iov[i].iov_base, iov[i].iov_len);
      xdr->curloc += iov[i].iov_len;
   }
   return 0;
}

int xdr_enc_string(xdr_enc_t *xdr, uint8_t *s)
{
   int len,e;
   uint16_t temp;
   if( xdr == NULL ) return -EINVAL;
   if(s == NULL ) len = 0;
   else len = strlen(s);
   if((e=grow_stream(xdr, len + 3)) != 0) return e;
   *(xdr->stream + xdr->curloc) = XDR_STRING;
   xdr->curloc +=1;
   temp = osi_cpu_to_be16(len);
   xdr_memcpy((xdr->stream + xdr->curloc), &temp, 2);
   xdr->curloc += 2;
   if( len > 0 ) {
      xdr_memcpy((xdr->stream + xdr->curloc), s, len);
      xdr->curloc += len;
   }
   return 0;
}

int xdr_enc_list_start(xdr_enc_t *xdr)
{
   int e;
   if( xdr == NULL ) return -EINVAL;
   if( (e=grow_stream(xdr, 1)) != 0) return e;
   *(xdr->stream + xdr->curloc) = XDR_LIST_START;
   xdr->curloc +=1;
   return 0;
}
int xdr_enc_list_stop(xdr_enc_t *xdr)
{
   int e;
   if( xdr == NULL ) return -EINVAL;
   if( (e=grow_stream(xdr, 1)) != 0) return e;
   *(xdr->stream + xdr->curloc) = XDR_LIST_STOP;
   xdr->curloc +=1;
   return 0;
}

/*****************************************************************************/

/**
 * get_next - 
 * @xdr: 
 * 
 * get what ever may be next, and put it into the buffer.
 * 
 * Returns: int
 */
static int get_next(xdr_dec_t *xdr)
{
   int err;
   uint16_t len;
   if( (err = xdr_recv(xdr->fd, xdr->stream, 1)) < 0 ) return err;
   if( err == 0 ) return -EPROTO;
   xdr->curloc = 1;
   if( *(xdr->stream) == XDR_UINT64 ) {
      len = sizeof(uint64_t);
   }else
   if( *(xdr->stream) == XDR_UINT32 ) {
      len = sizeof(uint32_t);
   }else
   if( *(xdr->stream) == XDR_UINT16 ) {
      len = sizeof(uint16_t);
   }else
   if( *(xdr->stream) == XDR_UINT8 ) {
      len = sizeof(uint8_t);
   }else
   if( *(xdr->stream) == XDR_IPv6 ) {
      len = 16;
   }else
   if( *(xdr->stream) == XDR_STRING ) {
      if( (err = xdr_recv(xdr->fd, (xdr->stream+1), 2)) < 0 ) return err;
      if( err == 0 ) return -EPROTO;
      len = osi_be16_to_cpu(*((uint16_t*)(xdr->stream + xdr->curloc)));
      xdr->curloc += 2;
   }else
   if( *(xdr->stream) == XDR_RAW ) {
      if( (err = xdr_recv(xdr->fd, (xdr->stream+1), 2)) < 0 ) return err;
      if( err == 0 ) return -EPROTO;
      len = osi_be16_to_cpu(*((uint16_t*)(xdr->stream + xdr->curloc)));
      xdr->curloc += 2;
   }else
   if( *(xdr->stream) == XDR_LIST_START ) {
      xdr->curloc = 0;
      return 0;
   }else
   if( *(xdr->stream) == XDR_LIST_STOP ) {
      xdr->curloc = 0;
      return 0;
   }else
   {
      return -1;
   }

   /* grow buffer if need be. */
   if( xdr->curloc + len > xdr->length ) {
      uint8_t *c;
      c = xdr_realloc(xdr->stream, xdr->curloc + len, xdr->length );
      if( c == NULL ) return -ENOMEM;
      xdr->stream = c;
      xdr->length = xdr->curloc + len;
   }

   if( len > 0 ) {
      if((err=xdr_recv(xdr->fd, (xdr->stream + xdr->curloc), len)) < 0 )
         return err;
      if( err == 0 ) return -EPROTO;
   }
   xdr->curloc = 0;
   return 0;
}

int xdr_dec_uint64(xdr_dec_t *xdr, uint64_t *i)
{
   int err;
   if( xdr == NULL || i == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_UINT64 ) return -ENOMSG;
   *i = osi_be64_to_cpu(  *((uint64_t*)(xdr->stream + 1))  );
   /* read the item out, mark that */
   *(xdr->stream) = XDR_NULL;
   return 0;
}

int xdr_dec_uint32(xdr_dec_t *xdr, uint32_t *i)
{
   int err;
   if( xdr == NULL || i == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_UINT32 ) return -ENOMSG;
   *i = osi_be32_to_cpu(  *((uint32_t*)(xdr->stream + 1))  );
   /* read the item out, mark that */
   *(xdr->stream) = XDR_NULL;
   return 0;
}

int xdr_dec_uint16(xdr_dec_t *xdr, uint16_t *i)
{
   int err;
   if( xdr == NULL || i == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_UINT16 ) return -ENOMSG;
   *i = osi_be16_to_cpu(  *((uint16_t*)(xdr->stream + 1))  );
   /* read the item out, mark that */
   *(xdr->stream) = XDR_NULL;
   return 0;
}

int xdr_dec_uint8(xdr_dec_t *xdr, uint8_t *i)
{
   int err;
   if( xdr == NULL || i == NULL ) return -EINVAL;

   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_UINT8 ) return -ENOMSG;
   *i =  *((uint8_t*)(xdr->stream + 1));
   /* read the item out, mark that */
   *(xdr->stream) = XDR_NULL;
   return 0;
}

int xdr_dec_ipv6(xdr_dec_t *xdr, struct in6_addr *ip)
{
   int err;
   if( xdr == NULL || ip == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_IPv6 ) return -ENOMSG;
   memcpy(ip, xdr->stream + 1, 16);
   /* read the item out, mark that */
   *(xdr->stream) = XDR_NULL;
   return 0;
}

/* mallocing version */
int xdr_dec_raw_m(xdr_dec_t *xdr, void **p, uint16_t *l)
{
   int len;
   void *str;
   int err;

   if( xdr == NULL || p == NULL || l == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_RAW ) return -ENOMSG;
   xdr->curloc = 1;

   len = osi_be16_to_cpu(*((uint16_t*)(xdr->stream + xdr->curloc)));
   xdr->curloc +=2;

   str = xdr_malloc(len);
   if( str == NULL ) return -ENOMEM;
   xdr_memcpy(str, (xdr->stream + xdr->curloc), len);
   xdr->curloc += len;

   *p = str;
   *l = len;
   /* read the item out, mark that */
   *(xdr->stream) = XDR_NULL;
   return 0;
}

/* non-mallocing version */
int xdr_dec_raw(xdr_dec_t *xdr, void *p, uint16_t *l)
{
   int len;
   int err;

   if( xdr == NULL || p == NULL || l == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_RAW ) return -ENOMSG;
   xdr->curloc = 1;

   len = osi_be16_to_cpu(*((uint16_t*)(xdr->stream + xdr->curloc)));
   xdr->curloc +=2;

   if( len > *l ) return -1;

   xdr_memcpy(p, (xdr->stream + xdr->curloc), len);
   xdr->curloc += len;

   *l = len;

   /* read the item out, mark that */
   *(xdr->stream) = XDR_NULL;
   return 0;
}

/**
 * xdr_dec_raw_ag - auto-growing version
 * @xdr: 
 * @p: <> pointer to buffer
 * @bl: <> size of the buffer
 * @rl: > size of data read from stream
 * 
 * This form of xdr_dec_raw will increase the size of a pre-malloced buffer
 * to fit the data it is reading.  It is kind of a merger of the
 * non-mallocing and mallocing versions.
 * 
 * Returns: int
 */
int xdr_dec_raw_ag(xdr_dec_t *xdr, void **p, uint16_t *bl, uint16_t *rl)
{
   int len;
   int err;

   if( xdr == NULL || p == NULL || bl == NULL || rl == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_RAW ) return -ENOMSG;
   xdr->curloc = 1;

   len = osi_be16_to_cpu(*((uint16_t*)(xdr->stream + xdr->curloc)));
   xdr->curloc +=2;

   if( len > *bl ) { /* grow p */
      void *temp;
      temp = xdr_realloc( *p, len, *bl );
      if( temp == NULL ) return -ENOMEM;
      *bl = len;
      *p = temp;
   }

   xdr_memcpy( *p, (xdr->stream + xdr->curloc), len);
   xdr->curloc += len;

   *rl = len;

   *(xdr->stream) = XDR_NULL;
   return 0;
}

/* mallocing version */
int xdr_dec_string(xdr_dec_t *xdr, uint8_t **strp)
{
   int len;
   char *str;
   int err;
   if( xdr == NULL || strp == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_STRING ) return -ENOMSG;
   xdr->curloc = 1;

   len = osi_be16_to_cpu(*((uint16_t*)(xdr->stream + xdr->curloc)));
   xdr->curloc +=2;

   if( len > 0 ) {
      str = xdr_malloc(len+1);
      if( str == NULL ) return -ENOMEM;
      xdr_memcpy(str, (xdr->stream + xdr->curloc), len);
      str[len] = '\0';
      xdr->curloc += len;

      *strp = str;
   }else{
      *strp = NULL;
   }

   /* read the item out, mark that */
   *(xdr->stream) = XDR_NULL;
   return 0;
}

/* non-mallocing version */
int xdr_dec_string_nm(xdr_dec_t *xdr, uint8_t *string, size_t l)
{
   int len;
   int err;
   if( xdr == NULL || string == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_STRING ) return -ENOMSG;
   xdr->curloc =1;

   len = osi_be16_to_cpu(*((uint16_t*)(xdr->stream + xdr->curloc)));
   xdr->curloc +=2;

   if( len > 0 ) {
      xdr_memcpy(string, (xdr->stream + xdr->curloc), MIN(len, l ));
      if( l > len ) {
         string[len] = '\0';
      }
      string[l-1] = '\0';
   }else{
      string[0] = '\0';
   }

   /* read the item out, mark that */
   *(xdr->stream) = XDR_NULL;
   return 0;
}

int xdr_dec_string_ag(xdr_dec_t *xdr, uint8_t **s, uint16_t *bl)
{
   int len;
   int err;
   if( xdr == NULL || s == NULL || bl == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_STRING ) return -ENOMSG;
   xdr->curloc =1;

   len = osi_be16_to_cpu(*((uint16_t*)(xdr->stream + xdr->curloc)));
   xdr->curloc +=2;

   if( len == 0 ) { /* empty string */
      if( *s != NULL ) **s = '\0';
      *(xdr->stream) = XDR_NULL;
      return 0;
   }

   if( len >= *bl ) { /* grow s */
      void *temp;
      temp = xdr_realloc( *s, len + 1, *bl );
      if( temp == NULL ) return -ENOMEM;
      *bl = len + 1;
      *s = temp;
   }

   xdr_memcpy( *s, (xdr->stream + xdr->curloc), len);
   (*s)[len] = '\0';

   *(xdr->stream) = XDR_NULL;
   return 0;
}

int xdr_dec_list_start(xdr_dec_t *xdr)
{
   int err;
   if( xdr == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_LIST_START ) return -ENOMSG;
   /* read the item out, mark that */
   *(xdr->stream) = XDR_NULL;
   return 0;
}
int xdr_dec_list_stop(xdr_dec_t *xdr)
{
   int err;
   if( xdr == NULL ) return -EINVAL;
   if( *(xdr->stream) == XDR_NULL ) {
      if( (err=get_next(xdr)) != 0 ) return err;
   }
   if( *(xdr->stream) != XDR_LIST_STOP ) return -ENOMSG;
   /* read the item out, mark that */
   *(xdr->stream) = XDR_NULL;
   return 0;
}

/* vim: set ai cin et sw=3 ts=3 : */
