/*
 * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
 * v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */
#include <assert.h>
#include <string.h>

#include "dds/ddsrt/endian.h"
#include "dds/ddsrt/md5.h"
#include "dds/ddsrt/heap.h"
#include "dds/ddsi/q_bswap.h"
#include "dds/ddsi/q_config.h"
#include "dds__stream.h"
#include "dds__alloc.h"

#if DDSRT_ENDIAN == DDSRT_LITTLE_ENDIAN
#define DDS_ENDIAN true
#else
#define DDS_ENDIAN false
#endif

static void dds_stream_write (dds_ostream_t * __restrict os, const char * __restrict data, const uint32_t * __restrict ops);
static void dds_stream_read (dds_istream_t * __restrict is, char * __restrict data, const uint32_t * __restrict ops);

static void dds_ostream_grow (dds_ostream_t * __restrict st, uint32_t size)
{
  uint32_t needed = size + st->m_index;

  /* Reallocate on 4k boundry */

  uint32_t newSize = (needed & ~(uint32_t)0xfff) + 0x1000;
  uint8_t *old = st->m_buffer;

  st->m_buffer = ddsrt_realloc (old, newSize);
  st->m_size = newSize;
}

static void dds_cdr_resize (dds_ostream_t * __restrict s, uint32_t l)
{
  if (s->m_size < l + s->m_index)
    dds_ostream_grow (s, l);
}

void dds_ostream_init (dds_ostream_t * __restrict st, uint32_t size)
{
  memset (st, 0, sizeof (*st));
  st->m_index = 0;
  dds_cdr_resize (st, size);
}

void dds_ostreamBE_init (dds_ostreamBE_t * __restrict st, uint32_t size)
{
  dds_ostream_init (&st->x, size);
}

void dds_ostream_fini (dds_ostream_t * __restrict st)
{
  if (st->m_size)
    dds_free (st->m_buffer);
}

void dds_ostreamBE_fini (dds_ostreamBE_t * __restrict st)
{
  dds_ostream_fini (&st->x);
}

static void dds_cdr_alignto (dds_istream_t * __restrict s, uint32_t a)
{
  s->m_index = (s->m_index + a - 1) & ~(a - 1);
  assert (s->m_index < s->m_size);
}

static uint32_t dds_cdr_alignto_clear_and_resize (dds_ostream_t * __restrict s, uint32_t a, uint32_t extra)
{
  const uint32_t m = s->m_index % a;
  if (m == 0)
  {
    dds_cdr_resize (s, extra);
    return 0;
  }
  else
  {
    const uint32_t pad = a - m;
    dds_cdr_resize (s, pad + extra);
    for (uint32_t i = 0; i < pad; i++)
      s->m_buffer[s->m_index++] = 0;
    return pad;
  }
}

static uint32_t dds_cdr_alignto_clear_and_resize_be (dds_ostreamBE_t * __restrict s, uint32_t a, uint32_t extra)
{
  return dds_cdr_alignto_clear_and_resize (&s->x, a, extra);
}

static uint8_t dds_is_get1 (dds_istream_t * __restrict s)
{
  assert (s->m_index < s->m_size);
  uint8_t v = *(s->m_buffer + s->m_index);
  s->m_index++;
  return v;
}

static uint16_t dds_is_get2 (dds_istream_t * __restrict s)
{
  dds_cdr_alignto (s, 2);
  uint16_t v = * ((uint16_t *) (s->m_buffer + s->m_index));
  s->m_index += 2;
  return v;
}

static uint32_t dds_is_get4 (dds_istream_t * __restrict s)
{
  dds_cdr_alignto (s, 4);
  uint32_t v = * ((uint32_t *) (s->m_buffer + s->m_index));
  s->m_index += 4;
  return v;
}

static uint64_t dds_is_get8 (dds_istream_t * __restrict s)
{
  dds_cdr_alignto (s, 8);
  uint64_t v = * ((uint64_t *) (s->m_buffer + s->m_index));
  s->m_index += 8;
  return v;
}

static void dds_is_get_bytes (dds_istream_t * __restrict s, void * __restrict b, uint32_t num, uint32_t elem_size)
{
  dds_cdr_alignto (s, elem_size);
  memcpy (b, s->m_buffer + s->m_index, num * elem_size);
  s->m_index += num * elem_size;
}

static void dds_os_put1 (dds_ostream_t * __restrict s, uint8_t v)
{
  dds_cdr_resize (s, 1);
  *((uint8_t *) (s->m_buffer + s->m_index)) = v;
  s->m_index += 1;
}

static void dds_os_put2 (dds_ostream_t * __restrict s, uint16_t v)
{
  dds_cdr_alignto_clear_and_resize (s, 2, 2);
  *((uint16_t *) (s->m_buffer + s->m_index)) = v;
  s->m_index += 2;
}

static void dds_os_put4 (dds_ostream_t * __restrict s, uint32_t v)
{
  dds_cdr_alignto_clear_and_resize (s, 4, 4);
  *((uint32_t *) (s->m_buffer + s->m_index)) = v;
  s->m_index += 4;
}

static void dds_os_put8 (dds_ostream_t * __restrict s, uint64_t v)
{
  dds_cdr_alignto_clear_and_resize (s, 8, 8);
  *((uint64_t *) (s->m_buffer + s->m_index)) = v;
  s->m_index += 8;
}

static void dds_os_put1be (dds_ostreamBE_t * __restrict s, uint8_t v)
{
  dds_os_put1 (&s->x, v);
}

static void dds_os_put2be (dds_ostreamBE_t * __restrict s, uint16_t v)
{
  dds_os_put2 (&s->x, toBE2u (v));
}

static void dds_os_put4be (dds_ostreamBE_t * __restrict s, uint32_t v)
{
  dds_os_put4 (&s->x, toBE4u (v));
}

static void dds_os_put8be (dds_ostreamBE_t * __restrict s, uint64_t v)
{
  dds_os_put8 (&s->x, toBE8u (v));
}

static void dds_os_put_bytes (dds_ostream_t * __restrict s, const void * __restrict b, uint32_t l)
{
  dds_cdr_resize (s, l);
  memcpy (s->m_buffer + s->m_index, b, l);
  s->m_index += l;
}

static void dds_os_put_bytes_aligned (dds_ostream_t * __restrict s, const void * __restrict b, uint32_t n, uint32_t a)
{
  const uint32_t l = n * a;
  dds_cdr_alignto_clear_and_resize (s, a, l);
  memcpy (s->m_buffer + s->m_index, b, l);
  s->m_index += l;
}

static uint32_t get_type_size (enum dds_stream_typecode type)
{
  DDSRT_STATIC_ASSERT (DDS_OP_VAL_1BY == 1 && DDS_OP_VAL_2BY == 2 && DDS_OP_VAL_4BY == 3 && DDS_OP_VAL_8BY == 4);
  assert (type == DDS_OP_VAL_1BY || type == DDS_OP_VAL_2BY || type == DDS_OP_VAL_4BY || type == DDS_OP_VAL_8BY);
  return (uint32_t)1 << ((uint32_t) type - 1);
}

static size_t dds_stream_check_optimize1 (const dds_topic_descriptor_t * __restrict desc)
{
  const uint32_t *ops = desc->m_ops;
  uint32_t insn;
  while ((insn = *ops) != DDS_OP_RTS)
  {
    if (DDS_OP (insn) != DDS_OP_ADR)
      return 0;

    switch (DDS_OP_TYPE (insn))
    {
      case DDS_OP_VAL_1BY:
      case DDS_OP_VAL_2BY:
      case DDS_OP_VAL_4BY:
      case DDS_OP_VAL_8BY:
        if ((ops[1] % get_type_size (DDS_OP_TYPE (insn))) != 0)
          return 0;
        ops += 2;
        break;

      case DDS_OP_VAL_ARR:
        switch (DDS_OP_SUBTYPE (insn))
        {
          case DDS_OP_VAL_1BY: case DDS_OP_VAL_2BY: case DDS_OP_VAL_4BY: case DDS_OP_VAL_8BY:
            if ((ops[1] % get_type_size (DDS_OP_SUBTYPE (insn))) != 0)
              return 0;
            ops += 3;
            break;
          default:
            return 0;
        }
        break;

      default:
        return 0;
    }
  }

  return desc->m_size;
}

size_t dds_stream_check_optimize (const dds_topic_descriptor_t * __restrict desc)
{
  return dds_stream_check_optimize1 (desc);
}

static char *dds_stream_reuse_string (dds_istream_t * __restrict is, char * __restrict str, const uint32_t bound)
{
  const uint32_t length = dds_is_get4 (is);
  const void *src = is->m_buffer + is->m_index;
  if (bound)
  {
    /* FIXME: validation now rejects data containing an oversize bounded string,
       so this check is superfluous, but perhaps rejecting such a sample is the
       wrong thing to do */
    assert (str != NULL);
    memcpy (str, src, length > bound ? bound : length);
  }
  else
  {
    if (str == NULL || strlen (str) + 1 < length)
      str = dds_realloc (str, length);
    memcpy (str, src, length);
  }
  is->m_index += length;
  return str;
}

