/***************************************************************************
 * Generic routines to unpack miniSEED records.
 *
 * Appropriate values from the record header will be byte-swapped to
 * the host order.  The purpose of this code is to provide a portable
 * way of accessing common SEED data record header information.  All
 * data structures in SEED 2.4 data records are supported.  The data
 * samples are optionally decompressed/unpacked.
 *
 * This file is part of the miniSEED Library.
 *
 * Copyright (c) 2023 Chad Trabant, EarthScope Data Services
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ***************************************************************************/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>

#include "libmseed.h"
#include "mseedformat.h"
#include "unpack.h"
#include "unpackdata.h"

/* Function(s) internal to this file */
static nstime_t ms_btime2nstime (uint8_t *btime, int8_t swapflag);

/* Test POINTER for alignment with BYTE_COUNT sized quantities */
#define is_aligned(POINTER, BYTE_COUNT) \
  (((uintptr_t) (const void *)(POINTER)) % (BYTE_COUNT) == 0)

/***************************************************************************
 * Unpack a miniSEED 3.x data record and populate a MS3Record struct.
 *
 * If MSF_UNPACKDATA is set in flags, the data samples are
 * unpacked/decompressed and the ::MS3Record.datasamples pointer is set
 * appropriately.  The data samples will be either 32-bit integers,
 * 32-bit floats or 64-bit floats (doubles) with the same byte order
 * as the host machine.  The MS3Record->numsamples will be set to the
 * actual number of samples unpacked/decompressed and
 * MS3Record->sampletype will indicated the sample type.
 *
 * If MSF_VALIDATECRC is set in flags, the CRC in the record will be
 * validated.  If the calculated CRC does not match, the MS_INVALIDCRC
 * error is returned.
 *
 * All appropriate values will be byte-swapped to the host order,
 * including the data samples.
 *
 * All MS3Record struct values, including data samples and data
 * samples will be overwritten by subsequent calls to this function.
 *
 * If the 'msr' struct is NULL it will be allocated.
 *
 * Returns MS_NOERROR and populates the MS3Record struct at *ppmsr on
 * success, otherwise returns a libmseed error code (listed in
 * libmseed.h).
 *
 * \ref MessageOnError - this function logs a message on error
 ***************************************************************************/
int64_t
msr3_unpack_mseed3 (const char *record, int reclen, MS3Record **ppmsr,
                    uint32_t flags, int8_t verbose)
{
  MS3Record *msr = NULL;
  uint32_t calculated_crc;
  uint32_t header_crc;
  uint8_t sidlength = 0;
  int8_t swapflag;
  int bigendianhost = ms_bigendianhost ();
  int64_t retval;

  if (!record || !ppmsr)
  {
    ms_log (2, "Required argument not defined: 'record' or 'ppmsr'\n");
    return MS_GENERROR;
  }

  /* Verify that passed record length is within supported range */
  if (reclen < MINRECLEN || reclen > MAXRECLEN)
  {
    ms_log (2, "Record length is out of allowed range: %d\n", reclen);
    return MS_OUTOFRANGE;
  }

  /* Verify that record includes a valid header */
  if (!MS3_ISVALIDHEADER (record))
  {
    ms_log (2, "Record header unrecognized, not a valid miniSEED record\n");
    return MS_NOTSEED;
  }

  /* miniSEED 3 is little endian */
  swapflag = (bigendianhost) ? 1 : 0;

  if (verbose > 2)
  {
    if (swapflag)
      ms_log (0, "Byte swapping needed for unpacking of header\n");
    else
      ms_log (0, "Byte swapping NOT needed for unpacking of header\n");
  }

  sidlength = *pMS3FSDH_SIDLENGTH (record);

  /* Record SID length must be at most one less than maximum size to leave a byte for termination */
  if (sidlength >= sizeof (msr->sid))
  {
    ms_log (2, "%.*s: Source identifier is longer (%d) than supported (%d)\n",
            sidlength, pMS3FSDH_SID (record), sidlength, (int)sizeof (msr->sid) - 1);
    return MS_GENERROR;
  }

  /* Validate the CRC */
  if (flags & MSF_VALIDATECRC)
  {
    /* Save header CRC, set value to 0, calculate CRC, restore CRC */
    header_crc = HO4u (*pMS3FSDH_CRC (record), swapflag);
    memset (pMS3FSDH_CRC(record), 0, sizeof(uint32_t));
    calculated_crc = ms_crc32c ((const uint8_t*)record, reclen, 0);
    *pMS3FSDH_CRC(record) = HO4u (header_crc, swapflag);

    if (header_crc != calculated_crc)
    {
      ms_log (2, "%.*s: CRC is invalid, miniSEED record may be corrupt\n",
              sidlength, pMS3FSDH_SID (record));
      return MS_INVALIDCRC;
    }
  }

  /* Initialize the MS3Record */
  if (!(*ppmsr = msr3_init (*ppmsr)))
    return MS_GENERROR;

  /* Shortcut pointer, historical and helps readability */
  msr = *ppmsr;

  /* Set raw record pointer and record length */
  msr->record = record;
  msr->reclen = reclen;

  /* Populate the header fields */
  msr->swapflag = (swapflag) ? MSSWAP_HEADER : 0;
  msr->formatversion = *pMS3FSDH_FORMATVERSION (record);
  msr->flags = *pMS3FSDH_FLAGS (record);

  memcpy (msr->sid, pMS3FSDH_SID (record), sidlength);
  msr->starttime = ms_time2nstime (HO2u (*pMS3FSDH_YEAR (record), msr->swapflag),
                                   HO2u (*pMS3FSDH_DAY (record), msr->swapflag),
                                   *pMS3FSDH_HOUR (record),
                                   *pMS3FSDH_MIN (record),
                                   *pMS3FSDH_SEC (record),
                                   HO4u (*pMS3FSDH_NSEC (record), msr->swapflag));
  if (msr->starttime == NSTERROR)
  {
    ms_log (2, "%.*s: Cannot convert start time to internal time representation\n",
            sidlength, pMS3FSDH_SID (record));
    return MS_GENERROR;
  }

  msr->encoding = *pMS3FSDH_ENCODING (record);
  msr->samprate = HO8f (*pMS3FSDH_SAMPLERATE (record), msr->swapflag);
  msr->samplecnt = HO4u (*pMS3FSDH_NUMSAMPLES (record), msr->swapflag);
  msr->crc = HO4u (*pMS3FSDH_CRC (record), msr->swapflag);
  msr->pubversion = *pMS3FSDH_PUBVERSION (record);

  /* Copy extra headers into a NULL-terminated string */
  msr->extralength = HO2u (*pMS3FSDH_EXTRALENGTH (record), msr->swapflag);
  if (msr->extralength)
  {
    if ((msr->extra = (char *)libmseed_memory.malloc (msr->extralength + 1)) == NULL)
    {
      ms_log (2, "%s: Cannot allocate memory for extra headers\n", msr->sid);
      return MS_GENERROR;
    }

    memcpy (msr->extra, record + MS3FSDH_LENGTH + sidlength, msr->extralength);
    msr->extra[msr->extralength] = '\0';
  }

  msr->datalength = HO2u (*pMS3FSDH_DATALENGTH (record), msr->swapflag);

  /* Determine data payload byte swapping.
     Steim encodings are big endian.
     All other encodings are little endian, matching the header. */
  if (msr->encoding == DE_STEIM1 || msr->encoding == DE_STEIM2)
  {
    if (! bigendianhost)
      msr->swapflag |= MSSWAP_PAYLOAD;
  }
  else if (msr->swapflag & MSSWAP_HEADER)
  {
    msr->swapflag |= MSSWAP_PAYLOAD;
  }

  /* Unpack the data samples if requested */
  if ((flags & MSF_UNPACKDATA) && msr->samplecnt > 0)
  {
    retval = msr3_unpack_data (msr, verbose);

    if (retval < 0)
      return retval;
    else
      msr->numsamples = retval;
  }
  else
  {
    if (msr->datasamples)
      libmseed_memory.free (msr->datasamples);

    msr->datasamples = NULL;
    msr->datasize = 0;
    msr->numsamples = 0;
  }

  return MS_NOERROR;
} /* End of msr3_unpack_mseed3() */

