/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <opencm.h>

#define RWBUFSZ 1024

#define MODE_RSTREAM 1
#define MODE_WSTREAM 2

struct serializer {
  void           (*onCreate)(SDR_stream *);

  void           (*w_u8)(const char *, SDR_stream *, uint8_t);
  uint8_t        (*r_u8)(const char *, SDR_stream *);

  void           (*w_u16)(const char *, SDR_stream *, uint16_t);
  uint16_t       (*r_u16)(const char *, SDR_stream *);

  void           (*w_u32)(const char *, SDR_stream *, uint32_t);
  uint32_t       (*r_u32)(const char *, SDR_stream *);

  void           (*w_u64)(const char *, SDR_stream *, uint64_t);
  uint64_t       (*r_u64)(const char *, SDR_stream *);

  void           (*w_buffer)(const char *elem, SDR_stream *, const Buffer*);
  Buffer *       (*r_buffer)(const char *elem, SDR_stream *, ocmoff_t len);

  void           (*w_bytes)(const char *, SDR_stream *, uint32_t len, const void *);
  void *         (*r_bytes)(const char *, SDR_stream *, uint32_t len);

  void           (*w_string)(const char *, SDR_stream *, const char *);
  char *         (*r_string)(const char *, SDR_stream *);

  void           (*w_obname)(const char *, SDR_stream *, const char *);
  char *         (*r_obname)(const char *, SDR_stream *);

  /* Interface for reading/writing serializable objects. Write is
     non-const because serializer may canonicalize. */
  void           (*write)(const char *, SDR_stream *, const void *);
  void *         (*read)(const char *, SDR_stream *);
} ;

static struct serializer sermodes[];

static void stream_autodetect_format(SDR_stream *stream);

/****************************************************************
 *
 * FILE STREAM LOGIC
 *
 ****************************************************************/
#ifdef NEW_FILE_STREAM
static OC_bool file_stream_wflush(SDR_stream *s);
static OC_bool file_stream_rfill(SDR_stream *s);
#else
static int file_stream_putc(SDR_stream *s, int c);
static ocmoff_t file_stream_puts(SDR_stream *s, const void *vp, ocmoff_t len);
static int file_stream_getc(SDR_stream *s);
static int file_stream_peekc(SDR_stream *s);
static ocmoff_t file_stream_gets(SDR_stream *s, void *vp, ocmoff_t len);
#endif
static Buffer *file_stream_asBuffer(SDR_stream *s);
static void file_stream_reread(SDR_stream *s);
static void file_stream_close(SDR_stream *s);
static void file_stream_finalizer(GC_PTR obj, GC_PTR data);

static SDR_stream*
stream_openfile(const char *fileName, uint32_t mode, uint32_t format)
{
  FILE *f;
  SDR_stream *strm = (SDR_stream *) GC_MALLOC(sizeof(SDR_stream));

  assert(mode == MODE_RSTREAM || mode == MODE_WSTREAM);
  
  strm->name   = xstrdup(fileName);
  strm->mode = mode;
  strm->format = format;
  strm->indent = 0;
  strm->type = STREAM_FILE;
  strm->pos = 0;
  strm->len = (mode == MODE_RSTREAM) ? path_file_length(fileName) : 0;
#ifndef NEW_SDR
  strm->bound = (mode == MODE_RSTREAM) ? strm->len : 0;
#endif

  f = xfopen(fileName, (mode == MODE_RSTREAM) ? 'r' : 'w', 'b');

#ifndef NEW_SDR
  strm->data = 0;
#endif
  strm->state = f;

#ifdef NEW_FILE_STREAM
  strm->rwbuf = GC_MALLOC_ATOMIC(RWBUFSZ);
  if (mode == MODE_WSTREAM) {
    strm->rwlim = strm->rwbuf + RWBUFSZ;
    strm->rwptr = strm->rwbuf;
  }
  else {
    strm->rwlim = strm->rwptr = strm->rwbuf; /* force rfill() */
  }

  strm->wflush = file_stream_wflush;
  strm->rfill = file_stream_rfill;
#else
  strm->rwbuf = strm->rwlim = strm->rwptr = 0;

  strm->strm_getc = file_stream_getc;
  strm->strm_peekc = file_stream_peekc;
  strm->strm_gets = file_stream_gets;
  strm->strm_putc = file_stream_putc;
  strm->strm_puts = file_stream_puts;
#endif

  strm->asBuffer = file_stream_asBuffer;
  strm->reread = file_stream_reread;
  strm->close = file_stream_close;

  GC_REGISTER_FINALIZER(strm, file_stream_finalizer, 0, 0, 0);

  return strm;
}

SDR_stream*
stream_createfile(const char *fileName, uint32_t format)
{
  SDR_stream *strm;
  strm = stream_openfile(fileName, MODE_WSTREAM, format);

  assert(sermodes[strm->format].onCreate);
  sermodes[strm->format].onCreate(strm);  

  return strm;
}


SDR_stream*
stream_fromfile(const char *fileName, uint32_t format)
{
  SDR_stream *strm = stream_openfile(fileName, MODE_RSTREAM, format);

  if (format != SDR_RAW)
    stream_autodetect_format(strm);

  return strm;
}

#ifdef NEW_FILE_STREAM
static OC_bool
file_stream_wflush(SDR_stream *s)
{
  if (s->mode == MODE_WSTREAM) {
    ocmoff_t rwBufLen =  (s->rwptr - s->rwbuf);

    size_t wlen = fwrite(s->rwbuf, 1, rwBufLen, (FILE*)s->state);

    s->len += wlen;
    s->pos += wlen;

    s->rwptr = s->rwbuf;
  }

  return TRUE;
}

static OC_bool
file_stream_rfill(SDR_stream *s)
{
  ocmoff_t rwBufLen =  (s->rwptr - s->rwbuf);
  size_t rlen;

  assert (s->mode == MODE_RSTREAM);

  s->pos += rwBufLen;

  rlen = fread(s->rwbuf, 1, RWBUFSZ, (FILE*)s->state);

  s->rwlim = s->rwbuf + rlen;
  s->rwptr = s->rwbuf;

  return TRUE;
}
#endif

#ifndef NEW_FILE_STREAM
static int
file_stream_putc(SDR_stream *s, int c)
{
  s->pos++;
  s->len++;
  s->bound++;
  return fputc(c, (FILE*)s->state);
}

static ocmoff_t
file_stream_puts(SDR_stream *s, const void *vp, ocmoff_t len)
{
  size_t wlen = fwrite(vp, 1, len, (FILE*)s->state);
  if(len != wlen || ferror((FILE*)s->state))
    THROW(ExTruncated, format("Writing to %s was truncated\n", s->name));
  s->pos += wlen;
  s->len += wlen;
  s->bound += wlen;
  
  return wlen;
}
#endif

static Buffer *
file_stream_asBuffer(SDR_stream *s)
{
  Buffer *buf = buffer_FromFilePtr(s->name, s->state);
  return buf;
}

#ifndef NEW_FILE_STREAM
static int
file_stream_getc(SDR_stream *s)
{
  int c;

  c = fgetc((FILE*)s->state);
  s->pos++;
  return c;
}

static int
file_stream_peekc(SDR_stream *s)
{
  int c;

  c = fgetc((FILE*)s->state);
  ungetc(c, (FILE*)s->state);
  s->pos++;
  return c;
}

static ocmoff_t
file_stream_gets(SDR_stream *s, void *vp, ocmoff_t len)
{
  size_t rlen = fread(vp, 1, len, (FILE*)s->state);
  s->pos += rlen;
  
  return rlen;
}
#endif

static void
file_stream_reread(SDR_stream *s)
{
  assert(s->state);

#ifdef NEW_FILE_STREAM
  stream_wdrain(s);

  s->rwlim = s->rwptr = s->rwbuf; /* force rfill */
#endif

  fflush((FILE*)s->state);
  rewind((FILE*)s->state);
  s->pos = 0;

  s->mode = MODE_RSTREAM;
}

/* Note: stream_close() needs to be robust against multiple calls,
   because exception handlers may in some cases call it
   redundantly. */
static void
file_stream_close(SDR_stream *s)
{
#ifdef NEW_FILE_STREAM
  stream_wdrain(s);
#endif

  if (s->state) {
    fflush((FILE*)s->state);
    xfclose((FILE *) s->state);
  }
  s->state = 0;
}

static void
file_stream_finalizer(GC_PTR obj, GC_PTR data)
{
  SDR_stream *strm = (SDR_stream *) obj;
  
  if (strm->type != STREAM_FILE)
    return;

  if (strm->state)
    fclose(strm->state);
}

/****************************************************************
 *
 * MEMORY STREAM LOGIC
 *
 ****************************************************************/
#ifdef NEW_MEMORY_STREAM
static OC_bool mem_stream_wflush(SDR_stream *s);
static OC_bool mem_stream_rfill(SDR_stream *s);
#else
static int mem_stream_putc(SDR_stream *s, int c);
static ocmoff_t mem_stream_puts(SDR_stream *s, const void *vp, ocmoff_t len);
static int mem_stream_getc(SDR_stream *s);
static int mem_stream_peekc(SDR_stream *s);
static ocmoff_t mem_stream_gets(SDR_stream *s, void *vp, ocmoff_t len);
#endif
static Buffer *mem_stream_asBuffer(SDR_stream *s);
static void mem_stream_reread(SDR_stream *s);
static void mem_stream_close(SDR_stream *s);