static void dds_stream_skip_forward (dds_istream_t * __restrict is, uint32_t len, const uint32_t elem_size)
{
  if (elem_size && len)
    is->m_index += len * elem_size;
}

static void dds_stream_skip_string (dds_istream_t * __restrict is)
{
  const uint32_t length = dds_is_get4 (is);
  dds_stream_skip_forward (is, length, 1);
}

static void dds_stream_write_string (dds_ostream_t * __restrict os, const char * __restrict val)
{
  uint32_t size = 1;

  if (val)
  {
    /* Type casting is done for the warning of conversion from 'size_t' to 'uint32_t', which may cause possible loss of data */
    size += (uint32_t) strlen (val);
  }

  dds_os_put4 (os, size);

  if (val)
  {
    dds_os_put_bytes (os, val, size);
  }
  else
  {
    dds_os_put1 (os, 0);
  }
}

static void dds_streamBE_write_string (dds_ostreamBE_t * __restrict os, const char * __restrict val)
{
  uint32_t size = 1;

  if (val)
  {
    /* Type casting is done for the warning of conversion from 'size_t' to 'uint32_t', which may cause possible loss of data */
    size += (uint32_t) strlen (val);
  }

  dds_os_put4be (os, size);

  if (val)
  {
    dds_os_put_bytes (&os->x, val, size);
  }
  else
  {
    dds_os_put1be (os, 0);
  }
}

#ifndef NDEBUG
static bool insn_key_ok_p (uint32_t insn)
{
  return (DDS_OP (insn) == DDS_OP_ADR && (insn & DDS_OP_FLAG_KEY) &&
          (DDS_OP_TYPE (insn) <= DDS_OP_VAL_BST ||
           (DDS_OP_TYPE (insn) == DDS_OP_VAL_ARR && DDS_OP_SUBTYPE (insn) <= DDS_OP_VAL_8BY)));
}
#endif

static uint32_t read_union_discriminant (dds_istream_t * __restrict is, enum dds_stream_typecode type)
{
  assert (type == DDS_OP_VAL_1BY || type == DDS_OP_VAL_2BY || type == DDS_OP_VAL_4BY);
  switch (type)
  {
    case DDS_OP_VAL_1BY: return dds_is_get1 (is);
    case DDS_OP_VAL_2BY: return dds_is_get2 (is);
    case DDS_OP_VAL_4BY: return dds_is_get4 (is);
    default: return 0;
  }
}

static uint32_t write_union_discriminant (dds_ostream_t * __restrict os, enum dds_stream_typecode type, const void * __restrict addr)
{
  assert (type == DDS_OP_VAL_1BY || type == DDS_OP_VAL_2BY || type == DDS_OP_VAL_4BY);
  switch (type)
  {
    case DDS_OP_VAL_1BY: { uint8_t  d8  = *((const uint8_t *) addr); dds_os_put1 (os, d8); return d8; }
    case DDS_OP_VAL_2BY: { uint16_t d16 = *((const uint16_t *) addr); dds_os_put2 (os, d16); return d16; }
    case DDS_OP_VAL_4BY: { uint32_t d32 = *((const uint32_t *) addr); dds_os_put4 (os, d32); return d32; }
    default: return 0;
  }
}

static const uint32_t *find_union_case (const uint32_t * __restrict union_ops, uint32_t disc)
{
  assert (DDS_OP_TYPE (*union_ops) == DDS_OP_VAL_UNI);
  const bool has_default = *union_ops & DDS_OP_FLAG_DEF;
  const uint32_t numcases = union_ops[2];
  const uint32_t *jeq_op = union_ops + DDS_OP_ADR_JSR (union_ops[3]);
  /* Find union case; default case is always the last one */
  assert (numcases > 0);
  uint32_t ci;
#ifndef NDEBUG
  for (ci = 0; ci < numcases; ci++)
    assert (DDS_OP (jeq_op[3 * ci]) == DDS_OP_JEQ);
#endif
  for (ci = 0; ci < numcases - (has_default ? 1 : 0); ci++, jeq_op += 3)
    if (jeq_op[1] == disc)
      return jeq_op;
  return (ci < numcases) ? jeq_op : NULL;
}

static const uint32_t *skip_sequence_insns (const uint32_t * __restrict ops, uint32_t insn)
{
  switch (DDS_OP_SUBTYPE (insn))
  {
    case DDS_OP_VAL_1BY: case DDS_OP_VAL_2BY: case DDS_OP_VAL_4BY: case DDS_OP_VAL_8BY: case DDS_OP_VAL_STR:
      return ops + 2;
    case DDS_OP_VAL_BST:
      return ops + 3; /* bound */
    case DDS_OP_VAL_SEQ: case DDS_OP_VAL_ARR: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
      const uint32_t jmp = DDS_OP_ADR_JMP (ops[3]);
      return ops + (jmp ? jmp : 4); /* FIXME: why would jmp be 0? */
    }
  }
  return NULL;
}

static const uint32_t *dds_stream_write_seq (dds_ostream_t * __restrict os, const char * __restrict addr, const uint32_t * __restrict ops, uint32_t insn)
{
  const dds_sequence_t * const seq = (const dds_sequence_t *) addr;
  const uint32_t num = seq->_length;

  dds_os_put4 (os, num);
  if (num == 0)
    return skip_sequence_insns (ops, insn);

  const enum dds_stream_typecode subtype = DDS_OP_SUBTYPE (insn);
  /* following length, stream is aligned to mod 4 */
  switch (subtype)
  {
    case DDS_OP_VAL_1BY: case DDS_OP_VAL_2BY: case DDS_OP_VAL_4BY: case DDS_OP_VAL_8BY:
      dds_os_put_bytes_aligned (os, seq->_buffer, num, get_type_size (subtype));
      return ops + 2;
    case DDS_OP_VAL_STR: {
      const char **ptr = (const char **) seq->_buffer;
      for (uint32_t i = 0; i < num; i++)
        dds_stream_write_string (os, ptr[i]);
      return ops + 2;
    }
    case DDS_OP_VAL_BST: {
      const char *ptr = (const char *) seq->_buffer;
      const uint32_t elem_size = ops[2];
      for (uint32_t i = 0; i < num; i++)
        dds_stream_write_string (os, ptr + i * elem_size);
      return ops + 3;
    }
    case DDS_OP_VAL_SEQ: case DDS_OP_VAL_ARR: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
      const uint32_t elem_size = ops[2];
      const uint32_t jmp = DDS_OP_ADR_JMP (ops[3]);
      uint32_t const * const jsr_ops = ops + DDS_OP_ADR_JSR (ops[3]);
      const char *ptr = (const char *) seq->_buffer;
      for (uint32_t i = 0; i < num; i++)
        dds_stream_write (os, ptr + i * elem_size, jsr_ops);
      return ops + (jmp ? jmp : 4); /* FIXME: why would jmp be 0? */
    }
  }
  return NULL;
}

static const uint32_t *dds_stream_write_arr (dds_ostream_t * __restrict os, const char * __restrict addr, const uint32_t * __restrict ops, uint32_t insn)
{
  const enum dds_stream_typecode subtype = DDS_OP_SUBTYPE (insn);
  const uint32_t num = ops[2];
  switch (subtype)
  {
    case DDS_OP_VAL_1BY: case DDS_OP_VAL_2BY: case DDS_OP_VAL_4BY: case DDS_OP_VAL_8BY:
      dds_os_put_bytes_aligned (os, addr, num, get_type_size (subtype));
      return ops + 3;
    case DDS_OP_VAL_STR: {
      const char **ptr = (const char **) addr;
      for (uint32_t i = 0; i < num; i++)
        dds_stream_write_string (os, ptr[i]);
      return ops + 3;
    }
    case DDS_OP_VAL_BST: {
      const char *ptr = (const char *) addr;
      const uint32_t elem_size = ops[4];
      for (uint32_t i = 0; i < num; i++)
        dds_stream_write_string (os, ptr + i * elem_size);
      return ops + 5;
      break;
    }
    case DDS_OP_VAL_SEQ: case DDS_OP_VAL_ARR: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
      const uint32_t * jsr_ops = ops + DDS_OP_ADR_JSR (ops[3]);
      const uint32_t jmp = DDS_OP_ADR_JMP (ops[3]);
      const uint32_t elem_size = ops[4];
      for (uint32_t i = 0; i < num; i++)
        dds_stream_write (os, addr + i * elem_size, jsr_ops);
      return ops + (jmp ? jmp : 5);
    }
  }
  return NULL;
}