/***************************************************************************
 * Unpack a miniSEED 2.x data record and populate a MS3Record struct.
 *
 * If MSF_UNPACKDATA is set in flags the data samples are
 * unpacked/decompressed and the ::MS3Record.datasamples pointer is set
 * appropriately.  The data samples will be either 32-bit integers,
 * 32-bit floats or 64-bit floats (doubles) with the same byte order
 * as the host machine.  The MS3Record->numsamples will be set to the
 * actual number of samples unpacked/decompressed and
 * MS3Record->sampletype will indicated the sample type.
 *
 * All appropriate values will be byte-swapped to the host order,
 * including the data samples.
 *
 * All MS3Record struct values, including data samples and data
 * samples will be overwritten by subsequent calls to this function.
 *
 * If the 'msr' struct is NULL it will be allocated.
 *
 * Returns MS_NOERROR and populates the MS3Record struct at *ppmsr on
 * success, otherwise returns a libmseed error code (listed in
 * libmseed.h).
 *
 * \ref MessageOnError - this function logs a message on error
 ***************************************************************************/
int64_t
msr3_unpack_mseed2 (const char *record, int reclen, MS3Record **ppmsr,
                    uint32_t flags, int8_t verbose)
{
  int B1000offset = 0;
  int B1001offset = 0;
  int bigendianhost = ms_bigendianhost ();
  int64_t retval;

  MS3Record *msr = NULL;
  char errorsid[64];

  int length;
  int ione = 1;
  int64_t ival;
  double dval;
  char sval[64];

  /* For blockette parsing */
  int blkt_offset;
  int blkt_count = 0;
  int blkt_length;
  int blkt_end = 0;
  uint16_t blkt_type;
  uint16_t next_blkt;

  LM_PARSED_JSON *parsestate = NULL;
  MSEHEventDetection eventdetection;
  MSEHCalibration calibration;
  MSEHTimingException exception;

  if (!record || !ppmsr)
  {
    ms_log (2, "Required argument not defined: 'record' or 'ppmsr'\n");
    return MS_GENERROR;
  }

  /* Verify that passed record length is within supported range */
  if (reclen < 64 || reclen > MAXRECLEN)
  {
    ms2_recordsid (record, errorsid, sizeof (errorsid));
    ms_log (2, "%s: Record length is out of allowed range: %d\n", errorsid, reclen);
    return MS_OUTOFRANGE;
  }

  /* Verify that record includes a valid header */
  if (!MS2_ISVALIDHEADER (record))
  {
    ms2_recordsid (record, errorsid, sizeof (errorsid));
    ms_log (2, "%s: Record header unrecognized, not a valid miniSEED record\n", errorsid);
    return MS_NOTSEED;
  }

  /* Initialize the MS3Record */
  if (!(*ppmsr = msr3_init (*ppmsr)))
    return MS_GENERROR;

  /* Shortcut pointer, historical and helps readability */
  msr = *ppmsr;

  /* Set raw record pointer and record length */
  msr->record = record;
  msr->reclen = reclen;

  /* Check to see if byte swapping is needed by testing the year and day */
  if (!MS_ISVALIDYEARDAY (*pMS2FSDH_YEAR (record), *pMS2FSDH_DAY (record)))
    msr->swapflag = MSSWAP_HEADER;

  /* Report byte swapping status */
  if (verbose > 2)
  {
    if (msr->swapflag)
      ms_log (0, "Byte swapping needed for unpacking of header\n");
    else
      ms_log (0, "Byte swapping NOT needed for unpacking of header\n");
  }

  /* Populate some of the common header fields */
  ms2_recordsid (record, msr->sid, sizeof (msr->sid));
  msr->formatversion = 2;
  msr->samprate = ms_nomsamprate (HO2d (*pMS2FSDH_SAMPLERATEFACT (record), msr->swapflag),
                                  HO2d (*pMS2FSDH_SAMPLERATEMULT (record), msr->swapflag));
  msr->samplecnt = HO2u (*pMS2FSDH_NUMSAMPLES (record), msr->swapflag);

  /* Map data quality indicator to publication version */
  if (*pMS2FSDH_DATAQUALITY (record) == 'M')
    msr->pubversion = 4;
  else if (*pMS2FSDH_DATAQUALITY (record) == 'Q')
    msr->pubversion = 3;
  else if (*pMS2FSDH_DATAQUALITY (record) == 'D')
    msr->pubversion = 2;
  else if (*pMS2FSDH_DATAQUALITY (record) == 'R')
    msr->pubversion = 1;
  else
    msr->pubversion = 0;

  /* Map activity bits */
  if (*pMS2FSDH_ACTFLAGS (record) & 0x01) /* Bit 0 */
    msr->flags |= 0x01;
  if (*pMS2FSDH_ACTFLAGS (record) & 0x04) /* Bit 2 */
    mseh_set_ptr_r (msr, "/FDSN/Event/Begin", &ione, 'b', &parsestate);
  if (*pMS2FSDH_ACTFLAGS (record) & 0x08) /* Bit 3 */
    mseh_set_ptr_r (msr, "/FDSN/Event/End", &ione, 'b', &parsestate);
  if (*pMS2FSDH_ACTFLAGS (record) & 0x10) /* Bit 4 */
  {
    ival = 1;
    mseh_set_ptr_r (msr, "/FDSN/Time/LeapSecond", &ival, 'i', &parsestate);
  }
  if (*pMS2FSDH_ACTFLAGS (record) & 0x20) /* Bit 5 */
  {
    ival = -1;
    mseh_set_ptr_r (msr, "/FDSN/Time/LeapSecond", &ival, 'i', &parsestate);
  }
  if (*pMS2FSDH_ACTFLAGS (record) & 0x40) /* Bit 6 */
    mseh_set_ptr_r (msr, "/FDSN/Event/InProgress", &ione, 'b', &parsestate);

  /* Map I/O and clock flags */
  if (*pMS2FSDH_IOFLAGS (record) & 0x01) /* Bit 0 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/StationVolumeParityError", &ione, 'b', &parsestate);
  if (*pMS2FSDH_IOFLAGS (record) & 0x02) /* Bit 1 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/LongRecordRead", &ione, 'b', &parsestate);
  if (*pMS2FSDH_IOFLAGS (record) & 0x04) /* Bit 2 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/ShortRecordRead", &ione, 'b', &parsestate);
  if (*pMS2FSDH_IOFLAGS (record) & 0x08) /* Bit 3 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/StartOfTimeSeries", &ione, 'b', &parsestate);
  if (*pMS2FSDH_IOFLAGS (record) & 0x10) /* Bit 4 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/EndOfTimeSeries", &ione, 'b', &parsestate);
  if (*pMS2FSDH_IOFLAGS (record) & 0x20) /* Bit 5 */
    msr->flags |= 0x04;

  /* Map data quality flags */
  if (*pMS2FSDH_DQFLAGS (record) & 0x01) /* Bit 0 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/AmplifierSaturation", &ione, 'b', &parsestate);
  if (*pMS2FSDH_DQFLAGS (record) & 0x02) /* Bit 1 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/DigitizerClipping", &ione, 'b', &parsestate);
  if (*pMS2FSDH_DQFLAGS (record) & 0x04) /* Bit 2 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/Spikes", &ione, 'b', &parsestate);
  if (*pMS2FSDH_DQFLAGS (record) & 0x08) /* Bit 3 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/Glitches", &ione, 'b', &parsestate);
  if (*pMS2FSDH_DQFLAGS (record) & 0x10) /* Bit 4 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/MissingData", &ione, 'b', &parsestate);
  if (*pMS2FSDH_DQFLAGS (record) & 0x20) /* Bit 5 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/TelemetrySyncError", &ione, 'b', &parsestate);
  if (*pMS2FSDH_DQFLAGS (record) & 0x40) /* Bit 6 */
    mseh_set_ptr_r (msr, "/FDSN/Flags/FilterCharging", &ione, 'b', &parsestate);
  if (*pMS2FSDH_DQFLAGS (record) & 0x80) /* Bit 7 */
    msr->flags |= 0x02;

  dval = (double)HO4d (*pMS2FSDH_TIMECORRECT (record), msr->swapflag);
  if (dval != 0.0)
  {
    dval = dval / 10000.0;
    mseh_set_ptr_r (msr, "/FDSN/Time/Correction", &dval, 'n', &parsestate);
  }

  /* Traverse the blockettes */
  blkt_offset = HO2u (*pMS2FSDH_BLOCKETTEOFFSET (record), msr->swapflag);

  while ((blkt_offset != 0) &&
         (blkt_offset < reclen) &&
         (blkt_offset < MAXRECLEN))
  {
    /* Every blockette has a similar 4 byte header: type and next */
    memcpy (&blkt_type, record + blkt_offset, 2);
    memcpy (&next_blkt, record + blkt_offset + 2, 2);

    if (msr->swapflag)
    {
      ms_gswap2 (&blkt_type);
      ms_gswap2 (&next_blkt);
    }

    /* Get blockette length */
    blkt_length = ms2_blktlen (blkt_type, record + blkt_offset, msr->swapflag);

    if (blkt_length == 0)
    {
      ms_log (2, "%s: Unknown blockette length for type %d\n", msr->sid, blkt_type);
      break;
    }

    /* Make sure blockette is contained within the msrecord buffer */
    if ((blkt_offset + blkt_length) > reclen)
    {
      ms_log (2, "%s: Blockette %d extends beyond record size, truncated?\n", msr->sid, blkt_type);
      break;
    }

    blkt_end = blkt_offset + blkt_length;

    if (blkt_type == 100)
    {
      msr->samprate = HO4f (*pMS2B100_SAMPRATE (record + blkt_offset), msr->swapflag);
    }

    /* Blockette 200, generic event detection */
    else if (blkt_type == 200)
    {
      memset (&eventdetection, 0, sizeof(eventdetection));

      strncpy (eventdetection.type, "GENERIC", sizeof (eventdetection.type));
      ms_strncpcleantail (eventdetection.detector, pMS2B200_DETECTOR (record + blkt_offset), 24);
      eventdetection.signalamplitude = HO4f (*pMS2B200_AMPLITUDE (record + blkt_offset), msr->swapflag);
      eventdetection.signalperiod = HO4f (*pMS2B200_PERIOD (record + blkt_offset), msr->swapflag);
      eventdetection.backgroundestimate = HO4f (*pMS2B200_BACKGROUNDEST (record + blkt_offset), msr->swapflag);

      /* If bit 2 is set, set compression wave according to bit 0 */
      if (*pMS2B200_FLAGS (record + blkt_offset) & 0x04)
      {
        if (*pMS2B200_FLAGS (record + blkt_offset) & 0x01)
          strncpy (eventdetection.wave, "DILATATION", sizeof (eventdetection.wave));
        else
          strncpy (eventdetection.wave, "COMPRESSION", sizeof (eventdetection.wave));
      }
      else
        eventdetection.wave[0] = '\0';

      if (*pMS2B200_FLAGS (record + blkt_offset) & 0x02)
        strncpy (eventdetection.units, "DECONVOLVED", sizeof (eventdetection.units));
      else
        strncpy (eventdetection.units, "COUNTS", sizeof (eventdetection.units));

      eventdetection.onsettime = ms_btime2nstime ((uint8_t*)pMS2B200_YEAR (record + blkt_offset), msr->swapflag);
      if (eventdetection.onsettime == NSTERROR)
        return MS_GENERROR;

      memset (eventdetection.medsnr, 0, 6);
      eventdetection.medlookback = -1;
      eventdetection.medpickalgorithm = -1;
      eventdetection.next = NULL;

      if (mseh_add_event_detection_r (msr, NULL, &eventdetection, &parsestate))
      {
        ms_log (2, "%s: Problem mapping Blockette 200 to extra headers\n", msr->sid);
        return MS_GENERROR;
      }
    }

    /* Blockette 201, Murdock event detection */
    else if (blkt_type == 201)
    {
      memset (&eventdetection, 0, sizeof(eventdetection));

      strncpy (eventdetection.type, "MURDOCK", sizeof (eventdetection.type));
      ms_strncpcleantail (eventdetection.detector, pMS2B201_DETECTOR (record + blkt_offset), 24);
      eventdetection.signalamplitude = HO4f (*pMS2B201_AMPLITUDE (record + blkt_offset), msr->swapflag);
      eventdetection.signalperiod = HO4f (*pMS2B201_PERIOD (record + blkt_offset), msr->swapflag);
      eventdetection.backgroundestimate = HO4f (*pMS2B201_BACKGROUNDEST (record + blkt_offset), msr->swapflag);

      /* If bit 0 is set, dilatation wave otherwise compression */
      if (*pMS2B201_FLAGS (record + blkt_offset) & 0x01)
        strncpy (eventdetection.wave, "DILATATION", sizeof (eventdetection.wave));
      else
        strncpy (eventdetection.wave, "COMPRESSION", sizeof (eventdetection.wave));

      eventdetection.onsettime = ms_btime2nstime ((uint8_t*)pMS2B201_YEAR (record + blkt_offset), msr->swapflag);
      if (eventdetection.onsettime == NSTERROR)
        return MS_GENERROR;

      memcpy (eventdetection.medsnr, pMS2B201_MEDSNR (record + blkt_offset), 6);
      eventdetection.medlookback = *pMS2B201_LOOPBACK (record + blkt_offset);
      eventdetection.medpickalgorithm = *pMS2B201_PICKALGORITHM (record + blkt_offset);
      eventdetection.next = NULL;

      if (mseh_add_event_detection_r (msr, NULL, &eventdetection, &parsestate))
      {
        ms_log (2, "%s: Problem mapping Blockette 201 to extra headers\n", msr->sid);
        return MS_GENERROR;
      }
    }

    /* Blockette 300, step calibration */
    else if (blkt_type == 300)
    {
      memset (&calibration, 0, sizeof(calibration));

      strncpy (calibration.type, "STEP", sizeof (calibration.type));

      calibration.begintime = ms_btime2nstime ((uint8_t*)pMS2B300_YEAR (record + blkt_offset), msr->swapflag);
      if (calibration.begintime == NSTERROR)
        return MS_GENERROR;

      calibration.endtime = NSTERROR;
      calibration.steps = *pMS2B300_NUMCALIBRATIONS (record + blkt_offset);

      /* If bit 0 is set, first puluse is positive */
      calibration.firstpulsepositive = -1;
      if (*pMS2B300_FLAGS (record + blkt_offset) & 0x01)
        calibration.firstpulsepositive = 1;

      /* If bit 1 is set, calibration's alternate sign */
      calibration.alternatesign = -1;
      if (*pMS2B300_FLAGS (record + blkt_offset) & 0x02)
        calibration.alternatesign = 1;

      /* If bit 2 is set, calibration is automatic, otherwise manual */
      if (*pMS2B300_FLAGS (record + blkt_offset) & 0x04)
        strncpy (calibration.trigger, "AUTOMATIC", sizeof (calibration.trigger));
      else
        strncpy (calibration.trigger, "MANUAL", sizeof (calibration.trigger));

      /* If bit 3 is set, continued from previous record */
      calibration.continued = -1;
      if (*pMS2B300_FLAGS (record + blkt_offset) & 0x08)
        calibration.continued = 1;

      calibration.duration = (double)(HO4u (*pMS2B300_STEPDURATION (record + blkt_offset), msr->swapflag) / 10000.0);
      calibration.stepbetween = (double)(HO4u (*pMS2B300_INTERVALDURATION (record + blkt_offset), msr->swapflag) / 10000.0);
      calibration.amplitude = HO4f (*pMS2B300_AMPLITUDE (record + blkt_offset), msr->swapflag);
      ms_strncpcleantail (calibration.inputchannel, pMS2B300_INPUTCHANNEL (record + blkt_offset), 3);
      calibration.inputunits[0] = '\0';
      calibration.amplituderange[0] = '\0';
      calibration.sineperiod = 0.0;
      calibration.refamplitude = (double)(HO4u (*pMS2B300_REFERENCEAMPLITUDE (record + blkt_offset), msr->swapflag));
      ms_strncpcleantail (calibration.coupling, pMS2B300_COUPLING (record + blkt_offset), 12);
      ms_strncpcleantail (calibration.rolloff, pMS2B300_ROLLOFF (record + blkt_offset), 12);
      calibration.noise[0] = '\0';
      calibration.next = NULL;

      if (mseh_add_calibration_r (msr, NULL, &calibration, &parsestate))
      {
        ms_log (2, "%s: Problem mapping Blockette 300 to extra headers\n", msr->sid);
        return MS_GENERROR;
      }
    }

    /* Blockette 310, sine calibration */
    else if (blkt_type == 310)
    {
      memset (&calibration, 0, sizeof(calibration));

      strncpy (calibration.type, "SINE", sizeof (calibration.type));

      calibration.begintime = ms_btime2nstime ((uint8_t*)pMS2B310_YEAR (record + blkt_offset), msr->swapflag);
      if (calibration.begintime == NSTERROR)
        return MS_GENERROR;

      calibration.endtime = NSTERROR;
      calibration.steps = -1;
      calibration.firstpulsepositive = -1;
      calibration.alternatesign = -1;

      /* If bit 2 is set, calibration is automatic, otherwise manual */
      if (*pMS2B310_FLAGS (record + blkt_offset) & 0x04)
        strncpy (calibration.trigger, "AUTOMATIC", sizeof (calibration.trigger));
      else
        strncpy (calibration.trigger, "MANUAL", sizeof (calibration.trigger));

      /* If bit 3 is set, continued from previous record */
      calibration.continued = -1;
      if (*pMS2B310_FLAGS (record + blkt_offset) & 0x08)
        calibration.continued = 1;

      calibration.amplituderange[0] = '\0';
      /* If bit 4 is set, peak to peak amplitude */
      if (*pMS2B310_FLAGS (record + blkt_offset) & 0x10)
        strncpy (calibration.amplituderange, "PEAKTOPEAK", sizeof (calibration.amplituderange));
      /* Otherwise, if bit 5 is set, zero to peak amplitude */
      else if (*pMS2B310_FLAGS (record + blkt_offset) & 0x20)
        strncpy (calibration.amplituderange, "ZEROTOPEAK", sizeof (calibration.amplituderange));
      /* Otherwise, if bit 6 is set, RMS amplitude */
      else if (*pMS2B310_FLAGS (record + blkt_offset) & 0x40)
        strncpy (calibration.amplituderange, "RMS", sizeof (calibration.amplituderange));

      calibration.duration = (double)(HO4u (*pMS2B310_DURATION (record + blkt_offset), msr->swapflag) / 10000.0);
      calibration.sineperiod = HO4f (*pMS2B310_PERIOD (record + blkt_offset), msr->swapflag);
      calibration.amplitude = HO4f (*pMS2B310_AMPLITUDE (record + blkt_offset), msr->swapflag);
      ms_strncpcleantail (calibration.inputchannel, pMS2B310_INPUTCHANNEL (record + blkt_offset), 3);
      calibration.refamplitude = (double)(HO4u (*pMS2B310_REFERENCEAMPLITUDE (record + blkt_offset), msr->swapflag));
      calibration.stepbetween = 0.0;
      calibration.inputunits[0] = '\0';
      ms_strncpcleantail (calibration.coupling, pMS2B310_COUPLING (record + blkt_offset), 12);
      ms_strncpcleantail (calibration.rolloff, pMS2B310_ROLLOFF (record + blkt_offset), 12);
      calibration.noise[0] = '\0';
      calibration.next = NULL;

      if (mseh_add_calibration_r (msr, NULL, &calibration, &parsestate))
      {
        ms_log (2, "%s: Problem mapping Blockette 310 to extra headers\n", msr->sid);
        return MS_GENERROR;
      }
    }

    /* Blockette 320, pseudo-random calibration */
    else if (blkt_type == 320)
    {
      memset (&calibration, 0, sizeof(calibration));

      strncpy (calibration.type, "PSEUDORANDOM", sizeof (calibration.type));

      calibration.begintime = ms_btime2nstime ((uint8_t*)pMS2B320_YEAR (record + blkt_offset), msr->swapflag);
      if (calibration.begintime == NSTERROR)
        return MS_GENERROR;

      calibration.endtime = NSTERROR;
      calibration.steps = -1;
      calibration.firstpulsepositive = -1;
      calibration.alternatesign = -1;

      /* If bit 2 is set, calibration is automatic, otherwise manual */
      if (*pMS2B320_FLAGS (record + blkt_offset) & 0x04)
        strncpy (calibration.trigger, "AUTOMATIC", sizeof (calibration.trigger));
      else
        strncpy (calibration.trigger, "MANUAL", sizeof (calibration.trigger));

      /* If bit 3 is set, continued from previous record */
      calibration.continued = -1;
      if (*pMS2B320_FLAGS (record + blkt_offset) & 0x08)
        calibration.continued = 1;

      calibration.amplituderange[0] = '\0';
      /* If bit 4 is set, peak to peak amplitude */
      if (*pMS2B320_FLAGS (record + blkt_offset) & 0x10)
        strncpy (calibration.amplituderange, "RANDOM", sizeof (calibration.amplituderange));

      calibration.duration = (double)(HO4u (*pMS2B320_DURATION (record + blkt_offset), msr->swapflag) / 10000.0);
      calibration.amplitude = HO4f (*pMS2B320_PTPAMPLITUDE (record + blkt_offset), msr->swapflag);
      ms_strncpcleantail (calibration.inputchannel, pMS2B320_INPUTCHANNEL (record + blkt_offset), 3);
      calibration.refamplitude = (double)(HO4u (*pMS2B320_REFERENCEAMPLITUDE (record + blkt_offset), msr->swapflag));
      calibration.sineperiod = 0.0;
      calibration.stepbetween = 0.0;
      calibration.inputunits[0] = '\0';
      ms_strncpcleantail (calibration.coupling, pMS2B320_COUPLING (record + blkt_offset), 12);
      ms_strncpcleantail (calibration.rolloff, pMS2B320_ROLLOFF (record + blkt_offset), 12);
      ms_strncpcleantail (calibration.noise, pMS2B320_NOISETYPE (record + blkt_offset), 8);
      calibration.next = NULL;

      if (mseh_add_calibration_r (msr, NULL, &calibration, &parsestate))
      {
        ms_log (2, "%s: Problem mapping Blockette 320 to extra headers\n", msr->sid);
        return MS_GENERROR;
      }
    }

    /* Blockette 390, generic calibration */
    else if (blkt_type == 390)
    {
      memset (&calibration, 0, sizeof(calibration));

      strncpy (calibration.type, "GENERIC", sizeof (calibration.type));

      calibration.begintime = ms_btime2nstime ((uint8_t*)pMS2B390_YEAR (record + blkt_offset), msr->swapflag);
      if (calibration.begintime == NSTERROR)
        return MS_GENERROR;

      calibration.endtime = NSTERROR;
      calibration.steps = -1;
      calibration.firstpulsepositive = -1;
      calibration.alternatesign = -1;

      /* If bit 2 is set, calibration is automatic, otherwise manual */
      if (*pMS2B390_FLAGS (record + blkt_offset) & 0x04)
        strncpy (calibration.trigger, "AUTOMATIC", sizeof (calibration.trigger));
      else
        strncpy (calibration.trigger, "MANUAL", sizeof (calibration.trigger));

      /* If bit 3 is set, continued from previous record */
      calibration.continued = -1;
      if (*pMS2B390_FLAGS (record + blkt_offset) & 0x08)
        calibration.continued = 1;

      calibration.amplituderange[0] = '\0';
      calibration.duration = (double)(HO4u (*pMS2B390_DURATION (record + blkt_offset), msr->swapflag) / 10000.0);
      calibration.amplitude = HO4f (*pMS2B390_AMPLITUDE (record + blkt_offset), msr->swapflag);
      ms_strncpcleantail (calibration.inputchannel, pMS2B390_INPUTCHANNEL (record + blkt_offset), 3);
      calibration.refamplitude = 0.0;
      calibration.sineperiod = 0.0;
      calibration.stepbetween = 0.0;
      calibration.inputunits[0] = '\0';
      calibration.coupling[0] = '\0';
      calibration.rolloff[0] = '\0';
      calibration.noise[0] = '\0';
      calibration.next = NULL;

      if (mseh_add_calibration_r (msr, NULL, &calibration, &parsestate))
      {
        ms_log (2, "%s: Problem mapping Blockette 390 to extra headers\n", msr->sid);
        return MS_GENERROR;
      }
    }

    /* Blockette 395, calibration abort */
    else if (blkt_type == 395)
    {
      memset (&calibration, 0, sizeof(calibration));

      strncpy (calibration.type, "ABORT", sizeof (calibration.type));

      calibration.begintime = NSTERROR;

      calibration.endtime = ms_btime2nstime ((uint8_t*)pMS2B395_YEAR (record + blkt_offset), msr->swapflag);
      if (calibration.endtime == NSTERROR)
        return MS_GENERROR;

      calibration.steps = -1;
      calibration.firstpulsepositive = -1;
      calibration.alternatesign = -1;
      calibration.trigger[0] = '\0';
      calibration.continued = -1;
      calibration.amplituderange[0] = '\0';
      calibration.duration = 0.0;
      calibration.amplitude = 0.0;
      calibration.inputchannel[0] = '\0';
      calibration.refamplitude = 0.0;
      calibration.sineperiod = 0.0;
      calibration.stepbetween = 0.0;
      calibration.inputunits[0] = '\0';
      calibration.coupling[0] = '\0';
      calibration.rolloff[0] = '\0';
      calibration.noise[0] = '\0';
      calibration.next = NULL;

      if (mseh_add_calibration_r (msr, NULL, &calibration, &parsestate))
      {
        ms_log (2, "%s: Problem mapping Blockette 395 to extra headers\n", msr->sid);
        return MS_GENERROR;
      }
    }

    /* Blockette 400, beam blockette */
    else if (blkt_type == 400)
    {
      ms_log (1, "%s: WARNING Blockette 400 is present but discarded\n", msr->sid);
    }

    /* Blockette 400, beam delay blockette */
    else if (blkt_type == 405)
    {
      ms_log (1, "%s: WARNING Blockette 405 is present but discarded\n", msr->sid);
    }

    /* Blockette 500, timing blockette */
    else if (blkt_type == 500)
    {
      memset (&exception, 0, sizeof(exception));

      exception.vcocorrection = HO4f (*pMS2B500_VCOCORRECTION (record + blkt_offset), msr->swapflag);

      exception.time = ms_btime2nstime ((uint8_t*)pMS2B500_YEAR (record + blkt_offset), msr->swapflag);
      if (exception.time == NSTERROR)
        return MS_GENERROR;

      /* Apply microsecond precision if non-zero */
      if (*pMS2B500_MICROSECOND (record + blkt_offset) != 0)
      {
        exception.time += (nstime_t)*pMS2B500_MICROSECOND (record + blkt_offset) * (NSTMODULUS / 1000000);
      }

      exception.receptionquality = *pMS2B500_RECEPTIONQUALITY (record + blkt_offset);
      exception.count = HO4u (*pMS2B500_EXCEPTIONCOUNT (record + blkt_offset), msr->swapflag);
      ms_strncpcleantail (exception.type, pMS2B500_EXCEPTIONTYPE (record + blkt_offset), 16);
      ms_strncpcleantail (exception.clockstatus, pMS2B500_CLOCKSTATUS (record + blkt_offset), 128);

      if (mseh_add_timing_exception_r (msr, NULL, &exception, &parsestate))
      {
        ms_log (2, "%s: Problem mapping Blockette 500 to extra headers\n", msr->sid);
        return MS_GENERROR;
      }

      /* Clock model maps to a single value at /FDSN/Clock/Model */
      ms_strncpcleantail (sval, pMS2B500_CLOCKMODEL (record + blkt_offset), 32);
      mseh_set_ptr_r (msr, "/FDSN/Clock/Model", sval, 's', &parsestate);
    }

    else if (blkt_type == 1000)
    {
      B1000offset = blkt_offset;

      /* Calculate record length in bytes as 2^(B1000->reclen) */
      msr->reclen = (uint32_t)1 << *pMS2B1000_RECLEN (record + blkt_offset);

      /* Compare against the specified length */
      if (msr->reclen != reclen && verbose)
      {
        ms_log (1, "%s: Record length in Blockette 1000 (%d) != specified length (%d)\n",
                msr->sid, msr->reclen, reclen);
      }

      msr->encoding = *pMS2B1000_ENCODING (record + blkt_offset);
    }

    else if (blkt_type == 1001)
    {
      B1001offset = blkt_offset;

      /* Optimization: if no other extra headers yet, directly print this common value */
      if (parsestate == NULL)
      {
        length = snprintf (sval, sizeof(sval), "{\"FDSN\":{\"Time\":{\"Quality\":%d}}}",
                           *pMS2B1001_TIMINGQUALITY (record + blkt_offset));

        if (!(msr->extra = (char *)libmseed_memory.malloc (length + 1)))
        {
          ms_log (2, "%s: Cannot allocate memory for extra headers\n", msr->sid);
          return MS_GENERROR;
        }
        memcpy (msr->extra, sval, length + 1);

        msr->extralength = length;
      }
      /* Otherwise add it to existing headers */
      else
      {
        ival = *pMS2B1001_TIMINGQUALITY (record + blkt_offset);
        mseh_set_ptr_r (msr, "/FDSN/Time/Quality", &ival, 'i', &parsestate);
      }
    }

    else if (blkt_type == 2000)
    {
      ms_log (1, "%s: WARNING Blockette 2000 is present but discarded\n", msr->sid);
    }

    else
    { /* Unknown blockette type */
      ms_log (1, "%s: WARNING, unsupported blockette type %d, skipping\n", msr->sid, blkt_type);
    }

    /* Check that the next blockette offset is beyond the current blockette */
    if (next_blkt && next_blkt < (blkt_offset + blkt_length))
    {
      ms_log (2, "%s: Offset to next blockette (%d) is within current blockette ending at byte %d\n",
              msr->sid, next_blkt, (blkt_offset + blkt_length));

      blkt_offset = 0;
    }
    /* Check that the offset is within record length */
    else if (next_blkt && next_blkt > reclen)
    {
      ms_log (2, "%s: Offset to next blockette (%d) from type %d is beyond record length\n",
              msr->sid, next_blkt, blkt_type);

      blkt_offset = 0;
    }
    else
    {
      blkt_offset = next_blkt;
    }

    blkt_count++;
  } /* End of while looping through blockettes */

  /* Serialize extra header JSON structure and free parsed state */
  if (parsestate)
  {
    mseh_serialize (msr, &parsestate);
    mseh_free_parsestate (&parsestate);
  }

  /* Check for a Blockette 1000 and log warning if not found */
  if (B1000offset == 0 && verbose > 1)
  {
    ms_log (1, "%s: Warning: No Blockette 1000 found\n", msr->sid);
  }

  /* Check that the data offset is after the blockette chain */
  if (blkt_end &&
      HO2u (*pMS2FSDH_NUMSAMPLES (record), msr->swapflag) &&
      HO2u (*pMS2FSDH_DATAOFFSET (record), msr->swapflag) < blkt_end)
  {
    ms_log (1, "%s: Warning: Data offset in fixed header (%d) is within the blockette chain ending at %d\n",
            msr->sid, HO2u (*pMS2FSDH_DATAOFFSET (record), msr->swapflag), blkt_end);
  }

  /* Check that the blockette count matches the number parsed */
  if (*pMS2FSDH_NUMBLOCKETTES (record) != blkt_count)
  {
    ms_log (1, "%s: Warning: Number of blockettes in fixed header (%d) does not match the number parsed (%d)\n",
            msr->sid, *pMS2FSDH_NUMBLOCKETTES (record), blkt_count);
  }

  /* Calculate start time */
  msr->starttime = ms_btime2nstime ((uint8_t*)pMS2FSDH_YEAR (record), msr->swapflag);
  if (msr->starttime == NSTERROR)
  {
    ms_log (2, "%s: Cannot convert start time to internal time stamp\n", msr->sid);
    return MS_GENERROR;
  }

  /* Check if a time correction is included and if it has been applied,
   * bit 1 of activity flags indicates if it has been appiled */
  if (HO4d (*pMS2FSDH_TIMECORRECT (record), msr->swapflag) != 0 &&
      !(*pMS2FSDH_ACTFLAGS (record) & 0x02))
  {
    msr->starttime += (nstime_t)HO4d (*pMS2FSDH_TIMECORRECT (record), msr->swapflag) * (NSTMODULUS / 10000);
  }

  /* Apply microsecond precision if Blockette 1001 is present */
  if (B1001offset)
  {
    msr->starttime += (nstime_t)*pMS2B1001_MICROSECOND (record + B1001offset) * (NSTMODULUS / 1000000);
  }

  msr->datalength = HO2u (*pMS2FSDH_DATAOFFSET (record), msr->swapflag);
  if (msr->datalength > 0)
    msr->datalength = msr->reclen - msr->datalength;

  /* Determine byte order of the data and set the swapflag as needed;
     if no Blkt1000, assume the order is the same as the header */
  if (B1000offset)
  {
    /* If BE host and LE data need swapping */
    if (bigendianhost && *pMS2B1000_BYTEORDER (record + B1000offset) == 0)
      msr->swapflag |= MSSWAP_PAYLOAD;
    /* If LE host and BE data (or bad byte order value) need swapping */
    else if (!bigendianhost && *pMS2B1000_BYTEORDER (record + B1000offset) > 0)
      msr->swapflag |= MSSWAP_PAYLOAD;
  }
  else if (msr->swapflag & MSSWAP_HEADER)
  {
    msr->swapflag |= MSSWAP_PAYLOAD;
  }

  /* Unpack the data samples if requested */
  if ((flags & MSF_UNPACKDATA) && msr->samplecnt > 0)
  {
    if (verbose > 2 && msr->swapflag & MSSWAP_PAYLOAD)
      ms_log (0, "%s: Byte swapping needed for unpacking of data samples\n", msr->sid);
    else if (verbose > 2)
      ms_log (0, "%s: Byte swapping NOT needed for unpacking of data samples\n", msr->sid);

    retval = msr3_unpack_data (msr, verbose);

    if (retval < 0)
      return retval;
    else
      msr->numsamples = retval;
  }
  else
  {
    if (msr->datasamples)
      libmseed_memory.free (msr->datasamples);

    msr->datasamples = NULL;
    msr->datasize = 0;
    msr->numsamples = 0;
  }

  return MS_NOERROR;
} /* End of msr3_unpack_mseed2() */