SDR_stream*
stream_createMemory(uint32_t format)
{
  SDR_stream *strm = (SDR_stream *) GC_MALLOC(sizeof(SDR_stream));

  strm->name   = "<memory (out)>";
  strm->mode   = MODE_WSTREAM;
  strm->format = format;
  strm->indent = 0;
  strm->type   = STREAM_MEM;

#ifndef NEW_SDR
  strm->data = 0;
#endif
  strm->state = 0;

#ifdef NEW_MEMORY_STREAM
  strm->rwbuf = GC_MALLOC_ATOMIC(RWBUFSZ);
  strm->rwlim = strm->rwbuf + RWBUFSZ;
  strm->rwptr = strm->rwbuf;

  strm->wflush = mem_stream_wflush;
  strm->rfill = mem_stream_rfill;
#else
  strm->rwbuf = strm->rwlim = strm->rwptr = 0;

  strm->strm_getc = mem_stream_getc;
  strm->strm_peekc = mem_stream_peekc;
  strm->strm_gets = mem_stream_gets;
  strm->strm_putc = mem_stream_putc;
  strm->strm_puts = mem_stream_puts;
#endif
  strm->asBuffer = mem_stream_asBuffer;
  strm->reread = mem_stream_reread;
  strm->close = mem_stream_close;

  strm->pos = 0;
  strm->len = 0;
#ifndef NEW_SDR
  strm->bound = 0;
#endif

  assert(sermodes[strm->format].onCreate);
  sermodes[strm->format].onCreate(strm);  

  return strm;
}

SDR_stream*
stream_fromMemory(const char *buffer, uint32_t len, uint32_t format)
{
  SDR_stream *strm = (SDR_stream *) GC_MALLOC(sizeof(SDR_stream));

  strm->name   = "<memory (in)>";
  strm->mode   = MODE_RSTREAM;
  strm->indent = 0;
  strm->type   = STREAM_MEM;
  strm->format = SDR_RAW; /* until proven otherwise below */

  /* Cast to void * here -- we will protect by knowing it's
     MODE_RSTREAM */
#ifndef NEW_SDR
  strm->data = (void *) buffer;
#endif
  strm->state = 0;

#ifdef NEW_MEMORY_STREAM
  strm->rwbuf = GC_MALLOC_ATOMIC(RWBUFSZ);
  strm->rwlim = strm->rwbuf + len;
  strm->rwptr = strm->rwbuf;

  strm->wflush = mem_stream_wflush;
  strm->rfill = mem_stream_rfill;
#else
  strm->rwbuf = strm->rwlim = strm->rwptr = 0;

  strm->strm_getc = mem_stream_getc;
  strm->strm_peekc = mem_stream_peekc;
  strm->strm_gets = mem_stream_gets;
  strm->strm_putc = mem_stream_putc;
  strm->strm_puts = mem_stream_puts;
#endif

  strm->asBuffer = mem_stream_asBuffer;
  strm->reread = mem_stream_reread;
  strm->close = mem_stream_close;

  strm->pos = 0;
  strm->len = len;
#ifndef NEW_SDR
  strm->bound = len;
#endif

  if (format != SDR_RAW)
    stream_autodetect_format(strm);

  return strm;
}

#ifdef NEW_MEMORY_STREAM
static OC_bool
mem_stream_wflush(SDR_stream *s)
{
  if (s->mode == MODE_WSTREAM) {
    /* Append the buffer to the s->state string, updating position and
       length accordingly. Then reset the input buffer pointer. */

    ocmoff_t bufLen =  (s->rwptr - s->rwbuf);

    assert(bufLen > 0);

    if (s->state)
      s->state = GC_REALLOC(s->state, s->len + bufLen);
    else
      s->state = GC_MALLOC_ATOMIC(RWBUFSZ);

    memcpy(((char *)s->state) + s->len, s->rwbuf, bufLen);

    s->len += bufLen;
    s->pos += bufLen;

    s->rwptr = s->rwbuf;
  }
  return TRUE;
}

static OC_bool
mem_stream_rfill(SDR_stream *s)
{
  THROW(ExAssert, "mem_stream_rfill() should never be called\n");
  return TRUE;
}
#endif

#ifndef NEW_MEMORY_STREAM
static int
mem_stream_putc(SDR_stream *s, int c)
{
  unsigned char *bp = s->data;
  
  assert (s->len <= s->bound);
  
  if (s->mode != MODE_WSTREAM)
    THROW(ExBadOpcode,
	  format("getc() on write stream \"%s\"", s->name));

  if (s->bound == s->len) {
    s->bound += RWBUFSZ;
    if (bp)
      bp = GC_REALLOC(bp, s->bound);
    else
      bp = GC_MALLOC_ATOMIC(s->bound);
  }
  bp[s->len] = c;
  
  s->len ++;
  s->pos ++;
  s->data = bp;
  s->state = bp;

  return c;
}

/* #define mem_stream_puts 0 */
#ifndef mem_stream_puts
static ocmoff_t
mem_stream_puts(SDR_stream *s, const void *vp, ocmoff_t len)
{
  unsigned char *bp = s->data;
  
  assert (s->len <= s->bound);
  
  if (s->mode != MODE_WSTREAM)
    THROW(ExBadOpcode,
	  format("putbuf() on read stream \"%s\"", s->name));

  if (s->bound < s->len + len) {
    while (s->bound < s->len + len)
      s->bound += RWBUFSZ;
    if (bp)
      bp = GC_REALLOC(bp, s->bound);
    else
      bp = GC_MALLOC_ATOMIC(s->bound);
  }
  
  memcpy(&bp[s->pos], vp, len);
  
  s->len += len;
  s->pos += len;
  s->data = bp;
  s->state = bp;

  return len;
}
#endif

static int
mem_stream_getc(SDR_stream *s)
{
  char *bp = s->data;

  if (s->pos >= s->len)
    return EOF;

  return bp[s->pos++];
}

static int
mem_stream_peekc(SDR_stream *s)
{
  char *bp = s->data;

  if (s->pos >= s->len)
    return EOF;

  return bp[s->pos];
}

static ocmoff_t
mem_stream_gets(SDR_stream *s, void *vp, ocmoff_t len)
{
  unsigned char *bp = s->data;
  
  if (s->pos + len > s->len)
    THROW(ExTruncated, "Requested read is too long!\n");
  /*    len = s->len - s->pos; */

  if (s->mode != MODE_RSTREAM)
    THROW(ExBadOpcode,
	  format("getbuf() on write stream \"%s\"", s->name));

  memcpy(vp, &bp[s->pos], len);
  
  s->pos += len;

  return len;
}
#endif

static Buffer *
mem_stream_asBuffer(SDR_stream *s)
{
  Buffer *buf = buffer_create();
  buffer_append(buf, s->state, s->len);
  buffer_freeze(buf);
  return buf;
}

static void
mem_stream_reread(SDR_stream *s)
{
#ifdef NEW_MEMORY_STREAM
  stream_wdrain(s);

  /* Convert to a readable rwbuf of fixed length */
  s->rwbuf = s->state;
  s->rwptr = s->rwbuf;
  s->rwlim = s->rwbuf + s->len;
#endif

  s->pos = 0;

  s->mode = MODE_RSTREAM;
}

/* Note: stream_close() needs to be robust against multiple calls,
   because exception handlers may in some cases call it
   redundantly. 

   Doing nothing is fairly robust this way. :-)
*/
static void
mem_stream_close(SDR_stream *s)
{
#ifdef NEW_MEMORY_STREAM
  stream_wdrain(s);
#endif
}

/****************************************************************
 *
 * BUFFER STREAM LOGIC
 *
 ****************************************************************/
#ifdef NEW_BUFFERED_STREAM
static OC_bool buf_stream_wflush(SDR_stream *s);
static OC_bool buf_stream_rfill(SDR_stream *s);
#else
static int buf_stream_putc(SDR_stream *s, int c);
static ocmoff_t buf_stream_puts(SDR_stream *s, const void *vp, ocmoff_t len);
static int buf_stream_getc(SDR_stream *s);
static int buf_stream_peekc(SDR_stream *s);
static ocmoff_t buf_stream_gets(SDR_stream *s, void *vp, ocmoff_t len);
#endif
static void buf_stream_reread(SDR_stream *s);
static Buffer *buf_stream_asBuffer(SDR_stream *s);

SDR_stream*
stream_createBuffer(uint32_t format)
{
  SDR_stream *strm = (SDR_stream *) GC_MALLOC(sizeof(SDR_stream));

  strm->name   = "<buffer (in)>";
  strm->mode   = MODE_WSTREAM;
  strm->indent = 0;
  strm->type   = STREAM_BUFFER;
  strm->format = format; /* until proven otherwise below */

#ifndef NEW_SDR
  strm->data = 0;
#endif
  strm->state = buffer_create();

#ifdef NEW_BUFFERED_STREAM
  strm->rwbuf = GC_MALLOC_ATOMIC(RWBUFSZ);
  strm->rwlim = strm->rwbuf + RWBUFSZ;
  strm->rwptr = strm->rwbuf;

  strm->wflush = buf_stream_wflush;
  strm->rfill = buf_stream_rfill;
#else
  strm->rwbuf = strm->rwlim = strm->rwptr = 0;

  strm->strm_getc = buf_stream_getc;
  strm->strm_peekc = buf_stream_peekc;
  strm->strm_gets = buf_stream_gets;
  strm->strm_putc = buf_stream_putc;
  strm->strm_puts = buf_stream_puts;
#endif
  strm->asBuffer = buf_stream_asBuffer;
  strm->reread = buf_stream_reread;
  strm->close = mem_stream_close;

  strm->pos = 0;
  strm->len = 0;
#ifndef NEW_SDR
  strm->bound = strm->len;
#endif

  assert(sermodes[strm->format].onCreate);
  sermodes[strm->format].onCreate(strm);  

  return strm;
}

