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.

2485 lines
75 KiB
C

/***************************************************************************
* Routines to handle TraceList and related structures.
*
* 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "libmseed.h"
MS3TraceSeg *mstl3_msr2seg (const MS3Record *msr, nstime_t endtime);
MS3TraceSeg *mstl3_addmsrtoseg (MS3TraceSeg *seg, const MS3Record *msr, nstime_t endtime, int8_t whence);
MS3TraceSeg *mstl3_addsegtoseg (MS3TraceSeg *seg1, MS3TraceSeg *seg2);
MS3RecordPtr *mstl3_add_recordptr (MS3TraceSeg *seg, const MS3Record *msr, nstime_t endtime, int8_t whence);
static uint32_t lm_lcg_r (uint64_t *state);
static uint8_t lm_random_height (uint8_t maximum, uint64_t *state);
/**********************************************************************/ /**
* @brief Initialize a ::MS3TraceList container
*
* A new ::MS3TraceList is allocated if needed. If the supplied
* ::MS3TraceList is not NULL it will be re-initialized and any
* associated memory will be freed including data at \a prvtptr pointers.
*
* @param[in] mstl ::MS3TraceList to reinitialize or NULL
*
* @returns a pointer to a MS3TraceList struct on success or NULL on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
MS3TraceList *
mstl3_init (MS3TraceList *mstl)
{
if (mstl)
{
mstl3_free (&mstl, 1);
}
mstl = (MS3TraceList *)libmseed_memory.malloc (sizeof (MS3TraceList));
if (mstl == NULL)
{
ms_log (2, "Cannot allocate memory\n");
return NULL;
}
memset (mstl, 0, sizeof (MS3TraceList));
/* Seed PRNG with 1, we only need random distribution */
mstl->prngstate = 1;
mstl->traces.height = MSTRACEID_SKIPLIST_HEIGHT;
return mstl;
} /* End of mstl3_init() */
/**********************************************************************/ /**
* @brief Free all memory associated with a ::MS3TraceList
*
* The pointer to the target ::MS3TraceList will be set to NULL.
*
* @param[in] ppmstl Pointer-to-pointer to the target ::MS3TraceList to free
* @param[in] freeprvtptr If true, also free any data at the \a
* prvtptr members of ::MS3TraceID.prvtptr, ::MS3TraceSeg.prvtptr, and
* ::MS3RecordPtr.prvtptr (in ::MS3TraceSeg.recordlist)
***************************************************************************/
void
mstl3_free (MS3TraceList **ppmstl, int8_t freeprvtptr)
{
MS3TraceID *id = 0;
MS3TraceID *nextid = 0;
MS3TraceSeg *seg = 0;
MS3TraceSeg *nextseg = 0;
MS3RecordPtr *recordptr;
MS3RecordPtr *nextrecordptr;
if (!ppmstl)
return;
/* Free any associated traces */
id = (*ppmstl)->traces.next[0];
while (id)
{
nextid = id->next[0];
/* Free any associated trace segments */
seg = id->first;
while (seg)
{
nextseg = seg->next;
/* Free private pointer data if present and requested */
if (freeprvtptr && seg->prvtptr)
libmseed_memory.free (seg->prvtptr);
/* Free data array if allocated */
if (seg->datasamples)
libmseed_memory.free (seg->datasamples);
/* Free associated record list and related private pointers */
if (seg->recordlist)
{
recordptr = seg->recordlist->first;
while (recordptr)
{
nextrecordptr = recordptr->next;
if (recordptr->msr)
msr3_free (&recordptr->msr);
if (freeprvtptr && recordptr->prvtptr)
libmseed_memory.free (recordptr->prvtptr);
libmseed_memory.free (recordptr);
recordptr = nextrecordptr;
}
libmseed_memory.free (seg->recordlist);
}
libmseed_memory.free (seg);
seg = nextseg;
}
/* Free private pointer data if present and requested */
if (freeprvtptr && id->prvtptr)
libmseed_memory.free (id->prvtptr);
libmseed_memory.free (id);
id = nextid;
}
libmseed_memory.free (*ppmstl);
*ppmstl = NULL;
return;
} /* End of mstl3_free() */
/**********************************************************************/ /**
* @brief Find matching ::MS3TraceID in a ::MS3TraceList
*
* Return the ::MS3TraceID matching the \a sid in the specified ::MS3TraceList.
*
* If \a prev is not NULL, set pointers to previous entries for the
* expected location of the trace ID. Useful for adding a new ID
* with mstl3_addID(), and should be set to \a NULL otherwise.
*
* @param[in] mstl Pointer to the ::MS3TraceList to search
* @param[in] sid Source ID to search for in the list
* @param[in] pubversion If non-zero, find the entry with this version
* @param[out] prev Pointers to previous entries in expected location or NULL
*
* @returns a pointer to the matching ::MS3TraceID or NULL if not found or error.
***************************************************************************/
MS3TraceID *
mstl3_findID (MS3TraceList *mstl, const char *sid, uint8_t pubversion, MS3TraceID **prev)
{
MS3TraceID *id = NULL;
int level;
int cmp;
if (!mstl || !sid)
{
ms_log (2, "Required argument not defined: 'mstl' or 'sid'\n");
return NULL;
}
level = MSTRACEID_SKIPLIST_HEIGHT - 1;
/* Search trace ID skip list, starting from the head/sentinel node */
id = &(mstl->traces);
while (id != NULL && level >= 0)
{
if (prev != NULL) /* Track previous entries at each level */
{
prev[level] = id;
}
if (id->next[level] == NULL)
{
level -= 1;
}
else
{
cmp = strcmp(id->next[level]->sid, sid);
/* If source IDs match, check publication if matching requested */
if (!cmp && pubversion && id->next[level]->pubversion != pubversion)
{
cmp = (id->next[level]->pubversion < pubversion) ? -1 : 1;
}
if (cmp == 0) /* Found matching trace ID */
{
return id->next[level];
}
else if (cmp > 0) /* Drop a level */
{
level -= 1;
}
else /* Continue at this level */
{
id = id->next[level];
}
}
}
return NULL;
} /* End of mstl3_findID() */
/**********************************************************************/ /**
* @brief Add ::MS3TraceID to a ::MS3TraceList
*
* The \a prev array is the list of pointers to previous entries at different
* levels of the skip list. It is common to first search the list using
* mstl3_findID() which returns this list of pointers for use here.
* If this value is NULL mstl3_findID() will be run to find the pointers.
*
* @param[in] mstl Add ID to this ::MS3TraceList
* @param[in] id The ::MS3TraceID to add
* @param[in] prev Pointers to previous entries in expected location, can be NULL
*
* @returns a pointer to the added ::MS3TraceID or NULL on error.
*
* \sa mstl3_findID()
***************************************************************************/
MS3TraceID *
mstl3_addID (MS3TraceList *mstl, MS3TraceID *id, MS3TraceID **prev)
{
MS3TraceID *local_prev[MSTRACEID_SKIPLIST_HEIGHT] = {NULL};
int level;
if (!mstl || !id)
{
ms_log (2, "Required argument not defined: 'mstl' or 'id'\n");
return NULL;
}
/* If previous list pointers not supplied, find them */
if (!prev)
{
mstl3_findID (mstl, id->sid, 0, local_prev);
prev = local_prev;
}
/* Set level of new entry to a random level within head height */
id->height = lm_random_height (MSTRACEID_SKIPLIST_HEIGHT, &(mstl->prngstate));
/* Set all pointers above new entry level to NULL */
for (level = MSTRACEID_SKIPLIST_HEIGHT - 1;
level > id->height;
level--)
{
id->next[level] = NULL;
}
/* Connect previous and new ID pointers */
for (level = id->height - 1;
level >= 0;
level--)
{
if (!prev[level])
{
ms_log (2, "No previous pointer at level %d for adding SID %s\n",
level, id->sid);
return NULL;
}
id->next[level] = prev[level]->next[level];
prev[level]->next[level] = id;
}
mstl->numtraceids++;
return id;
} /* End of mstl3_addID() */
/**********************************************************************/ /**
* @brief Add data coverage from an ::MS3Record to a ::MS3TraceList
*
* Searching the list for the appropriate ::MS3TraceID and
* ::MS3TraceSeg and either add data to existing entries or create new
* ones as needed.
*
* As this routine adds data to a trace list it constructs continuous
* time series, merging segments when possible. The \a tolerance
* pointer to a ::MS3Tolerance identifies function pointers that are
* expected to return time tolerace, in seconds, and sample rate
* tolerance, in Hertz, for the given ::MS3Record. If \a tolerance is
* NULL, or the function pointers it identifies are NULL, default
* tolerances will be used as follows: - Default time tolerance is 1/2
* the sampling period - Default sample rate (sr) tolerance is
* abs(1sr1/sr2) < 0.0001
*
* In recommended usage, the \a splitversion flag is \b 0 and
* different publication versions of otherwise matching data are
* merged. If more than one version contributes to a given source
* identifer's segments, its ::MS3TraceID.pubversion will be the set to
* the largest contributing version. If the \a splitversion flag is
* \b 1 the publication versions will be kept separate with each
* version isolated in separate ::MS3TraceID entries.
*
* If the \a autoheal flag is true, extra processing is invoked to
* conjoin trace segments that fit together after the ::MS3Record
* coverage is added. For segments that are removed, any memory at
* the ::MS3TraceSeg.prvtptr will be freed.
*
* If the \a pprecptr is not NULL a @ref record-list will be
* maintained for each segment. If the value of \c *pprecptr is NULL,
* a new ::MS3RecordPtr will be allocated, otherwise the supplied
* structure will be updated. The ::MS3RecordPtr will be added to the
* appropriate record list and the values of ::MS3RecordPtr.msr and
* ::MS3RecordPtr.endtime will be set, all other fields should be set
* by the caller.
*
* The lists are always maintained in a sorted order. An
* ::MS3TraceList is maintained with the ::MS3TraceID entries in
* ascending alphanumeric order of SID. If repeated SIDs are present
* due to splitting on publication version, they are maintained in
* order of ascending version. A ::MS3TraceID is always maintained
* with ::MS3TraceSeg entries in data time time order.
*
* @param[in] mstl Destination ::MS3TraceList to add data to
* @param[in] msr ::MS3Record containing the data to add to list
* @param[in] pprecptr Pointer to pointer to a ::MS3RecordPtr for @ref record-list
* @param[in] splitversion Flag to control splitting of version/quality
* @param[in] autoheal Flag to control automatic merging of segments
* @param[in] flags Flags to control optional functionality (unused)
* @param[in] tolerance Tolerance function pointers as ::MS3Tolerance
*
* @returns a pointer to the ::MS3TraceSeg updated or NULL on error.
*
* \sa mstl3_readbuffer()
* \sa ms3_readtracelist()
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
MS3TraceSeg *
mstl3_addmsr_recordptr (MS3TraceList *mstl, const MS3Record *msr, MS3RecordPtr **pprecptr,
int8_t splitversion, int8_t autoheal, uint32_t flags,
const MS3Tolerance *tolerance)
{
MS3TraceID *id = 0;
MS3TraceID *previd[MSTRACEID_SKIPLIST_HEIGHT] = {NULL};
MS3TraceSeg *seg = 0;
MS3TraceSeg *searchseg = 0;
MS3TraceSeg *segbefore = 0;
MS3TraceSeg *segafter = 0;
MS3TraceSeg *followseg = 0;
nstime_t endtime;
nstime_t pregap;
nstime_t postgap;
nstime_t lastgap;
nstime_t firstgap;
nstime_t nsdelta;
nstime_t nstimetol = 0;
nstime_t nnstimetol = 0;
double sampratehz;
double sampratetol = -1.0;
int8_t whence;
int8_t lastratecheck;
int8_t firstratecheck;
if (!mstl || !msr)
{
ms_log (2, "Required argument not defined: 'mstl' or 'msr'\n");
return NULL;
}
/* Calculate end time for MS3Record */
if ((endtime = msr3_endtime (msr)) == NSTERROR)
{
ms_log (2, "Error calculating record end time\n");
return NULL;
}
/* Search for matching trace ID */
id = mstl3_findID (mstl,
msr->sid,
(splitversion) ? msr->pubversion : 0,
previd);
/* If no matching ID was found create new MS3TraceID and MS3TraceSeg entries */
if (!id)
{
if (!(id = (MS3TraceID *)libmseed_memory.malloc (sizeof (MS3TraceID))))
{
ms_log (2, "Error allocating memory\n");
return NULL;
}
memset (id, 0, sizeof (MS3TraceID));
/* Populate MS3TraceID */
memcpy (id->sid, msr->sid, sizeof(id->sid));
id->pubversion = msr->pubversion;
id->earliest = msr->starttime;
id->latest = endtime;
id->numsegments = 1;
if (!(seg = mstl3_msr2seg (msr, endtime)))
{
return NULL;
}
id->first = id->last = seg;
/* Add MS3RecordPtr if requested */
if (pprecptr && !(*pprecptr = mstl3_add_recordptr (seg, msr, endtime, 1)))
{
return NULL;
}
/* Add new MS3TraceID to MS3TraceList */
if (mstl3_addID (mstl, id, previd) == NULL)
{
ms_log (2, "Error adding new ID to trace list\n");
return NULL;
}
}
/* Add data coverage to the matching MS3TraceID */
else
{
/* Calculate high-precision sample period, handling different rate notations */
if (msr->samprate > 0.0)
nsdelta = (nstime_t) (NSTMODULUS / msr->samprate); /* samples/second */
else if (msr->samprate < 0.0)
nsdelta = (nstime_t) (NSTMODULUS * -msr->samprate); /* period */
else
nsdelta = 0;
/* Calculate high-precision time tolerance */
if (tolerance && tolerance->time)
nstimetol = (nstime_t) (NSTMODULUS * tolerance->time (msr));
else
nstimetol = (nstime_t) (0.5 * nsdelta); /* Default time tolerance is 1/2 sample period */
nnstimetol = (nstimetol) ? -nstimetol : 0;
/* Calculate sample rate tolerance */
if (tolerance && tolerance->samprate)
sampratetol = tolerance->samprate (msr);
sampratehz = msr3_sampratehz(msr);
/* last/firstgap are negative when the record overlaps the trace
* segment and positive when there is a time gap. */
/* Gap relative to the last segment */
lastgap = msr->starttime - id->last->endtime - nsdelta;
/* Gap relative to the first segment */
firstgap = id->first->starttime - endtime - nsdelta;
/* Sample rate tolerance checks for first and last segments */
if (tolerance && tolerance->samprate)
{
lastratecheck = (sampratetol < 0.0 || ms_dabs (sampratehz - id->last->samprate) > sampratetol) ? 0 : 1;
firstratecheck = (sampratetol < 0.0 || ms_dabs (sampratehz - id->first->samprate) > sampratetol) ? 0 : 1;
}
else
{
lastratecheck = MS_ISRATETOLERABLE (sampratehz, id->last->samprate);
firstratecheck = MS_ISRATETOLERABLE (sampratehz, id->first->samprate);
}
/* Search first for the simple scenarios in order of likelihood:
* - Record fits at end of last segment
* - Record fits after all coverage
* - Record fits before all coverage
* - Record fits at beginning of first segment
*
* If none of those scenarios are true search the complete segment list.
*/
/* Record coverage fits at end of last segment */
if (lastgap <= nstimetol && lastgap >= nnstimetol && lastratecheck)
{
if (!mstl3_addmsrtoseg (id->last, msr, endtime, 1))
return NULL;
seg = id->last;
if (endtime > id->latest)
id->latest = endtime;
/* Add MS3RecordPtr if requested */
if (pprecptr && !(*pprecptr = mstl3_add_recordptr (seg, msr, endtime, 1)))
return NULL;
}
/* Record coverage is after all other coverage */
else if ((msr->starttime - nsdelta - nstimetol) > id->latest)
{
if (!(seg = mstl3_msr2seg (msr, endtime)))
return NULL;
/* Add to end of list */
id->last->next = seg;
seg->prev = id->last;
id->last = seg;
id->numsegments++;
if (endtime > id->latest)
id->latest = endtime;
/* Add MS3RecordPtr if requested */
if (pprecptr && !(*pprecptr = mstl3_add_recordptr (seg, msr, endtime, 0)))
return NULL;
}
/* Record coverage is before all other coverage */
else if ((endtime + nsdelta + nstimetol) < id->earliest)
{
if (!(seg = mstl3_msr2seg (msr, endtime)))
return NULL;
/* Add to beginning of list */
id->first->prev = seg;
seg->next = id->first;
id->first = seg;
id->numsegments++;
if (msr->starttime < id->earliest)
id->earliest = msr->starttime;
/* Add MS3RecordPtr if requested */
if (pprecptr && !(*pprecptr = mstl3_add_recordptr (seg, msr, endtime, 0)))
return NULL;
}
/* Record coverage fits at beginning of first segment */
else if (firstgap <= nstimetol && firstgap >= nnstimetol && firstratecheck)
{
if (!mstl3_addmsrtoseg (id->first, msr, endtime, 2))
return NULL;
seg = id->first;
if (msr->starttime < id->earliest)
id->earliest = msr->starttime;
/* Add MS3RecordPtr if requested */
if (pprecptr && !(*pprecptr = mstl3_add_recordptr (seg, msr, endtime, 2)))
return NULL;
}
/* Search complete segment list for matches */
else
{
searchseg = id->first;
segbefore = NULL; /* Find segment that record fits before */
segafter = NULL; /* Find segment that record fits after */
followseg = NULL; /* Track segment that record follows in time order */
while (searchseg)
{
/* Done searching if autohealing and record exactly matches
* a segment.
*
* Rationale: autohealing would have combined this segment
* with another if that were possible, so this record will
* also not fit with any other segment. */
if (autoheal &&
msr->starttime == searchseg->starttime &&
endtime == searchseg->endtime)
{
followseg = searchseg;
break;
}
if (msr->starttime > searchseg->starttime)
followseg = searchseg;
whence = 0;
postgap = msr->starttime - searchseg->endtime - nsdelta;
if (!segbefore && postgap <= nstimetol && postgap >= nnstimetol)
whence = 1;
pregap = searchseg->starttime - endtime - nsdelta;
if (!segafter && pregap <= nstimetol && pregap >= nnstimetol)
whence = 2;
if (!whence)
{
searchseg = searchseg->next;
continue;
}
if (tolerance && tolerance->samprate)
{
if (sampratetol >= 0 && ms_dabs (sampratehz - searchseg->samprate) > sampratetol)
{
searchseg = searchseg->next;
continue;
}
}
else
{
if (!MS_ISRATETOLERABLE (sampratehz, searchseg->samprate))
{
searchseg = searchseg->next;
continue;
}
}
if (whence == 1)
segbefore = searchseg;
else
segafter = searchseg;
/* Done searching if not autohealing */
if (!autoheal)
break;
/* Done searching if both before and after segments are found */
if (segbefore && segafter)
break;
searchseg = searchseg->next;
} /* Done looping through segments */
/* Add MS3Record coverage to end of segment before */
if (segbefore)
{
if (!mstl3_addmsrtoseg (segbefore, msr, endtime, 1))
{
return NULL;
}
/* Add MS3RecordPtr if requested */
if (pprecptr && !(*pprecptr = mstl3_add_recordptr (segbefore, msr, endtime, 1)))
{
return NULL;
}
/* Merge two segments that now fit if autohealing */
if (autoheal && segafter && segbefore != segafter)
{
/* Add segafter coverage to segbefore */
if (!mstl3_addsegtoseg (segbefore, segafter))
{
return NULL;
}
/* Shift last segment pointer if it's going to be removed */
if (segafter == id->last)
id->last = id->last->prev;
/* Remove segafter from list */
if (segafter->prev)
segafter->prev->next = segafter->next;
if (segafter->next)
segafter->next->prev = segafter->prev;
/* Free data samples, record list, private data and segment structure */
if (segafter->datasamples)
libmseed_memory.free (segafter->datasamples);
if (segafter->recordlist)
libmseed_memory.free (segafter->recordlist);
if (segafter->prvtptr)
libmseed_memory.free (segafter->prvtptr);
libmseed_memory.free (segafter);
id->numsegments -= 1;
}
seg = segbefore;
}
/* Add MS3Record coverage to beginning of segment after */
else if (segafter)
{
if (!mstl3_addmsrtoseg (segafter, msr, endtime, 2))
{
return NULL;
}
/* Add MS3RecordPtr if requested */
if (pprecptr && !(*pprecptr = mstl3_add_recordptr (segafter, msr, endtime, 2)))
{
return NULL;
}
seg = segafter;
}
/* Add MS3Record coverage to new segment */
else
{
/* Create new segment */
if (!(seg = mstl3_msr2seg (msr, endtime)))
{
return NULL;
}
/* Add MS3RecordPtr if requested */
if (pprecptr && !(*pprecptr = mstl3_add_recordptr (seg, msr, endtime, 0)))
{
return NULL;
}
/* Add new segment as first in list */
if (!followseg)
{
seg->next = id->first;
if (id->first)
id->first->prev = seg;
id->first = seg;
}
/* Add new segment after the followseg segment */
else
{
seg->next = followseg->next;
seg->prev = followseg;
if (followseg->next)
followseg->next->prev = seg;
followseg->next = seg;
if (followseg == id->last)
id->last = seg;
}
id->numsegments++;
}
} /* End of searching segment list */
/* Track largest publication version */
if (msr->pubversion > id->pubversion)
id->pubversion = msr->pubversion;
/* Track earliest and latest times */
if (msr->starttime < id->earliest)
id->earliest = msr->starttime;
if (endtime > id->latest)
id->latest = endtime;
} /* End of adding coverage to matching ID */
/* Sort modified segment into place, logic above should limit these to few shifts if any */
while (seg->next &&
(seg->starttime > seg->next->starttime ||
(seg->starttime == seg->next->starttime && seg->endtime < seg->next->endtime)))
{
/* Move segment down list, swap seg and seg->next */
segafter = seg->next;
if (seg->prev)
seg->prev->next = segafter;
if (segafter->next)
segafter->next->prev = seg;
segafter->prev = seg->prev;
seg->prev = segafter;
seg->next = segafter->next;
segafter->next = seg;
/* Reset first and last segment pointers if replaced */
if (id->first == seg)
id->first = segafter;
if (id->last == segafter)
id->last = seg;
}
while (seg->prev && (seg->starttime < seg->prev->starttime ||
(seg->starttime == seg->prev->starttime && seg->endtime > seg->prev->endtime)))
{
/* Move segment up list, swap seg and seg->prev */
segbefore = seg->prev;
if (seg->next)
seg->next->prev = segbefore;
if (segbefore->prev)
segbefore->prev->next = seg;
segbefore->next = seg->next;
seg->next = segbefore;
seg->prev = segbefore->prev;
segbefore->prev = seg;
/* Reset first and last segment pointers if replaced */
if (id->first == segbefore)
id->first = seg;
if (id->last == seg)
id->last = segbefore;
}
return seg;
} /* End of mstl3_addmsr_recordptr() */
/****************************************************************/ /**
* @brief Parse miniSEED from a buffer and populate a ::MS3TraceList
*
* For a full description of \a tolerance see mstl3_addmsr().
*
* If the ::MSF_UNPACKDATA flag is set in \a flags, the data samples
* will be unpacked. In most cases the caller probably wants this
* flag set, without it the trace list will merely be a list of
* channels.
*
* If the ::MSF_RECORDLIST flag is set in \a flags, a ::MS3RecordList
* will be built for each ::MS3TraceSeg. The ::MS3RecordPtr entries
* contain the location of the data record, bit flags, extra headers, etc.
*
* @param[in] ppmstl Pointer-to-point to destination MS3TraceList
* @param[in] buffer Source buffer to read miniSEED records from
* @param[in] bufferlength Maximum length of \a buffer
* @param[in] splitversion Flag to control splitting of version/quality
* @param[in] flags Flags to control parsing and optional functionality:
* @parblock
* - \c ::MSF_RECORDLIST : Build a ::MS3RecordList for each ::MS3TraceSeg
* - Flags supported by msr3_parse()
* - Flags supported by mstl3_addmsr()
* @endparblock
* @param[in] tolerance Tolerance function pointers as ::MS3Tolerance
* @param[in] verbose Controls verbosity, 0 means no diagnostic output
*
* @returns The number of records parsed on success, otherwise a
* negative library error code.
*
* \ref MessageOnError - this function logs a message on error
*
* \sa mstl3_addmsr()
*********************************************************************/
int64_t
mstl3_readbuffer (MS3TraceList **ppmstl, const char *buffer, uint64_t bufferlength,
int8_t splitversion, uint32_t flags,
const MS3Tolerance *tolerance, int8_t verbose)
{
return mstl3_readbuffer_selection (ppmstl, buffer, bufferlength,
splitversion, flags, tolerance,
NULL, verbose);
} /* End of mstl3_readbuffer() */
/****************************************************************/ /**
* @brief Parse miniSEED from a buffer and populate a ::MS3TraceList
*
* For a full description of \a tolerance see mstl3_addmsr().
*
* If the ::MSF_UNPACKDATA flag is set in \a flags, the data samples
* will be unpacked. In most cases the caller probably wants this
* flag set, without it the trace list will merely be a list of
* channels.
*
* If the ::MSF_RECORDLIST flag is set in \a flags, a ::MS3RecordList
* will be built for each ::MS3TraceSeg. The ::MS3RecordPtr entries
* contain the location of the data record, bit flags, extra headers, etc.
*
* If \a selections is not NULL, the ::MS3Selections will be used to
* limit what is returned to the caller. Any data not matching the
* selections will be skipped.
*
* @param[in] ppmstl Pointer-to-point to destination MS3TraceList
* @param[in] buffer Source buffer to read miniSEED records from
* @param[in] bufferlength Maximum length of \a buffer
* @param[in] splitversion Flag to control splitting of version/quality
* @param[in] flags Flags to control parsing and optional functionality:
* @parblock
* - \c ::MSF_RECORDLIST : Build a ::MS3RecordList for each ::MS3TraceSeg
* - Flags supported by msr3_parse()
* - Flags supported by mstl3_addmsr()
* @endparblock
* @param[in] tolerance Tolerance function pointers as ::MS3Tolerance
* @param[in] selections Specify limits to which data should be returned, see @ref data-selections
* @param[in] verbose Controls verbosity, 0 means no diagnostic output
*
* @returns The number of records parsed on success, otherwise a
* negative library error code.
*
* \ref MessageOnError - this function logs a message on error
*
* \sa mstl3_addmsr()
*********************************************************************/
int64_t
mstl3_readbuffer_selection (MS3TraceList **ppmstl, const char *buffer, uint64_t bufferlength,
int8_t splitversion, uint32_t flags,
const MS3Tolerance *tolerance, const MS3Selections *selections,
int8_t verbose)
{
MS3Record *msr = NULL;
MS3TraceSeg *seg = NULL;
MS3RecordPtr *recordptr = NULL;
uint32_t dataoffset;
uint32_t datasize;
uint64_t offset = 0;
uint32_t pflags = flags;
int64_t reccount = 0;
int parsevalue;
if (!ppmstl)
{
ms_log (2, "Required argument not defined: 'ppmstl'\n");
return MS_GENERROR;
}
/* Initialize MS3TraceList if needed */
if (!*ppmstl)
{
*ppmstl = mstl3_init (*ppmstl);
if (!*ppmstl)
return MS_GENERROR;
}
/* Defer data unpacking if selections are used by unsetting MSF_UNPACKDATA */
if ((flags & MSF_UNPACKDATA) && selections)
pflags &= ~(MSF_UNPACKDATA);
while ((bufferlength - offset) > MINRECLEN)
{
parsevalue = msr3_parse (buffer + offset, bufferlength - offset, &msr, pflags, verbose);
if (parsevalue < 0)
{
if (msr)
msr3_free (&msr);
return parsevalue;
}
if (parsevalue > 0)
break;
/* Test data against selections if specified */
if (selections)
{
if (!ms3_matchselect (selections, msr->sid, msr->starttime,
msr3_endtime (msr), msr->pubversion, NULL))
{
if (verbose > 1)
{
ms_log (0, "Skipping (selection) record for %s (%d bytes) starting at offset %" PRIu64 "\n",
msr->sid, msr->reclen, offset);
}
offset += msr->reclen;
continue;
}
/* Unpack data samples if this has been deferred */
if ((flags & MSF_UNPACKDATA) && msr->samplecnt > 0)
{
if (msr3_unpack_data (msr, verbose) != msr->samplecnt)
{
if (msr)
msr3_free (&msr);
return MS_GENERROR;
}
}
}
/* Add record to trace list */
seg = mstl3_addmsr_recordptr (*ppmstl, msr, (flags & MSF_RECORDLIST) ? &recordptr : NULL,
splitversion, 1, flags, tolerance);
if (seg == NULL)
{
if (msr)
msr3_free (&msr);
return MS_GENERROR;
}
/* Populate remaining fields of record pointer */
if (recordptr)
{
/* Determine offset to data and length of data payload */
if (msr3_data_bounds (msr, &dataoffset, &datasize))
return MS_GENERROR;
recordptr->bufferptr = buffer + offset;
recordptr->fileptr = NULL;
recordptr->filename = NULL;
recordptr->fileoffset = 0;
recordptr->dataoffset = dataoffset;
recordptr->prvtptr = NULL;
}
reccount += 1;
offset += msr->reclen;
}
if (msr)
msr3_free (&msr);
return reccount;
} /* End of mstl3_readbuffer_selection() */
/***************************************************************************
* Create an MS3TraceSeg structure from an MS3Record structure.
*
* Return a pointer to a MS3TraceSeg otherwise NULL on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
MS3TraceSeg *
mstl3_msr2seg (const MS3Record *msr, nstime_t endtime)
{
MS3TraceSeg *seg = 0;
size_t datasize = 0;
int samplesize;
if (!(seg = (MS3TraceSeg *)libmseed_memory.malloc (sizeof (MS3TraceSeg))))
{
ms_log (2, "Error allocating memory\n");
return NULL;
}
memset (seg, 0, sizeof (MS3TraceSeg));
/* Populate MS3TraceSeg */
seg->starttime = msr->starttime;
seg->endtime = endtime;
seg->samprate = msr3_sampratehz(msr);
seg->samplecnt = msr->samplecnt;
seg->sampletype = msr->sampletype;
seg->numsamples = msr->numsamples;
/* Allocate space for and copy datasamples */
if (msr->datasamples && msr->numsamples)
{
if (!(samplesize = ms_samplesize (msr->sampletype)))
{
ms_log (2, "Unknown sample size for sample type: %c\n", msr->sampletype);
return NULL;
}
datasize = samplesize * msr->numsamples;
if (!(seg->datasamples = libmseed_memory.malloc ((size_t) (datasize))))
{
ms_log (2, "Error allocating memory\n");
return NULL;
}
seg->datasize = datasize;
/* Copy data samples from MS3Record to MS3TraceSeg */
memcpy (seg->datasamples, msr->datasamples, datasize);
}
return seg;
} /* End of mstl3_msr2seg() */
/***************************************************************************
* Add data coverage from a MS3Record structure to a MS3TraceSeg structure.
*
* Data coverage is added to the beginning or end of MS3TraceSeg
* according to the whence flag:
* 1 : add coverage to the end
* 2 : add coverage to the beginninig
*
* Return a pointer to a MS3TraceSeg otherwise, NULL on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
MS3TraceSeg *
mstl3_addmsrtoseg (MS3TraceSeg *seg, const MS3Record *msr, nstime_t endtime, int8_t whence)
{
int samplesize = 0;
void *newdatasamples = NULL;
size_t newdatasize = 0;
if (!seg || !msr)
{
ms_log (2, "Required argument not defined: 'seg' or 'msr'\n");
return NULL;
}
/* Allocate more memory for data samples if included */
if (msr->datasamples && msr->numsamples > 0)
{
if (msr->sampletype != seg->sampletype)
{
ms_log (2, "MS3Record sample type (%c) does not match segment sample type (%c)\n",
msr->sampletype, seg->sampletype);
return NULL;
}
if (!(samplesize = ms_samplesize (msr->sampletype)))
{
ms_log (2, "Unknown sample size for sample type: %c\n", msr->sampletype);
return NULL;
}
newdatasize = (seg->numsamples + msr->numsamples) * samplesize;
if (libmseed_prealloc_block_size)
{
newdatasamples = libmseed_memory_prealloc (seg->datasamples, newdatasize, &(seg->datasize));
}
else
{
newdatasamples = libmseed_memory.realloc (seg->datasamples, newdatasize);
seg->datasize = newdatasize;
}
if (!newdatasamples)
{
ms_log (2, "Error allocating memory\n");
seg->datasize = 0;
return NULL;
}
seg->datasamples = newdatasamples;
}
/* Add coverage to end of segment */
if (whence == 1)
{
seg->endtime = endtime;
seg->samplecnt += msr->samplecnt;
if (msr->datasamples && msr->numsamples > 0)
{
memcpy ((char *)seg->datasamples + (seg->numsamples * samplesize),
msr->datasamples,
(size_t) (msr->numsamples * samplesize));
seg->numsamples += msr->numsamples;
}
}
/* Add coverage to beginning of segment */
else if (whence == 2)
{
seg->starttime = msr->starttime;
seg->samplecnt += msr->samplecnt;
if (msr->datasamples && msr->numsamples > 0)
{
memmove ((char *)seg->datasamples + (msr->numsamples * samplesize),
seg->datasamples,
(size_t) (seg->numsamples * samplesize));
memcpy (seg->datasamples,
msr->datasamples,
(size_t) (msr->numsamples * samplesize));
seg->numsamples += msr->numsamples;
}
}
else
{
ms_log (2, "unrecognized whence value: %d\n", whence);
return NULL;
}
return seg;
} /* End of mstl3_addmsrtoseg() */
/***************************************************************************
* Add data coverage from seg2 to seg1.
*
* Return a pointer to a seg1 otherwise NULL on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
MS3TraceSeg *
mstl3_addsegtoseg (MS3TraceSeg *seg1, MS3TraceSeg *seg2)
{
int samplesize = 0;
void *newdatasamples = NULL;
size_t newdatasize = 0;
if (!seg1 || !seg2)
{
ms_log (2, "Required argument not defined: 'seg1' or 'seg2'\n");
return NULL;
}
/* Allocate more memory for data samples if included */
if (seg2->datasamples && seg2->numsamples > 0)
{
if (seg2->sampletype != seg1->sampletype)
{
ms_log (2, "MS3TraceSeg sample types do not match (%c and %c)\n",
seg1->sampletype, seg2->sampletype);
return NULL;
}
if (!(samplesize = ms_samplesize (seg1->sampletype)))
{
ms_log (2, "Unknown sample size for sample type: %c\n", seg1->sampletype);
return NULL;
}
newdatasize = (seg1->numsamples + seg2->numsamples) * samplesize;
if (libmseed_prealloc_block_size)
{
newdatasamples = libmseed_memory_prealloc (seg1->datasamples, newdatasize, &(seg1->datasize));
}
else
{
newdatasamples = libmseed_memory.realloc (seg1->datasamples, newdatasize);
seg1->datasize = newdatasize;
}
if (!newdatasamples)
{
ms_log (2, "Error allocating memory\n");
seg1->datasize = 0;
return NULL;
}
seg1->datasamples = newdatasamples;
}
/* Add seg2 coverage to end of seg1 */
seg1->endtime = seg2->endtime;
seg1->samplecnt += seg2->samplecnt;
if (seg2->datasamples && seg2->numsamples > 0)
{
memcpy ((char *)seg1->datasamples + (seg1->numsamples * samplesize),
seg2->datasamples,
(size_t) (seg2->numsamples * samplesize));
seg1->numsamples += seg2->numsamples;
}
/* Add seg2 record list to end of seg1 record list */
if (seg2->recordlist)
{
if (seg1->recordlist == NULL)
{
seg1->recordlist = seg2->recordlist;
seg2->recordlist = NULL;
}
else
{
seg1->recordlist->last->next = seg2->recordlist->first;
seg1->recordlist->last = seg2->recordlist->last;
seg1->recordlist->recordcnt += seg2->recordlist->recordcnt;
}
}
return seg1;
} /* End of mstl3_addsegtoseg() */
/**********************************************************************/ /**
* @brief Add a ::MS3RecordPtr to the ::MS3RecordList of a ::MS3TraceSeg
*
* @param[in] seg ::MS3TraceSeg to add record to
* @param[in] msr ::MS3Record to be added, for record length and start/end times
* @param[in] endtime Time of last sample in record
* @param[in] whence Where to add record to list
* @parblock
* - \c 0 : New entry for new list, only when seg->recordlist == NULL
* - \c 1 : Add record pointer to end of list
* - \c 2 : Add record pointer to beginning of list
* @endparblock
*
* @returns Pointer to added ::MS3RecordPtr on success and NULL on error.
*
* \ref MessageOnError - this function logs a message on error
*
* \sa mstl3_unpack_recordlist()
* \sa mstl3_readbuffer()
* \sa mstl3_readbuffer_selection()
* \sa ms3_readtracelist()
* \sa ms3_readtracelist_timewin()
* \sa ms3_readtracelist_selection()
* \sa mstl3_addmsr()
***************************************************************************/
MS3RecordPtr *
mstl3_add_recordptr (MS3TraceSeg *seg, const MS3Record *msr, nstime_t endtime, int8_t whence)
{
MS3RecordPtr *recordptr = NULL;
if (!seg || !msr)
{
ms_log (2, "Required argument not defined: 'seg' or 'msr'\n");
return NULL;
}
if (seg->recordlist && whence != 1 && whence != 2)
{
ms_log (2, "Unsupported 'whence' value: %d\n", whence);
return NULL;
}
recordptr = (MS3RecordPtr *)libmseed_memory.malloc (sizeof (MS3RecordPtr));
if (recordptr == NULL)
{
ms_log (2, "Cannot allocate memory\n");
return NULL;
}
memset (recordptr, 0, sizeof(MS3RecordPtr));
recordptr->msr = msr3_duplicate (msr, 0);
recordptr->endtime = endtime;
if (recordptr->msr == NULL)
{
ms_log (2, "Cannot duplicate MS3Record\n");
libmseed_memory.free (recordptr);
return NULL;
}
/* If no record list for the segment is present, allocate and add record pointer */
if (seg->recordlist == NULL)
{
seg->recordlist = (MS3RecordList *)libmseed_memory.malloc (sizeof (MS3RecordList));
if (seg->recordlist == NULL)
{
ms_log (2, "Cannot allocate memory\n");
libmseed_memory.free (recordptr);
return NULL;
}
seg->recordlist->recordcnt = 1;
seg->recordlist->first = recordptr;
seg->recordlist->last = recordptr;
}
/* Otherwise, add record pointer to existing list */
else
{
/* Beginning of list */
if (whence == 2)
{
recordptr->next = seg->recordlist->first;
seg->recordlist->first = recordptr;
}
/* End of list */
else
{
seg->recordlist->last->next = recordptr;
seg->recordlist->last = recordptr;
}
seg->recordlist->recordcnt += 1;
}
return recordptr;
} /* End of mstl3_add_recordptr() */
/**********************************************************************/ /**
* @brief Convert the data samples associated with an MS3TraceSeg to another
* data type
*
* Text data samples cannot be converted, if supplied or requested an
* error will be returned.
*
* When converting float & double sample types to integer type a
* simple rounding is applied by adding 0.5 to the sample value before
* converting (truncating) to integer. This compensates for common
* machine representations of floating point values, e.g. "40.0"
* represented by "39.99999999".
*
* If the \a truncate flag is true (non-zero) data samples will be
* truncated to integers even if loss of sample precision is detected.
* If the truncate flag is false (zero) and loss of precision is
* detected an error is returned. Loss of precision is determined by
* testing that the difference between the floating point value and
* the (truncated) integer value is greater than 0.000001.
*
* @param[in] seg The target ::MS3TraceSeg to convert
* @param[in] type The desired data sample type:
* @parblock
* - \c 'i' - 32-bit integer data type
* - \c 'f' - 32-bit float data type
* - \c 'd' - 64-bit float (double) data type
* @endparblock
* @param[in] truncate Control truncation of floating point values to integers
*
* @returns 0 on success, and -1 on failure.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
mstl3_convertsamples (MS3TraceSeg *seg, char type, int8_t truncate)
{
int32_t *idata;
float *fdata;
double *ddata;
int64_t idx;
if (!seg)
{
ms_log (2, "Required argument not defined: 'seg'\n");
return -1;
}
/* No conversion necessary, report success */
if (seg->sampletype == type)
return 0;
if (seg->sampletype == 't' || type == 't' || seg->sampletype == 'a' || type == 'a')
{
ms_log (2, "Cannot convert text samples to/from numeric type\n");
return -1;
}
idata = (int32_t *)seg->datasamples;
fdata = (float *)seg->datasamples;
ddata = (double *)seg->datasamples;
/* Convert to 32-bit integers */
if (type == 'i')
{
if (seg->sampletype == 'f') /* Convert floats to integers with simple rounding */
{
for (idx = 0; idx < seg->numsamples; idx++)
{
/* Check for loss of sub-integer */
if (!truncate && (fdata[idx] - (int32_t)fdata[idx]) > 0.000001)
{
ms_log (2, "Loss of precision when converting floats to integers, loss: %g\n",
(fdata[idx] - (int32_t)fdata[idx]));
return -1;
}
idata[idx] = (int32_t) (fdata[idx] + 0.5);
}
}
else if (seg->sampletype == 'd') /* Convert doubles to integers with simple rounding */
{
for (idx = 0; idx < seg->numsamples; idx++)
{
/* Check for loss of sub-integer */
if (!truncate && (ddata[idx] - (int32_t)ddata[idx]) > 0.000001)
{
ms_log (2, "Loss of precision when converting doubles to integers, loss: %g\n",
(ddata[idx] - (int32_t)ddata[idx]));
return -1;
}
idata[idx] = (int32_t) (ddata[idx] + 0.5);
}
/* Reallocate buffer for reduced size needed, only if not pre-allocating */
if (libmseed_prealloc_block_size == 0)
{
if (!(seg->datasamples = libmseed_memory.realloc (seg->datasamples,
(size_t) (seg->numsamples * sizeof (int32_t)))))
{
ms_log (2, "Cannot re-allocate buffer for sample conversion\n");
return -1;
}
seg->datasize = seg->numsamples * sizeof (int32_t);
}
}
seg->sampletype = 'i';
} /* Done converting to 32-bit integers */
/* Convert to 32-bit floats */
else if (type == 'f')
{
if (seg->sampletype == 'i') /* Convert integers to floats */
{
for (idx = 0; idx < seg->numsamples; idx++)
fdata[idx] = (float)idata[idx];
}
else if (seg->sampletype == 'd') /* Convert doubles to floats */
{
for (idx = 0; idx < seg->numsamples; idx++)
fdata[idx] = (float)ddata[idx];
/* Reallocate buffer for reduced size needed, only if not pre-allocating */
if (libmseed_prealloc_block_size == 0)
{
if (!(seg->datasamples = libmseed_memory.realloc (seg->datasamples,
(size_t) (seg->numsamples * sizeof (float)))))
{
ms_log (2, "Cannot re-allocate buffer after sample conversion\n");
return -1;
}
seg->datasize = seg->numsamples * sizeof (float);
}
}
seg->sampletype = 'f';
} /* Done converting to 32-bit floats */
/* Convert to 64-bit doubles */
else if (type == 'd')
{
if (!(ddata = (double *)libmseed_memory.malloc ((size_t) (seg->numsamples * sizeof (double)))))
{
ms_log (2, "Cannot allocate buffer for sample conversion to doubles\n");
return -1;
}
if (seg->sampletype == 'i') /* Convert integers to doubles */
{
for (idx = 0; idx < seg->numsamples; idx++)
ddata[idx] = (double)idata[idx];
libmseed_memory.free (idata);
}
else if (seg->sampletype == 'f') /* Convert floats to doubles */
{
for (idx = 0; idx < seg->numsamples; idx++)
ddata[idx] = (double)fdata[idx];
libmseed_memory.free (fdata);
}
seg->datasamples = ddata;
seg->datasize = seg->numsamples * sizeof (double);
seg->sampletype = 'd';
} /* Done converting to 64-bit doubles */
return 0;
} /* End of mstl3_convertsamples() */
/**********************************************************************/ /**
* @brief Resize data sample buffers of ::MS3TraceList to what is needed
*
* This routine should only be used if pre-allocation of memory, via
* ::libmseed_prealloc_block_size, was enabled to allocate the buffers.
*
* @param[in] mstl ::MS3TraceList to resize buffers
*
* @returns Return 0 on success, otherwise returns a libmseed error code.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
mstl3_resize_buffers (MS3TraceList *mstl)
{
MS3TraceID *id = NULL;
MS3TraceSeg *seg = NULL;
uint8_t samplesize = 0;
size_t datasize;
if (!mstl)
{
ms_log (2, "Required argument not defined: 'mstl'\n");
return MS_GENERROR;
}
/* Loop through trace ID and segment lists */
id = mstl->traces.next[0];
while (id)
{
seg = id->first;
while (seg)
{
samplesize = ms_samplesize(seg->sampletype);
if (samplesize && seg->datasamples && seg->numsamples > 0)
{
datasize = (size_t) seg->numsamples * samplesize;
if (seg->datasize > datasize)
{
seg->datasamples = libmseed_memory.realloc (seg->datasamples, datasize);
if (seg->datasamples == NULL)
{
ms_log (2, "%s: Cannot (re)allocate memory\n", id->sid);
return MS_GENERROR;
}
seg->datasize = datasize;
}
}
seg = seg->next;
}
id = id->next[0];
}
return 0;
} /* End of mstl3_resize_buffers() */
/**********************************************************************/ /**
* @brief Unpack data samples in a @ref record-list associated with a ::MS3TraceList
*
* Normally a record list is created calling by ms3_readtracelist() or
* mstl3_readbuffer() with the ::MSF_RECORDLIST flag.
*
* Unpacked data samples are written to the provided \a output buffer
* (up to \a outputsize bytes). If \a output is NULL, a buffer will
* be allocated and associated with the ::MS3TraceSeg, just as if the
* data were unpacked while constructing the @ref trace-list.
*
* The sample type of the decoded data is stored at
* ::MS3TraceSeg.sampletype (i.e. \c seg->sampletype).
*
* A record pointer entry has multiple ways to identify the location
* of a record: memory buffer, open file (FILE *) or file name. This
* routine uses the first populated record location in the following
* order:
* -# Buffer pointer (::MS3RecordPtr.bufferptr)
* -# Open file and offset (::MS3RecordPtr.fileptr and ::MS3RecordPtr.fileoffset)
* -# File name and offset (::MS3RecordPtr.filename and ::MS3RecordPtr.fileoffset)
*
* It would be unusual to build a record list outside of the library,
* but should that ever occur note that the record list is assumed to
* be in correct time order and represent a contiguous time series.
*
* @param[in] id ::MS3TraceID for relevant ::MS3TraceSeg
* @param[in] seg ::MS3TraceSeg with associated @ref record-list to unpack
* @param[out] output Output buffer for data samples, can be NULL
* @param[in] outputsize Size of \a output buffer
* @param[in] verbose Controls logging verbosity, 0 is no diagnostic output
*
* @returns the number of samples unpacked or -1 on error.
*
* \ref MessageOnError - this function logs a message on error
*
* \sa mstl3_readbuffer()
* \sa mstl3_readbuffer_selection()
* \sa ms3_readtracelist()
* \sa ms3_readtracelist_selection()
***************************************************************************/
int64_t
mstl3_unpack_recordlist (MS3TraceID *id, MS3TraceSeg *seg, void *output,
size_t outputsize, int8_t verbose)
{
MS3RecordPtr *recordptr = NULL;
int64_t unpackedsamples = 0;
int64_t totalunpackedsamples = 0;
char *filebuffer = NULL;
size_t filebuffersize = 0;
size_t outputoffset = 0;
size_t decodedsize = 0;
uint8_t samplesize = 0;
char sampletype = 0;
char recsampletype = 0;
FILE *fileptr = NULL;
const char *input = NULL;
/* Linked list of open file pointers */
struct filelist_s {
const char *filename;
FILE *fileptr;
struct filelist_s *next;
};
struct filelist_s *filelist = NULL;
struct filelist_s *filelistptr = NULL;
if (!id || !seg)
{
ms_log (2, "Required argument not defined: 'id' or 'seg'\n");
return -1;
}
if (!seg->recordlist)
{
ms_log (2, "Required record list is not present (seg->recordlist)\n");
return -1;
}
recordptr = seg->recordlist->first;
if (ms_encoding_sizetype(recordptr->msr->encoding, &samplesize, &sampletype))
{
ms_log (2, "%s: Cannot determine sample size and type for encoding: %u\n",
id->sid, recordptr->msr->encoding);
return -1;
}
/* Calculate buffer size needed for unpacked samples */
decodedsize = (size_t)seg->samplecnt * samplesize;
/* If output buffer is supplied, check needed size */
if (output)
{
if (decodedsize > outputsize)
{
ms_log (2, "%s: Output buffer (%"PRIsize_t" bytes) is not large enough for decoded data (%"PRIsize_t" bytes)\n",
id->sid, decodedsize, outputsize);
return -1;
}
}
/* Otherwise check that buffer is not already allocated */
else if (seg->datasamples)
{
ms_log (2, "%s: Segment data buffer is already allocated, cannot replace\n", id->sid);
return -1;
}
/* Otherwise allocate new buffer */
else
{
if ((output = libmseed_memory.malloc (decodedsize)) == NULL)
{
ms_log (2, "%s: Cannot allocate memory for segment data samples\n", id->sid);
return -1;
}
/* Associate allocated memory with segment */
seg->datasamples = output;
seg->datasize = decodedsize;
}
/* Iterate through record list and unpack data samples */
while (recordptr)
{
/* Skip records with no samples */
if (recordptr->msr->samplecnt == 0)
{
recordptr = recordptr->next;
continue;
}
if (ms_encoding_sizetype(recordptr->msr->encoding, NULL, &recsampletype))
{
ms_log (2, "%s: Cannot determine sample type for encoding: %u\n", id->sid, recordptr->msr->encoding);
totalunpackedsamples = -1;
break;
}
if (recsampletype != sampletype)
{
ms_log (2, "%s: Mixed sample types cannot be decoded together: %c versus %c\n", id->sid, recsampletype, sampletype);
totalunpackedsamples = -1;
break;
}
/* Decode data from buffer */
if (recordptr->bufferptr)
{
input = recordptr->bufferptr + recordptr->dataoffset;
}
/* Decode data from a file at a byte offset */
else if (recordptr->fileptr || recordptr->filename)
{
if (recordptr->fileptr)
{
fileptr = recordptr->fileptr;
}
else
{
/* Search file list for matching entry */
filelistptr = filelist;
while (filelistptr)
{
if (filelistptr->filename == recordptr->filename)
break;
filelistptr = filelistptr->next;
}
/* Add new entry to list and open file if needed */
if (filelistptr == NULL)
{
if ((filelistptr = libmseed_memory.malloc (sizeof (struct filelist_s))) == NULL)
{
ms_log (2, "%s: Cannot allocate memory for file list entry for %s\n", id->sid, recordptr->filename);
totalunpackedsamples = -1;
break;
}
if ((filelistptr->fileptr = fopen (recordptr->filename, "rb")) == NULL)
{
ms_log (2, "%s: Cannot open file (%s): %s\n", id->sid, recordptr->filename, strerror(errno));
totalunpackedsamples = -1;
break;
}
filelistptr->filename = recordptr->filename;
filelistptr->next = filelist;
filelist = filelistptr;
}
fileptr = filelistptr->fileptr;
}
/* Allocate memory if needed, over-allocating (x2) to minimize reallocation */
if (recordptr->msr->reclen > filebuffersize)
{
if ((filebuffer = libmseed_memory.realloc (filebuffer, recordptr->msr->reclen * 2)) == NULL)
{
ms_log (2, "%s: Cannot allocate memory for file read buffer\n", id->sid);
totalunpackedsamples = -1;
break;
}
filebuffersize = recordptr->msr->reclen * 2;
}
/* Seek to record position in file */
if (lmp_fseek64 (fileptr, recordptr->fileoffset, SEEK_SET))
{
ms_log (2, "%s: Cannot seek in file: %s (%s)\n",
id->sid,
(recordptr->filename) ? recordptr->filename : "",
strerror (errno));
totalunpackedsamples = -1;
break;
}
/* Read record into buffer */
if (fread (filebuffer, 1, recordptr->msr->reclen, fileptr) != recordptr->msr->reclen)
{
ms_log (2, "%s: Cannot read record from file: %s (%s)\n",
id->sid,
(recordptr->filename) ? recordptr->filename : "",
strerror (errno));
totalunpackedsamples = -1;
break;
}
input = filebuffer + recordptr->dataoffset;
} /* Done reading from file */
else
{
ms_log (2, "%s: No buffer or file pointer for record\n", id->sid);
totalunpackedsamples = -1;
break;
}
/* Decode data from buffer */
unpackedsamples = ms_decode_data (input, recordptr->msr->reclen - recordptr->dataoffset,
recordptr->msr->encoding, recordptr->msr->samplecnt,
(unsigned char *)output + outputoffset, decodedsize - outputoffset,
&sampletype, recordptr->msr->swapflag, id->sid, verbose);
if (unpackedsamples < 0)
{
totalunpackedsamples = -1;
break;
}
outputoffset += unpackedsamples * samplesize;
totalunpackedsamples += unpackedsamples;
recordptr = recordptr->next;
} /* Done with record list entries */
/* Free file read buffer if used */
if (filebuffer)
libmseed_memory.free (filebuffer);
/* Close and free file list if used */
while (filelist)
{
filelistptr = filelist->next;
fclose (filelist->fileptr);
libmseed_memory.free (filelist);
filelist = filelistptr;
}
/* If output buffer was allocated here, do some maintenance */
if (output == seg->datasamples)
{
/* Free allocated memory on error */
if (totalunpackedsamples < 0)
{
libmseed_memory.free (output);
seg->datasamples = NULL;
seg->datasize = 0;
}
else
{
seg->numsamples = totalunpackedsamples;
}
}
if (totalunpackedsamples > 0)
seg->sampletype = sampletype;
return totalunpackedsamples;
} /* End of mstl3_unpack_recordlist() */
/**********************************************************************/ /**
* @brief Pack ::MS3TraceList data into miniSEED records
*
* The datasamples array, numsamples and starttime fields of each
* trace segment will be adjusted as data are packed unless the
* ::MSF_MAINTAINMSTL flag is specified in \a flags. If
* ::MSF_MAINTAINMSTL is specified a caller would also normally set
* the ::MSF_FLUSHDATA flag to pack all data in the trace list.
*
* <b>Use as a rolling buffer to generate data records:</b>
* The behavior of adjusting the trace list as data are packed is
* intended to allow using a ::MS3TraceList as an intermediate
* collection of data buffers to generate data records from an
* arbitrarily large data source, e.g. continuous data. In this
* pattern, data are added to a ::MS3TraceList and mstl3_pack() is
* called repeatedly. Data records are only produced if a complete
* record can be generated, which often leaves small amounts of data
* in each segment buffer. On completion or shutdown the caller
* usually makes a final call to mst3_pack() with the ::MSF_FLUSHDATA
* flag set to flush all data from the buffers.
*
* As each record is filled and finished they are passed to \a
* record_handler() which should expect 1) a \c char* to the record,
* 2) the length of the record and 3) a pointer supplied by the
* original caller containing optional private data (\a handlerdata).
* It is the responsibility of \a record_handler() to process the
* record, the memory will be re-used or freed when \a
* record_handler() returns.
*
* The requested \a encoding value is currently only used for integer
* data samples. The encoding is set automatially for text and
* floating point data samples as there is only a single encoding for
* them. A value of \c -1 can be used to request the default.
*
* If \a extra is not NULL it is expected to contain extraheaders, a
* string containing (compact) JSON, that will be added to each output
* record.
*
* @param[in] mstl ::MS3TraceList containing data to pack
* @param[in] record_handler() Callback function called for each record
* @param[in] handlerdata A pointer that will be provided to the \a record_handler()
* @param[in] reclen Maximum record length to create
* @param[in] encoding Encoding for data samples, see msr3_pack()
* @param[out] packedsamples The number of samples packed, returned to caller
* @param[in] flags Bit flags to control packing:
* @parblock
* - \c ::MSF_FLUSHDATA : Pack all data in the buffer
* - \c ::MSF_MAINTAINMSTL : Do not remove packe data from the buffer
* - \c ::MSF_PACKVER2 : Pack miniSEED version 2 instead of default 3
* @endparblock
* @param[in] verbose Controls logging verbosity, 0 is no diagnostic output
* @param[in] extra If not NULL, add this buffer of extra headers to all records
*
* @returns the number of records created on success and -1 on error.
*
* \ref MessageOnError - this function logs a message on error
*
* \sa msr3_pack()
***************************************************************************/
int64_t
mstl3_pack (MS3TraceList *mstl, void (*record_handler) (char *, int, void *),
void *handlerdata, int reclen, int8_t encoding,
int64_t *packedsamples, uint32_t flags, int8_t verbose,
char *extra)
{
MS3Record *msr = NULL;
MS3TraceID *id = NULL;
MS3TraceSeg *seg = NULL;
int64_t totalpackedrecords = 0;
int64_t totalpackedsamples = 0;
int segpackedrecords = 0;
int64_t segpackedsamples = 0;
int samplesize;
size_t bufsize;
size_t extralength;
if (!mstl)
{
ms_log (2, "Required argument not defined: 'mstl'\n");
return -1;
}
if (!record_handler)
{
ms_log (2, "callback record_handler() function pointer not set!\n");
return -1;
}
if (packedsamples)
*packedsamples = 0;
msr = msr3_init (NULL);
if (msr == NULL)
{
ms_log (2, "Error initializing msr, out of memory?\n");
return -1;
}
msr->reclen = reclen;
msr->encoding = encoding;
if (extra)
{
msr->extra = extra;
extralength = strlen(extra);
if (extralength > UINT16_MAX)
{
ms_log (2, "Extra headers are too long: %"PRIsize_t"\n", extralength);
return -1;
}
msr->extralength = (uint16_t)extralength;
}
/* Loop through trace list */
id = mstl->traces.next[0];
while (id)
{
memcpy (msr->sid, id->sid, sizeof(msr->sid));
msr->pubversion = id->pubversion;
/* Loop through segment list */
seg = id->first;
while (seg)
{
msr->starttime = seg->starttime;
msr->samprate = seg->samprate;
msr->samplecnt = seg->samplecnt;
msr->datasamples = seg->datasamples;
msr->numsamples = seg->numsamples;
msr->sampletype = seg->sampletype;
/* Set encoding for data types with only one encoding, otherwise requested */
switch (seg->sampletype)
{
case 't':
msr->encoding = DE_TEXT;
break;
case 'f':
msr->encoding = DE_FLOAT32;
break;
case 'd':
msr->encoding = DE_FLOAT64;
break;
default:
msr->encoding = encoding;
}
segpackedsamples = 0;
segpackedrecords = msr3_pack (msr, record_handler, handlerdata, &segpackedsamples, flags, verbose);
if (verbose > 1)
{
ms_log (0, "Packed %d records for %s segment\n", segpackedrecords, msr->sid);
}
/* If MSF_MAINTAINMSTL not set, adjust segment start time and reduce data array and sample counts */
if (!(flags & MSF_MAINTAINMSTL) && segpackedsamples > 0)
{
/* Calculate new start time, shortcut when all samples have been packed */
if (segpackedsamples == seg->numsamples)
seg->starttime = seg->endtime;
else
seg->starttime = ms_sampletime (seg->starttime, segpackedsamples, seg->samprate);
if (!(samplesize = ms_samplesize (seg->sampletype)))
{
ms_log (2, "Unknown sample size for sample type: %c\n", seg->sampletype);
return -1;
}
bufsize = (seg->numsamples - segpackedsamples) * samplesize;
if (bufsize > 0)
{
memmove (seg->datasamples,
(uint8_t *)seg->datasamples + (segpackedsamples * samplesize),
bufsize);
/* Reallocate buffer for reduced size needed, only if not pre-allocating */
if (libmseed_prealloc_block_size == 0)
{
seg->datasamples = libmseed_memory.realloc (seg->datasamples, bufsize);
if (seg->datasamples == NULL)
{
ms_log (2, "Cannot (re)allocate datasamples buffer\n");
return -1;
}
seg->datasize = bufsize;
}
}
else
{
if (seg->datasamples)
libmseed_memory.free (seg->datasamples);
seg->datasamples = NULL;
seg->datasize = 0;
}
seg->samplecnt -= segpackedsamples;
seg->numsamples -= segpackedsamples;
}
totalpackedrecords += segpackedrecords;
totalpackedsamples += segpackedsamples;
seg = seg->next;
}
id = id->next[0];
}
/* The record structure never owns the actual data so it should not free it */
msr->datasamples = NULL;
msr3_free (&msr);
if (packedsamples)
*packedsamples = totalpackedsamples;
return totalpackedrecords;
} /* End of mstl3_pack() */
/**********************************************************************/ /**
* @brief Print trace list summary information for a ::MS3TraceList
*
* By default only print the source ID, starttime and endtime for each
* trace. If \a details is greater than 0 include the sample rate,
* number of samples and a total trace count. If \a gaps is greater than
* 0 and the previous trace matches (SID & samprate) include the
* gap between the endtime of the last trace and the starttime of the
* current trace.
*
* @param[in] mstl ::MS3TraceList to print
* @param[in] timeformat Time string format, one of @ref ms_timeformat_t
* @param[in] details Flag to control inclusion of more details
* @param[in] gaps Flag to control inclusion of gap/overlap between segments
* @param[in] versions Flag to control inclusion of publication version on SourceIDs
***************************************************************************/
void
mstl3_printtracelist (const MS3TraceList *mstl, ms_timeformat_t timeformat,
int8_t details, int8_t gaps, int8_t versions)
{
const MS3TraceID *id = 0;
const MS3TraceSeg *seg = 0;
char stime[40];
char etime[40];
char gapstr[40];
int8_t nogap;
double gap;
double delta;
int tracecnt = 0;
int segcnt = 0;
char versioned_sid[LM_SIDLEN + 10] = {0};
const char *display_sid = NULL;
if (!mstl)
{
return;
}
/* Print out the appropriate header */
if (details > 0 && gaps > 0)
ms_log (0, " SourceID Start sample End sample Gap Hz Samples\n");
else if (details <= 0 && gaps > 0)
ms_log (0, " SourceID Start sample End sample Gap\n");
else if (details > 0 && gaps <= 0)
ms_log (0, " SourceID Start sample End sample Hz Samples\n");
else
ms_log (0, " SourceID Start sample End sample\n");
/* Loop through trace list */
id = mstl->traces.next[0];
while (id)
{
/* Generated versioned SID if requested */
if (versions)
{
snprintf (versioned_sid, sizeof(versioned_sid), "%s#%u", id->sid, id->pubversion);
display_sid = versioned_sid;
}
else
{
display_sid = id->sid;
}
/* Loop through segment list */
seg = id->first;
while (seg)
{
/* Create formatted time strings */
if (ms_nstime2timestr (seg->starttime, stime, timeformat, NANO_MICRO) == NULL)
return;
if (ms_nstime2timestr (seg->endtime, etime, timeformat, NANO_MICRO) == NULL)
return;
/* Print segment info at varying levels */
if (gaps > 0)
{
gap = 0.0;
nogap = 0;
if (seg->prev)
gap = (double)(seg->starttime - seg->prev->endtime) / NSTMODULUS;
else
nogap = 1;
/* Check that any overlap is not larger than the trace coverage */
if (gap < 0.0)
{
delta = (seg->samprate) ? (1.0 / seg->samprate) : 0.0;
if ((gap * -1.0) > (((double)(seg->endtime - seg->starttime) / NSTMODULUS) + delta))
gap = -(((double)(seg->endtime - seg->starttime) / NSTMODULUS) + delta);
}
/* Fix up gap display */
if (nogap)
snprintf (gapstr, sizeof (gapstr), " == ");
else if (gap >= 86400.0 || gap <= -86400.0)
snprintf (gapstr, sizeof (gapstr), "%-3.1fd", (gap / 86400));
else if (gap >= 3600.0 || gap <= -3600.0)
snprintf (gapstr, sizeof (gapstr), "%-3.1fh", (gap / 3600));
else if (gap == 0.0)
snprintf (gapstr, sizeof (gapstr), "-0 ");
else
snprintf (gapstr, sizeof (gapstr), "%-4.4g", gap);
if (details <= 0)
ms_log (0, "%-27s %-28s %-28s %-4s\n",
display_sid, stime, etime, gapstr);
else
ms_log (0, "%-27s %-28s %-28s %-s %-3.3g %-" PRId64 "\n",
display_sid, stime, etime, gapstr, seg->samprate, seg->samplecnt);
}
else if (details > 0 && gaps <= 0)
ms_log (0, "%-27s %-28s %-28s %-3.3g %-" PRId64 "\n",
display_sid, stime, etime, seg->samprate, seg->samplecnt);
else
ms_log (0, "%-27s %-28s %-28s\n", display_sid, stime, etime);
segcnt++;
seg = seg->next;
}
tracecnt++;
id = id->next[0];
}
if (details > 0)
ms_log (0, "Total: %d trace(s) with %d segment(s)\n", tracecnt, segcnt);
return;
} /* End of mstl3_printtracelist() */
/**********************************************************************/ /**
* @brief Print SYNC trace list summary information for a ::MS3TraceList
*
* The SYNC header line will be created using the supplied dccid, if
* the pointer is NULL the string "DCC" will be used instead.
*
* If the \a subsecond flag is true the segment start and end times will
* include subsecond precision, otherwise they will be truncated to
* integer seconds.
*
* @param[in] mstl ::MS3TraceList to print
* @param[in] dccid The DCC identifier to include in the output
* @param[in] subseconds Inclusion of subseconds, one of @ref ms_subseconds_t
***************************************************************************/
void
mstl3_printsynclist (const MS3TraceList *mstl, const char *dccid, ms_subseconds_t subseconds)
{
const MS3TraceID *id = 0;
const MS3TraceSeg *seg = 0;
char starttime[40];
char endtime[40];
char yearday[32];
char net[11] = {0};
char sta[11] = {0};
char loc[11] = {0};
char chan[11] = {0};
time_t now;
struct tm *nt;
if (!mstl)
{
return;
}
/* Generate current time stamp */
now = time (NULL);
nt = localtime (&now);
nt->tm_year += 1900;
nt->tm_yday += 1;
snprintf (yearday, sizeof (yearday), "%04d,%03d", nt->tm_year, nt->tm_yday);
/* Print SYNC header line */
ms_log (0, "%s|%s\n", (dccid) ? dccid : "DCC", yearday);
/* Loop through trace list */
id = mstl->traces.next[0];
while (id)
{
ms_sid2nslc (id->sid, net, sta, loc, chan);
/* Loop through segment list */
seg = id->first;
while (seg)
{
ms_nstime2timestr (seg->starttime, starttime, SEEDORDINAL, subseconds);
ms_nstime2timestr (seg->endtime, endtime, SEEDORDINAL, subseconds);
/* Print SYNC line */
ms_log (0, "%s|%s|%s|%s|%s|%s||%.10g|%" PRId64 "|||||||%s\n",
net, sta, loc, chan,
starttime, endtime,
seg->samprate, seg->samplecnt,
yearday);
seg = seg->next;
}
id = id->next[0];
}
return;
} /* End of mstl3_printsynclist() */
/**********************************************************************/ /**
* @brief Print gap/overlap list summary information for a ::MS3TraceList.
*
* Overlaps are printed as negative gaps.
*
* If \a mingap and \a maxgap are not NULL their values will be
* enforced and only gaps/overlaps matching their implied criteria
* will be printed.
*
* @param[in] mstl ::MS3TraceList to print
* @param[in] timeformat Time string format, one of @ref ms_timeformat_t
* @param[in] mingap Minimum gap to print in seconds (pointer to value)
* @param[in] maxgap Maximum gap to print in seconds (pointer to value)
***************************************************************************/
void
mstl3_printgaplist (const MS3TraceList *mstl, ms_timeformat_t timeformat,
double *mingap, double *maxgap)
{
const MS3TraceID *id = 0;
const MS3TraceSeg *seg = 0;
char time1[40], time2[40];
char gapstr[40];
double gap;
double delta;
double nsamples;
int8_t printflag;
int gapcnt = 0;
if (!mstl)
return;
if (!mstl->numtraceids)
return;
ms_log (0, " SourceID Last Sample Next Sample Gap Samples\n");
id = mstl->traces.next[0];
while (id)
{
seg = id->first;
while (seg->next)
{
/* Skip segments with 0 sample rate, usually from SOH records */
if (seg->samprate == 0.0)
{
seg = seg->next;
continue;
}
gap = (double)(seg->next->starttime - seg->endtime) / NSTMODULUS;
/* Check that any overlap is not larger than the trace coverage */
if (gap < 0.0)
{
delta = (seg->next->samprate) ? (1.0 / seg->next->samprate) : 0.0;
if ((gap * -1.0) > (((double)(seg->next->endtime - seg->next->starttime) / NSTMODULUS) + delta))
gap = -(((double)(seg->next->endtime - seg->next->starttime) / NSTMODULUS) + delta);
}
printflag = 1;
/* Check gap/overlap criteria */
if (mingap)
if (gap < *mingap)
printflag = 0;
if (maxgap)
if (gap > *maxgap)
printflag = 0;
if (printflag)
{
nsamples = ms_dabs (gap) * seg->samprate;
if (gap > 0.0)
nsamples -= 1.0;
else
nsamples += 1.0;
/* Fix up gap display */
if (gap >= 86400.0 || gap <= -86400.0)
snprintf (gapstr, sizeof (gapstr), "%-3.1fd", (gap / 86400));
else if (gap >= 3600.0 || gap <= -3600.0)
snprintf (gapstr, sizeof (gapstr), "%-3.1fh", (gap / 3600));
else if (gap == 0.0)
snprintf (gapstr, sizeof (gapstr), "-0 ");
else
snprintf (gapstr, sizeof (gapstr), "%-4.4g", gap);
/* Create formatted time strings */
if (ms_nstime2timestr (seg->endtime, time1, timeformat, NANO_MICRO) == NULL)
ms_log (2, "Cannot convert trace start time for %s\n", id->sid);
if (ms_nstime2timestr (seg->next->starttime, time2, timeformat, NANO_MICRO) == NULL)
ms_log (2, "Cannot convert trace end time for %s\n", id->sid);
ms_log (0, "%-17s %-24s %-24s %-4s %-.8g\n",
id->sid, time1, time2, gapstr, nsamples);
gapcnt++;
}
seg = seg->next;
}
id = id->next[0];
}
ms_log (0, "Total: %d gap(s)\n", gapcnt);
return;
} /* End of mstl3_printgaplist() */
/* Pseudo random number generator, as a linear congruential generator (LCG):
* https://en.wikipedia.org/wiki/Linear_congruential_generator
*
* Yields a sequence of pseudo-randomized numbers distributed
* between 0 and UINT32_MAX. Roughly half-chance of being above
* or below (UINT32_MAX / 2), good enough for coin-flipping.
*
* Uses 64-bit state but returns only the higher-order bits
* for statistically better values.
*/
static uint32_t
lm_lcg_r (uint64_t *state)
{
*state = 6364136223846793005ULL * *state + 1;
return *state >> 32;
}
/* Return random height from 1 up to maximum.
*
* Coin-flipping method using a pseudo random number generator.
*/
static uint8_t
lm_random_height (uint8_t maximum, uint64_t *state)
{
uint8_t height = 1;
while (height < maximum &&
lm_lcg_r (state) < (UINT32_MAX / 2))
{
height++;
}
return height;
}