static const uint32_t *dds_stream_write_uni (dds_ostream_t * __restrict os, const char * __restrict discaddr, const char * __restrict baseaddr, const uint32_t * __restrict ops, uint32_t insn)
{
  const uint32_t disc = write_union_discriminant (os, DDS_OP_SUBTYPE (insn), discaddr);
  uint32_t const * const jeq_op = find_union_case (ops, disc);
  ops += DDS_OP_ADR_JMP (ops[3]);
  if (jeq_op)
  {
    const enum dds_stream_typecode valtype = DDS_JEQ_TYPE (jeq_op[0]);
    const void *valaddr = baseaddr + jeq_op[2];
    switch (valtype)
    {
      case DDS_OP_VAL_1BY: dds_os_put1 (os, *(const uint8_t *) valaddr); break;
      case DDS_OP_VAL_2BY: dds_os_put2 (os, *(const uint16_t *) valaddr); break;
      case DDS_OP_VAL_4BY: dds_os_put4 (os, *(const uint32_t *) valaddr); break;
      case DDS_OP_VAL_8BY: dds_os_put8 (os, *(const uint64_t *) valaddr); break;
      case DDS_OP_VAL_STR: dds_stream_write_string (os, *(const char **) valaddr); break;
      case DDS_OP_VAL_BST: dds_stream_write_string (os, (const char *) valaddr); break;
      case DDS_OP_VAL_SEQ: case DDS_OP_VAL_ARR: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU:
        dds_stream_write (os, valaddr, jeq_op + DDS_OP_ADR_JSR (jeq_op[0]));
        break;
    }
  }
  return ops;
}

static void dds_stream_write (dds_ostream_t * __restrict os, const char * __restrict data, const uint32_t * __restrict ops)
{
  uint32_t insn;
  while ((insn = *ops) != DDS_OP_RTS)
  {
    switch (DDS_OP (insn))
    {
      case DDS_OP_ADR: {
        const void *addr = data + ops[1];
        switch (DDS_OP_TYPE (insn))
        {
          case DDS_OP_VAL_1BY: dds_os_put1 (os, *((const uint8_t *) addr)); ops += 2; break;
          case DDS_OP_VAL_2BY: dds_os_put2 (os, *((const uint16_t *) addr)); ops += 2; break;
          case DDS_OP_VAL_4BY: dds_os_put4 (os, *((const uint32_t *) addr)); ops += 2; break;
          case DDS_OP_VAL_8BY: dds_os_put8 (os, *((const uint64_t *) addr)); ops += 2; break;
          case DDS_OP_VAL_STR: dds_stream_write_string (os, *((const char **) addr)); ops += 2; break;
          case DDS_OP_VAL_BST: dds_stream_write_string (os, (const char *) addr); ops += 3; break;
          case DDS_OP_VAL_SEQ: ops = dds_stream_write_seq (os, addr, ops, insn); break;
          case DDS_OP_VAL_ARR: ops = dds_stream_write_arr (os, addr, ops, insn); break;
          case DDS_OP_VAL_UNI: ops = dds_stream_write_uni (os, addr, data, ops, insn); break;
          case DDS_OP_VAL_STU: abort (); break;
        }
        break;
      }
      case DDS_OP_JSR: {
        dds_stream_write (os, data, ops + DDS_OP_JUMP (insn));
        ops++;
        break;
      }
      case DDS_OP_RTS: case DDS_OP_JEQ: {
        abort ();
        break;
      }
    }
  }
}

static void realloc_sequence_buffer_if_needed (dds_sequence_t * __restrict seq, uint32_t num, uint32_t elem_size, bool init)
{
  const uint32_t size = num * elem_size;

  /* maintain max sequence length (may not have been set by caller) */
  if (seq->_length > seq->_maximum)
    seq->_maximum = seq->_length;

  if (num > seq->_maximum && seq->_release)
  {
    seq->_buffer = ddsrt_realloc (seq->_buffer, size);
    if (init)
    {
      const uint32_t off = seq->_maximum * elem_size;
      memset (seq->_buffer + off, 0, size - off);
    }
    seq->_maximum = num;
  }
  else if (num > 0 && seq->_maximum == 0)
  {
    seq->_buffer = ddsrt_malloc (size);
    if (init)
      memset (seq->_buffer, 0, size);
    seq->_release = true;
    seq->_maximum = num;
  }
}

static const uint32_t *dds_stream_read_seq (dds_istream_t * __restrict is, char * __restrict addr, const uint32_t * __restrict ops, uint32_t insn)
{
  dds_sequence_t * const seq = (dds_sequence_t *) addr;
  const enum dds_stream_typecode subtype = DDS_OP_SUBTYPE (insn);
  const uint32_t num = dds_is_get4 (is);
  if (num == 0)
  {
    seq->_length = 0;
    return skip_sequence_insns (ops, insn);
  }

  switch (subtype)
  {
    case DDS_OP_VAL_1BY: case DDS_OP_VAL_2BY: case DDS_OP_VAL_4BY: case DDS_OP_VAL_8BY: {
      const uint32_t elem_size = get_type_size (subtype);
      realloc_sequence_buffer_if_needed (seq, num, elem_size, false);
      seq->_length = (num <= seq->_maximum) ? num : seq->_maximum;
      dds_is_get_bytes (is, seq->_buffer, seq->_length, elem_size);
      if (seq->_length < num)
        dds_stream_skip_forward (is, num - seq->_length, elem_size);
      return ops + 2;
    }
    case DDS_OP_VAL_STR: {
      realloc_sequence_buffer_if_needed (seq, num, sizeof (char *), true);
      seq->_length = (num <= seq->_maximum) ? num : seq->_maximum;
      char **ptr = (char **) seq->_buffer;
      for (uint32_t i = 0; i < seq->_length; i++)
        ptr[i] = dds_stream_reuse_string (is, ptr[i], 0);
      for (uint32_t i = seq->_length; i < num; i++)
        dds_stream_skip_string (is);
      return ops + 2;
    }
    case DDS_OP_VAL_BST: {
      const uint32_t elem_size = ops[2];
      realloc_sequence_buffer_if_needed (seq, num, elem_size, false);
      seq->_length = (num <= seq->_maximum) ? num : seq->_maximum;
      char *ptr = (char *) seq->_buffer;
      for (uint32_t i = 0; i < seq->_length; i++)
        (void) dds_stream_reuse_string (is, ptr + i * elem_size, elem_size);
      for (uint32_t i = seq->_length; i < num; i++)
        dds_stream_skip_string (is);
      return ops + 3;
    }
    case DDS_OP_VAL_SEQ: case DDS_OP_VAL_ARR: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
      const uint32_t elem_size = ops[2];
      const uint32_t jmp = DDS_OP_ADR_JMP (ops[3]);
      uint32_t const * const jsr_ops = ops + DDS_OP_ADR_JSR (ops[3]);
      realloc_sequence_buffer_if_needed (seq, num, elem_size, true);
      seq->_length = (num <= seq->_maximum) ? num : seq->_maximum;
      char *ptr = (char *) seq->_buffer;
      for (uint32_t i = 0; i < num; i++)
        dds_stream_read (is, ptr + i * elem_size, jsr_ops);
      return ops + (jmp ? jmp : 4); /* FIXME: why would jmp be 0? */
    }
  }
  return NULL;
}

static const uint32_t *dds_stream_read_arr (dds_istream_t * __restrict is, char * __restrict addr, const uint32_t * __restrict ops, uint32_t insn)
{
  const enum dds_stream_typecode subtype = DDS_OP_SUBTYPE (insn);
  const uint32_t num = ops[2];
  switch (subtype)
  {
    case DDS_OP_VAL_1BY: case DDS_OP_VAL_2BY: case DDS_OP_VAL_4BY: case DDS_OP_VAL_8BY: {
      const uint32_t elem_size = get_type_size (subtype);
      dds_is_get_bytes (is, addr, num, elem_size);
      return ops + 3;
    }
    case DDS_OP_VAL_STR: {
      char **ptr = (char **) addr;
      for (uint32_t i = 0; i < num; i++)
        ptr[i] = dds_stream_reuse_string (is, ptr[i], 0);
      return ops + 3;
    }
    case DDS_OP_VAL_BST: {
      char *ptr = (char *) addr;
      const uint32_t elem_size = ops[4];
      for (uint32_t i = 0; i < num; i++)
        (void) dds_stream_reuse_string (is, ptr + i * elem_size, elem_size);
      return ops + 5;
    }
    case DDS_OP_VAL_SEQ: case DDS_OP_VAL_ARR: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
      const uint32_t *jsr_ops = ops + DDS_OP_ADR_JSR (ops[3]);
      const uint32_t jmp = DDS_OP_ADR_JMP (ops[3]);
      const uint32_t elem_size = ops[4];
      for (uint32_t i = 0; i < num; i++)
        dds_stream_read (is, addr + i * elem_size, jsr_ops);
      return ops + (jmp ? jmp : 5);
    }
  }
  return NULL;
}