SDR_stream*
stream_fromBuffer(Buffer *buffer, uint32_t format)
{
  SDR_stream *strm = (SDR_stream *) GC_MALLOC(sizeof(SDR_stream));

  strm->name   = "<buffer (in)>";
  strm->mode   = MODE_RSTREAM;
  strm->indent = 0;
  strm->type   = STREAM_BUFFER;
  strm->format = SDR_RAW; /* until proven otherwise below */

#ifndef NEW_SDR
  strm->data = 0;
#endif
  strm->state = buffer;
  strm->rwbuf = strm->rwlim = strm->rwptr = 0;

#ifdef NEW_BUFFERED_STREAM
  strm->rwbuf = GC_MALLOC_ATOMIC(RWBUFSZ);
  strm->rwlim = strm->rwptr = strm->rwbuf; /* force rfill */

  strm->wflush = buf_stream_wflush;
  strm->rfill = buf_stream_rfill;
#else
  strm->rwbuf = strm->rwlim = strm->rwptr = 0;

  strm->strm_getc = buf_stream_getc;
  strm->strm_peekc = buf_stream_peekc;
  strm->strm_gets = buf_stream_gets;
  strm->strm_putc = buf_stream_putc;
  strm->strm_puts = buf_stream_puts;
#endif
  strm->asBuffer = buf_stream_asBuffer;
  strm->reread = mem_stream_reread;
  strm->close = mem_stream_close;

  strm->pos = 0;
  strm->len = buffer_length(buffer);
#ifndef NEW_SDR
  strm->bound = strm->len;
#endif

  if (format != SDR_RAW)
    stream_autodetect_format(strm);

  return strm;
}

#ifdef NEW_BUFFERED_STREAM
static OC_bool
buf_stream_wflush(SDR_stream *s)
{
  if (s->mode == MODE_WSTREAM) {
    /* Append the rwbuffer to the captive buffer, updating position and
       length accordingly. Then reset the input buffer pointer. */

    ocmoff_t rwBufLen =  (s->rwptr - s->rwbuf);

    assert(rwBufLen > 0);

    buffer_append(s->state, s->rwbuf, rwBufLen);

    s->len += rwBufLen;
    s->pos += rwBufLen;

    s->rwptr = s->rwbuf;
  }
  return TRUE;
}

static OC_bool
buf_stream_rfill(SDR_stream *s)
{
  /* Update the s->pos index, then refill the buffer. */
  ocmoff_t rwBufLen =  (s->rwptr - s->rwbuf);
  ocmoff_t bufLen = buffer_length(s->state);
  s->pos += rwBufLen;

  {
    ocmoff_t nBytes = min(bufLen - s->pos, RWBUFSZ);

    buffer_read(s->state, s->rwbuf, s->pos, nBytes);
    s->rwlim = s->rwbuf + nBytes;
    s->rwptr = s->rwbuf;
  }

  return TRUE;
}
#endif

#ifndef NEW_BUFFERED_STREAM
static int
buf_stream_getc(SDR_stream *s)
{
  int c;

  if (s->pos >= s->len)
    return EOF;

  c = buffer_getc(s->state, s->pos);
  s->pos++;

  return c;
}

static int
buf_stream_peekc(SDR_stream *s)
{
  int c;

  if (s->pos >= s->len)
    return EOF;

  c = buffer_getc(s->state, s->pos);

  return c;
}

static ocmoff_t
buf_stream_gets(SDR_stream *s, void *vp, ocmoff_t len)
{
  if (s->pos + len > s->len)
    THROW(ExTruncated, "Requested read is too long!\n");
  /*    len = s->len - s->pos; */

  if (s->mode != MODE_RSTREAM)
    THROW(ExBadOpcode,
	  format("getbuf() on write stream \"%s\"", s->name));

  buffer_read(s->state, vp, s->pos, len);

  s->pos += len;

  return len;
}

static int
buf_stream_putc(SDR_stream *s, int c)
{
  unsigned char uc = c;

  buffer_append(s->state, &uc, 1);

  s->pos++;
  s->len++;

  return c;
}

static ocmoff_t
buf_stream_puts(SDR_stream *s, const void *vp, ocmoff_t len)
{
  if (s->mode != MODE_WSTREAM)
    THROW(ExBadOpcode,
	  format("putbuf() on read stream \"%s\"", s->name));

  buffer_append(s->state, vp, len);

  s->pos += len;
  s->len += len;

  return len;
}
#endif

static void
buf_stream_reread(SDR_stream *s)
{
#ifdef NEW_BUFFERED_STREAM
  stream_wdrain(s);

  s->rwlim = s->rwptr = s->rwbuf; /* force rfill */
#endif

  s->pos = 0;

  s->mode = MODE_RSTREAM;
}

static Buffer *
buf_stream_asBuffer(SDR_stream *s)
{
  stream_reread(s);

  return s->state;
}

/****************************************************************
 *
 * SHA1 STREAM LOGIC
 *
 ****************************************************************/
#ifdef NEW_MEMORY_STREAM
static OC_bool sha1_stream_wflush(SDR_stream *s);
#else
static int sha1_stream_putc(SDR_stream *s, int c);
static ocmoff_t sha1_stream_puts(SDR_stream *s, const void *vp, ocmoff_t len);
#endif
static Buffer *sha1_stream_asBuffer(SDR_stream *s);
static void sha1_stream_reread(SDR_stream *s);
static void sha1_stream_close(SDR_stream *s);

SDR_stream*
stream_create_sha1(void)
{
  SDR_stream *strm = (SDR_stream *) GC_MALLOC(sizeof(SDR_stream));

  strm->name   = "<sha1 (out)>";
  strm->mode   = MODE_WSTREAM;
  strm->format = SDR_BINARY;
  strm->indent = 0;
  strm->type   = STREAM_MEM;

#ifndef NEW_SDR
  strm->data = 0;
#endif
  strm->state = sha_create();
#ifndef NEW_SDR
  strm->bound = 0;
#endif

#ifdef NEW_MEMORY_STREAM
  strm->rwbuf = GC_MALLOC_ATOMIC(RWBUFSZ);
  strm->rwlim = strm->rwbuf + RWBUFSZ;
  strm->rwptr = strm->rwbuf;

  strm->wflush = sha1_stream_wflush;
  strm->rfill = mem_stream_rfill;
#else
  strm->rwbuf = strm->rwlim = strm->rwptr = 0;

  strm->strm_getc = mem_stream_getc;
  strm->strm_peekc = mem_stream_peekc;
  strm->strm_gets = mem_stream_gets;
  strm->strm_putc = sha1_stream_putc;
  strm->strm_puts = sha1_stream_puts;
#endif
  strm->asBuffer = sha1_stream_asBuffer;
  strm->reread = sha1_stream_reread;
  strm->close = sha1_stream_close;

  strm->pos = 0;
  strm->len = 0;

  assert(sermodes[strm->format].onCreate);
  sermodes[strm->format].onCreate(strm);  

  return strm;
}

#ifdef NEW_MEMORY_STREAM
static OC_bool
sha1_stream_wflush(SDR_stream *s)
{
  if (s->mode == MODE_WSTREAM) {
    /* Append the rwbuffer to the captive buffer, updating position and
       length accordingly. Then reset the input buffer pointer. */

    ocmoff_t rwBufLen =  (s->rwptr - s->rwbuf);

    assert(rwBufLen > 0);

    sha_append((OPENCM_SHA *)s->state, rwBufLen, s->rwbuf);

    s->len += rwBufLen;
    s->pos += rwBufLen;

    s->rwptr = s->rwbuf;
  }
  return TRUE;
}
#endif

#ifndef NEW_MEMORY_STREAM
static int
sha1_stream_putc(SDR_stream *s, int c)
{
  unsigned char cchar = (unsigned) c;

  sha_append((OPENCM_SHA *)s->state, 1, &cchar);

  s->pos++;
  s->len++;

  return c;
}

static ocmoff_t
sha1_stream_puts(SDR_stream *s, const void *v, ocmoff_t len)
{
  sha_append((OPENCM_SHA *)s->state, len, v);

  s->pos += len;
  s->len += len;

  return len;
}
#endif

static void
sha1_stream_finish(SDR_stream *s)
{
#ifdef NEW_MEMORY_STREAM
  if (s->state) {
    char *shex;
    stream_wdrain(s);
    sha_finish((OPENCM_SHA *)s->state);

    shex = sha_hexdigest((OPENCM_SHA *)s->state);

    s->len = strlen(shex);
#ifndef NEW_SDR
    s->bound = strlen(shex);
#endif
    s->pos = 0;

    /* Now convert to a memory read stream: */
    s->rwbuf = shex;
    s->rwptr = s->rwbuf;
    s->rwlim = shex + s->len;

    s->state = 0;
    s->mode = MODE_RSTREAM;
  }
#else

  if (s->state) {
    sha_finish((OPENCM_SHA *)s->state);
    s->data = sha_hexdigest((OPENCM_SHA *)s->state);
    s->len = strlen(s->data);
    s->bound = strlen(s->data);
    s->pos = 0;

    s->state = 0;
    s->mode = MODE_RSTREAM;
  }
#endif
}

static Buffer *
sha1_stream_asBuffer(SDR_stream *s)
{
  if (s->state) {
    sha1_stream_finish(s);
    s->asBuffer = mem_stream_asBuffer;
  }

  return mem_stream_asBuffer(s);
}

static void
sha1_stream_reread(SDR_stream *s)
{
  sha1_stream_finish(s);

  s->pos = 0;
}

/* Note: stream_close() needs to be robust against multiple calls,
   because exception handlers may in some cases call it
   redundantly. */
static void
sha1_stream_close(SDR_stream *s)
{
  sha1_stream_finish(s);
}

ocmoff_t
do_stream_write(SDR_stream *s, const void *vp, ocmoff_t len)
{
  const unsigned char *buf = vp;
  ocmoff_t u;
  
#ifdef NEW_SDR
  for (u = 0; u < len; u++)
    stream_putc(s, buf[u]);

  return len;
#else
  if (s->strm_puts) {
    ocmoff_t wlen = s->strm_puts(s, vp, len);
    return wlen;
  }
  else {
    for (u = 0; u < len; u++)
      stream_putc(s, buf[u]);

    return len;
  }
#endif
}