/*******************************************************************/ /**
 * @brief Determine the data payload bounds for a MS3Record
 *
 * Bounds are the starting offset in record and size.  For miniSEED
 * 2.x the raw record is expected to be located at the
 * ::MS3Record.record pointer.
 *
 * When the encoding is a fixed length per sample (text, integers,
 * or floats), calculate the data size based on the sample count and
 * use if less than size determined otherwise.
 *
 * When the encoding is Steim1 or Steim2, search for 64-byte padding
 * frames (all zeros) at the end of the payload and remove from the
 * reported size.
 *
 * @param[in] msr The ::MS3Record to determine payload bounds for
 * @param[out] dataoffset Offset from start of record to start of payload
 * @param[out] datasize Payload size in bytes
 *
 * @return 0 on success or negative library error code.
 *
 * \ref MessageOnError - this function logs a message on error
 ************************************************************************/
int
msr3_data_bounds (const MS3Record *msr, uint32_t *dataoffset, uint32_t *datasize)
{
  uint8_t nullframe[64] = {0};
  uint8_t samplebytes = 0;
  uint64_t rawsize;

  if (!msr || !dataoffset || !datasize)
  {
    ms_log (2, "Required argument not defined: 'msr', 'dataoffset' or 'datasize'\n");
    return MS_GENERROR;
  }

  /* Determine offset to data */
  if (msr->formatversion == 3)
  {
    *dataoffset = MS3FSDH_LENGTH + strlen (msr->sid) + msr->extralength;
    *datasize = msr->datalength;
  }
  else if (msr->formatversion == 2)
  {
    *dataoffset = HO2u (*pMS2FSDH_DATAOFFSET (msr->record), msr->swapflag & MSSWAP_HEADER);
    *datasize = msr->reclen - *dataoffset;
  }
  else
  {
    ms_log (2, "%s: Unrecognized format version: %d\n", msr->sid, msr->formatversion);
    return MS_GENERROR;
  }

  /* If a fixed sample length encoding, calculate size and use if less
   * than otherwise determined. */
  if (msr->encoding == DE_TEXT ||
      msr->encoding == DE_INT16 || msr->encoding == DE_INT32 ||
      msr->encoding == DE_FLOAT32 || msr->encoding == DE_FLOAT64)
  {
    switch (msr->encoding)
    {
    case DE_TEXT:
      samplebytes = 1;
      break;
    case DE_INT16:
      samplebytes = 2;
      break;
    case DE_INT32:
    case DE_FLOAT32:
      samplebytes = 4;
      break;
    case DE_FLOAT64:
      samplebytes = 8;
      break;
    }

    rawsize = msr->samplecnt * samplebytes;

    if (rawsize < *datasize)
      *datasize = (uint16_t)rawsize;
  }

  /* If datasize is a multiple of 64-bytes and a Steim encoding, test for
   * trailing, zeroed (empty) frames and subtract them from the size. */
  if (*datasize % 64 == 0 &&
      (msr->encoding == DE_STEIM1 || msr->encoding == DE_STEIM2))
  {
    while (*datasize > 0 &&
           memcmp (msr->record + (*datasize - 64), nullframe, 64) == 0)
    {
      *datasize -= 64;
    }
  }

  return 0;
} /* End of msr3_data_bounds() */