static const uint32_t *dds_stream_read_uni (dds_istream_t * __restrict is, char * __restrict discaddr, char * __restrict baseaddr, const uint32_t * __restrict ops, uint32_t insn)
{
  const uint32_t disc = read_union_discriminant (is, DDS_OP_SUBTYPE (insn));
  switch (DDS_OP_SUBTYPE (insn))
  {
    case DDS_OP_VAL_1BY: *((uint8_t *) discaddr) = (uint8_t) disc; break;
    case DDS_OP_VAL_2BY: *((uint16_t *) discaddr) = (uint16_t) disc; break;
    case DDS_OP_VAL_4BY: *((uint32_t *) discaddr) = disc; break;
    default: break;
  }
  uint32_t const * const jeq_op = find_union_case (ops, disc);
  ops += DDS_OP_ADR_JMP (ops[3]);
  if (jeq_op)
  {
    const enum dds_stream_typecode valtype = DDS_JEQ_TYPE (jeq_op[0]);
    void *valaddr = baseaddr + jeq_op[2];
    switch (valtype)
    {
      case DDS_OP_VAL_1BY: *((uint8_t *) valaddr) = dds_is_get1 (is); break;
      case DDS_OP_VAL_2BY: *((uint16_t *) valaddr) = dds_is_get2 (is); break;
      case DDS_OP_VAL_4BY: *((uint32_t *) valaddr) = dds_is_get4 (is); break;
      case DDS_OP_VAL_8BY: *((uint64_t *) valaddr) = dds_is_get8 (is); break;
      case DDS_OP_VAL_STR: *(char **) valaddr = dds_stream_reuse_string (is, *((char **) valaddr), 0); break;
      case DDS_OP_VAL_BST: case DDS_OP_VAL_SEQ: case DDS_OP_VAL_ARR: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU:
        dds_stream_read (is, valaddr, jeq_op + DDS_OP_ADR_JSR (jeq_op[0]));
        break;
    }
  }
  return ops;
}

static void dds_stream_read (dds_istream_t * __restrict is, char * __restrict data, const uint32_t * __restrict ops)
{
  uint32_t insn;
  while ((insn = *ops) != DDS_OP_RTS)
  {
    switch (DDS_OP (insn))
    {
      case DDS_OP_ADR: {
        void *addr = data + ops[1];
        switch (DDS_OP_TYPE (insn))
        {
          case DDS_OP_VAL_1BY: *((uint8_t *) addr) = dds_is_get1 (is); ops += 2; break;
          case DDS_OP_VAL_2BY: *((uint16_t *) addr) = dds_is_get2 (is); ops += 2; break;
          case DDS_OP_VAL_4BY: *((uint32_t *) addr) = dds_is_get4 (is); ops += 2; break;
          case DDS_OP_VAL_8BY: *((uint64_t *) addr) = dds_is_get8 (is); ops += 2; break;
          case DDS_OP_VAL_STR: *((char **) addr) = dds_stream_reuse_string (is, *((char **) addr), 0); ops += 2; break;
          case DDS_OP_VAL_BST: dds_stream_reuse_string (is, (char *) addr, ops[2]); ops += 3; break;
          case DDS_OP_VAL_SEQ: ops = dds_stream_read_seq (is, addr, ops, insn); break;
          case DDS_OP_VAL_ARR: ops = dds_stream_read_arr (is, addr, ops, insn); break;
          case DDS_OP_VAL_UNI: ops = dds_stream_read_uni (is, addr, data, ops, insn); break;
          case DDS_OP_VAL_STU: abort (); break;
        }
        break;
      }
      case DDS_OP_JSR: {
        dds_stream_read (is, data, ops + DDS_OP_JUMP (insn));
        ops++;
        break;
      }
      case DDS_OP_RTS: case DDS_OP_JEQ: {
        abort ();
        break;
      }
    }
  }
}

/*******************************************************************************************
 **
 **  Validation and conversion to native endian.
 **
 *******************************************************************************************/

/* Limit the size of the input buffer so we don't need to worry about adding
   padding and a primitive type overflowing our offset */
#define CDR_SIZE_MAX ((uint32_t) 0xfffffff0)

static bool stream_normalize (char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap, const uint32_t * __restrict ops);

static uint32_t check_align_prim (uint32_t off, uint32_t size, uint32_t a_lg2)
{
  assert (a_lg2 <= 3);
  const uint32_t a = 1u << a_lg2;
  assert (size <= CDR_SIZE_MAX);
  assert (off <= size);
  const uint32_t off1 = (off + a - 1) & ~(a - 1);
  assert (off <= off1 && off1 <= CDR_SIZE_MAX);
  if (size < off1 + a)
    return UINT32_MAX;
  return off1;
}

static uint32_t check_align_prim_many (uint32_t off, uint32_t size, uint32_t a_lg2, uint32_t n)
{
  assert (a_lg2 <= 3);
  const uint32_t a = 1u << a_lg2;
  assert (size <= CDR_SIZE_MAX);
  assert (off <= size);
  const uint32_t off1 = (off + a - 1) & ~(a - 1);
  assert (off <= off1 && off1 <= CDR_SIZE_MAX);
  if (size < off1 || ((size - off1) >> a_lg2) < n)
    return UINT32_MAX;
  return off1;
}

static bool normalize_uint8 (uint32_t *off, uint32_t size)
{
  if (*off == size)
    return false;
  (*off)++;
  return true;
}

static bool normalize_uint16 (char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap)
{
  if ((*off = check_align_prim (*off, size, 1)) == UINT32_MAX)
    return false;
  if (bswap)
    *((uint16_t *) (data + *off)) = bswap2u (*((uint16_t *) (data + *off)));
  (*off) += 2;
  return true;
}

static bool normalize_uint32 (char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap)
{
  if ((*off = check_align_prim (*off, size, 2)) == UINT32_MAX)
    return false;
  if (bswap)
    *((uint32_t *) (data + *off)) = bswap4u (*((uint32_t *) (data + *off)));
  (*off) += 4;
  return true;
}

static bool read_and_normalize_uint32 (uint32_t * __restrict val, char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap)
{
  if ((*off = check_align_prim (*off, size, 2)) == UINT32_MAX)
    return false;
  if (bswap)
    *((uint32_t *) (data + *off)) = bswap4u (*((uint32_t *) (data + *off)));
  *val = *((uint32_t *) (data + *off));
  (*off) += 4;
  return true;
}

static bool normalize_uint64 (char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap)
{
  if ((*off = check_align_prim (*off, size, 3)) == UINT32_MAX)
    return false;
  if (bswap)
    *((uint64_t *) (data + *off)) = bswap8u (*((uint64_t *) (data + *off)));
  (*off) += 8;
  return true;
}

static bool normalize_string (char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap, size_t maxsz)
{
  uint32_t sz;
  if (!read_and_normalize_uint32 (&sz, data, off, size, bswap))
    return false;
  if (sz == 0 || size - *off < sz || maxsz < sz)
    return false;
  if (data[*off + sz - 1] != 0)
    return false;
  *off += sz;
  return true;
}

static bool normalize_primarray (char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap, uint32_t num, enum dds_stream_typecode type)
{
  switch (type)
  {
    case DDS_OP_VAL_1BY:
      if ((*off = check_align_prim_many (*off, size, 0, num)) == UINT32_MAX)
        return false;
      *off += num;
      return true;
    case DDS_OP_VAL_2BY:
      if ((*off = check_align_prim_many (*off, size, 1, num)) == UINT32_MAX)
        return false;
      if (bswap)
      {
        uint16_t *xs = (uint16_t *) (data + *off);
        for (uint32_t i = 0; i < num; i++)
          xs[i] = bswap2u (xs[i]);
      }
      *off += 2 * num;
      return true;
    case DDS_OP_VAL_4BY:
      if ((*off = check_align_prim_many (*off, size, 2, num)) == UINT32_MAX)
        return false;
      if (bswap)
      {
        uint32_t *xs = (uint32_t *) (data + *off);
        for (uint32_t i = 0; i < num; i++)
          xs[i] = bswap4u (xs[i]);
      }
      *off += 4 * num;
      return true;
    case DDS_OP_VAL_8BY:
      if ((*off = check_align_prim_many (*off, size, 3, num)) == UINT32_MAX)
        return false;
      if (bswap)
      {
        uint64_t *xs = (uint64_t *) (data + *off);
        for (uint32_t i = 0; i < num; i++)
          xs[i] = bswap8u (xs[i]);
      }
      *off += 8 * num;
      return true;
    default:
      abort ();
      break;
  }
  return false;
}