ocmoff_t
do_stream_read(SDR_stream *s, void *vp, ocmoff_t len)
{
  unsigned char *buf = vp;
  ocmoff_t u;
  
#ifdef NEW_SDR
  for (u = 0; u < len; u++) {
    buf[u] = stream_getc(s);
  }
  return len;
#else
  if (s->strm_gets) {
    ocmoff_t rlen = s->strm_gets(s, vp, len);
    return rlen;
  }
  else {
    for (u = 0; u < len; u++) {
      buf[u] = stream_getc(s);
    }
    return len;
  }
#endif
}

Buffer *
stream_read_buffer(SDR_stream *s, ocmoff_t len)
{
  unsigned char tmpbuf[RWBUFSZ];
  Buffer *buf = buffer_create();

  while (len > 0) {
    ocmoff_t take = min (len, RWBUFSZ);
    stream_read(s, tmpbuf, take);
    buffer_append(buf, tmpbuf, take);
    len -= take;
  }

  return buf;
}

void
stream_write_partial_buffer(SDR_stream *s, const Buffer *buf, 
			    ocmoff_t pos, ocmoff_t len)
{
  ocmoff_t end = pos + len;

  while (pos < end) {
    BufferChunk bc = buffer_getChunk(buf, pos, end - pos);
    assert(bc.len <= (end - pos));

    if (stream_write(s, bc.ptr, bc.len) < bc.len)
      THROW(ExTruncated, "Write to buffer truncated");

    pos += bc.len;
  }
}

void
stream_write_buffer(SDR_stream *s, const Buffer *buf)
{
  stream_write_partial_buffer(s, buf, 0, buffer_length(buf));
}

#ifndef NEW_SDR
int
old_stream_getc(SDR_stream *strm)
{
  if (strm->mode != MODE_RSTREAM)
    THROW(ExBadOpcode,
	  format("getc() on write stream \"%s\"", strm->name));

  if (strm->pos == strm->bound)
    THROW(ExMalformed,
	  format("Stream length for \"%s\" exceeded", strm->name));

  return strm->strm_getc(strm);
}

int
old_stream_putc(SDR_stream *strm, int c)
{
  if (strm->mode != MODE_WSTREAM)
    THROW(ExBadOpcode,
	  format("getc() on write stream \"%s\"", strm->name));

  return strm->strm_putc(strm, c);
}

int
old_stream_peekc(SDR_stream *strm)
{
  if (strm->mode != MODE_RSTREAM)
    THROW(ExBadOpcode,
	  format("getc() on write stream \"%s\"", strm->name));

  if (strm->pos == strm->bound)
    THROW(ExMalformed,
	  format("Stream length for \"%s\" exceeded", strm->name));

  return strm->strm_peekc(strm);
}
#endif

/* This is derived from the EROS kprintf code */
size_t
stream_vprintf(SDR_stream *s, const char *fmt, va_list ap)
{
  unsigned long len;
  unsigned long width = 0;
  OC_bool sign;
  OC_bool rightAdjust;
  char fillchar;
  char buf[20];
  char *p, *pend;
  size_t output_count = 0;
  
  for( ; *fmt; fmt++) {
    if (*fmt != '%') {
      stream_putc(s, *fmt);
      output_count ++;
      continue;
    }

    /* largest thing we might convert fits in 20 digits (unsigned long
     * long as decimal */
    
    pend = &buf[20];
    p = pend;

    fmt++;			/* now looking at specification */

    /* check for left adjust.. */
    rightAdjust = TRUE;
    if (*fmt == '-') {
      rightAdjust = FALSE;
      fmt++;
    }
      
    fillchar = ' ';
    
    /* we just saw a format character.  See if what follows
     * is a width specifier:
     */
    width = 0;

    if (*fmt == '0')
      fillchar = '0';

    while (*fmt && *fmt >= '0' && *fmt <= '9') {
      width *= 10;
      width += (*fmt - '0');
      fmt++;
    }
    
    assert (*fmt);		/* check bogus fmt */

    sign = FALSE;
    
    switch (*fmt) {
    case '%':
      {
	stream_putc(s, *fmt);
	output_count ++;
	break;
      }
    case 's':
      {
	p = pend = va_arg(ap, char *);
      
	if (pend == 0)
	  p = pend = "<null>";

	while (*pend)
	  pend++;
	break;
      }
    case 'c':
      {
	long c;
	c = va_arg(ap, long);
	*(--p) = (char) c;
	break;
      }	    
    case 'd':
      {
	long l;
	unsigned long ul;

	l = va_arg(ap, long);
	      
	if (l == 0) {
	  *(--p) = '0';
	}
	else {
	  if (l < 0)
	    sign = '-';

	  ul = (l < 0) ? (unsigned) -l : (unsigned) l;

	  if (l == LONG_MIN)
	    ul = ((unsigned long) LONG_MAX) + 1ul;

	  while(ul) {
	    *(--p) = '0' + (ul % 10);
	    ul = ul / 10;
	  }

	  if (sign)
	    *--p = sign;
	}
	break;
      }
    case 'u':
      {
	unsigned long ul;

	ul = va_arg(ap, unsigned long);
	      
	if (ul == 0) {
	  *(--p) = '0';
	}
	else {
	  while(ul) {
	    *(--p) = '0' + (ul % 10);
	    ul = ul / 10;
	  }
	}
	break;
      }
    case 'x':
      {
	unsigned long ul;

	char *hexout = "0123456789abcdef";

	ul = va_arg(ap, unsigned long);
	      
	if (ul == 0) {
	  *(--p) = '0';
	}
	else {
	  while(ul) {
	    *(--p) = hexout[ul % 16];
	    ul = ul / 16;
	  }
	}
	break;
      }
    default:
      {
	assert(FALSE);
      }
    }
  
    len = pend - p;
    if (sign)
      len++;

      /* do padding with initial spaces for right justification: */
    if (width && rightAdjust && len < width) {
      while (len < width) {
	stream_putc(s, fillchar);
	output_count ++;
	width--;
      }
    }

    /* output the text */
    while (p != pend) {
      stream_putc(s, *p++);
      output_count ++;
    }
    
      /* do padding with initial spaces for left justification: */
    if (width && rightAdjust == FALSE && len < width) {
      while (len < width) {
	stream_putc(s, fillchar);
	output_count ++;
	width--;
      }
    }
  }
    
  return output_count;
}

/* This is derived from the EROS kprintf code */
size_t
stream_printf(SDR_stream *s, const char *fmt, ...)
{
  size_t output_count = 0;

  va_list ap;

  va_start(ap, fmt);
    
  output_count = stream_vprintf(s, fmt, ap);

  va_end(ap);

  return output_count;
}

const char *
format(const char *fmt, ...)
{
  const char *str;

  SDR_stream *s = stream_createMemory(SDR_RAW);

  va_list ap;

  va_start(ap, fmt);
    
  stream_vprintf(s, fmt, ap);

  va_end(ap);

  stream_putc(s, '\0');
  stream_close(s);

  str = s->state;

  return str;
}

size_t
stream_scanf(SDR_stream *s, const char *fmt, ...)
{
  char c;
  OC_bool negate;
  size_t input_count = 0;
  
  va_list ap;

  va_start(ap, fmt);
    
  for( ; *fmt; fmt++) {
    if (*fmt != '%') {
    match_one_char:
      c = stream_peekc(s);

      if (*fmt != c)
	return input_count;

      (void)stream_getc(s);
      input_count ++;
      
      continue;
    }

    fmt++;			/* now looking at specification */

    assert (*fmt);		/* check bogus fmt */

    negate = FALSE;
    
    switch (*fmt) {
    case '%':
	goto match_one_char;

    case 'u':
    case 'U':
      {
	unsigned long ull = 0;

	while (isspace(stream_peekc(s))) {
	  (void)stream_getc(s);
	  input_count++;
	}

	/* Should be looking at first character. Check for digit:*/
	if (!isdigit(stream_peekc(s)))
	  return input_count;

	while (isdigit(stream_peekc(s))) {
	  c = stream_getc(s);
	  input_count++;
	  ull = ull * 10;
	  ull += (c - '0');
	}
	
	if (*fmt == 'u') {
	  unsigned long *ulp;

	  ulp = va_arg(ap, unsigned long *);
	  *ulp = (unsigned long) ull;
	  assert(ull <= ULONG_MAX);
	}
	else {
	  uint64_t *ullp;
	  assert(*fmt == 'U');

	  ullp = va_arg(ap, uint64_t *);
	  *ullp = ull;
	}
	break;
      }
    case 'x':
      {
	unsigned long ul = 0;
	unsigned long *ulp;

	ulp = va_arg(ap, unsigned long *);

	while (isspace(stream_peekc(s))) {
	  (void)stream_getc(s);
	  input_count++;
	}

	/* Should be looking at '0' */
	if (stream_peekc(s) != '0')
	  return input_count;

	(void) stream_getc(s);

	/* Should be looking at 'x' or 'X' */
	c = stream_peekc(s);
	if (c != 'x' && c != 'X')
	  return input_count;

	(void) stream_getc(s);

	/* Should now be looking at hex digits */
	if (!isxdigit(stream_peekc(s)))
	  return input_count;

	while (isxdigit(stream_peekc(s))) {
	  c = stream_getc(s);
	  input_count++;
	  ul = ul * 16;
	  if (c >= 'a' && c <= 'f')
	    c = (c - 'a' + 10);
	  if (c >= 'A' && c <= 'F')
	    c = (c - 'A' + 10);
	  if (c >= '0' && c <= '9')
	    c = (c - '0');

	  ul += c;
	}
	
	*ulp = ul;
	break;
      }
    case 'X':
      {
	uint64_t ull = 0;
	uint64_t *ullp;

	ullp = va_arg(ap, uint64_t *);

	while (isspace(stream_peekc(s))) {
	  (void)stream_getc(s);
	  input_count++;
	}

	/* Should be looking at '0' */
	if (stream_peekc(s) != '0')
	  return input_count;

	(void) stream_getc(s);

	/* Should be looking at 'x' or 'X' */
	c = stream_peekc(s);
	if (c != 'x' && c != 'X')
	  return input_count;

	(void) stream_getc(s);

	/* Should now be looking at hex digits */
	if (!isxdigit(stream_peekc(s)))
	  return input_count;

	while (isxdigit(stream_peekc(s))) {
	  c = stream_getc(s);
	  input_count++;
	  ull = ull * 16;
	  if (c >= 'a' && c <= 'f')
	    c = (c - 'a' + 10);
	  if (c >= 'A' && c <= 'F')
	    c = (c - 'A' + 10);
	  if (c >= '0' && c <= '9')
	    c = (c - '0');

	  ull += c;
	}
	
	*ullp = ull;
	break;
      }
    default:
      {
	assert(FALSE);
      }
    }
  }

  return input_count;
    
  va_end(ap);

  return input_count;
}

