/****************************************************************************
 * Routines to parse miniSEED.
 *
 * This file is part of the miniSEED Library.
 *
 * Copyright (c) 2024 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

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

/***********************************************************************/ /**
 * @brief Parse miniSEED from a buffer
 *
 * This routine will attempt to parse (detect and unpack) a miniSEED
 * record from a specified memory buffer and populate a supplied
 * ::MS3Record structure.  Both miniSEED 2.x and 3.x records are
 * supported.
 *
 * The record length is automatically detected.  For miniSEED 2.x this
 * means the record must contain a 1000 blockette.
 *
 * @param record Buffer containing record to parse
 * @param recbuflen Buffer length in bytes
 * @param ppmsr Pointer-to-point to a ::MS3Record that will be populated
 * @param flags Flags controlling features:
 * @parblock
 *  - \c ::MSF_UNPACKDATA - Unpack data samples
 *  - \c ::MSF_VALIDATECRC Validate CRC (if present in format)
 * @endparblock
 * @param verbose control verbosity of diagnostic output
 *
 * @return Parsing status
 * @retval 0 Success, populates the supplied ::MS3Record.
 * @retval >0 Data record detected but not enough data is present, the
 *       return value is a hint of how many more bytes are needed.
 * @retval <0 library error code is returned.
 *
 * \ref MessageOnError - this function logs a message on error except MS_NOTSEED
 ***************************************************************************/