/*******************************************************************/ /**
 * @brief Unpack data samples for a ::MS3Record
 *
 * This routine can be used to unpack the data samples for a
 * ::MS3Record that was earlier parsed without the data samples being
 * decoded.
 *
 * The packed/encoded data is accessed in the record indicated by
 * ::MS3Record.record and the unpacked samples are placed in
 * ::MS3Record.datasamples.  The resulting data samples are either
 * text characters, 32-bit integers, 32-bit floats or 64-bit
 * floats in host byte order.
 *
 * An internal buffer is allocated if the encoded data is not aligned
 * for the sample size, which is a decent indicator of the alignment
 * needed for decoding efficiently.
 *
 * @param[in] msr Target ::MS3Record to unpack data samples
 * @param[in] verbose Flag to control verbosity, 0 means no diagnostic output
 *
 * @return number of samples unpacked or negative libmseed error code.
 *
 * \ref MessageOnError - this function logs a message on error
 ************************************************************************/
int64_t
msr3_unpack_data (MS3Record *msr, int8_t verbose)
{
  uint32_t datasize; /* byte size of data samples in record */
  int64_t nsamples; /* number of samples unpacked */
  size_t unpacksize; /* byte size of unpacked samples */
  uint8_t samplesize = 0; /* size of the data samples in bytes */
  uint32_t dataoffset = 0;
  const char *encoded = NULL;
  char *encoded_allocated = NULL;

  if (!msr)
  {
    ms_log (2, "Required argument not defined: 'msr'\n");
    return MS_GENERROR;
  }

  if (msr->samplecnt <= 0)
    return 0;

  if (!msr->record)
  {
    ms_log (2, "%s: Raw record pointer is unset\n", msr->sid);
    return MS_GENERROR;
  }

  /* Sanity check record length */
  if (msr->reclen < 0)
  {
    ms_log (2, "%s: Record size unknown\n", msr->sid);
    return MS_NOTSEED;
  }
  else if (msr->reclen < MINRECLEN || msr->reclen > MAXRECLEN)
  {
    ms_log (2, "%s: Unsupported record length: %d\n", msr->sid, msr->reclen);
    return MS_OUTOFRANGE;
  }

  if (msr->samplecnt > INT32_MAX)
  {
    ms_log (2, "%s: Too many samples to unpack: %" PRId64 "\n", msr->sid, msr->samplecnt);
    return MS_GENERROR;
  }

  /* Determine offset to data and length of data payload */
  if (msr3_data_bounds (msr, &dataoffset, &datasize))
    return MS_GENERROR;

  /* Sanity check data offset before creating a pointer based on the value */
  if (dataoffset < MINRECLEN || dataoffset >= (uint32_t)msr->reclen)
  {
    ms_log (2, "%s: Data offset value is not valid: %u\n", msr->sid, dataoffset);
    return MS_GENERROR;
  }

  /* Fallback encoding for when encoding is unknown */
  if (msr->encoding < 0)
  {
    if (verbose > 2)
      ms_log (0, "%s: No data encoding (no blockette 1000?), assuming Steim-1\n", msr->sid);

    msr->encoding = DE_STEIM1;
  }

  if (ms_encoding_sizetype(msr->encoding, &samplesize, NULL))
  {
    ms_log (2, "%s: Cannot determine sample size for encoding: %u\n", msr->sid, msr->encoding);
    return MS_GENERROR;
  }

  encoded = msr->record + dataoffset;

  /* Copy encoded data to aligned/malloc'd buffer if not aligned for sample size */
  if (samplesize && !is_aligned (encoded, samplesize))
  {
    if ((encoded_allocated = (char *) libmseed_memory.malloc (datasize)) == NULL)
    {
      ms_log (2, "Cannot allocate memory for encoded data\n");
      return MS_GENERROR;
    }

    memcpy (encoded_allocated, encoded, datasize);
    encoded = encoded_allocated;
  }

  /* Calculate buffer size needed for unpacked samples */
  unpacksize = (size_t)msr->samplecnt * samplesize;

  /* (Re)Allocate space for the unpacked data */
  if (unpacksize > 0)
  {
    if (libmseed_prealloc_block_size)
    {
      msr->datasamples = libmseed_memory_prealloc (msr->datasamples, unpacksize, &(msr->datasize));
    }
    else
    {
      msr->datasamples = libmseed_memory.realloc (msr->datasamples, unpacksize);
      msr->datasize = unpacksize;
    }

    if (msr->datasamples == NULL)
    {
      ms_log (2, "%s: Cannot (re)allocate memory\n", msr->sid);
      msr->datasize = 0;
      if (encoded_allocated)
        libmseed_memory.free (encoded_allocated);
      return MS_GENERROR;
    }
  }
  else
  {
    if (msr->datasamples)
      libmseed_memory.free (msr->datasamples);
    msr->datasamples = NULL;
    msr->datasize = 0;
    msr->numsamples = 0;
  }

  if (verbose > 2)
    ms_log (0, "%s: Unpacking %" PRId64 " samples\n", msr->sid, msr->samplecnt);

  nsamples = ms_decode_data (encoded, datasize, msr->encoding, msr->samplecnt,
                             msr->datasamples, msr->datasize, &(msr->sampletype),
                             (msr->swapflag & MSSWAP_PAYLOAD), msr->sid, verbose);

  if (encoded_allocated)
    libmseed_memory.free (encoded_allocated);

  if (nsamples > 0)
    msr->numsamples = nsamples;

  return nsamples;
} /* End of msr3_unpack_data() */