static void
stream_autodetect_format(SDR_stream *stream)
{
  int c = stream_getc(stream);

  switch (c) {
  case '\0':
    {
      stream->format = SDR_BINARY;
      /* Do NOT return the null to the input stream, as subsequent
       * I/Os will assume that it is not there. */
      break;
    }
  case 'T':
    {
      /* Texty format starts with TEXTY\n
       *
       * The newline isn't really necessary, but otherwise the file
       * will look kind of ugly, with stupid stuff like TEXTYobtype 5...
       */
      const char* expecting = "EXTY\n";

      do {
        c = stream_getc(stream);
        if(c != *expecting)
          THROW(ExMalformed,
                format("Bad stream format (thought it was TEXTY); "
                       "source \"%s\", starts with char %x\n",
                       stream->name, c));
        expecting++;
      } while (c != '\n');

      stream->format = SDR_TEXTY;

      break;
    }
  case 'D':
    {
      /* Texty format starts with DTEXTY\n
       */
      const char* expecting = "TEXTY\n";

      do {
        c = stream_getc(stream);
        if(c != *expecting)
          THROW(ExMalformed,
                format("Bad stream format (thought it was DTEXTY); "
                       "source \"%s\", starts with char %x\n",
                       stream->name, c));
        expecting++;
      } while (c != '\n');

      stream->format = SDR_DTEXTY;

      break;
    }
  default:
    {
      THROW(ExMalformed, 
	    format("Stream format unknown -- source \"%s\" starts "
		   "with char %x",
		   stream->name, c));
    }
  }
}

static void 
sdr_noaction_onCreate(SDR_stream * strm)
{
  /* do nothing */
}

static void 
sdr_throw_onCreate(SDR_stream * strm)
{
  THROW(ExBadValue, "Cannot create AUTO streams");
}

/* HUMAN READABLE (sort-of) */
void
sdr_show(const void *ob)
{
  const Serializable *s = ob;

  SerialType *ty = s ? s->ser_type : &Null_SerType;

  ty->show(ob);
}

void
sdr_nshow(const void *ob)
{
  SDR_stream *out =stream_createBuffer(SDR_DTEXTY);

  if (GETTYPE(ob) == TY_Mutable)
    sdr_write("MUTABLE", out, ob);
  else if (GETTYPE(ob) == TY_Revision)
    sdr_write("REVISION", out, ob);
  else
    sdr_write("ENTITY", out, ob);

  {
    Buffer *buf = stream_asBuffer(out);
    ocmoff_t end = buffer_length(buf);
    ocmoff_t pos = 0;

    while (pos < end) {
      BufferChunk bc = buffer_getChunk(buf, pos, end - pos);

      fwrite(bc.ptr, 1, bc.len, stdout);

      pos += bc.len;
    }
  }
}

OC_bool
sdr_check(const void *ob)
{
  const Serializable *s = ob;

  SerialType *ty = s ? s->ser_type : &Null_SerType;

  return ty->check(ob);
}

/* GENERIC WRAPPERS */
void
sdr_w_u8(const char *elem, SDR_stream *strm, uint8_t u)
{
  assert(sermodes[strm->format].w_u8);
  sermodes[strm->format].w_u8(elem, strm, u);  
}

uint8_t
sdr_r_u8(const char *elem, SDR_stream *strm)
{
  assert(sermodes[strm->format].r_u8);
  return sermodes[strm->format].r_u8(elem, strm);  
}

void
sdr_w_u16(const char *elem, SDR_stream *strm, uint16_t u)
{
  assert(sermodes[strm->format].w_u16);
  sermodes[strm->format].w_u16(elem, strm, u);  
}

uint16_t
sdr_r_u16(const char *elem, SDR_stream *strm)
{
  assert(sermodes[strm->format].r_u16);
  return sermodes[strm->format].r_u16(elem, strm);  
}

void
sdr_w_u32(const char *elem, SDR_stream *strm, uint32_t u)
{
  assert(sermodes[strm->format].w_u32);
  sermodes[strm->format].w_u32(elem, strm, u);  
}

uint32_t
sdr_r_u32(const char *elem, SDR_stream *strm)
{
  assert(sermodes[strm->format].r_u32);
  return sermodes[strm->format].r_u32(elem, strm);  
}

void
sdr_w_u64(const char *elem, SDR_stream *strm, uint64_t u)
{
  assert(sermodes[strm->format].w_u64);
  sermodes[strm->format].w_u64(elem, strm, u);  
}

uint64_t
sdr_r_u64(const char *elem, SDR_stream *strm)
{
  assert(sermodes[strm->format].r_u64);
  return sermodes[strm->format].r_u64(elem, strm);  
}

void
sdr_w_buffer(const char *elem, SDR_stream *strm, const Buffer *buf)
{
  assert(sermodes[strm->format].w_buffer);
  sermodes[strm->format].w_buffer(elem, strm, buf);  
}

Buffer *
sdr_r_buffer(const char *elem, SDR_stream *strm, ocmoff_t len)
{
  assert(sermodes[strm->format].r_buffer);
  return sermodes[strm->format].r_buffer(elem, strm, len);  
}

void
sdr_w_bytes(const char *elem, SDR_stream *strm, uint32_t len, const void *v)
{
  assert(sermodes[strm->format].w_bytes);
  sermodes[strm->format].w_bytes(elem, strm, len, v);  
}

void *
sdr_r_bytes(const char *elem, SDR_stream *strm, uint32_t len)
{
  assert(sermodes[strm->format].r_bytes);
  return sermodes[strm->format].r_bytes(elem, strm, len);  
}

void
sdr_w_string(const char *elem, SDR_stream *strm, const char *s)
{
  assert(sermodes[strm->format].w_string);
  sermodes[strm->format].w_string(elem, strm, s);  
}

char *
sdr_r_string(const char *elem, SDR_stream *strm)
{
  assert(sermodes[strm->format].r_string);
  return sermodes[strm->format].r_string(elem, strm);  
}

void
sdr_w_obname(const char *elem, SDR_stream *strm, const char *s)
{
  assert(sermodes[strm->format].w_obname);
  sermodes[strm->format].w_obname(elem, strm, s);  
}

char *
sdr_r_obname(const char *elem, SDR_stream *strm)
{
  assert(sermodes[strm->format].r_obname);
  return sermodes[strm->format].r_obname(elem, strm);  
}

void
sdr_write(const char *elem, SDR_stream *strm, const void *ob)
{
  assert(sermodes[strm->format].write);
  sermodes[strm->format].write(elem, strm, ob);  
}

void *
sdr_read(const char *elem, SDR_stream *strm)
{
  assert(sermodes[strm->format].read);
  return sermodes[strm->format].read(elem, strm);  
}

/* BINARY FORMAT SERIALIZER */
static void
bin_onCreate(SDR_stream * strm)
{
  stream_putc(strm, '\0');
}

static void
bin_w_u8(const char *elem, SDR_stream *strm, uint8_t u)
{
  if (stream_write(strm, &u, sizeof(u)) < sizeof(u))
    THROW(ExTruncated, "Serialization write error");
}

static uint8_t
bin_r_u8(const char *elem, SDR_stream *strm)
{
  uint8_t u;

  if (stream_read(strm, &u, sizeof(u)) < sizeof(u))
    THROW(ExTruncated, "Serialization read error");

  return u;
}

static void
bin_w_u16(const char *elem, SDR_stream *strm, uint16_t u)
{
  int i;
  unsigned char bytes[sizeof(u)];

  for (i = 0; i < sizeof(u); i++) {
    bytes[i] = u & 0xffu;
    u >>= 8;
  }

  if (stream_write(strm, bytes, sizeof(u)) < sizeof(u))
    THROW(ExTruncated, "Serialization write error");
}

static uint16_t
bin_r_u16(const char *elem, SDR_stream *strm)
{
  int i;
  uint16_t u;
  unsigned char bytes[sizeof(u)];

  if (stream_read(strm, bytes, sizeof(u)) < sizeof(u))
    THROW(ExTruncated, "Serialization read error");

  i = sizeof(u);
  u = 0;

  do {
    i--;
    u = u << 8;
    u = u | bytes[i];
  } while (i > 0);
  
  return u;
}

static void
bin_w_u32(const char *elem, SDR_stream *strm, uint32_t u)
{
  int i;
  unsigned char bytes[sizeof(u)];

  for (i = 0; i < sizeof(u); i++) {
    bytes[i] = u & 0xffu;
    u >>= 8;
  }

  if (stream_write(strm, bytes, sizeof(u)) < sizeof(u))
    THROW(ExTruncated, "Serialization write error");
}