int
msr3_parse (const char *record, uint64_t recbuflen, MS3Record **ppmsr,
            uint32_t flags, int8_t verbose)
{
  int64_t reclen = 0;
  int64_t retcode = MS_NOERROR;
  uint8_t formatversion = 0;

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

  /* Detect record, determine length and format version */
  reclen = ms3_detect (record, recbuflen, &formatversion);

  /* Return record length implied by buffer length if:
     - version 2
     - length could not be determined
     - buffer is at the end of the file
     - buffer length is a power of 2
     - within supported record length

     Power of two if (X & (X - 1)) == 0 */
  if (formatversion == 2 &&
      reclen == 0 &&
      flags & MSF_ATENDOFFILE &&
      (recbuflen & (recbuflen - 1)) == 0 &&
      recbuflen <= MAXRECLEN)
  {
    reclen = (int64_t)recbuflen;
  }

  /* No data record detected */
  if (reclen < 0)
  {
    return MS_NOTSEED;
  }

  /* Found record but could not determine length */
  if (reclen == 0)
  {
    return MINRECLEN;
  }

  if (verbose > 2)
  {
    ms_log (0, "Detected record length of %" PRId64 " bytes\n", reclen);
  }

  /* Check that record length is in supported range */
  if (reclen < MINRECLEN || reclen > MAXRECLEN)
  {
    ms_log (2, "Record length of %" PRId64 " is out of range allowed: %d to %d)\n",
            reclen, MINRECLEN, MAXRECLEN);

    return MS_OUTOFRANGE;
  }
  /* Check if more data is required, return hint */
  else if ((uint64_t)reclen > recbuflen)
  {
    uint64_t need = reclen - recbuflen;

    if (verbose > 2)
      ms_log (0, "Detected %" PRId64 " byte record, need %" PRIu64 " more bytes\n",
              reclen, need);

    return (need > MAXRECLEN) ? MAXRECLEN : (int)need;
  }

  /* Unpack record */
  if (formatversion == 3)
  {
    retcode = msr3_unpack_mseed3 (record, (int)reclen, ppmsr, flags, verbose);
  }
  else if (formatversion == 2)
  {
    retcode = msr3_unpack_mseed2 (record, (int)reclen, ppmsr, flags, verbose);
  }
  else
  {
    ms_log (2, "Unrecognized format version: %d\n", formatversion);

    return MS_GENERROR;
  }

  if (retcode != MS_NOERROR)
  {
    msr3_free (ppmsr);

    return (int)retcode;
  }

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

/***************************************************************/ /**
 * @brief Detect miniSEED record in buffer
 *
 * Determine if the buffer contains a miniSEED data record by
 * verifying known signatures (fields with known limited values).
 *
 * If miniSEED 2.x is detected, search the record up to recbuflen
 * bytes for a 1000 blockette. If no blockette 1000 is found, search
 * at 64-byte offsets for the fixed section of the next header,
 * thereby implying the record length.
 *
 * @param[in] record Buffer to test for record
 * @param[in] recbuflen Length of buffer
 * @param[out] formatversion Major version of format detected, 0 if unknown
 *
 * @retval -1 Data record not detected or error
 * @retval 0 Data record detected but could not determine length
 * @retval >0 Size of the record in bytes
 *
 * \ref MessageOnError - this function logs a message on error
 *********************************************************************/
int64_t
ms3_detect (const char *record, uint64_t recbuflen, uint8_t *formatversion)
{
  uint8_t swapflag = 0; /* Byte swapping flag */
  uint8_t foundlen = 0; /* Found record length */
  int64_t reclen = -1; /* Size of record in bytes */

  uint16_t blkt_offset; /* Byte offset for next blockette */
  uint16_t blkt_type;
  uint16_t next_blkt;
  const char *nextfsdh;

  if (!record || !formatversion)
  {
    ms_log (2, "%s(): Required input not defined: 'record' or 'formatversion'\n",
            __func__);
    return -1;
  }

  /* Buffer must be at least MINRECLEN */
  if (recbuflen < MINRECLEN)
    return -1;

  /* Check for valid header, set format version */
  *formatversion = 0;
  if (MS3_ISVALIDHEADER (record))
  {
    *formatversion = 3;

    reclen = MS3FSDH_LENGTH                   /* Length of fixed portion of header */
             + *pMS3FSDH_SIDLENGTH (record)   /* Length of source identifier */
             + *pMS3FSDH_EXTRALENGTH (record) /* Length of extra headers */
             + *pMS3FSDH_DATALENGTH (record); /* Length of data payload */

    foundlen = 1;
  }
  else if (MS2_ISVALIDHEADER (record))
  {
    *formatversion = 2;

    /* Check to see if byte swapping is needed by checking for sane year and day */
    if (!MS_ISVALIDYEARDAY (*pMS2FSDH_YEAR(record), *pMS2FSDH_DAY(record)))
      swapflag = 1;

    blkt_offset = HO2u(*pMS2FSDH_BLOCKETTEOFFSET (record), swapflag);

    /* Loop through blockettes as long as number is non-zero and viable */
    while (blkt_offset != 0 &&
           blkt_offset > 47 &&
           blkt_offset <= recbuflen)
    {
      memcpy (&blkt_type, record + blkt_offset, 2);
      memcpy (&next_blkt, record + blkt_offset + 2, 2);

      if (swapflag)
      {
        ms_gswap2 (&blkt_type);
        ms_gswap2 (&next_blkt);
      }

      /* Found a 1000 blockette, not truncated */
      if (blkt_type == 1000 &&
          (uint64_t)(blkt_offset + 8) <= recbuflen)
      {
        foundlen = 1;

        /* Field 3 of B1000 is a uint8_t value describing the record
         * length as 2^(value).  Calculate 2-raised with a shift. */
        reclen = (uint64_t)1 << *pMS2B1000_RECLEN (record + blkt_offset);

        break;
      }

      /* Safety check for invalid offset */
      if (next_blkt != 0 && (next_blkt < 4 || (next_blkt - 4) <= blkt_offset))
      {
        ms_log (2, "Invalid blockette offset (%d) less than or equal to current offset (%d)\n",
                next_blkt, blkt_offset);
        return -1;
      }

      blkt_offset = next_blkt;
    }

    /* If record length was not determined by a 1000 blockette scan the buffer
     * and search for the next record */
    if (reclen == -1)
    {
      nextfsdh = record + 64;

      /* Check for record header or blank/noise record at MINRECLEN byte offsets */
      while (((uint64_t)(nextfsdh - record) + 48) < recbuflen)
      {
        if (MS2_ISVALIDHEADER (nextfsdh))
        {
          foundlen = 1;
          reclen = nextfsdh - record;
          break;
        }

        nextfsdh += 64;
      }
    }
  } /* End of miniSEED 2.x detection */

  if (*formatversion && !foundlen)
    return 0;
  else
    return reclen;
} /* End of ms3_detect() */

/**********************************************************************/ /**
 * @brief Parse and verify a miniSEED 3.x record header
 *
 * Parsing is done at the lowest level, printing error messages for
 * invalid header values and optionally print raw header values.
 *
 * The buffer at \a record is assumed to be a miniSEED record.  Not
 * every possible test is performed, common errors and those causing
 * library parsing to fail should be detected.
 *
 * This routine is primarily intended to diagnose invalid miniSEED headers.
 *
 * @param[in] record Buffer to parse as miniSEED
 * @param[in] maxreclen Maximum length to search in buffer
 * @param[in] details Controls diagnostic output as follows:
 * @parblock
 *  - \c 0 - only print error messages for invalid header fields
 *  - \c 1 - print basic fields in addition to invalid field errors
 *  - \c 2 - print all fields in addition to invalid field errors
 * @endparblock
 *
 * @returns 0 when no errors were detected or a positive count of
 * errors detected.
 *
 * \ref MessageOnError - this function logs a message on error
 ***************************************************************************/
int
ms_parse_raw3 (const char *record, int maxreclen, int8_t details)
{
  MS3Record msr;
  const char *X;
  uint8_t b;

  int retval = 0;
  int8_t swapflag;
  uint8_t sidlength;
  char *sid = NULL;

  if (!record)
  {
    ms_log (2, "%s(): Required input not defined: 'record'\n", __func__);
    return 1;
  }

  if (maxreclen < MINRECLEN)
  {
    ms_log (2, "%s(): The maxreclen value cannot be smaller than MINRECLEN\n",
            __func__);
    return 1;
  }

  swapflag = (ms_bigendianhost()) ? 1 : 0;

  if (details > 1)
  {
    if (swapflag == 1)
      ms_log (0, "Swapping multi-byte quantities in header\n");
    else
      ms_log (0, "Not swapping multi-byte quantities in header\n");
  }

  sidlength = *pMS3FSDH_SIDLENGTH(record);

  /* Check if source identifier length is unreasonably small */
  if (sidlength < 4)
  {
    ms_log (2, "Unlikely source identifier length: '%d'\n", sidlength);
    return 1;
  }

  /* Make sure buffer contains the identifier */
  if ((MS3FSDH_LENGTH + sidlength) > maxreclen)
  {
    ms_log (2, "Not enough buffer contain the identifer: '%d'\n", maxreclen);
    return 1;
  }

  sid = pMS3FSDH_SID(record);

  /* Validate fixed section header fields */
  X = record; /* Pointer of convenience */

  /* Check record indicator == 'MS' */
  if (*(X) != 'M' || *(X + 1) != 'S')
  {
    ms_log (2, "%.*s: Invalid miniSEED 3 record indicator: '%c%c'\n",
            sidlength, sid, *(X), *(X + 1));
    retval++;
  }

  /* Check data format == 3 */
  if (((uint8_t)*(X + 2)) != 3)
  {
    ms_log (2, "%.*s: Invalid miniSEED format version: '%d'\n",
            sidlength, sid, (uint8_t)*(X + 2));
    retval++;
  }

  /* Check start time fields */
  if (HO2u(*pMS3FSDH_YEAR (record), swapflag) < 1900 || HO2u(*pMS3FSDH_YEAR (record), swapflag) > 2100)
  {
    ms_log (2, "%.*s: Unlikely start year (1900-2100): '%d'\n",
            sidlength, sid, HO2u(*pMS3FSDH_YEAR (record), swapflag));
    retval++;
  }
  if (HO2u(*pMS3FSDH_DAY (record), swapflag) < 1 || HO2u(*pMS3FSDH_DAY (record), swapflag) > 366)
  {
    ms_log (2, "%.*s: Invalid start day (1-366): '%d'\n",
            sidlength, sid, HO2u(*pMS3FSDH_DAY (record), swapflag));
    retval++;
  }
  if (*pMS3FSDH_HOUR (record) > 23)
  {
    ms_log (2, "%.*s: Invalid start hour (0-23): '%d'\n",
            sidlength, sid, *pMS3FSDH_HOUR (record));
    retval++;
  }
  if (*pMS3FSDH_MIN (record) > 59)
  {
    ms_log (2, "%.*s: Invalid start minute (0-59): '%d'\n",
            sidlength, sid, *pMS3FSDH_MIN (record));
    retval++;
  }
  if (*pMS3FSDH_SEC (record) > 60)
  {
    ms_log (2, "%.*s: Invalid start second (0-60): '%d'\n",
            sidlength, sid, *pMS3FSDH_SEC (record));
    retval++;
  }
  if (HO4u(*pMS3FSDH_NSEC (record), swapflag) > 999999999)
  {
    ms_log (2, "%.*s: Invalid start nanoseconds (0-999999999): '%u'\n",
            sidlength, sid, HO4u(*pMS3FSDH_NSEC (record), swapflag));
    retval++;
  }

  /* Print raw header details */
  if (details >= 1)
  {
    /* Print header values */
    ms_log (0, "RECORD -- %.*s\n", sidlength, sid);
    ms_log (0, "       record indicator: '%c%c'\n",
            pMS3FSDH_INDICATOR (record)[0], pMS3FSDH_INDICATOR (record)[1]);
    /* Flags */
    b = *pMS3FSDH_FLAGS (record);
    ms_log (0, "         activity flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
            bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
            bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));

    if (details > 1)
    {
      if (b & 0x01)
        ms_log (0, "                         [Bit 0] Calibration signals present\n");
      if (b & 0x02)
        ms_log (0, "                         [Bit 1] Time tag questionable\n");
      if (b & 0x04)
        ms_log (0, "                         [Bit 2] Clock locked\n");
      if (b & 0x08)
        ms_log (0, "                         [Bit 3] Undefined bit set\n");
      if (b & 0x10)
        ms_log (0, "                         [Bit 4] Undefined bit set\n");
      if (b & 0x20)
        ms_log (0, "                         [Bit 5] Undefined bit set\n");
      if (b & 0x40)
        ms_log (0, "                         [Bit 6] Undefined bit set\n");
      if (b & 0x80)
        ms_log (0, "                         [Bit 7] Undefined bit set\n");
    }
    ms_log (0, "             start time: %u,%u,%u:%u:%u.%09u\n",
            HO2u(*pMS3FSDH_YEAR (record), swapflag),
            HO2u(*pMS3FSDH_DAY (record), swapflag),
            *pMS3FSDH_HOUR (record),
            *pMS3FSDH_MIN (record),
            *pMS3FSDH_SEC (record),
            HO4u(*pMS3FSDH_NSEC (record), swapflag));
    ms_log (0, "   sample rate+/period-: %g\n", HO8f(*pMS3FSDH_SAMPLERATE (record), swapflag));
    ms_log (0, "          data encoding: %u\n", *pMS3FSDH_ENCODING (record));
    ms_log (0, "    publication version: %u\n", *pMS3FSDH_PUBVERSION (record));
    ms_log (0, "      number of samples: %u\n", HO4u(*pMS3FSDH_NUMSAMPLES (record), swapflag));
    ms_log (0, "                    CRC: 0x%X\n", HO4u(*pMS3FSDH_CRC (record), swapflag));
    ms_log (0, "   length of identifier: %u\n", *pMS3FSDH_SIDLENGTH (record));
    ms_log (0, "length of extra headers: %u\n", HO2u(*pMS3FSDH_EXTRALENGTH (record), swapflag));
    ms_log (0, " length of data payload: %u\n", HO4u(*pMS3FSDH_DATALENGTH (record), swapflag));
  } /* Done printing raw header details */

  /* Print extra headers */
  msr.extralength = HO2u(*pMS3FSDH_EXTRALENGTH (record), swapflag);

  if (details > 1 && msr.extralength > 0)
  {
    ms_log (0, "          extra headers:\n");
    if ((MS3FSDH_LENGTH + sidlength + msr.extralength) <= maxreclen)
    {
      msr.extra = (char *)record + MS3FSDH_LENGTH + sidlength;
      mseh_print (&msr, 10);
    }
    else
    {
      ms_log (0, "      [buffer does not contain extra headers]\n");
    }
  }

  return retval;
} /* End of ms_parse_raw3() */