/*******************************************************************/ /**
 * @brief Decode data samples to a supplied buffer
 *
 * @param[in] input Encoded data
 * @param[in] inputsize Size of \a input buffer in bytes
 * @param[in] encoding Data encoding
 * @param[in] samplecount Number of samples to decode
 * @param[out] output Decoded data
 * @param[in] outputsize Size of \a output buffer in bytes
 * @param[out] sampletype Pointer to (single character) sample type of decoded data
 * @param[in] swapflag Flag indicating if encoded data needs swapping
 * @param[in] sid Source identifier to include in diagnostic/error messages
 * @param[in] verbose Flag to control verbosity, 0 means no diagnostic output
 *
 * @return number of samples decoded or negative libmseed error code.
 *
 * \ref MessageOnError - this function logs a message on error
 ************************************************************************/
int64_t
ms_decode_data (const void *input, size_t inputsize, uint8_t encoding,
                int64_t samplecount, void *output, size_t outputsize,
                char *sampletype, int8_t swapflag, const char *sid, int8_t verbose)
{
  size_t decodedsize; /* byte size of decodeded samples */
  int32_t nsamples; /* number of samples unpacked */
  uint8_t samplesize = 0; /* size of the data samples in bytes */

  if (!input || !output || !sampletype)
  {
    ms_log (2, "Required argument not defined: 'input', 'output' or 'sampletype'\n");
    return MS_GENERROR;
  }

  if (samplecount <= 0)
    return 0;

  if (ms_encoding_sizetype(encoding, &samplesize, sampletype))
    samplesize = 0;

  /* Calculate buffer size needed for unpacked samples */
  decodedsize = (size_t)samplecount * samplesize;

  if (decodedsize > outputsize)
  {
    ms_log (2, "%s: Output buffer (%"PRIsize_t" bytes) is not large enought for decoded data (%"PRIsize_t" bytes)\n",
            (sid) ? sid : "", decodedsize, outputsize);
    return MS_GENERROR;
  }

  /* Decode data samples according to encoding */
  switch (encoding)
  {
  case DE_TEXT:
    if (verbose > 1)
      ms_log (0, "%s: Decoding TEXT data\n", (sid) ? sid : "");

    nsamples = (int32_t)samplecount;
    if (nsamples > 0)
    {
      memcpy (output, input, nsamples);
    }
    else
    {
      nsamples = 0;
    }
    break;

  case DE_INT16:
    if (verbose > 1)
      ms_log (0, "%s: Decoding INT16 data samples\n", (sid) ? sid : "");

    nsamples = msr_decode_int16 ((int16_t *)input, samplecount,
                                 (int32_t *)output, decodedsize, swapflag);
    break;

  case DE_INT32:
    if (verbose > 1)
      ms_log (0, "%s: Decoding INT32 data samples\n", (sid) ? sid : "");

    nsamples = msr_decode_int32 ((int32_t *)input, samplecount,
                                 (int32_t *)output, decodedsize, swapflag);
    break;

  case DE_FLOAT32:
    if (verbose > 1)
      ms_log (0, "%s: Decoding FLOAT32 data samples\n", (sid) ? sid : "");

    nsamples = msr_decode_float32 ((float *)input, samplecount,
                                   (float *)output, decodedsize, swapflag);
    break;

  case DE_FLOAT64:
    if (verbose > 1)
      ms_log (0, "%s: Decoding FLOAT64 data samples\n", (sid) ? sid : "");

    nsamples = msr_decode_float64 ((double *)input, samplecount,
                                   (double *)output, decodedsize, swapflag);
    break;

  case DE_STEIM1:
    if (verbose > 1)
      ms_log (0, "%s: Decoding Steim1 data frames\n", (sid) ? sid : "");

    nsamples = msr_decode_steim1 ((int32_t *)input, inputsize, samplecount,
                                  (int32_t *)output, decodedsize, (sid) ? sid : "", swapflag);

    if (nsamples < 0)
    {
      nsamples = MS_GENERROR;
      break;
    }

    break;

  case DE_STEIM2:
    if (verbose > 1)
      ms_log (0, "%s: Decoding Steim2 data frames\n", (sid) ? sid : "");

    nsamples = msr_decode_steim2 ((int32_t *)input, inputsize, samplecount,
                                  (int32_t *)output, decodedsize, (sid) ? sid : "", swapflag);

    if (nsamples < 0)
    {
      nsamples = MS_GENERROR;
      break;
    }

    break;

  case DE_GEOSCOPE24:
  case DE_GEOSCOPE163:
  case DE_GEOSCOPE164:
    if (verbose > 1)
    {
      if (encoding == DE_GEOSCOPE24)
        ms_log (0, "%s: Decoding GEOSCOPE 24bit integer data samples\n", (sid) ? sid : "");
      if (encoding == DE_GEOSCOPE163)
        ms_log (0, "%s: Decoding GEOSCOPE 16bit gain ranged/3bit exponent data samples\n", (sid) ? sid : "");
      if (encoding == DE_GEOSCOPE164)
        ms_log (0, "%s: Decoding GEOSCOPE 16bit gain ranged/4bit exponent data samples\n", (sid) ? sid : "");
    }

    nsamples = msr_decode_geoscope ((char *)input, samplecount, (float *)output,
                                    decodedsize, encoding, (sid) ? sid : "", swapflag);
    break;

  case DE_CDSN:
    if (verbose > 1)
      ms_log (0, "%s: Decoding CDSN encoded data samples\n", (sid) ? sid : "");

    nsamples = msr_decode_cdsn ((int16_t *)input, samplecount, (int32_t *)output,
                                decodedsize, swapflag);
    break;

  case DE_SRO:
    if (verbose > 1)
      ms_log (0, "%s: Decoding SRO encoded data samples\n", (sid) ? sid : "");

    nsamples = msr_decode_sro ((int16_t *)input, samplecount, (int32_t *)output,
                               decodedsize, (sid) ? sid : "", swapflag);
    break;

  case DE_DWWSSN:
    if (verbose > 1)
      ms_log (0, "%s: Decoding DWWSSN encoded data samples\n", (sid) ? sid : "");

    nsamples = msr_decode_dwwssn ((int16_t *)input, samplecount, (int32_t *)output,
                                  decodedsize, swapflag);
    break;

  default:
    ms_log (2, "%s: Unsupported encoding format %d (%s)\n",
            (sid) ? sid : "", encoding, (char *)ms_encodingstr (encoding));

    nsamples = MS_UNKNOWNFORMAT;
    break;
  }

  if (nsamples >= 0 && nsamples != samplecount)
  {
    ms_log (2, "%s: only decoded %d samples of %" PRId64 " expected\n",
            (sid) ? sid : "", nsamples, samplecount);
    return MS_GENERROR;
  }

  return nsamples;
} /* End of ms_decode_data() */