static uint32_t
bin_r_u32(const char *elem, SDR_stream *strm)
{
  int i;
  uint32_t u;
  unsigned char bytes[sizeof(u)];

  if (stream_read(strm, bytes, sizeof(u)) < sizeof(u))
    THROW(ExTruncated, "Serialization read error");

  i = sizeof(u);
  u = 0;

  do {
    i--;
    u = u << 8;
    u = u | bytes[i];
  } while (i > 0);
  
  return u;
}

static void
bin_w_u64(const char *elem, SDR_stream *strm, uint64_t u)
{
  int i;
  unsigned char bytes[sizeof(u)];

  for (i = 0; i < sizeof(u); i++) {
    bytes[i] = u & 0xffu;
    u >>= 8;
  }

  if (stream_write(strm, bytes, sizeof(u)) < sizeof(u))
    THROW(ExTruncated, "Serialization write error");
}

static uint64_t
bin_r_u64(const char *elem, SDR_stream *strm)
{
  int i;
  uint64_t u;
  unsigned char bytes[sizeof(u)];

  if (stream_read(strm, bytes, sizeof(u)) < sizeof(u))
    THROW(ExTruncated, "Serialization read error");

  i = sizeof(u);
  u = 0;

  do {
    i--;
    u = u << 8;
    u = u | bytes[i];
  } while (i > 0);
  
  return u;
}

/* Note that these do NOT read/write the actual length! */
static void
bin_w_bytes(const char *elem, SDR_stream *strm, uint32_t len,
	    const void *v)
{
  const char *s = (const char *) v;

  if (stream_write(strm, s, len) < len)
    THROW(ExTruncated, "Serialization write error");
}

static void *
bin_r_bytes(const char *elem, SDR_stream *strm, uint32_t len)
{
  char * s = (char *) GC_MALLOC_ATOMIC(sizeof(char) * (len+1));
  s[len] = 0;
  
  if (stream_read(strm, s, len) < len)
    THROW(ExTruncated, "Serialization read error");

  return s;
}

/* Note that these do NOT read/write the actual length! */
static void
bin_w_buffer(const char *elem, SDR_stream *strm, const Buffer *buf)
{
  stream_write_buffer(strm, buf);
}

static Buffer *
bin_r_buffer(const char *elem, SDR_stream *strm, ocmoff_t len)
{
  return stream_read_buffer(strm, len);
}

static void
bin_w_string(const char *elem, SDR_stream *strm, const char *s)
{
  uint32_t len = 0;
  
  /* Include the null byte so that empty strings can be differentiated
     from null strings. */
  if (s)
    len = strlen(s) + 1;
  
  sdr_w_u32("len", strm, len);

  if (s) {
    if (stream_write(strm, s, len) < len)
      THROW(ExTruncated, "Serialization write error");
  }
}

static char *
bin_r_string(const char *elem, SDR_stream *strm)
{
  uint32_t len = sdr_r_u32("len", strm);

  if (len == 0)
    return 0;
  else {
    char * s = (char *) GC_MALLOC_ATOMIC (sizeof(char) * len);
    s[len-1] = 0;
  
    if (stream_read(strm, s, len) < len)
      THROW(ExTruncated, "Serialization read error");

    return s;
  }
}

static void
bin_w_obname(const char *elem, SDR_stream *strm, const char *s)
{
  uint32_t len = 0;
  
  /* Include the null byte so that empty strings can be differentiated
     from null strings. */
  if (s)
    len = strlen(s) + 1;
  
  sdr_w_u32("len", strm, len);

  if (s) {
    if (stream_write(strm, s, len) < len)
      THROW(ExTruncated, "Serialization write error");
  }
}

static char *
bin_r_obname(const char *elem, SDR_stream *strm)
{
  uint32_t len = sdr_r_u32("len", strm);

  if (len == 0)
    return 0;
  else {
    char * s = (char *) GC_MALLOC_ATOMIC (sizeof(char) * len);
    s[len-1] = 0;
  
    if (stream_read(strm, s, len) < len)
      THROW(ExTruncated, "Serialization read error");

    return s;
  }
}

static void
bin_write(const char *elem, SDR_stream *strm, const void *ob)
{
  const Serializable *s = ob;

  SerialType *ty = s ? s->ser_type : &Null_SerType;
  uint32_t ver = s ? s->ser_ver : ty->ver;

  sdr_w_u32("obtype", strm, ty->typ);
  sdr_w_u32("obver", strm, ver);
  
  ty->serialize(strm, ob);
}

static void *
bin_read(const char *elem, SDR_stream *strm)
{
  DeserializeInfo di;
  Serializable *s;
  
  di.tyConst = sdr_r_u32("obtype", strm);
  di.ver = sdr_r_u32("obver", strm);
  di.st = ser_find_type(di.tyConst);

  if (di.ver > di.st->ver)
    THROW(ExVersionError, "Unknown object version during deserialize");

  s = di.st->deserialize(&di, strm);

  assert((s == 0) || (s->ser_type == di.st));  

  return s;
}

/* TEXTY FORMAT SERIALIZER */
static void
texty_do_indent(SDR_stream *strm)
{
  uint32_t u;
  for (u = 0; u < strm->indent; u++)
    stream_write(strm, " ", 1);
}

static void
texty_increase_indent(SDR_stream *strm, uint32_t i)
{
  strm->indent += i;
}

static void texty_onCreate(SDR_stream *strm)
{
  stream_printf(strm, "TEXTY\n");
}

/* This makes things a lot simpler */
typedef struct texty_field {
  char* name;			/* element name */
  char  ty;			/* Type that the representation will
				   decode to. One of integer (32
				   bits), long (64 bits), byte string,
				   or character string: I,L,B,S */

  uint64_t value;		/* length if it is a string */

  void* rep;			/* Pointer to the actual data
				   representation string.  Either to
				   the number (I,L), or the whole
				   chunk of data (B,S) */
} texty_field;

/* texty_get_field is always called at the beginning of a valid
 * object entry. Objects in the texty format take one of the following
 * forms:
 *
 *    name I <digits>\n            32-bit unsigned
 *    name L <digits>\n            64-bit unsigned
 *    name B <digits>\n<bytes>\n   byte string (encoded as a string)
 *    name S <digits>\n<bytes>\n   string
 *    name N <digits> <bytes>\n    one line string
 *    name N 0\n                   null one line string
 *    name O <digits> <bytes>\n    object name
 *    name O 0\n                   null object name
 *    name X <digits>\n<bytes>\n   buffer
 *    name X 0\n                   empty buffer
 *
 * The 'name' can be preceded by whitespace.
 *
 * In the case on B and S, it's possible to have a completely null pointer
 * (p=0), versus an empty string (strcmp(p, "") == 0) as a value, and some
 * other code in OpenCM uses this. To represent this possiblity, we set <bytes>
 * == 0. If, OTOH, the value is the empty string "", we set <bytes> at 1, and
 * have an empty line following.  This is an unambigious encoding because, when
 * writing S or B, we always follow the actual data with a single trailing
 * newline, unless <bytes> == 0. Essentially we can know that the next <bytes>
 * bytes of the stream are part of this S/B object, and can thus read them
 * all out at once.
 */
static texty_field*
texty_get_field(const char *name, char ty, SDR_stream *strm)
{
  int c;
  texty_field* fld = (texty_field *) GC_MALLOC(sizeof(texty_field)); 

  /* Skip leading whitespace: */
  while(stream_peekc(strm) == ' ')
    (void)stream_getc(strm);

  /* Pick off the name and the trailing whitespace. */
  {
    SDR_stream *nm = stream_createMemory(SDR_RAW);

    do {
      c = stream_getc(strm);
      if (c != ' ')
	stream_putc(nm, c);
    } while (c != ' ');

    stream_putc(nm, '\0');

    stream_close(nm);

    fld->name = nm->state;
  }

  if(strcmp(fld->name, name) != 0)
    THROW(ExMalformed, 
	  format("Mistmatched field name in stream \"%s\"", strm->name));

  /* Next character should be the type field. Grab it and type check
     it. Next character should be a space. */
  fld->ty = stream_peekc(strm);

  switch(fld->ty) {
  case 'I':
  case 'L':
  case 'S':
  case 'B':
  case 'N':
  case 'O':
  case 'X':
    {
      if (fld->ty != ty)
        THROW(ExMalformed, 
	      format("Typecode mismatch in stream \"%s\" offset %s",
		     strm->name, xunsigned64_str(strm->pos)));
      (void)stream_getc(strm);
      break;
    }
  default:
    THROW(ExMalformed, 
	  format("Bad type code at file \"%s\" offset %s", 
		 strm->name, xunsigned64_str(strm->pos)));
    break;
  }

  if (stream_peekc(strm) != ' ')
    THROW(ExMalformed, 
	  format("Bad format (wanted space) at stream \"%s\" offset %s", 
		 strm->name, xunsigned64_str(strm->pos)));

  (void) stream_getc(strm);

  /* Pick off the integer value that follows: */
  {
    SDR_stream *num = stream_createMemory(SDR_RAW);

    while (isdigit(stream_peekc(strm))) {
      c = stream_getc(strm);
      stream_putc(num, c);
    }

    stream_putc(num, '\0');
    stream_close(num);

    fld->value = atollu(num->state);

    c = stream_peekc(strm);

    /* If this is N format there should be a space, else a newline: */
    if ((ty == 'N' || ty == 'O') && fld->value && c == ' ') {
      (void)stream_getc(strm);
      /* okay */
    }
    else if (c == '\n') {
      (void)stream_getc(strm);
    }
    else {
      THROW(ExMalformed, 
	    format("Bad format (wanted newline or space) "
		   "at stream \"%s\" offset %s", 
		   strm->name, xunsigned64_str(strm->pos)));
    }
  }

  /* FIX: check for range error here */

  fld->rep = 0;

  if (fld->ty == 'I' || fld->ty == 'L')
    return fld;

  if (fld->value == 0) {
    if (fld->ty == 'X')
      fld->rep = buffer_create();

    return fld;
  }

  /* IFF this is a (byte)string, pick off the required number of
     characters: */

  {
    uint64_t len = fld->value - 1;
    fld->rep = stream_read_buffer(strm, len);

    if (stream_peekc(strm) != '\n')
      THROW(ExMalformed, 
	    format("Bad format (wanted newline) at stream \"%s\" offset %s", 
		   strm->name, xunsigned64_str(strm->pos)));

    (void)stream_getc(strm);
  }

  if (fld->ty != 'X')
    fld->rep = buffer_asString(fld->rep);

  return fld;
}