static const uint32_t *normalize_seq (char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap, const uint32_t * __restrict ops, uint32_t insn)
{
  const enum dds_stream_typecode subtype = DDS_OP_SUBTYPE (insn);
  uint32_t num;
  if (!read_and_normalize_uint32 (&num, data, off, size, bswap))
    return NULL;
  if (num == 0)
    return skip_sequence_insns (ops, insn);
  switch (subtype)
  {
    case DDS_OP_VAL_1BY: case DDS_OP_VAL_2BY: case DDS_OP_VAL_4BY: case DDS_OP_VAL_8BY:
      if (!normalize_primarray (data, off, size, bswap, num, subtype))
        return NULL;
      return ops + 2;
    case DDS_OP_VAL_STR: case DDS_OP_VAL_BST: {
      const size_t maxsz = (subtype == DDS_OP_VAL_STR) ? SIZE_MAX : ops[2];
      for (uint32_t i = 0; i < num; i++)
        if (!normalize_string (data, off, size, bswap, maxsz))
          return NULL;
      return ops + (subtype == DDS_OP_VAL_STR ? 2 : 3);
    }
    case DDS_OP_VAL_SEQ: case DDS_OP_VAL_ARR: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
      const uint32_t jmp = DDS_OP_ADR_JMP (ops[3]);
      uint32_t const * const jsr_ops = ops + DDS_OP_ADR_JSR (ops[3]);
      for (uint32_t i = 0; i < num; i++)
        if (!stream_normalize (data, off, size, bswap, jsr_ops))
          return NULL;
      return ops + (jmp ? jmp : 4); /* FIXME: why would jmp be 0? */
    }
  }
  return NULL;
}

static const uint32_t *normalize_arr (char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap, const uint32_t * __restrict ops, uint32_t insn)
{
  const enum dds_stream_typecode subtype = DDS_OP_SUBTYPE (insn);
  const uint32_t num = ops[2];
  switch (subtype)
  {
    case DDS_OP_VAL_1BY: case DDS_OP_VAL_2BY: case DDS_OP_VAL_4BY: case DDS_OP_VAL_8BY:
      if (!normalize_primarray (data, off, size, bswap, num, subtype))
        return NULL;
      return ops + 3;
    case DDS_OP_VAL_STR: case DDS_OP_VAL_BST: {
      const size_t maxsz = (subtype == DDS_OP_VAL_STR) ? SIZE_MAX : ops[4];
      for (uint32_t i = 0; i < num; i++)
        if (!normalize_string (data, off, size, bswap, maxsz))
          return NULL;
      return ops + (subtype == DDS_OP_VAL_STR ? 3 : 5);
    }
    case DDS_OP_VAL_SEQ: case DDS_OP_VAL_ARR: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
      const uint32_t *jsr_ops = ops + DDS_OP_ADR_JSR (ops[3]);
      const uint32_t jmp = DDS_OP_ADR_JMP (ops[3]);
      for (uint32_t i = 0; i < num; i++)
        if (!stream_normalize (data, off, size, bswap, jsr_ops))
          return NULL;
      return ops + (jmp ? jmp : 5);
    }
  }
  return NULL;
}

static bool normalize_uni_disc (uint32_t * __restrict val, char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap, enum dds_stream_typecode disctype)
{
  switch (disctype)
  {
    case DDS_OP_VAL_1BY:
      if ((*off = check_align_prim (*off, size, 0)) == UINT32_MAX)
        return false;
      *val = *((uint8_t *) (data + *off));
      (*off) += 1;
      return true;
    case DDS_OP_VAL_2BY:
      if ((*off = check_align_prim (*off, size, 1)) == UINT32_MAX)
        return false;
      if (bswap)
        *((uint16_t *) (data + *off)) = bswap2u (*((uint16_t *) (data + *off)));
      *val = *((uint16_t *) (data + *off));
      (*off) += 2;
      return true;
    case DDS_OP_VAL_4BY:
      if ((*off = check_align_prim (*off, size, 2)) == UINT32_MAX)
        return false;
      if (bswap)
        *((uint32_t *) (data + *off)) = bswap4u (*((uint32_t *) (data + *off)));
      *val = *((uint32_t *) (data + *off));
      (*off) += 4;
      return true;
    default:
      abort ();
  }
  return false;
}

static const uint32_t *normalize_uni (char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap, const uint32_t * __restrict ops, uint32_t insn)
{
  uint32_t disc;
  if (!normalize_uni_disc (&disc, data, off, size, bswap, DDS_OP_SUBTYPE (insn)))
    return NULL;
  uint32_t const * const jeq_op = find_union_case (ops, disc);
  ops += DDS_OP_ADR_JMP (ops[3]);
  if (jeq_op)
  {
    const enum dds_stream_typecode valtype = DDS_JEQ_TYPE (jeq_op[0]);
    switch (valtype)
    {
      case DDS_OP_VAL_1BY: if (!normalize_uint8 (off, size)) return NULL; break;
      case DDS_OP_VAL_2BY: if (!normalize_uint16 (data, off, size, bswap)) return NULL; break;
      case DDS_OP_VAL_4BY: if (!normalize_uint32 (data, off, size, bswap)) return NULL; break;
      case DDS_OP_VAL_8BY: if (!normalize_uint64 (data, off, size, bswap)) return NULL; break;
      case DDS_OP_VAL_STR: if (!normalize_string (data, off, size, bswap, SIZE_MAX)) return NULL; break;
      case DDS_OP_VAL_BST: case DDS_OP_VAL_SEQ: case DDS_OP_VAL_ARR: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU:
        if (!stream_normalize (data, off, size, bswap, jeq_op + DDS_OP_ADR_JSR (jeq_op[0])))
          return NULL;
        break;
    }
  }
  return ops;
}

static bool stream_normalize (char * __restrict data, uint32_t * __restrict off, uint32_t size, bool bswap, const uint32_t * __restrict ops)
{
  uint32_t insn;
  while ((insn = *ops) != DDS_OP_RTS)
  {
    switch (DDS_OP (insn))
    {
      case DDS_OP_ADR: {
        switch (DDS_OP_TYPE (insn))
        {
          case DDS_OP_VAL_1BY: if (!normalize_uint8 (off, size)) return false; ops += 2; break;
          case DDS_OP_VAL_2BY: if (!normalize_uint16 (data, off, size, bswap)) return false; ops += 2; break;
          case DDS_OP_VAL_4BY: if (!normalize_uint32 (data, off, size, bswap)) return false; ops += 2; break;
          case DDS_OP_VAL_8BY: if (!normalize_uint64 (data, off, size, bswap)) return false; ops += 2; break;
          case DDS_OP_VAL_STR: if (!normalize_string (data, off, size, bswap, SIZE_MAX)) return false; ops += 2; break;
          case DDS_OP_VAL_BST: if (!normalize_string (data, off, size, bswap, ops[2])) return false; ops += 3; break;
          case DDS_OP_VAL_SEQ: ops = normalize_seq (data, off, size, bswap, ops, insn); if (!ops) return false; break;
          case DDS_OP_VAL_ARR: ops = normalize_arr (data, off, size, bswap, ops, insn); if (!ops) return false; break;
          case DDS_OP_VAL_UNI: ops = normalize_uni (data, off, size, bswap, ops, insn); if (!ops) return false; break;
          case DDS_OP_VAL_STU: abort (); break;
        }
        break;
      }
      case DDS_OP_JSR: {
        if (!stream_normalize (data, off, size, bswap, ops + DDS_OP_JUMP (insn)))
          return false;
        ops++;
        break;
      }
      case DDS_OP_RTS: case DDS_OP_JEQ: {
        abort ();
        break;
      }
    }
  }
  return true;
}

static bool stream_normalize_key (void * __restrict data, uint32_t size, bool bswap, const struct dds_topic_descriptor * __restrict desc)
{
  uint32_t off = 0;
  for (uint32_t i = 0; i < desc->m_nkeys; i++)
  {
    const uint32_t *op = desc->m_ops + desc->m_keys[i].m_index;
    assert (insn_key_ok_p (*op));
    switch (DDS_OP_TYPE (*op))
    {
      case DDS_OP_VAL_1BY: if (!normalize_uint8 (&off, size)) return false; break;
      case DDS_OP_VAL_2BY: if (!normalize_uint16 (data, &off, size, bswap)) return false; break;
      case DDS_OP_VAL_4BY: if (!normalize_uint32 (data, &off, size, bswap)) return false; break;
      case DDS_OP_VAL_8BY: if (!normalize_uint64 (data, &off, size, bswap)) return false; break;
      case DDS_OP_VAL_STR: if (!normalize_string (data, &off, size, bswap, SIZE_MAX)) return false; break;
      case DDS_OP_VAL_BST: if (!normalize_string (data, &off, size, bswap, op[2])) return false; break;
      case DDS_OP_VAL_ARR: if (!normalize_arr (data, &off, size, bswap, op, *op)) return false; break;
      case DDS_OP_VAL_SEQ: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU:
        abort ();
        break;
    }
  }
  return true;
}

bool dds_stream_normalize (void * __restrict data, uint32_t size, bool bswap, const struct ddsi_sertopic_default * __restrict topic, bool just_key)
{
  if (size > CDR_SIZE_MAX)
    return false;
  if (just_key)
    return stream_normalize_key (data, size, bswap, topic->type);
  else
  {
    uint32_t off = 0;
    return stream_normalize (data, &off, size, bswap, topic->type->m_ops);
  }
}

/*******************************************************************************************
 **
 **  Read/write of samples and keys -- i.e., DDSI payloads.
 **
 *******************************************************************************************/