/**********************************************************************/ /**
 * @brief Parse and verify a miniSEED 2.x record header
 *
 * Parsing is done at the lowest level, printing error messages for
 * invalid header values and optionally print raw header values.
 *
 * The buffer \a record is assumed to be a miniSEED record.  Not every
 * possible test is performed, common errors and those causing
 * libmseed parsing to fail should be detected.
 *
 * This routine is primarily intended to diagnose invalid miniSEED headers.
 *
 * @param[in] record Buffer to parse as miniSEED
 * @param[in] maxreclen Maximum length to search in buffer
 * @param[in] details Controls diagnostic output as follows:
 * @parblock
 *  - \c 0 - only print error messages for invalid header fields
 *  - \c 1 - print basic fields in addition to invalid field errors
 *  - \c 2 - print all fields in addition to invalid field errors
 * @endparblock
 * @param[in] swapflag Flag controlling byte-swapping as follows:
 * @parblock
 *  - \c 1 - swap multibyte quantities
 *  - \c 0 - do not swap
 *  - \c -1 - autodetect byte order using year test, swap if needed
 * @endparblock
 *
 * @returns 0 when no errors were detected or a positive count of
 * errors detected.
 *
 * \ref MessageOnError - this function logs a message on error
 ***************************************************************************/
int
ms_parse_raw2 (const char *record, int maxreclen, int8_t details, int8_t swapflag)
{
  double nomsamprate;
  char sid[LM_SIDLEN] = {0};
  const char *X;
  uint8_t b;
  int retval = 0;
  int b1000encoding = -1;
  int b1000reclen = -1;
  int endofblockettes = -1;
  int idx;

  if (!record)
  {
    ms_log (2, "%s(): Required input not defined: 'record'\n", __func__);
    return 1;
  }

  if (maxreclen < 48)
  {
    ms_log (2, "%s(): The maxreclen value cannot be smaller than 48\n", __func__);
    return 1;
  }

  /* Build source identifier for this record */
  ms2_recordsid (record, sid, sizeof (sid));

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

  if (details > 1)
  {
    if (swapflag == 1)
      ms_log (0, "Swapping multi-byte quantities in header\n");
    else
      ms_log (0, "Not swapping multi-byte quantities in header\n");
  }

  /* Validate fixed section header fields */
  X = record; /* Pointer of convenience */

  /* Check record sequence number, 6 ASCII digits */
  if (!isdigit ((int)*(X)) || !isdigit ((int)*(X + 1)) ||
      !isdigit ((int)*(X + 2)) || !isdigit ((int)*(X + 3)) ||
      !isdigit ((int)*(X + 4)) || !isdigit ((int)*(X + 5)))
  {
    ms_log (2, "%s: Invalid sequence number: '%c%c%c%c%c%c'\n",
            sid, *X, *(X + 1), *(X + 2), *(X + 3), *(X + 4), *(X + 5));
    retval++;
  }

  /* Check header data/quality indicator */
  if (!MS2_ISDATAINDICATOR (*(X + 6)))
  {
    ms_log (2, "%s: Invalid header indicator (DRQM): '%c'\n", sid, *(X + 6));
    retval++;
  }

  /* Check reserved byte, space or NULL */
  if (!(*(X + 7) == ' ' || *(X + 7) == '\0'))
  {
    ms_log (2, "%s: Invalid fixed section reserved byte (space): '%c'\n", sid, *(X + 7));
    retval++;
  }

  /* Check station code, 5 alphanumerics or spaces */
  if (!(isalnum ((unsigned char)*(X + 8)) || *(X + 8) == ' ') ||
      !(isalnum ((unsigned char)*(X + 9)) || *(X + 9) == ' ') ||
      !(isalnum ((unsigned char)*(X + 10)) || *(X + 10) == ' ') ||
      !(isalnum ((unsigned char)*(X + 11)) || *(X + 11) == ' ') ||
      !(isalnum ((unsigned char)*(X + 12)) || *(X + 12) == ' '))
  {
    ms_log (2, "%s: Invalid station code: '%c%c%c%c%c'\n",
            sid, *(X + 8), *(X + 9), *(X + 10), *(X + 11), *(X + 12));
    retval++;
  }

  /* Check location ID, 2 alphanumerics or spaces */
  if (!(isalnum ((unsigned char)*(X + 13)) || *(X + 13) == ' ') ||
      !(isalnum ((unsigned char)*(X + 14)) || *(X + 14) == ' '))
  {
    ms_log (2, "%s: Invalid location ID: '%c%c'\n", sid, *(X + 13), *(X + 14));
    retval++;
  }

  /* Check channel codes, 3 alphanumerics or spaces */
  if (!(isalnum ((unsigned char)*(X + 15)) || *(X + 15) == ' ') ||
      !(isalnum ((unsigned char)*(X + 16)) || *(X + 16) == ' ') ||
      !(isalnum ((unsigned char)*(X + 17)) || *(X + 17) == ' '))
  {
    ms_log (2, "%s: Invalid channel codes: '%c%c%c'\n", sid, *(X + 15), *(X + 16), *(X + 17));
    retval++;
  }

  /* Check network code, 2 alphanumerics or spaces */
  if (!(isalnum ((unsigned char)*(X + 18)) || *(X + 18) == ' ') ||
      !(isalnum ((unsigned char)*(X + 19)) || *(X + 19) == ' '))
  {
    ms_log (2, "%s: Invalid network code: '%c%c'\n", sid, *(X + 18), *(X + 19));
    retval++;
  }

  /* Check start time fields */
  if (HO2u(*pMS2FSDH_YEAR (record), swapflag) < 1900 || HO2u(*pMS2FSDH_YEAR (record), swapflag) > 2100)
  {
    ms_log (2, "%s: Unlikely start year (1900-2100): '%d'\n", sid, HO2u(*pMS2FSDH_YEAR (record), swapflag));
    retval++;
  }
  if (HO2u(*pMS2FSDH_DAY (record), swapflag) < 1 || HO2u(*pMS2FSDH_DAY (record), swapflag) > 366)
  {
    ms_log (2, "%s: Invalid start day (1-366): '%d'\n", sid, HO2u(*pMS2FSDH_DAY (record), swapflag));
    retval++;
  }
  if (*pMS2FSDH_HOUR (record) > 23)
  {
    ms_log (2, "%s: Invalid start hour (0-23): '%d'\n", sid, *pMS2FSDH_HOUR (record));
    retval++;
  }
  if (*pMS2FSDH_MIN (record) > 59)
  {
    ms_log (2, "%s: Invalid start minute (0-59): '%d'\n", sid, *pMS2FSDH_MIN (record));
    retval++;
  }
  if (*pMS2FSDH_SEC (record) > 60)
  {
    ms_log (2, "%s: Invalid start second (0-60): '%d'\n", sid, *pMS2FSDH_SEC (record));
    retval++;
  }
  if (HO2u(*pMS2FSDH_FSEC (record), swapflag) > 9999)
  {
    ms_log (2, "%s: Invalid start fractional seconds (0-9999): '%d'\n", sid, HO2u(*pMS2FSDH_FSEC (record), swapflag));
    retval++;
  }

  /* Check number of samples, max samples in 4096-byte Steim-2 encoded record: 6601 */
  if (HO2u(*pMS2FSDH_NUMSAMPLES(record), swapflag) > 20000)
  {
    ms_log (2, "%s: Unlikely number of samples (>20000): '%d'\n",
            sid, HO2u(*pMS2FSDH_NUMSAMPLES(record), swapflag));
    retval++;
  }

  /* Sanity check that there is space for blockettes when both data and blockettes are present */
  if (HO2u(*pMS2FSDH_NUMSAMPLES(record), swapflag) > 0 &&
      *pMS2FSDH_NUMBLOCKETTES(record) > 0 &&
      HO2u(*pMS2FSDH_DATAOFFSET(record), swapflag) <= HO2u(*pMS2FSDH_BLOCKETTEOFFSET(record), swapflag))
  {
    ms_log (2, "%s: No space for %d blockettes, data offset: %d, blockette offset: %d\n", sid,
            *pMS2FSDH_NUMBLOCKETTES(record),
            HO2u(*pMS2FSDH_DATAOFFSET(record), swapflag),
            HO2u(*pMS2FSDH_BLOCKETTEOFFSET(record), swapflag));
    retval++;
  }

  /* Print raw header details */
  if (details >= 1)
  {
    /* Determine nominal sample rate */
    nomsamprate = ms_nomsamprate (HO2d(*pMS2FSDH_SAMPLERATEFACT (record), swapflag),
                                  HO2d(*pMS2FSDH_SAMPLERATEMULT (record), swapflag));

    /* Print header values */
    ms_log (0, "RECORD -- %s\n", sid);
    ms_log (0, "        sequence number: '%c%c%c%c%c%c'\n",
            pMS2FSDH_SEQNUM (record)[0], pMS2FSDH_SEQNUM (record)[1],
            pMS2FSDH_SEQNUM (record)[2], pMS2FSDH_SEQNUM (record)[3],
            pMS2FSDH_SEQNUM (record)[4], pMS2FSDH_SEQNUM (record)[5]);
    ms_log (0, " data quality indicator: '%c'\n", *pMS2FSDH_DATAQUALITY (record));
    if (details > 0)
      ms_log (0, "               reserved: '%c'\n", *pMS2FSDH_RESERVED (record));
    ms_log (0, "           station code: '%c%c%c%c%c'\n",
            pMS2FSDH_STATION (record)[0], pMS2FSDH_STATION (record)[1],
            pMS2FSDH_STATION (record)[2], pMS2FSDH_STATION (record)[3], pMS2FSDH_STATION (record)[4]);
    ms_log (0, "            location ID: '%c%c'\n",
            pMS2FSDH_LOCATION (record)[0], pMS2FSDH_LOCATION (record)[1]);
    ms_log (0, "          channel codes: '%c%c%c'\n",
            pMS2FSDH_CHANNEL (record)[0], pMS2FSDH_CHANNEL (record)[1], pMS2FSDH_CHANNEL (record)[2]);
    ms_log (0, "           network code: '%c%c'\n",
            pMS2FSDH_NETWORK (record)[0], pMS2FSDH_NETWORK (record)[1]);
    ms_log (0, "             start time: %d,%d,%d:%d:%d.%04d (unused: %d)\n",
            HO2u(*pMS2FSDH_YEAR (record), swapflag),
            HO2u(*pMS2FSDH_DAY (record), swapflag),
            *pMS2FSDH_HOUR (record),
            *pMS2FSDH_MIN (record),
            *pMS2FSDH_SEC (record),
            HO2u(*pMS2FSDH_FSEC (record), swapflag),
            *pMS2FSDH_UNUSED (record));
    ms_log (0, "      number of samples: %d\n", HO2u(*pMS2FSDH_NUMSAMPLES (record), swapflag));
    ms_log (0, "     sample rate factor: %d  (%.10g samples per second)\n",
            HO2d(*pMS2FSDH_SAMPLERATEFACT (record), swapflag), nomsamprate);
    ms_log (0, " sample rate multiplier: %d\n", HO2d(*pMS2FSDH_SAMPLERATEMULT (record), swapflag));

    /* Print flag details if requested */
    if (details > 1)
    {
      /* Activity flags */
      b = *pMS2FSDH_ACTFLAGS (record);
      ms_log (0, "         activity flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
              bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
              bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));
      if (b & 0x01)
        ms_log (0, "                         [Bit 0] Calibration signals present\n");
      if (b & 0x02)
        ms_log (0, "                         [Bit 1] Time correction applied\n");
      if (b & 0x04)
        ms_log (0, "                         [Bit 2] Beginning of an event, station trigger\n");
      if (b & 0x08)
        ms_log (0, "                         [Bit 3] End of an event, station detrigger\n");
      if (b & 0x10)
        ms_log (0, "                         [Bit 4] A positive leap second happened in this record\n");
      if (b & 0x20)
        ms_log (0, "                         [Bit 5] A negative leap second happened in this record\n");
      if (b & 0x40)
        ms_log (0, "                         [Bit 6] Event in progress\n");
      if (b & 0x80)
        ms_log (0, "                         [Bit 7] Undefined bit set\n");

      /* I/O and clock flags */
      b = *pMS2FSDH_IOFLAGS (record);
      ms_log (0, "    I/O and clock flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
              bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
              bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));
      if (b & 0x01)
        ms_log (0, "                         [Bit 0] Station volume parity error possibly present\n");
      if (b & 0x02)
        ms_log (0, "                         [Bit 1] Long record read (possibly no problem)\n");
      if (b & 0x04)
        ms_log (0, "                         [Bit 2] Short record read (record padded)\n");
      if (b & 0x08)
        ms_log (0, "                         [Bit 3] Start of time series\n");
      if (b & 0x10)
        ms_log (0, "                         [Bit 4] End of time series\n");
      if (b & 0x20)
        ms_log (0, "                         [Bit 5] Clock locked\n");
      if (b & 0x40)
        ms_log (0, "                         [Bit 6] Undefined bit set\n");
      if (b & 0x80)
        ms_log (0, "                         [Bit 7] Undefined bit set\n");

      /* Data quality flags */
      b = *pMS2FSDH_DQFLAGS (record);
      ms_log (0, "     data quality flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
              bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
              bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));
      if (b & 0x01)
        ms_log (0, "                         [Bit 0] Amplifier saturation detected\n");
      if (b & 0x02)
        ms_log (0, "                         [Bit 1] Digitizer clipping detected\n");
      if (b & 0x04)
        ms_log (0, "                         [Bit 2] Spikes detected\n");
      if (b & 0x08)
        ms_log (0, "                         [Bit 3] Glitches detected\n");
      if (b & 0x10)
        ms_log (0, "                         [Bit 4] Missing/padded data present\n");
      if (b & 0x20)
        ms_log (0, "                         [Bit 5] Telemetry synchronization error\n");
      if (b & 0x40)
        ms_log (0, "                         [Bit 6] A digital filter may be charging\n");
      if (b & 0x80)
        ms_log (0, "                         [Bit 7] Time tag is questionable\n");
    }

    ms_log (0, "   number of blockettes: %d\n", *pMS2FSDH_NUMBLOCKETTES (record));
    ms_log (0, "        time correction: %ld\n", (long int)HO4d(*pMS2FSDH_TIMECORRECT (record), swapflag));
    ms_log (0, "            data offset: %d\n", HO2u(*pMS2FSDH_DATAOFFSET (record), swapflag));
    ms_log (0, " first blockette offset: %d\n", HO2u(*pMS2FSDH_BLOCKETTEOFFSET (record), swapflag));
  } /* Done printing raw header details */

  /* Validate and report information in the blockette chain */
  if (HO2u(*pMS2FSDH_BLOCKETTEOFFSET (record), swapflag) > 46 &&
      HO2u(*pMS2FSDH_BLOCKETTEOFFSET (record), swapflag) < maxreclen)
  {
    int blkt_offset = HO2u(*pMS2FSDH_BLOCKETTEOFFSET (record), swapflag);
    int blkt_count  = 0;
    int blkt_length;
    uint16_t blkt_type;
    uint16_t next_blkt;
    const char *blkt_desc;

    /* Traverse blockette chain */
    while (blkt_offset != 0 && 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 (swapflag)
      {
        ms_gswap2 (&blkt_type);
        ms_gswap2 (&next_blkt);
      }

      /* Print common header fields */
      if (details >= 1)
      {
        blkt_desc = ms2_blktdesc (blkt_type);
        ms_log (0, "          BLOCKETTE %u: (%s)\n", blkt_type, (blkt_desc) ? blkt_desc : "Unknown");
        ms_log (0, "              next blockette: %u\n", next_blkt);
      }

      blkt_length = ms2_blktlen (blkt_type, record + blkt_offset, swapflag);
      if (blkt_length == 0)
      {
        ms_log (2, "%s: Unknown blockette length for type %d\n", sid, blkt_type);
        retval++;
      }

      /* Track end of blockette chain */
      endofblockettes = blkt_offset + blkt_length - 1;

      /* Sanity check that the blockette is contained in the record */
      if (endofblockettes > maxreclen)
      {
        ms_log (2, "%s: Blockette type %d at offset %d with length %d does not fit in record (%d)\n",
                sid, blkt_type, blkt_offset, blkt_length, maxreclen);
        retval++;
        break;
      }

      if (blkt_type == 100)
      {
        if (details >= 1)
        {
          ms_log (0, "          actual sample rate: %.10g\n",
                  HO4f(*pMS2B100_SAMPRATE(record + blkt_offset), swapflag));

          if (details > 1)
          {
            b = *pMS2B100_FLAGS(record + blkt_offset);
            ms_log (0, "             undefined flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
                    bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
                    bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));

            ms_log (0, "          reserved bytes (3): %u,%u,%u\n",
                    pMS2B100_RESERVED(record + blkt_offset)[0],
                    pMS2B100_RESERVED(record + blkt_offset)[1],
                    pMS2B100_RESERVED(record + blkt_offset)[2]);
          }
        }
      }

      else if (blkt_type == 200)
      {
        if (details >= 1)
        {
          ms_log (0, "            signal amplitude: %g\n", HO4f(*pMS2B200_AMPLITUDE(record + blkt_offset), swapflag));
          ms_log (0, "               signal period: %g\n", HO4f(*pMS2B200_PERIOD(record + blkt_offset), swapflag));
          ms_log (0, "         background estimate: %g\n", HO4f(*pMS2B200_BACKGROUNDEST(record + blkt_offset), swapflag));

          if (details > 1)
          {
            b = *pMS2B200_FLAGS(record + blkt_offset);
            ms_log (0, "       event detection flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
                    bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
                    bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));
            if (b & 0x01)
              ms_log (0, "                         [Bit 0] 1: Dilatation wave\n");
            else
              ms_log (0, "                         [Bit 0] 0: Compression wave\n");
            if (b & 0x02)
              ms_log (0, "                         [Bit 1] 1: Units after deconvolution\n");
            else
              ms_log (0, "                         [Bit 1] 0: Units are digital counts\n");
            if (b & 0x04)
              ms_log (0, "                         [Bit 2] Bit 0 is undetermined\n");
            ms_log (0, "               reserved byte: %u\n", *pMS2B200_RESERVED (record + blkt_offset));
          }

          ms_log (0, "           signal onset time: %d,%d,%d:%d:%d.%04d (unused: %d)\n",
                  HO2u(*pMS2B200_YEAR (record + blkt_offset), swapflag),
                  HO2u(*pMS2B200_DAY (record + blkt_offset), swapflag),
                  *pMS2B200_HOUR (record + blkt_offset),
                  *pMS2B200_MIN (record + blkt_offset),
                  *pMS2B200_SEC (record + blkt_offset),
                  HO2u(*pMS2B200_FSEC (record + blkt_offset), swapflag),
                  *pMS2B200_UNUSED (record + blkt_offset));
          ms_log (0, "               detector name: %.24s\n", pMS2B200_DETECTOR (record + blkt_offset));
        }
      }

      else if (blkt_type == 201)
      {
        if (details >= 1)
        {
          ms_log (0, "            signal amplitude: %g\n", HO4f(*pMS2B201_AMPLITUDE(record + blkt_offset), swapflag));
          ms_log (0, "               signal period: %g\n", HO4f(*pMS2B201_PERIOD(record + blkt_offset), swapflag));
          ms_log (0, "         background estimate: %g\n", HO4f(*pMS2B201_BACKGROUNDEST(record + blkt_offset), swapflag));

          b = *pMS2B201_FLAGS(record + blkt_offset);
          ms_log (0, "       event detection flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
                  bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
                  bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));
          if (b & 0x01)
            ms_log (0, "                         [Bit 0] 1: Dilation wave\n");
          else
            ms_log (0, "                         [Bit 0] 0: Compression wave\n");

          if (details > 1)
            ms_log (0, "               reserved byte: %u\n", *pMS2B201_RESERVED(record + blkt_offset));
          ms_log (0, "           signal onset time: %d,%d,%d:%d:%d.%04d (unused: %d)\n",
                  HO2u(*pMS2B201_YEAR (record + blkt_offset), swapflag),
                  HO2u(*pMS2B201_DAY (record + blkt_offset), swapflag),
                  *pMS2B201_HOUR (record + blkt_offset),
                  *pMS2B201_MIN (record + blkt_offset),
                  *pMS2B201_SEC (record + blkt_offset),
                  HO2u(*pMS2B201_FSEC (record + blkt_offset), swapflag),
                  *pMS2B201_UNUSED (record + blkt_offset));
          ms_log (0, "                  SNR values: ");

          for (idx = 0; idx < 6; idx++)
            ms_log (0, "%u  ", pMS2B201_MEDSNR (record + blkt_offset)[idx]);
          ms_log (0, "\n");
          ms_log (0, "              loopback value: %u\n", *pMS2B201_LOOPBACK (record + blkt_offset));
          ms_log (0, "              pick algorithm: %u\n", *pMS2B201_PICKALGORITHM (record + blkt_offset));
          ms_log (0, "               detector name: %.24s\n", pMS2B201_DETECTOR (record + blkt_offset));
        }
      }

      else if (blkt_type == 300)
      {
        if (details >= 1)
        {
          ms_log (0, "      calibration start time: %d,%d,%d:%d:%d.%04d (unused: %d)\n",
                  HO2u(*pMS2B300_YEAR (record + blkt_offset), swapflag),
                  HO2u(*pMS2B300_DAY (record + blkt_offset), swapflag),
                  *pMS2B300_HOUR (record + blkt_offset),
                  *pMS2B300_MIN (record + blkt_offset),
                  *pMS2B300_SEC (record + blkt_offset),
                  HO2u(*pMS2B300_FSEC (record + blkt_offset), swapflag),
                  *pMS2B300_UNUSED (record + blkt_offset));
          ms_log (0, "      number of calibrations: %u\n", *pMS2B300_NUMCALIBRATIONS (record + blkt_offset));

          b = *pMS2B300_FLAGS (record + blkt_offset);
          ms_log (0, "           calibration flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
                  bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
                  bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));
          if (b & 0x01)
            ms_log (0, "                         [Bit 0] First pulse is positive\n");
          if (b & 0x02)
            ms_log (0, "                         [Bit 1] Calibration's alternate sign\n");
          if (b & 0x04)
            ms_log (0, "                         [Bit 2] Calibration was automatic\n");
          if (b & 0x08)
            ms_log (0, "                         [Bit 3] Calibration continued from previous record(s)\n");

          ms_log (0, "               step duration: %u\n", HO4u(*pMS2B300_STEPDURATION (record + blkt_offset), swapflag));
          ms_log (0, "           interval duration: %u\n", HO4u(*pMS2B300_INTERVALDURATION (record + blkt_offset), swapflag));
          ms_log (0, "            signal amplitude: %g\n", HO4f(*pMS2B300_AMPLITUDE (record + blkt_offset), swapflag));
          ms_log (0, "        input signal channel: %.3s\n", pMS2B300_INPUTCHANNEL (record + blkt_offset));
          if (details > 1)
            ms_log (0, "               reserved byte: %u\n", *pMS2B300_RESERVED (record + blkt_offset));
          ms_log (0, "         reference amplitude: %u\n", HO4u(*pMS2B300_REFERENCEAMPLITUDE (record + blkt_offset), swapflag));
          ms_log (0, "                    coupling: %.12s\n", pMS2B300_COUPLING (record + blkt_offset));
          ms_log (0, "                     rolloff: %.12s\n", pMS2B300_ROLLOFF (record + blkt_offset));
        }
      }

      else if (blkt_type == 310)
      {
        if (details >= 1)
        {
          ms_log (0, "      calibration start time: %d,%d,%d:%d:%d.%04d (unused: %d)\n",
                  HO2u(*pMS2B310_YEAR (record + blkt_offset), swapflag),
                  HO2u(*pMS2B310_DAY (record + blkt_offset), swapflag),
                  *pMS2B310_HOUR (record + blkt_offset),
                  *pMS2B310_MIN (record + blkt_offset),
                  *pMS2B310_SEC (record + blkt_offset),
                  HO2u(*pMS2B310_FSEC (record + blkt_offset), swapflag),
                  *pMS2B310_UNUSED (record + blkt_offset));
          if (details > 1)
            ms_log (0, "               reserved byte: %u\n", *pMS2B310_RESERVED1 (record + blkt_offset));

          b = *pMS2B310_FLAGS (record + blkt_offset);
          ms_log (0, "           calibration flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
                  bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
                  bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));
          if (b & 0x04)
            ms_log (0, "                         [Bit 2] Calibration was automatic\n");
          if (b & 0x08)
            ms_log (0, "                         [Bit 3] Calibration continued from previous record(s)\n");
          if (b & 0x10)
            ms_log (0, "                         [Bit 4] Peak-to-peak amplitude\n");
          if (b & 0x20)
            ms_log (0, "                         [Bit 5] Zero-to-peak amplitude\n");
          if (b & 0x40)
            ms_log (0, "                         [Bit 6] RMS amplitude\n");

          ms_log (0, "        calibration duration: %u\n", HO4u(*pMS2B310_DURATION (record + blkt_offset), swapflag));
          ms_log (0, "               signal period: %g\n", HO4f(*pMS2B310_PERIOD (record + blkt_offset), swapflag));
          ms_log (0, "            signal amplitude: %g\n", HO4f(*pMS2B310_AMPLITUDE (record + blkt_offset), swapflag));
          ms_log (0, "        input signal channel: %.3s\n", pMS2B310_INPUTCHANNEL (record + blkt_offset));
          if (details > 1)
            ms_log (0, "               reserved byte: %u\n", *pMS2B310_RESERVED2 (record + blkt_offset));
          ms_log (0, "         reference amplitude: %u\n", HO4u(*pMS2B310_REFERENCEAMPLITUDE (record + blkt_offset), swapflag));
          ms_log (0, "                    coupling: %.12s\n", pMS2B310_COUPLING (record + blkt_offset));
          ms_log (0, "                     rolloff: %.12s\n", pMS2B310_ROLLOFF (record + blkt_offset));
        }
      }

      else if (blkt_type == 320)
      {
        if (details >= 1)
        {
          ms_log (0, "      calibration start time: %d,%d,%d:%d:%d.%04d (unused: %d)\n",
                  HO2u(*pMS2B320_YEAR (record + blkt_offset), swapflag),
                  HO2u(*pMS2B320_DAY (record + blkt_offset), swapflag),
                  *pMS2B320_HOUR (record + blkt_offset),
                  *pMS2B320_MIN (record + blkt_offset),
                  *pMS2B320_SEC (record + blkt_offset),
                  HO2u(*pMS2B320_FSEC (record + blkt_offset), swapflag),
                  *pMS2B320_UNUSED (record + blkt_offset));
          if (details > 1)
            ms_log (0, "               reserved byte: %u\n", *pMS2B320_RESERVED1 (record + blkt_offset));

          b = *pMS2B320_FLAGS (record + blkt_offset);
          ms_log (0, "           calibration flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
                  bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
                  bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));
          if (b & 0x04)
            ms_log (0, "                         [Bit 2] Calibration was automatic\n");
          if (b & 0x08)
            ms_log (0, "                         [Bit 3] Calibration continued from previous record(s)\n");
          if (b & 0x10)
            ms_log (0, "                         [Bit 4] Random amplitudes\n");

          ms_log (0, "        calibration duration: %u\n", HO4u(*pMS2B320_DURATION (record + blkt_offset), swapflag));
          ms_log (0, "      peak-to-peak amplitude: %g\n", HO4f(*pMS2B320_PTPAMPLITUDE (record + blkt_offset), swapflag));
          ms_log (0, "        input signal channel: %.3s\n", pMS2B320_INPUTCHANNEL (record + blkt_offset));
          if (details > 1)
            ms_log (0, "               reserved byte: %u\n", *pMS2B320_RESERVED2 (record + blkt_offset));
          ms_log (0, "         reference amplitude: %u\n", HO4u(*pMS2B320_REFERENCEAMPLITUDE (record + blkt_offset), swapflag));
          ms_log (0, "                    coupling: %.12s\n", pMS2B320_COUPLING (record + blkt_offset));
          ms_log (0, "                     rolloff: %.12s\n", pMS2B320_ROLLOFF (record + blkt_offset));
          ms_log (0, "                  noise type: %.8s\n", pMS2B320_NOISETYPE (record + blkt_offset));
        }
      }

      else if (blkt_type == 390)
      {
        if (details >= 1)
        {
          ms_log (0, "      calibration start time: %d,%d,%d:%d:%d.%04d (unused: %d)\n",
                  HO2u(*pMS2B390_YEAR (record + blkt_offset), swapflag),
                  HO2u(*pMS2B390_DAY (record + blkt_offset), swapflag),
                  *pMS2B390_HOUR (record + blkt_offset),
                  *pMS2B390_MIN (record + blkt_offset),
                  *pMS2B390_SEC (record + blkt_offset),
                  HO2u(*pMS2B390_FSEC (record + blkt_offset), swapflag),
                  *pMS2B390_UNUSED (record + blkt_offset));
          if (details > 1)
            ms_log (0, "               reserved byte: %u\n", *pMS2B390_RESERVED1 (record + blkt_offset));

          b = *pMS2B390_FLAGS (record + blkt_offset);
          ms_log (0, "           calibration flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
                  bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
                  bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));
          if (b & 0x04)
            ms_log (0, "                         [Bit 2] Calibration was automatic\n");
          if (b & 0x08)
            ms_log (0, "                         [Bit 3] Calibration continued from previous record(s)\n");

          ms_log (0, "        calibration duration: %u\n", HO4u(*pMS2B390_DURATION (record + blkt_offset), swapflag));
          ms_log (0, "            signal amplitude: %g\n", HO4f(*pMS2B390_AMPLITUDE (record + blkt_offset), swapflag));
          ms_log (0, "        input signal channel: %.3s\n", pMS2B390_INPUTCHANNEL (record + blkt_offset));
          if (details > 1)
            ms_log (0, "               reserved byte: %u\n", *pMS2B390_RESERVED2 (record + blkt_offset));
        }
      }

      else if (blkt_type == 395)
      {
        if (details >= 1)
        {
          ms_log (0, "        calibration end time: %d,%d,%d:%d:%d.%04d (unused: %d)\n",
                  HO2u(*pMS2B395_YEAR (record + blkt_offset), swapflag),
                  HO2u(*pMS2B395_DAY (record + blkt_offset), swapflag),
                  *pMS2B395_HOUR (record + blkt_offset),
                  *pMS2B395_MIN (record + blkt_offset),
                  *pMS2B395_SEC (record + blkt_offset),
                  HO2u(*pMS2B395_FSEC (record + blkt_offset), swapflag),
                  *pMS2B395_UNUSED (record + blkt_offset));
          if (details > 1)
            ms_log (0, "          reserved bytes (2): %u,%u\n",
                    pMS2B395_RESERVED (record + blkt_offset)[0], pMS2B395_RESERVED (record + blkt_offset)[1]);
        }
      }

      else if (blkt_type == 400)
      {
        if (details >= 1)
        {
          ms_log (0, "      beam azimuth (degrees): %g\n", HO4f(*pMS2B400_AZIMUTH (record + blkt_offset), swapflag));
          ms_log (0, "  beam slowness (sec/degree): %g\n", HO4f(*pMS2B400_SLOWNESS (record + blkt_offset), swapflag));
          ms_log (0, "               configuration: %u\n", HO2u(*pMS2B400_CONFIGURATION (record + blkt_offset), swapflag));
          if (details > 1)
            ms_log (0, "          reserved bytes (2): %u,%u\n",
                    pMS2B400_RESERVED (record + blkt_offset)[0], pMS2B400_RESERVED (record + blkt_offset)[1]);
        }
      }

      else if (blkt_type == 405)
      {
        if (details >= 1)
          ms_log (0, "           first delay value: %u\n", HO2u(*pMS2B405_DELAYVALUES (record + blkt_offset), swapflag));
      }

      else if (blkt_type == 500)
      {
        if (details >= 1)
        {
          ms_log (0, "              VCO correction: %g%%\n", HO4f(*pMS2B500_VCOCORRECTION (record + blkt_offset), swapflag));
          ms_log (0, "           time of exception: %d,%d,%d:%d:%d.%04d (unused: %d)\n",
                  HO2u(*pMS2B500_YEAR (record + blkt_offset), swapflag),
                  HO2u(*pMS2B500_DAY (record + blkt_offset), swapflag),
                  *pMS2B500_HOUR (record + blkt_offset),
                  *pMS2B500_MIN (record + blkt_offset),
                  *pMS2B500_SEC (record + blkt_offset),
                  HO2u(*pMS2B500_FSEC (record + blkt_offset), swapflag),
                  *pMS2B500_UNUSED (record + blkt_offset));
          ms_log (0, "                        usec: %d\n", *pMS2B500_MICROSECOND (record + blkt_offset));
          ms_log (0, "           reception quality: %u%%\n", *pMS2B500_RECEPTIONQUALITY (record + blkt_offset));
          ms_log (0, "             exception count: %u\n", HO4u(*pMS2B500_EXCEPTIONCOUNT (record + blkt_offset), swapflag));
          ms_log (0, "              exception type: %.16s\n", pMS2B500_EXCEPTIONTYPE (record + blkt_offset));
          ms_log (0, "                 clock model: %.32s\n", pMS2B500_CLOCKMODEL (record + blkt_offset));
          ms_log (0, "                clock status: %.128s\n", pMS2B500_CLOCKSTATUS (record + blkt_offset));
        }
      }

      else if (blkt_type == 1000)
      {
        char order[40];

        /* Calculate record size in bytes as 2^(blkt_1000->rec_len) */
        b1000reclen = (uint32_t)1 << *pMS2B1000_RECLEN (record + blkt_offset);

        /* Big or little endian? */
        if (*pMS2B1000_BYTEORDER (record + blkt_offset) == 0)
          strncpy (order, "Little endian", sizeof (order) - 1);
        else if (*pMS2B1000_BYTEORDER (record + blkt_offset) == 1)
          strncpy (order, "Big endian", sizeof (order) - 1);
        else
          strncpy (order, "Unknown value", sizeof (order) - 1);

        if (details >= 1)
        {
          ms_log (0, "                    encoding: %s (val:%u)\n",
                  (char *)ms_encodingstr (*pMS2B1000_ENCODING (record + blkt_offset)),
                  *pMS2B1000_ENCODING (record + blkt_offset));
          ms_log (0, "                  byte order: %s (val:%u)\n",
                  order, *pMS2B1000_BYTEORDER (record + blkt_offset));
          ms_log (0, "               record length: %d (val:%u)\n",
                  b1000reclen, *pMS2B1000_RECLEN (record + blkt_offset));

          if (details > 1)
            ms_log (0, "               reserved byte: %u\n", *pMS2B1000_RESERVED (record + blkt_offset));
        }

        /* Save encoding format */
        b1000encoding = *pMS2B1000_ENCODING (record + blkt_offset);

        /* Sanity check encoding format */
        if (!(b1000encoding >= 0 && b1000encoding <= 5) &&
            !(b1000encoding >= 10 && b1000encoding <= 19) &&
            !(b1000encoding >= 30 && b1000encoding <= 33))
        {
          ms_log (2, "%s: Blockette 1000 encoding format invalid (0-5,10-19,30-33): %d\n", sid, b1000encoding);
          retval++;
        }

        /* Sanity check byte order flag */
        if (*pMS2B1000_BYTEORDER (record + blkt_offset) != 0 &&
            *pMS2B1000_BYTEORDER (record + blkt_offset) != 1)
        {
          ms_log (2, "%s: Blockette 1000 byte order flag invalid (0 or 1): %d\n", sid,
                  *pMS2B1000_BYTEORDER (record + blkt_offset));
          retval++;
        }
      }

      else if (blkt_type == 1001)
      {
        if (details >= 1)
        {
          ms_log (0, "              timing quality: %u%%\n", *pMS2B1001_TIMINGQUALITY (record + blkt_offset));
          ms_log (0, "                micro second: %d\n", *pMS2B1001_MICROSECOND (record + blkt_offset));

          if (details > 1)
            ms_log (0, "               reserved byte: %u\n", *pMS2B1001_RESERVED (record + blkt_offset));

          ms_log (0, "                 frame count: %u\n", *pMS2B1001_FRAMECOUNT (record + blkt_offset));
        }
      }

      else if (blkt_type == 2000)
      {
        char order[40];

        /* Big or little endian? */
        if (*pMS2B2000_BYTEORDER (record + blkt_offset) == 0)
          strncpy (order, "Little endian", sizeof (order) - 1);
        else if (*pMS2B2000_BYTEORDER (record + blkt_offset) == 1)
          strncpy (order, "Big endian", sizeof (order) - 1);
        else
          strncpy (order, "Unknown value", sizeof (order) - 1);

        if (details >= 1)
        {
          ms_log (0, "            blockette length: %u\n", HO2u(*pMS2B2000_LENGTH (record + blkt_offset), swapflag));
          ms_log (0, "                 data offset: %u\n", HO2u(*pMS2B2000_DATAOFFSET (record + blkt_offset), swapflag));
          ms_log (0, "               record number: %u\n", HO4u(*pMS2B2000_RECNUM (record + blkt_offset), swapflag));
          ms_log (0, "                  byte order: %s (val:%u)\n",
                  order, *pMS2B2000_BYTEORDER (record + blkt_offset));
          b = *pMS2B2000_FLAGS (record + blkt_offset);
          ms_log (0, "                  data flags: [%d%d%d%d%d%d%d%d] 8 bits\n",
                  bit (b, 0x80), bit (b, 0x40), bit (b, 0x20), bit (b, 0x10),
                  bit (b, 0x08), bit (b, 0x04), bit (b, 0x02), bit (b, 0x01));

          if (details > 1)
          {
            if (b & 0x01)
              ms_log (0, "                         [Bit 0] 1: Stream oriented\n");
            else
              ms_log (0, "                         [Bit 0] 0: Record oriented\n");
            if (b & 0x02)
              ms_log (0, "                         [Bit 1] 1: Blockette 2000s may NOT be packaged\n");
            else
              ms_log (0, "                         [Bit 1] 0: Blockette 2000s may be packaged\n");
            if (!(b & 0x04) && !(b & 0x08))
              ms_log (0, "                      [Bits 2-3] 00: Complete blockette\n");
            else if (!(b & 0x04) && (b & 0x08))
              ms_log (0, "                      [Bits 2-3] 01: First blockette in span\n");
            else if ((b & 0x04) && (b & 0x08))
              ms_log (0, "                      [Bits 2-3] 11: Continuation blockette in span\n");
            else if ((b & 0x04) && !(b & 0x08))
              ms_log (0, "                      [Bits 2-3] 10: Final blockette in span\n");
            if (!(b & 0x10) && !(b & 0x20))
              ms_log (0, "                      [Bits 4-5] 00: Not file oriented\n");
            else if (!(b & 0x10) && (b & 0x20))
              ms_log (0, "                      [Bits 4-5] 01: First blockette of file\n");
            else if ((b & 0x10) && !(b & 0x20))
              ms_log (0, "                      [Bits 4-5] 10: Continuation of file\n");
            else if ((b & 0x10) && (b & 0x20))
              ms_log (0, "                      [Bits 4-5] 11: Last blockette of file\n");
          }

          ms_log (0, "           number of headers: %u\n", *pMS2B2000_NUMHEADERS (record + blkt_offset));

          /* Crude display of the opaque data headers, hopefully printable */
          if (details > 1)
            ms_log (0, "                     headers: %.*s\n",
                    (HO2u(*pMS2B2000_DATAOFFSET (record + blkt_offset), swapflag) - 15),
                    pMS2B2000_PAYLOAD (record + blkt_offset));
        }
      }

      else
      {
        ms_log (2, "%s: Unrecognized blockette type: %d\n", sid, blkt_type);
        retval++;
      }

      /* Sanity check the next blockette offset */
      if (next_blkt && next_blkt <= endofblockettes)
      {
        ms_log (2, "%s: Next blockette offset (%d) is within current blockette ending at byte %d\n",
                sid, next_blkt, endofblockettes);
        blkt_offset = 0;
      }
      else
      {
        blkt_offset = next_blkt;
      }

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

    /* Check that the blockette offset is within the maximum record size */
    if (blkt_offset > maxreclen)
    {
      ms_log (2, "%s: Blockette offset (%d) beyond maximum record length (%d)\n",
              sid, blkt_offset, maxreclen);
      retval++;
    }

    /* Check that the data and blockette offsets are within the record */
    if (b1000reclen && HO2u(*pMS2FSDH_DATAOFFSET (record), swapflag) > b1000reclen)
    {
      ms_log (2, "%s: Data offset (%d) beyond record length (%d)\n",
              sid, HO2u(*pMS2FSDH_DATAOFFSET (record), swapflag), b1000reclen);
      retval++;
    }
    if (b1000reclen && HO2u(*pMS2FSDH_BLOCKETTEOFFSET (record), swapflag) > b1000reclen)
    {
      ms_log (2, "%s: Blockette offset (%d) beyond record length (%d)\n",
              sid, HO2u(*pMS2FSDH_BLOCKETTEOFFSET (record), swapflag), b1000reclen);
      retval++;
    }

    /* Check that the data offset is beyond the end of the blockettes */
    if (HO2u(*pMS2FSDH_NUMSAMPLES (record), swapflag) &&
        HO2u(*pMS2FSDH_DATAOFFSET (record), swapflag) <= endofblockettes)
    {
      ms_log (2, "%s: Data offset (%d) is within blockette chain (end of blockettes: %d)\n",
              sid, HO2u(*pMS2FSDH_DATAOFFSET (record), swapflag), endofblockettes);
      retval++;
    }

    /* Check that the correct number of blockettes were parsed */
    if (*pMS2FSDH_NUMBLOCKETTES (record) != blkt_count)
    {
      ms_log (2, "%s: Specified number of blockettes (%d) not equal to those parsed (%d)\n",
              sid, *pMS2FSDH_NUMBLOCKETTES (record), blkt_count);
      retval++;
    }
  }

  return retval;
} /* End of ms_parse_raw2() */