static void
texty_w_u8(const char *elem, SDR_stream *strm, uint8_t num)
{
  texty_do_indent(strm);
  stream_printf(strm, "%s I %u\n", elem, num);
}

static void
texty_w_u16(const char *elem, SDR_stream *strm, uint16_t num)
{
  texty_do_indent(strm);
  stream_printf(strm, "%s I %u\n", elem, num);
}

static void
texty_w_u32(const char *elem, SDR_stream *strm, uint32_t num)
{
  texty_do_indent(strm);
  stream_printf(strm, "%s I %u\n", elem, num);
}

static uint8_t
texty_r_u8(const char *elem, SDR_stream *strm)
{
  texty_field *fld = texty_get_field(elem, 'I', strm);
  return fld->value;
}

static uint16_t
texty_r_u16(const char *elem, SDR_stream *strm)
{
  texty_field *fld = texty_get_field(elem, 'I', strm);
  return fld->value;
}

static uint32_t
texty_r_u32(const char *elem, SDR_stream *strm)
{
  texty_field *fld = texty_get_field(elem, 'I', strm);
  return fld->value;
}

static void
texty_w_u64(const char *elem, SDR_stream *strm, uint64_t num)
{
  texty_do_indent(strm);
  stream_printf(strm, "%s L %s\n", elem, xunsigned64_str(num));
}

static uint64_t
texty_r_u64(const char *elem, SDR_stream *strm)
{
  texty_field *fld = texty_get_field(elem, 'L', strm);
  return fld->value;
}

static void
texty_w_bytestring(const char *elem, SDR_stream *strm, const char ty, 
		   uint32_t len, const void *vp)
{
  texty_do_indent(strm);

  if(vp)
  {
    stream_printf(strm, "%s %c %u", elem, ty, len+1);
    if (ty == 'N' || ty == 'O')
      stream_putc(strm, ' ');
    else
      stream_putc(strm, '\n');
    stream_write(strm, vp, len);
    stream_putc(strm, '\n');
  }
  else
    stream_printf(strm, "%s %c 0\n", elem, ty);
}

static void
texty_w_buffer(const char *elem, SDR_stream *strm, const Buffer *buf)
{
  ocmoff_t len = buffer_length(buf);
  texty_do_indent(strm);

  stream_printf(strm, "%s X %s", elem, xunsigned64_str(len + 1));
  stream_putc(strm, '\n');

  if (len) {
    stream_write_buffer(strm, buf);
    stream_putc(strm, '\n');
  }
}

static Buffer *
texty_r_buffer(const char *elem, SDR_stream *strm, ocmoff_t len)
{
  texty_field *fld = texty_get_field(elem, 'X', strm);
  return fld->rep;
}

static void
texty_w_bytes(const char *elem, SDR_stream *strm, uint32_t len,
              const void *obj)
{
  texty_w_bytestring(elem, strm, 'B', len, obj);
}

static void *
texty_r_bytes(const char *elem, SDR_stream *strm, uint32_t len)
{
  texty_field *fld = texty_get_field(elem, 'B', strm);
  return fld->rep;
}

static void
texty_w_string(const char *elem, SDR_stream *strm, const char *s)
{
  texty_w_bytestring(elem, strm, 'S', s ? strlen(s) : 0, s);
}

static char *
texty_r_string(const char *elem, SDR_stream *strm)
{
  texty_field *fld = texty_get_field(elem, 'S', strm);
  return fld->rep;
}

static void
texty_w_obname(const char *elem, SDR_stream *strm, const char *s)
{
  texty_w_bytestring(elem, strm, 'N', s ? strlen(s) : 0, s);
}

static char *
texty_r_obname(const char *elem, SDR_stream *strm)
{
  texty_field *fld = texty_get_field(elem, 'N', strm);
  return fld->rep;
}

static void
texty_write(const char *elem, SDR_stream *strm, const void *ob)
{
  const Serializable *s = (Serializable*)ob;
  SerialType *ty = s ? s->ser_type : &Null_SerType;
  uint32_t ver = s ? s->ser_ver : ty->ver;

  texty_w_bytestring(elem, strm, 'O', strlen(ty->tyName), ty->tyName);

  texty_increase_indent(strm, 2);

  sdr_w_u32("obtype", strm, ty->typ);
  sdr_w_u32("obver", strm, ver);
  ty->serialize(strm, ob);

  texty_increase_indent(strm, -2);
}

static void *
texty_read(const char *elem, SDR_stream *strm)
{
  DeserializeInfo di;
  Serializable *s;

  (void) texty_get_field(elem, 'O', strm);

  di.tyConst = sdr_r_u32("obtype", strm);
  di.ver = sdr_r_u32("obver", strm);
  di.st = ser_find_type(di.tyConst);

  if (di.ver > di.st->ver)
    THROW(ExVersionError, "Unknown object version during deserialize");

  s = di.st->deserialize(&di, strm);

  assert((s == 0) || (s->ser_type == di.st));  

  return s;
}

/* DTEXTY */

/* SHAP: The original implementations of dtexty_w_bytestring and
   dtexty_get_field would not have worked. The problem was that the
   length written was the length of the raw string, not the encoded
   string. In consequence, the read-back of any encoded field would
   get back a short (and possibly malformed) string.

   The simplest thing to do is to do this in two passes. First encode
   the string, then call texty_w_bytestring, passing the length of the
   ENCODED string. Similarly, on decode grab the encoded string and
   decode it, learning the length of the decoded string in the process
   of the decode.  That's what I'm doing here at present. 
   
   Note, however, that this produces an in-memory copy of the data,
   and the data may be quite large, so this is a most unfortunate
   thing to do. To avoid completely unnecessary copies I make a
   pre-pass to see if the string is already safe. This deals with
   truenames and most one-liners without a marginal copy.
*/

#define ENCODE_CHAR '@'
static OC_bool 
isSafeChar(unsigned char c)
{
  return (isprint(c) && c != ENCODE_CHAR) || isspace(c);
}

static void dtexty_onCreate(SDR_stream *strm)
{
  stream_printf(strm, "DTEXTY\n");
}

static ocmoff_t
encodedLength(const void *vp, ocmoff_t len)
{
  ocmoff_t u;
  ocmoff_t elen = 0;
  unsigned const char *bp = vp;

  for (u = 0; u < len; u++) {
    if (isSafeChar(bp[u]))
      elen++;
    else
      elen += 3;		/* hex encoding */
  }

  return elen;
}

#define hex(i) (((i) < 10) ? ('0' + (i)) : ('a' + ((i) - 10)))

static char *
encodeString(const void *vp, ocmoff_t len, ocmoff_t encodedLen)
{
  ocmoff_t u;
  unsigned const char *bp = vp;
  unsigned char *ostring = GC_MALLOC_ATOMIC(encodedLen + 1);
  unsigned char *op = ostring;

  ostring[encodedLen] = 0;

  for (u = 0; u < len; u++) {
    if (isSafeChar(bp[u]))
      *op++ = bp[u];
    else {
      *op++ = '@';
      *op++ = hex(bp[u] >> 4);
      *op++ = hex(bp[u] & 0xfu);
    }
  }

  return ostring;
}

static void
dtexty_w_bytestring(const char *elem, SDR_stream *strm, const char ty, 
                    uint32_t len, const void *vp)
{
  ocmoff_t elen = len;		/* encoded length */
  const void *evp = vp;		/* encoded string */

  if (ty == 'I' || ty == 'L')
    goto done;

  elen = encodedLength(vp, len);

  if (elen == len)
    goto done;

  evp = encodeString(vp, len, elen);

 done:
  texty_w_bytestring(elem, strm, ty, elen, evp);
}

/* Do a wrapper, so we don't have to replicate quite so much code. */
static texty_field*
dtexty_get_field(const char *name, char ty, SDR_stream *strm)
{
  texty_field *fld;

  /* If we ultimately want a buffer, fetch the input as a bytestring
     to simplify later conversion. */
  fld = texty_get_field(name, (ty == 'X') ? 'B' : ty, strm);

  if (ty == 'I' || ty == 'L')
    return fld;

  if (fld->value == 0)		/* zero length string */
    return fld;

  {
    /* Do an in-place conversion to safe form on the string that came
       back: */
    unsigned char *base = fld->rep;
    unsigned char *end = base + fld->value;
    unsigned char *to = fld->rep;
    unsigned char *from = fld->rep;

    while (from != end) {
      unsigned char c = *from++;
      
      if (c == '@') {
	unsigned char digit = *from++;

	if (digit >= '0' && digit <= '9')
	  c = (digit - '0') << 4;
	else if (digit >= 'a' && digit <= 'f')
	  c = ((digit - 'a') + 10) << 4;
	else if (digit >= 'A' && digit <= 'F')
	  c = ((digit - 'A') + 10) << 4;
	else
	  THROW(ExMalformed, "Mis-encoded or malformed input string");

	digit = *from++;

	if (digit >= '0' && digit <= '9')
	  c |= (digit - '0');
	else if (digit >= 'a' && digit <= 'f')
	  c |= ((digit - 'a') + 10);
	else if (digit >= 'A' && digit <= 'F')
	  c |= ((digit - 'A') + 10);
	else
	  THROW(ExMalformed, "Mis-encoded or malformed input string");

      }
      *to++ = c;
    }

    fld->value = to - base;	/* len excludes terminating NUL */
    *to = 0;			/* always NUL terminate */
  }

  /* If we wanted a buffer, re-convert: */
  if (ty == 'X') {
    Buffer *buf = buffer_create();
    buffer_append(buf, fld->rep, fld->value);
    fld->rep = buf;
  }

  return fld;
}