/***************************************************************************
 * Calculate a sample rate from SEED sample rate factor and multiplier
 * as stored in the fixed section header of data records.
 *
 * Returns the positive sample rate.
 ***************************************************************************/
double
ms_nomsamprate (int factor, int multiplier)
{
  double samprate = 0.0;

  if (factor > 0)
    samprate = (double)factor;
  else if (factor < 0)
    samprate = -1.0 / (double)factor;
  if (multiplier > 0)
    samprate = samprate * (double)multiplier;
  else if (multiplier < 0)
    samprate = -1.0 * (samprate / (double)multiplier);

  return samprate;
} /* End of ms_nomsamprate() */

/***************************************************************************
 * ms2_recordsid:
 *
 * Generate an FDSN source identifier string for a specified raw
 * miniSEED 2.x data record.
 *
 * Returns a pointer to the resulting string or NULL on error.
 ***************************************************************************/
char *
ms2_recordsid (const char *record, char *sid, int sidlen)
{
  char net[3] = {0};
  char sta[6] = {0};
  char loc[3] = {0};
  char chan[6] = {0};

  if (!record || !sid)
    return NULL;

  ms_strncpclean (net, pMS2FSDH_NETWORK (record), 2);
  ms_strncpclean (sta, pMS2FSDH_STATION (record), 5);
  ms_strncpclean (loc, pMS2FSDH_LOCATION (record), 2);

  /* Map 3 channel codes to BAND_SOURCE_POSITION */
  chan[0] = *pMS2FSDH_CHANNEL (record);
  chan[1] = '_';
  chan[2] = *(pMS2FSDH_CHANNEL (record) + 1);
  chan[3] = '_';
  chan[4] = *(pMS2FSDH_CHANNEL (record) + 2);

  if (ms_nslc2sid (sid, sidlen, 0, net, sta, loc, chan) < 0)
    return NULL;

  return sid;
} /* End of ms2_recordsid() */