void dds_stream_read_sample (dds_istream_t * __restrict is, void * __restrict data, const struct ddsi_sertopic_default * __restrict topic)
{
  const struct dds_topic_descriptor *desc = topic->type;
  if (topic->opt_size)
    dds_is_get_bytes (is, data, desc->m_size, 1);
  else
  {
    if (desc->m_flagset & DDS_TOPIC_CONTAINS_UNION)
    {
      /* Switching union cases causes big trouble if some cases have sequences or strings,
         and other cases have other things mapped to those addresses.  So, pretend to be
         nice by freeing whatever was allocated, then clearing all memory.  This will
         make any preallocated buffers go to waste, but it does allow reusing the message
         from read-to-read, at the somewhat reasonable price of a slower deserialization
         and not being able to use preallocated sequences in topics containing unions. */
      dds_sample_free_contents (data, desc->m_ops);
      memset (data, 0, desc->m_size);
    }
    dds_stream_read (is, data, desc->m_ops);
  }
}

void dds_stream_write_sample (dds_ostream_t * __restrict os, const void * __restrict data, const struct ddsi_sertopic_default * __restrict topic)
{
  const struct dds_topic_descriptor *desc = topic->type;
  if (topic->opt_size && desc->m_align && (os->m_index % desc->m_align) == 0)
    dds_os_put_bytes (os, data, desc->m_size);
  else
    dds_stream_write (os, data, desc->m_ops);
}

void dds_stream_read_key (dds_istream_t * __restrict is, char * __restrict sample, const struct ddsi_sertopic_default * __restrict topic)
{
  const dds_topic_descriptor_t *desc = topic->type;
  for (uint32_t i = 0; i < desc->m_nkeys; i++)
  {
    const uint32_t *op = desc->m_ops + desc->m_keys[i].m_index;
    char *dst = sample + op[1];
    assert (insn_key_ok_p (*op));
    switch (DDS_OP_TYPE (*op))
    {
      case DDS_OP_VAL_1BY: *((uint8_t *) dst) = dds_is_get1 (is); break;
      case DDS_OP_VAL_2BY: *((uint16_t *) dst) = dds_is_get2 (is); break;
      case DDS_OP_VAL_4BY: *((uint32_t *) dst) = dds_is_get4 (is); break;
      case DDS_OP_VAL_8BY: *((uint64_t *) dst) = dds_is_get8 (is); break;
      case DDS_OP_VAL_STR: *((char **) dst) = dds_stream_reuse_string (is, *((char **) dst), 0); break;
      case DDS_OP_VAL_BST: dds_stream_reuse_string (is, dst, op[2]); break;
      case DDS_OP_VAL_ARR:
        dds_is_get_bytes (is, dst, op[2], get_type_size (DDS_OP_SUBTYPE (*op)));
        break;
      case DDS_OP_VAL_SEQ: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU:
        abort ();
        break;
    }
  }
}

void dds_stream_write_key (dds_ostream_t * __restrict os, const char * __restrict sample, const struct ddsi_sertopic_default * __restrict topic)
{
  const struct dds_topic_descriptor *desc = (const struct dds_topic_descriptor *) topic->type;
  for (uint32_t i = 0; i < desc->m_nkeys; i++)
  {
    const uint32_t *insnp = desc->m_ops + desc->m_keys[i].m_index;
    const void *src = sample + insnp[1];
    assert (insn_key_ok_p (*insnp));
    switch (DDS_OP_TYPE (*insnp))
    {
      case DDS_OP_VAL_1BY: dds_os_put1 (os, *((uint8_t *) src)); break;
      case DDS_OP_VAL_2BY: dds_os_put2 (os, *((uint16_t *) src)); break;
      case DDS_OP_VAL_4BY: dds_os_put4 (os, *((uint32_t *) src)); break;
      case DDS_OP_VAL_8BY: dds_os_put8 (os, *((uint64_t *) src)); break;
      case DDS_OP_VAL_STR: dds_stream_write_string (os, *(char **) src); break;
      case DDS_OP_VAL_BST: dds_stream_write_string (os, src); break;
      case DDS_OP_VAL_ARR: {
        const uint32_t elem_size = get_type_size (DDS_OP_SUBTYPE (*insnp));
        const uint32_t num = insnp[2];
        dds_cdr_alignto_clear_and_resize(os, elem_size, num * elem_size);
        dds_os_put_bytes (os, src, num * elem_size);
        break;
      }
      case DDS_OP_VAL_SEQ: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
        abort ();
        break;
      }
    }
  }
}

#if DDSRT_ENDIAN == DDSRT_LITTLE_ENDIAN
static void dds_stream_swap_insitu (void * __restrict vbuf, uint32_t size, uint32_t num)
{
  assert (size == 1 || size == 2 || size == 4 || size == 8);
  switch (size)
  {
    case 1:
      break;
    case 2: {
      uint16_t *buf = vbuf;
      for (uint32_t i = 0; i < num; i++)
        buf[i] = bswap2u (buf[i]);
      break;
    }
    case 4: {
      uint32_t *buf = vbuf;
      for (uint32_t i = 0; i < num; i++)
        buf[i] = bswap4u (buf[i]);
      break;
    }
    case 8: {
      uint64_t *buf = vbuf;
      for (uint32_t i = 0; i < num; i++)
        buf[i] = bswap8u (buf[i]);
      break;
    }
  }
}

void dds_stream_write_keyBE (dds_ostreamBE_t * __restrict os, const char * __restrict sample, const struct ddsi_sertopic_default * __restrict topic)
{
  const struct dds_topic_descriptor *desc = (const struct dds_topic_descriptor *) topic->type;
  for (uint32_t i = 0; i < desc->m_nkeys; i++)
  {
    const uint32_t *insnp = desc->m_ops + desc->m_keys[i].m_index;
    const void *src = sample + insnp[1];
    assert (insn_key_ok_p (*insnp));
    switch (DDS_OP_TYPE (*insnp))
    {
      case DDS_OP_VAL_1BY: dds_os_put1be (os, *((uint8_t *) src)); break;
      case DDS_OP_VAL_2BY: dds_os_put2be (os, *((uint16_t *) src)); break;
      case DDS_OP_VAL_4BY: dds_os_put4be (os, *((uint32_t *) src)); break;
      case DDS_OP_VAL_8BY: dds_os_put8be (os, *((uint64_t *) src)); break;
      case DDS_OP_VAL_STR: dds_streamBE_write_string (os, *(char **) src); break;
      case DDS_OP_VAL_BST: dds_streamBE_write_string (os, src); break;
      case DDS_OP_VAL_ARR: {
        const uint32_t elem_size = get_type_size (DDS_OP_SUBTYPE (*insnp));
        const uint32_t num = insnp[2];
        dds_cdr_alignto_clear_and_resize_be (os, elem_size, num * elem_size);
        void * const dst = os->x.m_buffer + os->x.m_index;
        dds_os_put_bytes (&os->x, src, num * elem_size);
        dds_stream_swap_insitu (dst, elem_size, num);
        break;
      }
      case DDS_OP_VAL_SEQ: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
        abort ();
        break;
      }
    }
  }
}
#elif DDSRT_ENDIAN == DDSRT_BIG_ENDIAN
void dds_stream_write_keyBE (dds_ostreamBE_t * __restrict os, const char * __restrict sample, const struct ddsi_sertopic_default * __restrict topic)
{
  dds_stream_write_key (&os->x, sample, topic);
}
#else
#error "DDSRT_ENDIAN neither LITTLE nor BIG"
#endif

/*******************************************************************************************
 **
 **  Extracting key/keyhash (the only difference that a keyhash MUST be big-endian,
 **  padding MUST be cleared, and that it may be necessary to run the value through
 **  MD5.
 **
 *******************************************************************************************/

static void dds_stream_extract_key_from_data1 (dds_istream_t * __restrict is, dds_ostream_t * __restrict os, const uint32_t * __restrict ops, uint32_t * __restrict keys_remaining);

static void dds_stream_extract_key_from_key_prim_op (dds_istream_t * __restrict is, dds_ostream_t * __restrict os, const uint32_t * __restrict op)
{
  assert ((*op & DDS_OP_FLAG_KEY) && ((DDS_OP (*op)) == DDS_OP_ADR));
  switch (DDS_OP_TYPE (*op))
  {
    case DDS_OP_VAL_1BY: dds_os_put1 (os, dds_is_get1 (is)); break;
    case DDS_OP_VAL_2BY: dds_os_put2 (os, dds_is_get2 (is)); break;
    case DDS_OP_VAL_4BY: dds_os_put4 (os, dds_is_get4 (is)); break;
    case DDS_OP_VAL_8BY: dds_os_put8 (os, dds_is_get8 (is)); break;
    case DDS_OP_VAL_STR: case DDS_OP_VAL_BST: {
      uint32_t sz = dds_is_get4 (is);
      dds_os_put4 (os, sz);
      dds_os_put_bytes (os, is->m_buffer + is->m_index, sz);
      is->m_index += sz;
      break;
    }
    case DDS_OP_VAL_ARR: {
      const uint32_t subtype = DDS_OP_SUBTYPE (*op);
      assert (subtype <= DDS_OP_VAL_8BY);
      const uint32_t align = get_type_size (subtype);
      const uint32_t num = op[2];
      dds_cdr_alignto_clear_and_resize (os, align, num * align);
      void * const dst = os->m_buffer + os->m_index;
      dds_is_get_bytes (is, dst, num, align);
      os->m_index += num * align;
      is->m_index += num * align;
      break;
    }
    case DDS_OP_VAL_SEQ: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
      abort ();
      break;
    }
  }
}

