Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libmseed for openSUSE:Factory checked in at 2026-07-31 15:27:29 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libmseed (Old) and /work/SRC/openSUSE:Factory/.libmseed.new.2004 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libmseed" Fri Jul 31 15:27:29 2026 rev:17 rq:1368676 version:3.5.3 Changes: -------- --- /work/SRC/openSUSE:Factory/libmseed/libmseed.changes 2026-07-26 11:33:08.140348491 +0200 +++ /work/SRC/openSUSE:Factory/.libmseed.new.2004/libmseed.changes 2026-07-31 16:08:54.807781433 +0200 @@ -1,0 +2,23 @@ +Thu Jul 30 18:12:18 UTC 2026 - Andreas Stieger <[email protected]> + +- update to 3.5.3: + * optimizations for segment handling + +------------------------------------------------------------------- +Wed Jul 29 20:26:15 UTC 2026 - Andreas Stieger <[email protected]> + +- update to 3.5.2: + * Add MSF_RECORDLIST_NOEXTRAS to skip copying extra headers into + MS3RecordList entries when building a record list. + * Reject a blockette offset within the fixed header as fatal. + * Break ms_parse_raw2() blockette walk on unknown length, treat + a self-referential next offset as fatal, add Blockette 405 length. + * Reject sample count that would overflow size_t when converting to + doubles in mstl3_convertsamples(), avoiding undersized malloc on + 32-bit. + * Reject size_t overflow in libmseed_memory_prealloc() growth + loop, avoiding an infinite hang on huge allocation requests. + * Free duplicated MS3Record when record-list entry allocation fails. + * Reject out-of-range leap-second epochs in ms_readleapsecondfile(). + +------------------------------------------------------------------- Old: ---- libmseed-3.5.1.tar.gz New: ---- libmseed-3.5.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libmseed.spec ++++++ --- /var/tmp/diff_new_pack.tJh95d/_old 2026-07-31 16:08:55.351800358 +0200 +++ /var/tmp/diff_new_pack.tJh95d/_new 2026-07-31 16:08:55.355800497 +0200 @@ -18,7 +18,7 @@ %define sover 3 Name: libmseed -Version: 3.5.1 +Version: 3.5.3 Release: 0 Summary: MiniSEED data format library License: Apache-2.0 ++++++ libmseed-3.5.1.tar.gz -> libmseed-3.5.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libmseed-3.5.1/ChangeLog new/libmseed-3.5.3/ChangeLog --- old/libmseed-3.5.1/ChangeLog 2026-07-25 22:25:49.000000000 +0200 +++ new/libmseed-3.5.3/ChangeLog 2026-07-30 18:15:35.000000000 +0200 @@ -1,4 +1,28 @@ -2026.206: v3.5.1 +2026.211: v3.5.3 + - Optimize segment searches by tracking recently-active segments per trace ID, + a significant improvement for creating trace lists from near time ordered + input data. + - End the segment search early once a segment starts beyond the record + end plus tolerance, as no later segment can match in the sorted list. + - Search backward from the last segment for zero sample rate records, + which can only match a following segment, avoiding a full list scan. + - Avoid FP division in sample rate comparisons: short-circuit identical + rates and use a multiplicative form of MS_ISRATETOLERABLE(). + +2026.208: v3.5.2 + - Add MSF_RECORDLIST_NOEXTRAS to skip copying extra headers into + MS3RecordList entries when building a record list. + - Reject a blockette offset within the fixed header as fatal. + - Break ms_parse_raw2() blockette walk on unknown length, treat a + self-referential next offset as fatal, add Blockette 405 length. + - Reject sample count that would overflow size_t when converting to + doubles in mstl3_convertsamples(), avoiding undersized malloc on 32-bit. + - Reject size_t overflow in libmseed_memory_prealloc() growth loop, + avoiding an infinite hang on huge allocation requests. + - Free duplicated MS3Record when record-list entry allocation fails. + - Reject out-of-range leap-second epochs in ms_readleapsecondfile(). + + 2026.206: v3.5.1 - Set MS3RecordPtr.msr->record to NULL when the raw record is not available, instead of leaving it dangling to a source buffer that may be freed or reused. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libmseed-3.5.1/fileutils.c new/libmseed-3.5.3/fileutils.c --- old/libmseed-3.5.1/fileutils.c 2026-07-25 22:25:49.000000000 +0200 +++ new/libmseed-3.5.3/fileutils.c 2026-07-30 18:15:35.000000000 +0200 @@ -790,6 +790,7 @@ * If the ::MSF_RECORDLIST flag is set in @p flags, a ::MS3RecordList * will be built for each ::MS3TraceSeg. The ::MS3RecordPtr entries * contain the location of the data record, bit flags, extra headers, etc. + * Extra headers are omitted if ::MSF_RECORDLIST_NOEXTRAS is also set. * * @param[out] ppmstl Pointer-to-pointer to a ::MS3TraceList to populate * @param[in] mspath File to read @@ -799,6 +800,7 @@ * @param[in] flags * @parblock * - @c ::MSF_RECORDLIST : Build a ::MS3RecordList for each ::MS3TraceSeg + * - @c ::MSF_RECORDLIST_NOEXTRAS : Do not copy extra headers into record list entries * - @c ::MSF_SKIPADJACENTDUPLICATES : Skip adjacent duplicate records * - Flags supported by msr3_parse() * - Flags supported by mstl3_addmsr() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libmseed-3.5.1/genutils.c new/libmseed-3.5.3/genutils.c --- old/libmseed-3.5.1/genutils.c 2026-07-25 22:25:49.000000000 +0200 +++ new/libmseed-3.5.3/genutils.c 2026-07-30 18:15:35.000000000 +0200 @@ -59,9 +59,16 @@ return ptr; /* Calculate new size needed for request by adding blocks */ + if (*currentsize > SIZE_MAX - libmseed_prealloc_block_size) + return NULL; + newsize = *currentsize + libmseed_prealloc_block_size; while (newsize < size) + { + if (newsize > SIZE_MAX - libmseed_prealloc_block_size) + return NULL; newsize += libmseed_prealloc_block_size; + } newptr = libmseed_memory.realloc (ptr, newsize); @@ -2052,6 +2059,16 @@ if (fields == 2) { + /* Convert NTP epoch to Unix epoch; guard against int64 overflow */ + leapsecond = leapsecond - NTPPOSIXEPOCHDELTA; + + if (leapsecond > INT64_MAX / NSTMODULUS || leapsecond < INT64_MIN / NSTMODULUS) + { + ms_log (2, "Leap second epoch is beyond the representable nstime range\n"); + fclose (fp); + return -1; + } + if ((ls = (LeapSecond *)libmseed_memory.malloc (sizeof (LeapSecond))) == NULL) { ms_log (2, "Cannot allocate LeapSecond entry, out of memory?\n"); @@ -2059,8 +2076,7 @@ return -1; } - /* Convert NTP epoch time to Unix epoch time and then to nttime_t */ - ls->leapsecond = MS_EPOCH2NSTIME (leapsecond - NTPPOSIXEPOCHDELTA); + ls->leapsecond = MS_EPOCH2NSTIME (leapsecond); ls->TAIdelta = TAIdelta; ls->next = NULL; count++; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libmseed-3.5.1/internalstate.h new/libmseed-3.5.3/internalstate.h --- old/libmseed-3.5.1/internalstate.h 2026-07-25 22:25:49.000000000 +0200 +++ new/libmseed-3.5.3/internalstate.h 2026-07-30 18:15:35.000000000 +0200 @@ -3,7 +3,7 @@ * * This file is part of the miniSEED Library. * - * Copyright (c) 2024 Chad Trabant, EarthScope Data Services + * Copyright (c) 2026 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. @@ -74,6 +74,44 @@ int64_t totalpackedrecords; /* Total records packed */ }; +/* Number of most-recently-active segments tracked per MS3TraceID, used to + * bound the segment-list search in _mstl3_addmsr_impl() */ +#define LM_RECENTSEGS 4 + +/* Maximum hops walked when resolving list order among recent segments or + * falling back to a direct list walk, beyond which the fast path is + * refused in favor of a full scan */ +#define LM_RECENTSEGS_MAXWALK 8 + +/* Private extension of MS3TraceList (opaque in public header). + * + * The public struct is the first member so public pointers, sizeof, and + * field offsets are unaffected by the extension. */ +typedef struct +{ + MS3TraceList mstl; + int8_t foreignid; /* Set if an MS3TraceID not allocated by this library may be present */ +} LMTraceListNode; + +/* Private extension of MS3TraceID (opaque in public header). + * + * Tracks the most-recently-active segments of a trace ID (the "recent set") + * and nonrecentendbound, an upper bound on the end time of every segment + * NOT in the recent set. The bound may overestimate, which only costs + * search performance, but must never underestimate, which would cause a + * match to be missed. It starts at INT64_MIN and only ever rises, folding + * in the end time of segments as they are evicted from the recent set or + * removed from the trace list. + * + * The public struct is the first member so public pointers, sizeof, and + * field offsets are unaffected by the extension. */ +typedef struct +{ + MS3TraceID id; + MS3TraceSeg *recentseg[LM_RECENTSEGS]; + nstime_t nonrecentendbound; +} LMTraceIDNode; + #ifdef __cplusplus } #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libmseed-3.5.1/libmseed.h new/libmseed-3.5.3/libmseed.h --- old/libmseed-3.5.1/libmseed.h 2026-07-25 22:25:49.000000000 +0200 +++ new/libmseed-3.5.3/libmseed.h 2026-07-30 18:15:35.000000000 +0200 @@ -29,8 +29,8 @@ { #endif -#define LIBMSEED_VERSION "3.5.1" //!< Library version -#define LIBMSEED_RELEASE "2026.206" //!< Library release date +#define LIBMSEED_VERSION "3.5.3" //!< Library version +#define LIBMSEED_RELEASE "2026.211" //!< Library release date /** @defgroup io-functions File and URL I/O */ /** @defgroup miniseed-record Record Handling */ @@ -146,8 +146,8 @@ #define LM_SIDLEN 64 //!< Length of source ID string /** @def MS_ISRATETOLERABLE - @brief Macro to test default sample rate tolerance: abs(1-sr1/sr2) < 0.0001 */ -#define MS_ISRATETOLERABLE(A, B) (fabs (1.0 - ((A) / (B))) < 0.0001) + @brief Macro to test default sample rate tolerance: abs(sr2-sr1) < 0.0001*abs(sr2) */ +#define MS_ISRATETOLERABLE(A, B) (fabs ((B) - (A)) < 0.0001 * fabs (B)) /** @def MS2_ISDATAINDICATOR @brief Macro to test a character for miniSEED 2.x data record/quality indicators */ @@ -1137,6 +1137,8 @@ the ::MSF_RECORDLIST flag to @ref mstl3_readbuffer() and @ref ms3_readtracelist(). Alternatively, a record list can be built by adding records to a @ref trace-list using mstl3_addmsr_recordptr(). + Extra headers are copied into each ::MS3RecordPtr by default; set + ::MSF_RECORDLIST_NOEXTRAS to omit them, usually to reduce memory usage. The main purpose of this functionality is to support an efficient, 2-pass pattern of first reading a summary of data followed by @@ -1636,6 +1638,7 @@ #define MSF_PPUPDATETIME 0x0400 //!< [TraceList] Store update time (as nstime_t) at ::MS3TraceSeg.prvtptr #define MSF_SPLITISVERSION 0x0800 //!< [TraceList] Use the splitversion value as version instead of record version #define MSF_SKIPADJACENTDUPLICATES 0x1000 //!< [TraceList] Skip adjacent duplicate records +#define MSF_RECORDLIST_NOEXTRAS 0x2000 //!< [TraceList] Do not copy extra headers to the record list /** @} */ #ifdef __cplusplus diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libmseed-3.5.1/parseutils.c new/libmseed-3.5.3/parseutils.c --- old/libmseed-3.5.1/parseutils.c 2026-07-25 22:25:49.000000000 +0200 +++ new/libmseed-3.5.3/parseutils.c 2026-07-30 18:15:35.000000000 +0200 @@ -235,7 +235,8 @@ /* Loop through blockettes as long as number is non-zero and the 4-byte * blockette header (type and next offset) is fully within the buffer */ - while (blkt_offset != 0 && blkt_offset > 47 && (uint64_t)blkt_offset + 4 <= recbuflen) + while (blkt_offset != 0 && blkt_offset >= MS2FSDH_LENGTH && + (uint64_t)blkt_offset + 4 <= recbuflen) { memcpy (&blkt_type, record + blkt_offset, 2); memcpy (&next_blkt, record + blkt_offset + 2, 2); @@ -850,6 +851,7 @@ { ms_log (2, "%s: Unknown blockette length for type %d\n", sid, blkt_type); retval++; + break; } /* Track end of blockette chain */ @@ -1397,7 +1399,7 @@ } /* Sanity check the next blockette offset */ - if (next_blkt && next_blkt <= endofblockettes) + if (next_blkt && (next_blkt == blkt_offset || next_blkt <= endofblockettes)) { ms_log (2, "%s: Next blockette offset (%d) is within current blockette ending at byte %d\n", sid, next_blkt, endofblockettes); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libmseed-3.5.1/test/test-read.c new/libmseed-3.5.3/test/test-read.c --- old/libmseed-3.5.1/test/test-read.c 2026-07-25 22:25:49.000000000 +0200 +++ new/libmseed-3.5.3/test/test-read.c 2026-07-30 18:15:35.000000000 +0200 @@ -623,9 +623,10 @@ CHECK (int32s[3631] == 26, "Decoded sample value mismatch"); ms3_readmsr(&msr, NULL, flags, 0); - /* Invalid blockette chain (format version 2). One could argue these should not be readable. */ + /* Invalid blockette chain (format version 2): FSDH blockette offset (40) + * falls within the 48-byte fixed header, not a valid blockette start. */ rv = ms3_readmsr (&msr, "data/testdata-invalid-blockette-offsets.mseed2", flags, 0); - REQUIRE (rv == MS_NOERROR, "ms3_readmsr() did not return expected MS_NOERROR"); + REQUIRE (rv == MS_GENERROR, "ms3_readmsr() did not return expected MS_GENERROR"); ms3_readmsr (&msr, NULL, flags, 0); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libmseed-3.5.1/test/test-tracelist.c new/libmseed-3.5.3/test/test-tracelist.c --- old/libmseed-3.5.1/test/test-tracelist.c 2026-07-25 22:25:49.000000000 +0200 +++ new/libmseed-3.5.3/test/test-tracelist.c 2026-07-30 18:15:35.000000000 +0200 @@ -204,6 +204,59 @@ mstl3_free (&mstl, 1); } +/* This test adds a header-only record with extra headers to a MS3TraceList + * via mstl3_addmsr_recordptr(), once with default flags and once with the + * MSF_RECORDLIST_NOEXTRAS flag set, verifying that the extra headers are + * copied to the resulting MS3RecordPtr by default and omitted when the flag + * is set. + */ +TEST (tracelist, mstl3_addmsr_recordptr_noextras) +{ + MS3TraceList *mstl = NULL; + MS3RecordPtr *recptr = NULL; + MS3Record msr = MS3Record_INITIALIZER; + char *extra = "{\"FDSN\":{\"Time\":{\"Quality\":100}}}"; + + strcpy (msr.sid, "FDSN:XX_TEST__X_H_Z"); + msr.formatversion = 3; + msr.pubversion = 1; + msr.starttime = ms_timestr2nstime ("2024-01-01T00:00:00.0Z"); + msr.samprate = 1.0; + msr.sampletype = 'i'; + msr.samplecnt = 100; + msr.numsamples = 0; + msr.datasamples = NULL; + msr.extra = extra; + msr.extralength = (uint16_t)strlen (extra); + + /* Default flags: extra headers are copied to the record list entry */ + REQUIRE (mstl = mstl3_init (NULL), "mstl3_init() returned unexpected NULL"); + REQUIRE (mstl3_addmsr_recordptr (mstl, &msr, &recptr, 0, 1, 0, NULL) != NULL, + "mstl3_addmsr_recordptr() returned unexpected NULL"); + + REQUIRE (recptr != NULL, "recptr is unexpected NULL"); + REQUIRE (recptr->msr != NULL, "recptr->msr is unexpected NULL"); + CHECK (recptr->msr->extralength == msr.extralength, + "recptr->msr->extralength does not match source extralength"); + REQUIRE (recptr->msr->extra != NULL, "recptr->msr->extra is unexpected NULL"); + CHECK_STREQ (recptr->msr->extra, extra); + + mstl3_free (&mstl, 0); + + /* MSF_RECORDLIST_NOEXTRAS: extra headers are not copied */ + recptr = NULL; + REQUIRE (mstl = mstl3_init (NULL), "mstl3_init() returned unexpected NULL"); + REQUIRE (mstl3_addmsr_recordptr (mstl, &msr, &recptr, 0, 1, MSF_RECORDLIST_NOEXTRAS, NULL) != NULL, + "mstl3_addmsr_recordptr() returned unexpected NULL"); + + REQUIRE (recptr != NULL, "recptr is unexpected NULL"); + REQUIRE (recptr->msr != NULL, "recptr->msr is unexpected NULL"); + CHECK (recptr->msr->extralength == 0, "recptr->msr->extralength is not expected 0"); + CHECK (recptr->msr->extra == NULL, "recptr->msr->extra is not expected NULL"); + + mstl3_free (&mstl, 0); +} + /* This test reads miniSEED from a file into a MS3TraceList while using the * MSF_PPUPDATETIME flag to set the segment prvtptr to the update time of the * record. The expected value of the segment prvtptr is verified to be within diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libmseed-3.5.1/tracelist.c new/libmseed-3.5.3/tracelist.c --- old/libmseed-3.5.1/tracelist.c 2026-07-25 22:25:49.000000000 +0200 +++ new/libmseed-3.5.3/tracelist.c 2026-07-30 18:15:35.000000000 +0200 @@ -27,12 +27,23 @@ #include "internalstate.h" #include "libmseed.h" +static MS3TraceID *lm_addID (MS3TraceList *mstl, MS3TraceID *id, MS3TraceID **prev); static MS3TraceSeg *lm_msr2seg (const MS3Record *msr, nstime_t endtime); static MS3TraceSeg *lm_addmsrtoseg (MS3TraceSeg *seg, const MS3Record *msr, nstime_t endtime, int8_t whence); static MS3TraceSeg *lm_addsegtoseg (MS3TraceSeg *seg1, MS3TraceSeg *seg2); static MS3RecordPtr *lm_add_recordptr (MS3TraceSeg *seg, const MS3Record *msr, nstime_t endtime, - int8_t whence); + int8_t whence, uint32_t flags); + +static void lm_recentseg_touch (LMTraceIDNode *idnode, MS3TraceSeg *seg); +static void lm_recentseg_remove (LMTraceIDNode *idnode, MS3TraceSeg *seg); +static void lm_endbound_fold (LMTraceIDNode *idnode, nstime_t endtime); +static int lm_seg_listorder (const MS3TraceSeg *a, const MS3TraceSeg *b, int *order); +static int lm_scan_recent (LMTraceIDNode *idnode, const MS3Record *msr, nstime_t endtime, + nstime_t nsperiod, nstime_t nstimetol, nstime_t nnstimetol, + double sampratehz, double sampratetol, int8_t autoheal, + MS3TraceSeg **psegbefore, MS3TraceSeg **psegafter, + MS3TraceSeg **pfollowseg); static void lm_free_segment_memory (MS3TraceSeg *seg, int8_t freeprvtptr); static int lm_remove_segment (MS3TraceList *mstl, MS3TraceID *id, MS3TraceSeg *seg, @@ -45,7 +56,7 @@ /* Test if two sample rates are similar using either specified tolerance (if non-negative) or * default tolerance */ #define IS_SAMPRATE_SIMILAR(SR1, SR2, SRT) \ - ((SRT >= 0.0) ? fabs (SR1 - SR2) <= SRT : MS_ISRATETOLERABLE (SR1, SR2)) + ((SR1) == (SR2) || ((SRT >= 0.0) ? fabs (SR1 - SR2) <= SRT : MS_ISRATETOLERABLE (SR1, SR2))) /* Test if a MS3TraceSeg represents time coverage */ #define SEGMENT_HAS_TIME_COVERAGE(seg) ((seg)->samplecnt > 0 && (seg)->samprate != 0.0) @@ -73,7 +84,7 @@ mstl3_free (&mstl, 1); } - mstl = (MS3TraceList *)libmseed_memory.malloc (sizeof (MS3TraceList)); + mstl = (MS3TraceList *)libmseed_memory.malloc (sizeof (LMTraceListNode)); if (mstl == NULL) { @@ -81,7 +92,7 @@ return NULL; } - memset (mstl, 0, sizeof (MS3TraceList)); + memset (mstl, 0, sizeof (LMTraceListNode)); /* Seed PRNG with 1, we only need random distribution */ mstl->prngstate = 1; @@ -236,6 +247,19 @@ MS3TraceID * mstl3_addID (MS3TraceList *mstl, MS3TraceID *id, MS3TraceID **prev) { + /* An ID added through this public entry point may not carry the private + * tail this library allocates internally, so disable the state that + * relies on it for the containing list. */ + if (mstl) + ((LMTraceListNode *)mstl)->foreignid = 1; + + return lm_addID (mstl, id, prev); +} /* End of mstl3_addID() */ + +/* Add a MS3TraceID to a MS3TraceList, see mstl3_addID() for the public entry point. */ +static MS3TraceID * +lm_addID (MS3TraceList *mstl, MS3TraceID *id, MS3TraceID **prev) +{ MS3TraceID *local_prev[MSTRACEID_SKIPLIST_HEIGHT] = {NULL}; int level; @@ -281,7 +305,279 @@ mstl->numtraceids++; return id; -} /* End of mstl3_addID() */ +} /* End of lm_addID() */ + +/*************************************************************************** + * Move a segment into the most-recently-used slot of a trace ID's recent + * set, evicting the least-recently-used entry if the segment was not + * already tracked. An evicted segment's current end time is folded into + * the non-recent end-time bound before it is dropped. + ***************************************************************************/ +static void +lm_recentseg_touch (LMTraceIDNode *idnode, MS3TraceSeg *seg) +{ + MS3TraceSeg *evicted; + int found = -1; + int i; + + if (!seg) + return; + + for (i = 0; i < LM_RECENTSEGS; i++) + { + if (idnode->recentseg[i] == seg) + { + found = i; + break; + } + } + + /* Only the least-recently-used slot is evicted, and only when the + * segment being touched was not already present */ + evicted = (found < 0) ? idnode->recentseg[LM_RECENTSEGS - 1] : NULL; + + /* Shift entries from the found (or last) slot down to make room at the front */ + for (i = (found < 0) ? LM_RECENTSEGS - 1 : found; i > 0; i--) + { + idnode->recentseg[i] = idnode->recentseg[i - 1]; + } + + idnode->recentseg[0] = seg; + + if (evicted) + lm_endbound_fold (idnode, evicted->endtime); +} /* End of lm_recentseg_touch() */ + +/*************************************************************************** + * Remove a segment from a trace ID's recent set, if present, compacting the + * array. The end-time bound is not lowered; it may now overestimate for + * this segment, which only costs search performance. + ***************************************************************************/ +static void +lm_recentseg_remove (LMTraceIDNode *idnode, MS3TraceSeg *seg) +{ + int i, j; + + for (i = 0; i < LM_RECENTSEGS; i++) + { + if (idnode->recentseg[i] == seg) + { + for (j = i; j < LM_RECENTSEGS - 1; j++) + idnode->recentseg[j] = idnode->recentseg[j + 1]; + + idnode->recentseg[LM_RECENTSEGS - 1] = NULL; + break; + } + } +} /* End of lm_recentseg_remove() */ + +/*************************************************************************** + * Raise a trace ID's non-recent end-time bound to cover a known end time. + * The bound only ever grows. + ***************************************************************************/ +static void +lm_endbound_fold (LMTraceIDNode *idnode, nstime_t endtime) +{ + if (endtime > idnode->nonrecentendbound) + idnode->nonrecentendbound = endtime; +} /* End of lm_endbound_fold() */ + +/*************************************************************************** + * Determine the list order of two segments: starttime ascending, endtime + * descending. Segments with fully equal keys are adjacent in the list; + * such ties are resolved by walking a bounded number of hops from 'a' in + * each direction looking for 'b'. + * + * On success returns 1 and sets *order to -1 (a before b), 0 (a is b), or + * 1 (a after b). Returns 0 if the order could not be resolved within the + * walk bound. + ***************************************************************************/ +static int +lm_seg_listorder (const MS3TraceSeg *a, const MS3TraceSeg *b, int *order) +{ + const MS3TraceSeg *walk; + int hops; + + if (a == b) + { + *order = 0; + return 1; + } + + if (a->starttime != b->starttime) + { + *order = (a->starttime < b->starttime) ? -1 : 1; + return 1; + } + + if (a->endtime != b->endtime) + { + *order = (a->endtime > b->endtime) ? -1 : 1; + return 1; + } + + for (walk = a->next, hops = 0; walk && hops < LM_RECENTSEGS_MAXWALK; walk = walk->next, hops++) + { + if (walk == b) + { + *order = -1; + return 1; + } + } + + for (walk = a->prev, hops = 0; walk && hops < LM_RECENTSEGS_MAXWALK; walk = walk->prev, hops++) + { + if (walk == b) + { + *order = 1; + return 1; + } + } + + return 0; +} /* End of lm_seg_listorder() */ + +/*************************************************************************** + * Reproduce the segment-list search of _mstl3_addmsr_impl() using only + * the recent segments of a trace ID. Callable only when every segment not + * in the recent set is provably out of range for segbefore, segafter, and + * the autoheal exact match, and necessarily sorts before the record (see + * the guard at the call site); under that condition the outcome of the + * recent segments alone matches a full scan. + * + * This loop body mirrors the general search in _mstl3_addmsr_impl() and + * must be kept in lockstep with it. + * + * Returns 1 with *psegbefore, *psegafter and *pfollowseg set (any may be + * NULL) when the shortcut applies. Returns 0, with the outputs untouched, + * when the search cannot be resolved from the recent set and the caller + * must fall back to a full scan. + ***************************************************************************/ +static int +lm_scan_recent (LMTraceIDNode *idnode, const MS3Record *msr, nstime_t endtime, nstime_t nsperiod, + nstime_t nstimetol, nstime_t nnstimetol, double sampratehz, double sampratetol, + int8_t autoheal, MS3TraceSeg **psegbefore, MS3TraceSeg **psegafter, + MS3TraceSeg **pfollowseg) +{ + MS3TraceSeg *recent[LM_RECENTSEGS]; + MS3TraceSeg *searchseg; + MS3TraceSeg *segbefore = NULL; + MS3TraceSeg *segafter = NULL; + MS3TraceSeg *followseg = NULL; + nstime_t postgap; + nstime_t pregap; + int order; + int count = 0; + int i, j; + + /* Collect the tracked recent segments */ + for (i = 0; i < LM_RECENTSEGS; i++) + { + if (idnode->recentseg[i]) + recent[count++] = idnode->recentseg[i]; + } + + /* Sort the collected segments into list order; a small insertion sort + * is sufficient for this fixed, small-size array */ + for (i = 1; i < count; i++) + { + for (j = i; j > 0; j--) + { + if (!lm_seg_listorder (recent[j - 1], recent[j], &order)) + return 0; + + if (order <= 0) + break; + + searchseg = recent[j - 1]; + recent[j - 1] = recent[j]; + recent[j] = searchseg; + } + } + + /* Identical loop body to the general search in _mstl3_addmsr_impl(), + * run over the recent segments only, in list order */ + for (i = 0; i < count; i++) + { + searchseg = recent[i]; + + /* Done searching when segment starts beyond the record end plus tolerance */ + if (searchseg->starttime > endtime + nsperiod + nstimetol) + break; + + /* Skip segments with no time coverage, these cannot be extended */ + if (!SEGMENT_HAS_TIME_COVERAGE (searchseg)) + continue; + + /* Done searching if autohealing and record exactly matches a segment */ + if (autoheal && msr->starttime == searchseg->starttime && endtime == searchseg->endtime) + { + followseg = searchseg; + break; + } + + if (msr->starttime > searchseg->starttime) + followseg = searchseg; + + if (!segbefore) + { + postgap = msr->starttime - searchseg->endtime - nsperiod; + + if (postgap <= nstimetol && postgap >= nnstimetol && + IS_SAMPRATE_SIMILAR (sampratehz, searchseg->samprate, sampratetol)) + segbefore = searchseg; + } + + if (!segafter) + { + pregap = searchseg->starttime - endtime - nsperiod; + + if (pregap <= nstimetol && pregap >= nnstimetol && + IS_SAMPRATE_SIMILAR (sampratehz, searchseg->samprate, sampratetol)) + segafter = searchseg; + } + + /* Done searching if both before and after segments are found */ + if (segbefore && segafter) + break; + /* Done searching if not autohealing and one match found */ + else if (!autoheal && (segbefore || segafter)) + break; + } + + /* If nothing matched, followseg must be the globally latest-starting + * coverage segment before the record, which may be outside the recent + * set: walk backward from the list end, skipping segments that start + * at/after the record (necessarily recent, already considered above) or + * that lack coverage. */ + if (!segbefore && !segafter && !followseg) + { + int hops; + + searchseg = idnode->id.last; + + for (hops = 0; searchseg && hops < LM_RECENTSEGS_MAXWALK; hops++) + { + if (SEGMENT_HAS_TIME_COVERAGE (searchseg) && searchseg->starttime < msr->starttime) + { + followseg = searchseg; + break; + } + + searchseg = searchseg->prev; + } + + /* Walk bound exceeded without resolving followseg, refuse the shortcut */ + if (!followseg && searchseg) + return 0; + } + + *psegbefore = segbefore; + *psegafter = segafter; + *pfollowseg = followseg; + + return 1; +} /* End of lm_scan_recent() */ /*************************************************************************** * Implementation of MS3TraceList addition functions @@ -338,12 +634,12 @@ /* If no matching ID was found create new MS3TraceID and MS3TraceSeg entries */ if (!id) { - if (!(id = (MS3TraceID *)libmseed_memory.malloc (sizeof (MS3TraceID)))) + if (!(id = (MS3TraceID *)libmseed_memory.malloc (sizeof (LMTraceIDNode)))) { ms_log (2, "Error allocating memory\n"); return NULL; } - memset (id, 0, sizeof (MS3TraceID)); + memset (id, 0, sizeof (LMTraceIDNode)); /* Populate MS3TraceID */ memcpy (id->sid, msr->sid, sizeof (id->sid)); @@ -352,6 +648,9 @@ id->latest = endtime; id->numsegments = 1; + /* End-time bound starts below any possible end time, recent set starts empty */ + ((LMTraceIDNode *)id)->nonrecentendbound = INT64_MIN; + if (!(seg = lm_msr2seg (msr, endtime))) { libmseed_memory.free (id); @@ -360,7 +659,7 @@ id->first = id->last = seg; /* Add MS3RecordPtr if requested */ - if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 1))) + if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 1, flags))) { lm_free_segment_memory (seg, 0); libmseed_memory.free (id); @@ -368,7 +667,7 @@ } /* Add new MS3TraceID to MS3TraceList */ - if (mstl3_addID (mstl, id, previd) == NULL) + if (lm_addID (mstl, id, previd) == NULL) { ms_log (2, "Error adding new ID to trace list\n"); lm_free_segment_memory (seg, 0); @@ -447,8 +746,13 @@ id->latest = endtime; /* Add MS3RecordPtr if requested */ - if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 1))) + if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 1, flags))) + { + /* seg's end time was already extended above; keep the end-time bound valid */ + if (!((LMTraceListNode *)mstl)->foreignid) + lm_endbound_fold ((LMTraceIDNode *)id, seg->endtime); return NULL; + } } /* Record coverage is after all other coverage */ else if ((msr->starttime - nsperiod - nstimetol) > id->latest) @@ -466,8 +770,13 @@ id->latest = endtime; /* Add MS3RecordPtr if requested */ - if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 0))) + if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 0, flags))) + { + /* seg is already linked into the list; keep the end-time bound valid */ + if (!((LMTraceListNode *)mstl)->foreignid) + lm_endbound_fold ((LMTraceIDNode *)id, seg->endtime); return NULL; + } } /* Record coverage is before all other coverage */ else if ((endtime + nsperiod + nstimetol) < id->earliest) @@ -485,8 +794,13 @@ id->earliest = msr->starttime; /* Add MS3RecordPtr if requested */ - if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 0))) + if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 0, flags))) + { + /* seg is already linked into the list; keep the end-time bound valid */ + if (!((LMTraceListNode *)mstl)->foreignid) + lm_endbound_fold ((LMTraceIDNode *)id, seg->endtime); return NULL; + } } /* Record coverage fits at beginning of first segment */ else if (firstgap <= nstimetol && firstgap >= nnstimetol && @@ -502,7 +816,7 @@ id->earliest = msr->starttime; /* Add MS3RecordPtr if requested */ - if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 2))) + if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 2, flags))) return NULL; } /* Search complete segment list for matches */ @@ -512,57 +826,110 @@ segbefore = NULL; /* The first segment end that matches the record start (within tolerance) */ segafter = NULL; /* The first segment start that matches the record end (within tolerance) */ followseg = NULL; /* The segment with latest start time before the record start */ - searchseg = id->first; - while (searchseg) + + /* A zero rate record cannot match segbefore/segafter without an explicit + * tolerance, so only followseg is needed: search backward from the last segment. */ + if (sampratehz == 0.0 && sampratetol < 0.0) { - /* Skip segments with no time coverage, these cannot be extended */ - if (!SEGMENT_HAS_TIME_COVERAGE (searchseg)) + searchseg = id->last; + while (searchseg) { - searchseg = searchseg->next; - continue; - } + /* Skip segments with no time coverage, these cannot be extended */ + if (!SEGMENT_HAS_TIME_COVERAGE (searchseg)) + { + searchseg = searchseg->prev; + continue; + } - /* 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; - } + /* 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; + /* Done searching at the first (i.e. latest) segment starting before the record */ + if (searchseg->starttime < msr->starttime) + { + followseg = searchseg; + break; + } - if (!segbefore) + searchseg = searchseg->prev; + } + } + /* If every segment outside the recent set is provably too old to match, + * the recent set alone determines the result of the search below. */ + else if (!((LMTraceListNode *)mstl)->foreignid && + (msr->starttime - nsperiod - nstimetol) > ((LMTraceIDNode *)id)->nonrecentendbound && + lm_scan_recent ((LMTraceIDNode *)id, msr, endtime, nsperiod, nstimetol, nnstimetol, + sampratehz, sampratetol, autoheal, &segbefore, &segafter, + &followseg)) + { + /* segbefore, segafter and followseg set by lm_scan_recent() */ + } + else + { + searchseg = id->first; + while (searchseg) { - postgap = msr->starttime - searchseg->endtime - nsperiod; + /* Done searching when segment starts beyond the record end plus tolerance */ + if (searchseg->starttime > endtime + nsperiod + nstimetol) + break; - if (postgap <= nstimetol && postgap >= nnstimetol && - IS_SAMPRATE_SIMILAR (sampratehz, searchseg->samprate, sampratetol)) - segbefore = searchseg; - } + /* Skip segments with no time coverage, these cannot be extended */ + if (!SEGMENT_HAS_TIME_COVERAGE (searchseg)) + { + searchseg = searchseg->next; + continue; + } - if (!segafter) - { - pregap = searchseg->starttime - endtime - nsperiod; + /* 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 (pregap <= nstimetol && pregap >= nnstimetol && - IS_SAMPRATE_SIMILAR (sampratehz, searchseg->samprate, sampratetol)) - segafter = searchseg; - } + if (msr->starttime > searchseg->starttime) + followseg = searchseg; - /* Done searching if both before and after segments are found */ - if (segbefore && segafter) - break; - /* Done searching if not autohealing and one match found */ - else if (!autoheal && (segbefore || segafter)) - break; + if (!segbefore) + { + postgap = msr->starttime - searchseg->endtime - nsperiod; - searchseg = searchseg->next; - } /* Done looping through segments */ + if (postgap <= nstimetol && postgap >= nnstimetol && + IS_SAMPRATE_SIMILAR (sampratehz, searchseg->samprate, sampratetol)) + segbefore = searchseg; + } + + if (!segafter) + { + pregap = searchseg->starttime - endtime - nsperiod; + + if (pregap <= nstimetol && pregap >= nnstimetol && + IS_SAMPRATE_SIMILAR (sampratehz, searchseg->samprate, sampratetol)) + segafter = searchseg; + } + + /* Done searching if both before and after segments are found */ + if (segbefore && segafter) + break; + /* Done searching if not autohealing and one match found */ + else if (!autoheal && (segbefore || segafter)) + break; + + searchseg = searchseg->next; + } /* Done looping through segments */ + } /* Add MS3Record coverage to end of segment before */ if (segbefore) @@ -573,8 +940,11 @@ } /* Add MS3RecordPtr if requested */ - if (pprecptr && !(*pprecptr = lm_add_recordptr (segbefore, msr, endtime, 1))) + if (pprecptr && !(*pprecptr = lm_add_recordptr (segbefore, msr, endtime, 1, flags))) { + /* segbefore's end time was already extended above; keep the end-time bound valid */ + if (!((LMTraceListNode *)mstl)->foreignid) + lm_endbound_fold ((LMTraceIDNode *)id, segbefore->endtime); return NULL; } @@ -584,6 +954,8 @@ /* Add segafter coverage to segbefore */ if (!lm_addsegtoseg (segbefore, segafter)) { + if (!((LMTraceListNode *)mstl)->foreignid) + lm_endbound_fold ((LMTraceIDNode *)id, segbefore->endtime); return NULL; } @@ -597,6 +969,10 @@ if (segafter->next) segafter->next->prev = segafter->prev; + /* Drop segafter from the recent set before it is freed */ + if (!((LMTraceListNode *)mstl)->foreignid) + lm_recentseg_remove ((LMTraceIDNode *)id, segafter); + /* Free all memory associated with the segment after that has been merged */ lm_free_segment_memory (segafter, 1); @@ -614,7 +990,7 @@ } /* Add MS3RecordPtr if requested */ - if (pprecptr && !(*pprecptr = lm_add_recordptr (segafter, msr, endtime, 2))) + if (pprecptr && !(*pprecptr = lm_add_recordptr (segafter, msr, endtime, 2, flags))) { return NULL; } @@ -631,7 +1007,7 @@ } /* Add MS3RecordPtr if requested */ - if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 0))) + if (pprecptr && !(*pprecptr = lm_add_recordptr (seg, msr, endtime, 0, flags))) { /* seg is not yet linked into the segment list, free it directly */ lm_free_segment_memory (seg, 0); @@ -728,6 +1104,13 @@ id->last = segbefore; } + /* Track the most-recently-active segments to bound future searches */ + if (seg && !((LMTraceListNode *)mstl)->foreignid) + { + lm_recentseg_touch ((LMTraceIDNode *)id, seg); + lm_recentseg_touch ((LMTraceIDNode *)id, id->last); + } + /* Store update time at seg.prvtptr, allocate if needed */ if (seg && flags & MSF_PPUPDATETIME) { @@ -793,6 +1176,7 @@ * @parblock * - @c ::MSF_PPUPDATETIME : Store update time (as nstime_t) at ::MS3TraceSeg.prvtptr * - @c ::MSF_SPLITISVERSION : Use @p splitversion as the version, otherwise use msr->pubversion + * - @c ::MSF_RECORDLIST_NOEXTRAS : Do not copy extra headers into record list entries * @endparblock * @param[in] tolerance Tolerance function pointers as ::MS3Tolerance * @@ -847,6 +1231,7 @@ * If the ::MSF_RECORDLIST flag is set in @p flags, a ::MS3RecordList * will be built for each ::MS3TraceSeg. The ::MS3RecordPtr entries * contain the location of the data record, bit flags, extra headers, etc. + * Extra headers are omitted if ::MSF_RECORDLIST_NOEXTRAS is also set. * * @param[in] ppmstl Pointer-to-point to destination MS3TraceList * @param[in] buffer Source buffer to read miniSEED records from @@ -855,6 +1240,7 @@ * @param[in] flags Flags to control parsing and optional functionality: * @parblock * - @c ::MSF_RECORDLIST : Build a ::MS3RecordList for each ::MS3TraceSeg + * - @c ::MSF_RECORDLIST_NOEXTRAS : Do not copy extra headers into record list entries * - Flags supported by msr3_parse() * - Flags supported by mstl3_addmsr() * @endparblock @@ -890,6 +1276,7 @@ * If the ::MSF_RECORDLIST flag is set in @p flags, a ::MS3RecordList * will be built for each ::MS3TraceSeg. The ::MS3RecordPtr entries * contain the location of the data record, bit flags, extra headers, etc. + * Extra headers are omitted if ::MSF_RECORDLIST_NOEXTRAS is also set. * * If @p selections is not NULL, the ::MS3Selections will be used to * limit what is returned to the caller. Any data not matching the @@ -902,6 +1289,7 @@ * @param[in] flags Flags to control parsing and optional functionality: * @parblock * - @c ::MSF_RECORDLIST : Build a ::MS3RecordList for each ::MS3TraceSeg + * - @c ::MSF_RECORDLIST_NOEXTRAS : Do not copy extra headers into record list entries * - Flags supported by msr3_parse() * - Flags supported by mstl3_addmsr() * @endparblock @@ -1356,7 +1744,8 @@ * @see mstl3_addmsr() ***************************************************************************/ static MS3RecordPtr * -lm_add_recordptr (MS3TraceSeg *seg, const MS3Record *msr, nstime_t endtime, int8_t whence) +lm_add_recordptr (MS3TraceSeg *seg, const MS3Record *msr, nstime_t endtime, int8_t whence, + uint32_t flags) { MS3RecordPtr *recordptr = NULL; @@ -1381,7 +1770,7 @@ } memset (recordptr, 0, sizeof (MS3RecordPtr)); - recordptr->msr = msr3_duplicate (msr, 0); + recordptr->msr = msr3_duplicate_extra (msr, 0, (flags & MSF_RECORDLIST_NOEXTRAS) ? 0 : 1); recordptr->endtime = endtime; if (recordptr->msr == NULL) @@ -1402,6 +1791,7 @@ if (seg->recordlist == NULL) { ms_log (2, "Cannot allocate memory\n"); + msr3_free (&recordptr->msr); libmseed_memory.free (recordptr); return NULL; } @@ -1615,7 +2005,17 @@ /* Convert to 64-bit doubles */ else if (type == 'd') { - if (!(ddata = (double *)libmseed_memory.malloc ((size_t)(seg->numsamples * sizeof (double))))) + size_t datasize; + + if (seg->numsamples < 0 || (uint64_t)seg->numsamples > SIZE_MAX / sizeof (double)) + { + ms_log (2, "Data buffer size overflow for %" PRId64 " samples\n", seg->numsamples); + return -1; + } + + datasize = (size_t)seg->numsamples * sizeof (double); + + if (!(ddata = (double *)libmseed_memory.malloc (datasize))) { ms_log (2, "Cannot allocate buffer for sample conversion to doubles\n"); return -1; @@ -1637,7 +2037,7 @@ } seg->datasamples = ddata; - seg->datasize = seg->numsamples * sizeof (double); + seg->datasize = datasize; seg->sampletype = 'd'; } /* Done converting to 64-bit doubles */ @@ -3422,6 +3822,10 @@ /* Decrement segment count */ id->numsegments -= 1; + /* Drop the segment from the recent set before it is freed */ + if (!((LMTraceListNode *)mstl)->foreignid) + lm_recentseg_remove ((LMTraceIDNode *)id, seg); + /* Free all memory associated with the segment */ lm_free_segment_memory (seg, freeprvtptr); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libmseed-3.5.1/unpack.c new/libmseed-3.5.3/unpack.c --- old/libmseed-3.5.1/unpack.c 2026-07-25 22:25:49.000000000 +0200 +++ new/libmseed-3.5.3/unpack.c 2026-07-30 18:15:35.000000000 +0200 @@ -456,6 +456,14 @@ while ((blkt_offset != 0) && ((blkt_offset + 4) <= reclen) && (blkt_offset < MAXRECLEN)) { + /* Reject an offset within the fixed section of the header */ + if (blkt_offset < MS2FSDH_LENGTH) + { + ms_log (2, "%s: Blockette offset (%d) is within the fixed header, impossible\n", msr->sid, + blkt_offset); + goto error_return; + } + /* 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); @@ -472,7 +480,7 @@ { ms_log (2, "%s: Blockette 2000 length field extends beyond record size, truncated?\n", msr->sid); - break; + goto error_return; } /* Get blockette length */ @@ -481,14 +489,27 @@ if (blkt_length == 0) { ms_log (2, "%s: Unknown blockette length for type %d\n", msr->sid, blkt_type); - break; + + /* A terminated blockette chain with unknown blockette stops here */ + if (next_blkt == 0) + break; + + /* Skip via next offset only when it advances and stays in-bounds */ + if (next_blkt > blkt_offset && next_blkt >= MS2FSDH_LENGTH && next_blkt <= reclen) + { + blkt_offset = next_blkt; + blkt_count++; + continue; + } + + goto error_return; } /* 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; + goto error_return; } blkt_end = blkt_offset + blkt_length; @@ -1287,7 +1308,7 @@ return MS_GENERROR; } - /* Fallback encoding for when encoding is unknown */ + /* Fallback encoding for when encoding is unknown (legacy bare SEED data records) */ if (msr->encoding < 0) { if (verbose > 2) @@ -1710,6 +1731,9 @@ case 400: return "Beam"; break; + case 405: + return "Beam Delay"; + break; case 500: return "Timing"; break; @@ -1768,6 +1792,9 @@ case 400: /* Beam */ blktlen = 16; break; + case 405: /* Beam Delay */ + blktlen = 6; + break; case 500: /* Timing */ blktlen = 200; break;