#if 0
/* Do a wrapper, so we don't have to replicate quite so much code. */
static texty_field*
dtexty_get_field(const char *name, char ty, SDR_stream *strm)
{
  texty_field* fld = texty_get_field(name, ty, strm); 
  /* We probably don't need to check 'O', since we choose those values
     ourselves.
  */
  if(ty == 'S' || ty == 'B' || ty == 'N' || ty == 'O')
  {
    /* The decoded length cannot possibly be longer than the encoded length */
    char* decoded_rep = GC_MALLOC_ATOMIC(fld->value);
    char* start_of_decoded_rep = decoded_rep;
    char* encoded_rep = fld->rep;

    printf("We got back: %s\n", encoded_rep);

    if(!encoded_rep)
      return fld;

    while(*encoded_rep)
    {
      if(*encoded_rep == '\\')
      {
        encoded_rep++;

        xassert(*encoded_rep == '\\' || *encoded_rep == 'x');

        if(*encoded_rep == 'x') /* followed by 2 hex chars */
        {
          char* decoded;
          char hex[2];
          uint32_t len = 2;
          encoded_rep++;
          hex[0] = *encoded_rep++;
          hex[1] = *encoded_rep++;

          if(!isxdigit(hex[0]) || !isxdigit(hex[1]))
            THROW(ExMalformed,
                  format("dtexty_get_field: invalid hex char(s) %c%c in file "
                         "%s, offset %d",
                         hex[0], hex[1], strm->name, strm->pos));

          decoded = hex_decode(hex, &len);

          xassert(len == 1);

          *decoded_rep++ = *decoded;
        }
        else /* we just got a \\ escape */
        {
          *decoded_rep++ = '\\';
        }

        encoded_rep++;

      }

      else /* just a regular character */
        *decoded_rep++ = *encoded_rep++;

    }

    printf("Decoded as : %s\n", start_of_decoded_rep);

    fld->rep = start_of_decoded_rep;
  }

  return fld;
}
#endif

#if 0
static void
dtexty_w_bytestring(const char *elem, SDR_stream *strm, const char ty, 
                    uint32_t len, const void *vp)
{
  texty_do_indent(strm);

  if(vp)
  {
    const unsigned char* dat = (const unsigned char*)vp;
    unsigned int j;

    stream_printf(strm, "%s %c %u", elem, ty, len+1);
    if (ty == 'N' || ty == 'O')
      stream_putc(strm, ' ');
    else
      stream_putc(strm, '\n');

    printf("Data was  : ");
    for(j = 0; j != len; j++)
      printf("%c", ((const unsigned char*)vp)[j]);
    printf("\n");
    printf("Encoded as: ");
    for(j = 0; j != len; j++)
    {
      if(dat[j] == '\\')
      {
        stream_putc(strm, '\\');
        stream_putc(strm, '\\');
        printf("\\");
        printf("\\");
      }
      else if(isprint(dat[j]))
      {
        stream_putc(strm, dat[j]);
        printf("%c", dat[j]);
      }
      else
      {
        char* hex_encoded = hex_encode(dat + j, 1);

        stream_putc(strm, '\\');
        stream_putc(strm, 'x');
        stream_putc(strm, hex_encoded[0]);
        stream_putc(strm, hex_encoded[1]);
        printf("\\x%c%c", hex_encoded[0], hex_encoded[1]);
      }
    }
    printf("\n");
    
    stream_putc(strm, '\n');
  }
  else
    stream_printf(strm, "%s %c 0\n", elem, ty);
}
#endif

/* FIX: This is a TEMPORARY EXPEDIENT. There is no good reason to
   convert this to a string here, and many good reasons NOT to do
   so. The sole reason we are doing so is that texty_w_bytestring
   really needs to get split into an upper and lower half, and I want
   to do one thing at a time. -- shap */
static void
dtexty_w_buffer(const char *elem, SDR_stream *strm, const Buffer *buf)
{
  const char *s = buffer_asString(buf);
  ocmoff_t len = buffer_length(buf);

  dtexty_w_bytestring(elem, strm, 'X', len, s);
}

#if 0
static Buffer *
dtexty_r_buffer(const char *elem, SDR_stream *strm, ocmoff_t len)
{
  texty_field *fld = texty_get_field(elem, 'X', strm);
  return fld->rep;
}
#endif

/* These are exactly the same as the TEXTY versions, except that they call
   dtexty_w_bytestring and dtexty_get_field. Yuck.
*/
static void
dtexty_w_bytes(const char *elem, SDR_stream *strm, uint32_t len,
               const void *s)
{
  dtexty_w_bytestring(elem, strm, 'B', len, s);
}

static void *
dtexty_r_bytes(const char *elem, SDR_stream *strm, uint32_t len)
{
  texty_field *fld = dtexty_get_field(elem, 'B', strm);
  return fld->rep;
}

static void
dtexty_w_string(const char *elem, SDR_stream *strm, const char *s)
{
  dtexty_w_bytestring(elem, strm, 'S', s ? strlen(s) : 0, s);
}

static char *
dtexty_r_string(const char *elem, SDR_stream *strm)
{
  texty_field *fld = dtexty_get_field(elem, 'S', strm);
  return fld->rep;
}

static void
dtexty_w_obname(const char *elem, SDR_stream *strm, const char *s)
{
  dtexty_w_bytestring(elem, strm, 'N', s ? strlen(s) : 0, s);
}

static char *
dtexty_r_obname(const char *elem, SDR_stream *strm)
{
  texty_field *fld = dtexty_get_field(elem, 'N', strm);
  return fld->rep;
}

static void
dtexty_write(const char *elem, SDR_stream *strm, const void *ob)
{
  const Serializable *s = (Serializable*)ob;
  SerialType *ty = s ? s->ser_type : &Null_SerType;
  uint32_t ver = s ? s->ser_ver : ty->ver;

  dtexty_w_bytestring(elem, strm, 'O', strlen(ty->tyName), ty->tyName);

  texty_increase_indent(strm, 2);

  sdr_w_u32("obtype", strm, ty->typ);
  sdr_w_u32("obver", strm, ver);
  ty->serialize(strm, ob);

  texty_increase_indent(strm, -2);
}

static void *
dtexty_read(const char *elem, SDR_stream *strm)
{
  DeserializeInfo di;
  Serializable *s;

  (void) dtexty_get_field(elem, 'O', strm);

  di.tyConst = sdr_r_u32("obtype", strm);
  di.ver = sdr_r_u32("obver", strm);
  di.st = ser_find_type(di.tyConst);

  if (di.ver > di.st->ver)
    THROW(ExVersionError, "Unknown object version during deserialize");

  s = di.st->deserialize(&di, strm);

  assert((s == 0) || (s->ser_type == di.st));  

  return s;
}

static struct serializer sermodes[] = {
  /* AUTO streams (dummy entry) */
  { sdr_throw_onCreate,
    bin_w_u8, bin_r_u8,
    bin_w_u16, bin_r_u16,
    bin_w_u32, bin_r_u32,
    bin_w_u64, bin_r_u64,
    bin_w_buffer, bin_r_buffer,
    bin_w_bytes, bin_r_bytes,
    bin_w_string, bin_r_string,
    bin_w_obname, bin_r_obname,
    bin_write, bin_read },
  /* RAW streams */
  { sdr_noaction_onCreate,
    bin_w_u8, bin_r_u8,
    bin_w_u16, bin_r_u16,
    bin_w_u32, bin_r_u32,
    bin_w_u64, bin_r_u64,
    bin_w_buffer, bin_r_buffer,
    bin_w_bytes, bin_r_bytes,
    bin_w_string, bin_r_string,
    bin_w_obname, bin_r_obname,
    bin_write, bin_read },
  /* BINARY streams */
  { bin_onCreate,
    bin_w_u8, bin_r_u8,
    bin_w_u16, bin_r_u16,
    bin_w_u32, bin_r_u32,
    bin_w_u64, bin_r_u64,
    bin_w_buffer, bin_r_buffer,
    bin_w_bytes, bin_r_bytes,
    bin_w_string, bin_r_string,
    bin_w_obname, bin_r_obname,
    bin_write, bin_read },
  /* TEXTY streams */
  { texty_onCreate,
    texty_w_u8, texty_r_u8,
    texty_w_u16, texty_r_u16,
    texty_w_u32, texty_r_u32,
    texty_w_u64, texty_r_u64,
    texty_w_buffer, texty_r_buffer,
    texty_w_bytes, texty_r_bytes,
    texty_w_string, texty_r_string,
    texty_w_obname, texty_r_obname,
    texty_write, texty_read },
  /* DTEXTY streams */
  { dtexty_onCreate,
    texty_w_u8, texty_r_u8,
    texty_w_u16, texty_r_u16,
    texty_w_u32, texty_r_u32,
    texty_w_u64, texty_r_u64,
    dtexty_w_buffer, texty_r_buffer, /* FIX: temporary expedient */
    dtexty_w_bytes, dtexty_r_bytes,
    dtexty_w_string, dtexty_r_string,
    dtexty_w_obname, dtexty_r_obname,
    dtexty_write, dtexty_read },
};