#if DDSRT_ENDIAN == DDSRT_LITTLE_ENDIAN
static void dds_stream_swap_copy (void * __restrict vdst, const void * __restrict vsrc, uint32_t size, uint32_t num)
{
  assert (size == 2 || size == 4 || size == 8);
  switch (size)
  {
    case 2: {
      const uint16_t *src = vsrc;
      uint16_t *dst = vdst;
      for (uint32_t i = 0; i < num; i++)
        dst[i] = bswap2u (src[i]);
      break;
    }
    case 4: {
      const uint32_t *src = vsrc;
      uint32_t *dst = vdst;
      for (uint32_t i = 0; i < num; i++)
        dst[i] = bswap4u (src[i]);
      break;
    }
    case 8: {
      const uint64_t *src = vsrc;
      uint64_t *dst = vdst;
      for (uint32_t i = 0; i < num; i++)
        dst[i] = bswap8u (src[i]);
      break;
    }
  }
}
#endif

static void dds_stream_extract_keyBE_from_key_prim_op (dds_istream_t * __restrict is, dds_ostreamBE_t * __restrict os, const uint32_t * __restrict op)
{
  assert ((*op & DDS_OP_FLAG_KEY) && ((DDS_OP (*op)) == DDS_OP_ADR));
  switch (DDS_OP_TYPE (*op))
  {
    case DDS_OP_VAL_1BY: dds_os_put1be (os, dds_is_get1 (is)); break;
    case DDS_OP_VAL_2BY: dds_os_put2be (os, dds_is_get2 (is)); break;
    case DDS_OP_VAL_4BY: dds_os_put4be (os, dds_is_get4 (is)); break;
    case DDS_OP_VAL_8BY: dds_os_put8be (os, dds_is_get8 (is)); break;
    case DDS_OP_VAL_STR: case DDS_OP_VAL_BST: {
      uint32_t sz = dds_is_get4 (is);
      dds_os_put4be (os, sz);
      dds_os_put_bytes (&os->x, is->m_buffer + is->m_index, sz);
      is->m_index += sz;
      break;
    }
    case DDS_OP_VAL_ARR: {
      const uint32_t subtype = DDS_OP_SUBTYPE (*op);
      assert (subtype <= DDS_OP_VAL_8BY);
      const uint32_t align = get_type_size (subtype);
      const uint32_t num = op[2];
      dds_cdr_alignto (is, align);
      dds_cdr_alignto_clear_and_resize_be (os, align, num * align);
      void const * const src = is->m_buffer + is->m_index;
      void * const dst = os->x.m_buffer + os->x.m_index;
#if DDSRT_ENDIAN == DDSRT_LITTLE_ENDIAN
      dds_stream_swap_copy (dst, src, num, align);
#else
      memcpy (dst, src, num * align);
#endif
      os->x.m_index += num * align;
      is->m_index += num * align;
      break;
    }
    case DDS_OP_VAL_SEQ: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
      abort ();
      break;
    }
  }
}

static void dds_stream_extract_keyBE_from_key (dds_istream_t * __restrict is, dds_ostreamBE_t * __restrict os, const struct ddsi_sertopic_default * __restrict topic)
{
  const dds_topic_descriptor_t *desc = topic->type;
  for (uint32_t i = 0; i < desc->m_nkeys; i++)
  {
    uint32_t const * const op = desc->m_ops + desc->m_keys[i].m_index;
    dds_stream_extract_keyBE_from_key_prim_op (is, os, op);
  }
}

static void dds_stream_extract_key_from_data_skip_subtype (dds_istream_t * __restrict is, uint32_t num, uint32_t subtype, const uint32_t * __restrict subops)
{
  switch (subtype)
  {
    case DDS_OP_VAL_1BY: case DDS_OP_VAL_2BY: case DDS_OP_VAL_4BY: case DDS_OP_VAL_8BY: {
      const uint32_t elem_size = get_type_size (subtype);
      dds_cdr_alignto (is, elem_size);
      is->m_index += num * elem_size;
      break;
    }
    case DDS_OP_VAL_STR: case DDS_OP_VAL_BST: {
      for (uint32_t i = 0; i < num; i++)
      {
        const uint32_t len = dds_is_get4 (is);
        is->m_index += len;
      }
      break;
    }
    case DDS_OP_VAL_SEQ: case DDS_OP_VAL_ARR: case DDS_OP_VAL_UNI: case DDS_OP_VAL_STU: {
      uint32_t remain = UINT32_MAX;
      for (uint32_t i = 0; i < num; i++)
        dds_stream_extract_key_from_data1 (is, NULL, subops, &remain);
      break;
    }
  }
}

static const uint32_t *dds_stream_extract_key_from_data_skip_array (dds_istream_t * __restrict is, const uint32_t * __restrict ops)
{
  const uint32_t op = *ops;
  assert (DDS_OP_TYPE (op) == DDS_OP_VAL_ARR);
  const uint32_t subtype = DDS_OP_SUBTYPE (op);
  const uint32_t num = ops[2];
  if (subtype > DDS_OP_VAL_BST)
  {
    const uint32_t *jsr_ops = ops + DDS_OP_ADR_JSR (ops[3]);
    const uint32_t jmp = DDS_OP_ADR_JMP (ops[3]);
    dds_stream_extract_key_from_data_skip_subtype (is, num, subtype, jsr_ops);
    return ops + (jmp ? jmp : 5);
  }
  else
  {
    dds_stream_extract_key_from_data_skip_subtype (is, num, subtype, NULL);
    return ops + 3;
  }
}

static const uint32_t *dds_stream_extract_key_from_data_skip_sequence (dds_istream_t * __restrict is, const uint32_t * __restrict ops)
{
  const uint32_t op = *ops;
  const enum dds_stream_typecode type = DDS_OP_TYPE (op);
  assert (type == DDS_OP_VAL_SEQ);
  const uint32_t subtype = DDS_OP_SUBTYPE (op);
  const uint32_t num = dds_is_get4 (is);
  if (num == 0)
    return ops + 2 + (type == DDS_OP_VAL_BST || type == DDS_OP_VAL_ARR);
  else if (subtype > DDS_OP_VAL_BST)
  {
    const uint32_t * jsr_ops = ops + DDS_OP_ADR_JSR (ops[3]);
    const uint32_t jmp = DDS_OP_ADR_JMP (ops[3]);
    dds_stream_extract_key_from_data_skip_subtype (is, num, subtype, jsr_ops);
    return ops + (jmp ? jmp : 4);
  }
  else
  {
    dds_stream_extract_key_from_data_skip_subtype (is, num, subtype, NULL);
    return ops + 2 + (type == DDS_OP_VAL_BST || type == DDS_OP_VAL_ARR);
  }
}

static const uint32_t *dds_stream_extract_key_from_data_skip_union (dds_istream_t * __restrict is, const uint32_t * __restrict ops)
{
  const uint32_t op = *ops;
  assert (DDS_OP_TYPE (op) == DDS_OP_VAL_UNI);
  const uint32_t disc = read_union_discriminant (is, DDS_OP_SUBTYPE (op));
  uint32_t const * const jeq_op = find_union_case (ops, disc);
  if (jeq_op)
    dds_stream_extract_key_from_data_skip_subtype (is, 1, DDS_JEQ_TYPE (jeq_op[0]), jeq_op + DDS_OP_ADR_JSR (jeq_op[0]));
  return ops + DDS_OP_ADR_JMP (ops[3]);
}

static void dds_stream_extract_key_from_data1 (dds_istream_t * __restrict is, dds_ostream_t * __restrict os, const uint32_t * __restrict ops, uint32_t * __restrict keys_remaining)
{
  uint32_t op;
  while ((op = *ops) != DDS_OP_RTS)
  {
    switch (DDS_OP (op))
    {
      case DDS_OP_ADR: {
        const uint32_t type = DDS_OP_TYPE (op);
        const bool is_key = (op & DDS_OP_FLAG_KEY) && (os != NULL);
        if (is_key)
        {
          dds_stream_extract_key_from_key_prim_op (is, os, ops);
          if (--(*keys_remaining) == 0)
            return;
          ops += 2 + (type == DDS_OP_VAL_BST || type == DDS_OP_VAL_ARR);
        }
        else
        {
          switch (type)
          {
            case DDS_OP_VAL_1BY: case DDS_OP_VAL_2BY: case DDS_OP_VAL_4BY: case DDS_OP_VAL_8BY: case DDS_OP_VAL_STR: case DDS_OP_VAL_BST:
              dds_stream_extract_key_from_data_skip_subtype (is, 1, type, NULL);
              ops += 2 + (type == DDS_OP_VAL_BST || type == DDS_OP_VAL_ARR);
              break;
            case DDS_OP_VAL_SEQ:
              ops = dds_stream_extract_key_from_data_skip_sequence (is, ops);
              break;
            case DDS_OP_VAL_ARR:
              ops = dds_stream_extract_key_from_data_skip_array (is, ops);
              break;
            case DDS_OP_VAL_UNI:
              ops = dds_stream_extract_key_from_data_skip_union (is, ops);
              break;
            case DDS_OP_VAL_STU:
              abort ();
          }
        }
        break;
      }
      case DDS_OP_JSR: { /* Implies nested type */
        ops += 2;
        dds_stream_extract_key_from_data1 (is, os, ops + DDS_OP_JUMP (op), keys_remaining);
        if (--(*keys_remaining) == 0)
          return;
        ops++;
        break;
      }
      case DDS_OP_RTS: case DDS_OP_JEQ: {
        abort ();
        break;
      }
    }
  }
}