/***************************************************************************
 * ms2_blktdesc():
 *
 * Return a string describing a given blockette type or NULL if the
 * type is unknown.
 ***************************************************************************/
const char *
ms2_blktdesc (uint16_t blkttype)
{
  switch (blkttype)
  {
  case 100:
    return "Sample Rate";
    break;
  case 200:
    return "Generic Event Detection";
    break;
  case 201:
    return "Murdock Event Detection";
    break;
  case 300:
    return "Step Calibration";
    break;
  case 310:
    return "Sine Calibration";
    break;
  case 320:
    return "Pseudo-random Calibration";
    break;
  case 390:
    return "Generic Calibration";
    break;
  case 395:
    return "Calibration Abort";
    break;
  case 400:
    return "Beam";
    break;
  case 500:
    return "Timing";
    break;
  case 1000:
    return "Data Only SEED";
    break;
  case 1001:
    return "Data Extension";
    break;
  case 2000:
    return "Opaque Data";
    break;
  } /* end switch */

  return NULL;

} /* End of ms2_blktdesc() */

/***************************************************************************
 * ms2_blktlen():
 *
 * Returns the total length of a given blockette type in bytes or 0 if
 * type unknown.
 ***************************************************************************/
uint16_t
ms2_blktlen (uint16_t blkttype, const char *blkt, int8_t swapflag)
{
  uint16_t blktlen = 0;

  switch (blkttype)
  {
  case 100: /* Sample Rate */
    blktlen = 12;
    break;
  case 200: /* Generic Event Detection */
    blktlen = 28;
    break;
  case 201: /* Murdock Event Detection */
    blktlen = 36;
    break;
  case 300: /* Step Calibration */
    blktlen = 32;
    break;
  case 310: /* Sine Calibration */
    blktlen = 32;
    break;
  case 320: /* Pseudo-random Calibration */
    blktlen = 28;
    break;
  case 390: /* Generic Calibration */
    blktlen = 28;
    break;
  case 395: /* Calibration Abort */
    blktlen = 16;
    break;
  case 400: /* Beam */
    blktlen = 16;
    break;
  case 500: /* Timing */
    blktlen = 8;
    break;
  case 1000: /* Data Only SEED */
    blktlen = 8;
    break;
  case 1001: /* Data Extension */
    blktlen = 8;
    break;
  case 2000: /* Opaque Data */
    /* First 2-byte field after the blockette header is the length */
    if (blkt)
    {
      memcpy ((void *)&blktlen, blkt + 4, sizeof (int16_t));
      if (swapflag)
        ms_gswap2 (&blktlen);
    }
    break;
  } /* end switch */

  return blktlen;

} /* End of ms2_blktlen() */

