You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1665 lines
56 KiB
C

/***************************************************************************
* 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 (09999)
*
* 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() */