static void dds_stream_extract_keyBE_from_data1 (dds_istream_t * __restrict is, dds_ostreamBE_t * __restrict os, const uint32_t * __restrict ops, uint32_t * __restrict keys_remaining)
{
  uint32_t op;
  while ((op = *ops) != DDS_OP_RTS)
  {
    switch (DDS_OP (op))
    {
      case DDS_OP_ADR: {
        const uint32_t type = DDS_OP_TYPE (op);
        const bool is_key = (op & DDS_OP_FLAG_KEY) && (os != NULL);
        if (is_key)
        {
          dds_stream_extract_keyBE_from_key_prim_op (is, os, ops);
          if (--(*keys_remaining) == 0)
            return;
          ops += 2 + (type == DDS_OP_VAL_BST || type == DDS_OP_VAL_ARR);
        }
        else
        {
          switch (type)
          {
            case DDS_OP_VAL_1BY: case DDS_OP_VAL_2BY: case DDS_OP_VAL_4BY: case DDS_OP_VAL_8BY: case DDS_OP_VAL_STR: case DDS_OP_VAL_BST:
              dds_stream_extract_key_from_data_skip_subtype (is, 1, type, NULL);
              ops += 2 + (type == DDS_OP_VAL_BST || type == DDS_OP_VAL_ARR);
              break;
            case DDS_OP_VAL_SEQ:
              ops = dds_stream_extract_key_from_data_skip_sequence (is, ops);
              break;
            case DDS_OP_VAL_ARR:
              ops = dds_stream_extract_key_from_data_skip_array (is, ops);
              break;
            case DDS_OP_VAL_UNI:
              ops = dds_stream_extract_key_from_data_skip_union (is, ops);
              break;
            case DDS_OP_VAL_STU:
              abort ();
          }
        }
        break;
      }
      case DDS_OP_JSR: { /* Implies nested type */
        ops += 2;
        dds_stream_extract_keyBE_from_data1 (is, os, ops + DDS_OP_JUMP (op), keys_remaining);
        if (--(*keys_remaining) == 0)
          return;
        ops++;
        break;
      }
      case DDS_OP_RTS: case DDS_OP_JEQ: {
        abort ();
        break;
      }
    }
  }
}

void dds_stream_extract_key_from_data (dds_istream_t * __restrict is, dds_ostream_t * __restrict os, const struct ddsi_sertopic_default * __restrict topic)
{
  const dds_topic_descriptor_t *desc = topic->type;
  uint32_t keys_remaining = desc->m_nkeys;
  dds_stream_extract_key_from_data1 (is, os, desc->m_ops, &keys_remaining);
}

void dds_stream_extract_keyBE_from_data (dds_istream_t * __restrict is, dds_ostreamBE_t * __restrict os, const struct ddsi_sertopic_default * __restrict topic)
{
  const dds_topic_descriptor_t *desc = topic->type;
  uint32_t keys_remaining = desc->m_nkeys;
  dds_stream_extract_keyBE_from_data1 (is, os, desc->m_ops, &keys_remaining);
}

void dds_stream_extract_keyhash (dds_istream_t * __restrict is, dds_keyhash_t * __restrict kh, const struct ddsi_sertopic_default * __restrict topic, const bool just_key)
{
  const dds_topic_descriptor_t *desc = topic->type;
  kh->m_set = 1;
  if (desc->m_nkeys == 0)
    kh->m_iskey = 1;
  else if (desc->m_flagset & DDS_TOPIC_FIXED_KEY)
  {
    dds_ostreamBE_t os;
    kh->m_iskey = 1;
    dds_ostreamBE_init (&os, 0);
    os.x.m_buffer = kh->m_hash;
    os.x.m_size = 16;
    if (just_key)
      dds_stream_extract_keyBE_from_key (is, &os, topic);
    else
      dds_stream_extract_keyBE_from_data (is, &os, topic);
    assert (os.x.m_index <= 16);
  }
  else
  {
    dds_ostreamBE_t os;
    ddsrt_md5_state_t md5st;
    kh->m_iskey = 0;
    dds_ostreamBE_init (&os, 0);
    if (just_key)
      dds_stream_extract_keyBE_from_key (is, &os, topic);
    else
      dds_stream_extract_keyBE_from_data (is, &os, topic);
    ddsrt_md5_init (&md5st);
    ddsrt_md5_append (&md5st, os.x.m_buffer, os.x.m_index);
    ddsrt_md5_finish (&md5st, kh->m_hash);
    dds_ostreamBE_fini (&os);
  }
}

/*******************************************************************************************
 **
 **  Stuff to make it possible to treat a ddsi_serdata_default as a stream
 **
 *******************************************************************************************/

DDSRT_STATIC_ASSERT ((offsetof (struct ddsi_serdata_default, data) % 8) == 0);

void dds_istream_from_serdata_default (dds_istream_t * __restrict s, const struct ddsi_serdata_default * __restrict d)
{
  s->m_buffer = (const unsigned char *) d;
  s->m_index = (uint32_t) offsetof (struct ddsi_serdata_default, data);
  s->m_size = d->size + s->m_index;
#if DDSRT_ENDIAN == DDSRT_LITTLE_ENDIAN
  assert (d->hdr.identifier == CDR_LE);
#elif DDSRT_ENDIAN == DDSRT_BIG_ENDIAN
  assert (d->hdr.identifier == CDR_BE);
#else
#error "DDSRT_ENDIAN neither LITTLE nor BIG"
#endif
}

void dds_ostream_from_serdata_default (dds_ostream_t * __restrict s, struct ddsi_serdata_default * __restrict d)
{
  s->m_buffer = (unsigned char *) d;
  s->m_index = (uint32_t) offsetof (struct ddsi_serdata_default, data);
  s->m_size = d->size + s->m_index;
#if DDSRT_ENDIAN == DDSRT_LITTLE_ENDIAN
  assert (d->hdr.identifier == CDR_LE);
#elif DDSRT_ENDIAN == DDSRT_BIG_ENDIAN
  assert (d->hdr.identifier == CDR_BE);
#else
#error "DDSRT_ENDIAN neither LITTLE nor BIG"
#endif
}

void dds_ostream_add_to_serdata_default (dds_ostream_t * __restrict s, struct ddsi_serdata_default ** __restrict d)
{
  /* DDSI requires 4 byte alignment */

  const uint32_t pad = dds_cdr_alignto_clear_and_resize (s, 4, 0);
  assert (pad <= 3);

  /* Reset data pointer as stream may have reallocated */

  (*d) = (void *) s->m_buffer;
  (*d)->pos = (s->m_index - (uint32_t) offsetof (struct ddsi_serdata_default, data));
  (*d)->size = (s->m_size - (uint32_t) offsetof (struct ddsi_serdata_default, data));
  (*d)->hdr.options = toBE2u ((uint16_t) pad);
}

void dds_ostreamBE_from_serdata_default (dds_ostreamBE_t * __restrict s, struct ddsi_serdata_default * __restrict d)
{
  s->x.m_buffer = (unsigned char *) d;
  s->x.m_index = (uint32_t) offsetof (struct ddsi_serdata_default, data);
  s->x.m_size = d->size + s->x.m_index;
  assert (d->hdr.identifier == CDR_BE);
}

void dds_ostreamBE_add_to_serdata_default (dds_ostreamBE_t * __restrict s, struct ddsi_serdata_default ** __restrict d)
{
  /* DDSI requires 4 byte alignment */

  const uint32_t pad = dds_cdr_alignto_clear_and_resize_be (s, 4, 0);
  assert (pad <= 3);

  /* Reset data pointer as stream may have reallocated */

  (*d) = (void *) s->x.m_buffer;
  (*d)->pos = (s->x.m_index - (uint32_t) offsetof (struct ddsi_serdata_default, data));
  (*d)->size = (s->x.m_size - (uint32_t) offsetof (struct ddsi_serdata_default, data));
  (*d)->hdr.options = toBE2u ((uint16_t) pad);
}