/***************************************************************************
 * Static inline convenience function to convert a SEED 2.x "BTIME"
 * structure to an nstime_t value.
 *
 * The 10-byte BTIME structure layout:
 *
 * Value  Type      Offset  Description
 * year   uint16_t  0       Four digit year (e.g. 1987)
 * day    uint16_t  2       Day of year (Jan 1st is 1)
 * hour   uint8_t   4       Hour (0 - 23)
 * min    uint8_t   5       Minute (0 - 59)
 * sec    uint8_t   6       Second (0 - 59, 60 for leap seconds)
 * unused uint8_t   7       Unused, included for alignment
 * fract  uint16_t  8       0.0001 seconds, i.e. 1/10ths of milliseconds (0—9999)
 *
 * Return nstime_t value on success, NSTUNSET when year is 0, and NSTERROR on error.
 *
 * \ref MessageOnError - this function logs a message on error
 ***************************************************************************/
static inline nstime_t
ms_btime2nstime (uint8_t *btime, int8_t swapflag)
{
  uint16_t year;

  if (btime == NULL)
  {
    return NSTERROR;
  }

  year = HO2u (*((uint16_t*)(btime)), swapflag);

  /* Special case, if year 0 return unset value */
  if (year == 0)
  {
    return NSTUNSET;
  }

  return ms_time2nstime (year,
                         HO2u (*((uint16_t *)(btime + 2)), swapflag),
                         *(btime + 4),
                         *(btime + 5),
                         *(btime + 6),
                         (uint32_t)HO2u (*(uint16_t *)(btime + 8), swapflag) * (NSTMODULUS / 10000));
} /* End of ms_btime2nstime() */
