/*************************************************************************** * Routines for dealing with libmseed extra headers stored as JSON. * * 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 "libmseed.h" #include "extraheaders.h" /* Private allocation wrappers for yyjson's allocator definition */ void *_priv_malloc(void *ctx, size_t size) { UNUSED(ctx); return libmseed_memory.malloc(size); } void *_priv_realloc(void *ctx, void *ptr, size_t oldsize, size_t size) { UNUSED(ctx); UNUSED(oldsize); return libmseed_memory.realloc(ptr, size); } void _priv_free(void *ctx, void *ptr) { UNUSED(ctx); libmseed_memory.free(ptr); } /*************************************************************************** * Internal routine to parse JSON strings into internal state storage. * * The state container is allocated if needed and if JSON is supplied, * it is parsed into the immutable form. * * @param[in] jsonstring JSON string to parse * @param[in] length Length of JSON string * @param[in] parsed Internal parsed state * * @returns A LM_PARSED_JSON* on success or NULL on error * * \sa mseh_free_parsestate() ***************************************************************************/ static LM_PARSED_JSON* parse_json (char *jsonstring, size_t length, LM_PARSED_JSON *parsed) { yyjson_read_flag flg = YYJSON_READ_NOFLAG; yyjson_read_err err; yyjson_alc alc = {_priv_malloc, _priv_realloc, _priv_free, NULL}; /* Allocate parsed state if needed */ if (!parsed) { if ((parsed = libmseed_memory.malloc (sizeof (LM_PARSED_JSON))) == NULL) { ms_log (2, "%s() Cannot allocate memory for internal JSON parsing state\n", __func__); return NULL; } else { parsed->doc = NULL; parsed->mut_doc = NULL; } } /* Nothing to parse */ if (jsonstring == NULL || length == 0) { return parsed; } /* Free existing immutable doc */ if (parsed->doc) { yyjson_doc_free (parsed->doc); parsed->doc = NULL; } /* Free existing mutable document */ if (parsed->mut_doc != NULL) { yyjson_mut_doc_free (parsed->mut_doc); parsed->mut_doc = NULL; } /* Parse JSON into immutable form */ if ((parsed->doc = yyjson_read_opts (jsonstring, length, flg, &alc, &err)) == NULL) { ms_log (2, "%s() Cannot parse extra header JSON: %s\n", __func__, (err.msg) ? err.msg : "Unknown error"); return NULL; } return parsed; } /**********************************************************************/ /** * @brief Search for and return an extra header value. * * The extra header value is specified as a JSON Pointer (RFC 6901), e.g. * \c '/objectA/objectB/header'. * * This routine can get used to test for the existence of a value * without returning the value by setting \a value to NULL. * * If the target item is found (and \a value parameter is set) the * value will be copied into the memory specified by \c value. The * \a type value specifies the data type expected. * * If a \a parsestate pointer is supplied, the parsed (deserialized) JSON * data are stored here. This value may be used in subsequent calls to * avoid re-parsing the JSON. The data must be freed with * mseh_free_parsestate() when done reading the JSON. If this value * is NULL the parse state will be created and destroyed on each call. * * @param[in] msr Parsed miniSEED record to search * @param[in] ptr Header value desired, as JSON Pointer * @param[out] value Buffer for value, of type \c type * @param[in] type Type of value expected, one of: * @parblock * - \c 'n' - \a value is type \a double * - \c 'i' - \a value is type \a int64_t * - \c 's' - \a value is type \a char* (maximum length is: \c maxlength - 1) * - \c 'b' - \a value of type \a int (boolean value of 0 or 1) * @endparblock * @param[in] maxlength Maximum length of string value * @param[in] parsestate Parsed state for multiple operations, can be NULL * * @retval 0 on success * @retval 1 when the value was not found * @retval 2 when the value is of a different type * @returns A (negative) libmseed error code on error * * \ref MessageOnError - this function logs a message on error * * \sa mseh_free_parsestate() ***************************************************************************/ int mseh_get_ptr_r (const MS3Record *msr, const char *ptr, void *value, char type, size_t maxlength, LM_PARSED_JSON **parsestate) { LM_PARSED_JSON *parsed = (parsestate) ? *parsestate : NULL; yyjson_alc alc = {_priv_malloc, _priv_realloc, _priv_free, NULL}; yyjson_val *extravalue = NULL; const char *stringvalue = NULL; int retval = 0; if (!msr || !ptr) { ms_log (2, "%s() Required argument not defined: 'msr' or 'ptr'\n", __func__); return MS_GENERROR; } /* Nothing can be found in no headers */ if (!msr->extralength) { return 1; } if (!msr->extra) { ms_log (2, "%s() Expected extra headers (msr->extra) are not present\n", __func__); return MS_GENERROR; } /* Detect invalid JSON Pointer, i.e. with no root '/' designation */ if (ptr[0] != '/') { ms_log (2, "%s() Unsupported ptr notation: %s\n", __func__, ptr); return MS_GENERROR; } /* Parse JSON extra headers if not available in state */ if (parsed == NULL) { /* Parse to immutable state */ parsed = parse_json(msr->extra, msr->extralength, parsed); if (parsed == NULL) { return MS_GENERROR; } /* Set supplied state pointer */ if (parsestate != NULL) { *parsestate = parsed; } } /* Create immutable document from mutable if needed */ if (parsed->mut_doc != NULL && parsed->doc == NULL) { if ((parsed->doc = yyjson_mut_doc_imut_copy (parsed->mut_doc, &alc)) == NULL) { ms_log (2, "%s() Cannot create immutable document from mutable\n", __func__); if (parsestate == NULL) { mseh_free_parsestate (&parsed); } return MS_GENERROR; } } /* Get target value */ extravalue = yyjson_doc_ptr_get(parsed->doc, ptr); if (extravalue == NULL) { retval = 1; } else if (type == 'n' && yyjson_is_num (extravalue)) { if (value) *((double *)value) = unsafe_yyjson_get_num (extravalue); } else if (type == 'i' && yyjson_is_int (extravalue)) { if (value) *((int64_t *)value) = unsafe_yyjson_get_int (extravalue); } else if (type == 's' && yyjson_is_str (extravalue)) { if (value) { stringvalue = unsafe_yyjson_get_str (extravalue); strncpy ((char *)value, stringvalue, maxlength - 1); ((char *)value)[maxlength - 1] = '\0'; } } else if (type == 'b' && yyjson_is_bool(extravalue)) { if (value) *((int *)value) = (unsafe_yyjson_get_bool (extravalue)) ? 1 : 0; } /* Return wrong type indicator if a value was requested */ else if (value) { retval = 2; } /* Free parse state if not being retained */ if (parsestate == NULL) { mseh_free_parsestate (&parsed); } return retval; } /* End of mseh_get_ptr_r() */ /**********************************************************************/ /** * @brief Set the value of extra header values * * The extra header value is specified as a JSON Pointer (RFC 6901), e.g. * \c '/objectA/objectB/header'. * * For most value types, if the \a ptr or final header values do not exist * they will be created. If the header value exists it will be replaced. * When the value type is 'M', for Merge Patch (RFC 7386), the location * indicated by \a ptr must exist. * * The \a type value specifies the data type expected for \c value. * * If a \a parsestate pointer is supplied, the parsed (deserialized) JSON * data are stored here. This value may be used in subsequent calls to * avoid re-parsing the JSON. When done setting headers using this * functionality the following \a must be done: * 1. call mseh_serialize() to create the JSON headers before writing the record * 2. free the \a parsestate data with mseh_free_parsestate() * If this value is NULL the parse state will be created and destroyed * on each call. * * @param[in] msr Parsed miniSEED record to modify * @param[in] ptr Header value to set as JSON Pointer, or JSON Merge Patch * @param[in] value Buffer for value, of type \c type * @param[in] type Type of value expected, one of: * @parblock * - \c 'n' - \a value is type \a double * - \c 'i' - \a value is type \a int64_t * - \c 's' - \a value is type \a char* * - \c 'b' - \a value is type \a int (boolean value of 0 or 1) * - \c 'M' - \a value is type \a char* and a Merge Patch to apply at \a ptr * - \c 'V' - \a value is type \a yyjson_mut_val* to _set/replace_ (internal use) * - \c 'A' - \a value is type \a yyjson_mut_val* to _append to array_ (internal use) * @endparblock * @param[in] parsestate Parsed state for multiple operations, can be NULL * * @retval 0 on success, otherwise a (negative) libmseed error code. * * \ref MessageOnError - this function logs a message on error * * \sa mseh_free_parsestate() * \sa mseh_serialize() ***************************************************************************/ int mseh_set_ptr_r (MS3Record *msr, const char *ptr, void *value, char type, LM_PARSED_JSON **parsestate) { LM_PARSED_JSON *parsed = (parsestate) ? *parsestate : NULL; yyjson_alc alc = {_priv_malloc, _priv_realloc, _priv_free, NULL}; yyjson_doc *patch_idoc = NULL; yyjson_mut_doc *patch_doc = NULL; yyjson_mut_val *merged_val = NULL; yyjson_mut_val *target_val = NULL; yyjson_mut_val *array_val = NULL; bool rv = false; if (!msr || !ptr || !value) { ms_log (2, "%s() Required argument not defined: 'msr', 'ptr', or 'value'\n", __func__); return MS_GENERROR; } /* Detect invalid JSON Pointer, i.e. with no root '/' designation */ if (ptr[0] != '/' && type != 'M') { ms_log (2, "%s() Unsupported JSON Pointer notation: %s\n", __func__, ptr); return MS_GENERROR; } /* Parse JSON extra headers if not available in state */ if (parsed == NULL) { /* Allocate state container and parse to immutable form */ parsed = parse_json (msr->extra, msr->extralength, parsed); if (parsed == NULL) { return MS_GENERROR; } /* Set supplied state pointer */ if (parsestate != NULL) { *parsestate = parsed; } } /* Generate mutable document from immutable form if needed */ if (parsed->doc != NULL && parsed->mut_doc == NULL) { if ((parsed->mut_doc = yyjson_doc_mut_copy (parsed->doc, &alc)) == NULL) { ms_log (2, "%s() Cannot create mutable JSON document\n", __func__); if (parsestate == NULL) { mseh_free_parsestate (&parsed); } return MS_GENERROR; } } /* Initialize empty mutable document if needed */ if (parsed->mut_doc == NULL) { if ((parsed->mut_doc = yyjson_mut_doc_new (&alc)) == NULL) { ms_log (2, "%s() Cannot initialize mutable JSON document\n", __func__); if (parsestate == NULL) { mseh_free_parsestate (&parsed); } return MS_GENERROR; } } /* Set (or replace) header value at ptr */ switch (type) { case 'n': rv = yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr, yyjson_mut_real (parsed->mut_doc, *((double *)value))); break; case 'i': rv = yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr, yyjson_mut_sint (parsed->mut_doc, *((int64_t *)value))); break; case 's': rv = yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr, yyjson_mut_strcpy (parsed->mut_doc, (const char *)value)); break; case 'b': rv = yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr, yyjson_mut_bool (parsed->mut_doc, *((int *)value) ? true : false)); break; case 'M': /* Parse supplied patch */ if ((patch_idoc = yyjson_read_opts ((char *)value, strlen ((char *)value), 0, &alc, NULL))) { if ((patch_doc = yyjson_doc_mut_copy (patch_idoc, &alc))) { /* Get patch target value */ if ((target_val = yyjson_mut_doc_ptr_get (parsed->mut_doc, ptr))) { /* Generate merged value */ if ((merged_val = yyjson_mut_merge_patch (parsed->mut_doc, target_val, yyjson_mut_doc_get_root (patch_doc)))) { /* Replace value at pointer with merged value */ rv = yyjson_mut_doc_ptr_replace (parsed->mut_doc, ptr, merged_val); } } } } else { ms_log (2, "%s() Cannot parse JSON Merge Patch: %s'\n", __func__, (char *)value); } yyjson_doc_free(patch_idoc); yyjson_mut_doc_free (patch_doc); break; case 'V': rv = yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr, yyjson_mut_val_mut_copy (parsed->mut_doc, (yyjson_mut_val *)value)); break; case 'A': /* Search for existing array, create if necessary */ if ((array_val = yyjson_mut_doc_ptr_get (parsed->mut_doc, ptr)) == NULL) { if ((array_val = yyjson_mut_arr (parsed->mut_doc))) { if (yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr, array_val) == false) { array_val = NULL; } } } /* Append supplied value, will return false if array_val == NULL */ rv = yyjson_mut_arr_append (array_val, yyjson_mut_val_mut_copy (parsed->mut_doc, (yyjson_mut_val *)value)); break; default: ms_log (2, "%s() Unrecognized value type '%d'\n", __func__, type); rv = false; } /* Serialized extra headers and free parse state if not being retained */ if (parsestate == NULL) { mseh_serialize (msr, &parsed); mseh_free_parsestate (&parsed); } /* If changes were applied, the immutable form of the document is now invalid */ else if (rv ==true && parsed->doc != NULL) { yyjson_doc_free (parsed->doc); parsed->doc = NULL; } return (rv == true) ? 0 : MS_GENERROR; } /* End of mseh_set_ptr_r() */ /**********************************************************************/ /** * @brief Add event detection to the extra headers of the given record. * * If \a ptr is NULL, the default is \c '/FDSN/Event/Detection'. * * @param[in] msr Parsed miniSEED record to query * @param[in] ptr Header value desired, specified in dot notation * @param[in] eventdetection Structure with event detection values * @param[in] parsestate Parsed state for multiple operations, can be NULL * * @returns 0 on success, otherwise a (negative) libmseed error code. * * \ref MessageOnError - this function logs a message on error ***************************************************************************/ int mseh_add_event_detection_r (MS3Record *msr, const char *ptr, MSEHEventDetection *eventdetection, LM_PARSED_JSON **parsestate) { yyjson_mut_val entry; yyjson_mut_val type_key, type; yyjson_mut_val sigamp_key, sigamp; yyjson_mut_val sigper_key, sigper; yyjson_mut_val bgest_key, bgest; yyjson_mut_val wave_key, wave; yyjson_mut_val units_key, units; yyjson_mut_val onset_key, onset; yyjson_mut_val medsnr_key, medsnr; yyjson_mut_val medsnr_value[6]; yyjson_mut_val medlookback_key, medlookback; yyjson_mut_val medpickalg_key, medpickalg; yyjson_mut_val detector_key, detector; char timestring[40]; int idx; if (!msr || !eventdetection) { ms_log (2, "%s() Required argument not defined: 'msr' or 'eventdetection'\n", __func__); return MS_GENERROR; } yyjson_mut_set_obj (&entry); /* Add elements to new object */ if (eventdetection->type[0]) { yyjson_mut_set_str (&type_key, "Type"); yyjson_mut_set_str (&type, eventdetection->type); yyjson_mut_obj_add (&entry, &type_key, &type); } if (eventdetection->signalamplitude != 0.0) { yyjson_mut_set_str (&sigamp_key, "SignalAmplitude"); yyjson_mut_set_real (&sigamp, eventdetection->signalamplitude); yyjson_mut_obj_add (&entry, &sigamp_key, &sigamp); } if (eventdetection->signalperiod != 0.0) { yyjson_mut_set_str (&sigper_key, "SignalPeriod"); yyjson_mut_set_real (&sigper, eventdetection->signalperiod); yyjson_mut_obj_add (&entry, &sigper_key, &sigper); } if (eventdetection->backgroundestimate != 0.0) { yyjson_mut_set_str (&bgest_key, "BackgroundEstimate"); yyjson_mut_set_real (&bgest, eventdetection->backgroundestimate); yyjson_mut_obj_add (&entry, &bgest_key, &bgest); } if (eventdetection->wave[0]) { yyjson_mut_set_str (&wave_key, "Wave"); yyjson_mut_set_str (&wave, eventdetection->wave); yyjson_mut_obj_add (&entry, &wave_key, &wave); } if (eventdetection->units[0]) { yyjson_mut_set_str (&units_key, "Units"); yyjson_mut_set_str (&units, eventdetection->units); yyjson_mut_obj_add (&entry, &units_key, &units); } if (eventdetection->onsettime != NSTERROR && eventdetection->onsettime != NSTUNSET) { if (ms_nstime2timestr (eventdetection->onsettime, timestring, ISOMONTHDAY_Z, NANO_MICRO_NONE)) { yyjson_mut_set_str (&onset_key, "OnsetTime"); yyjson_mut_set_str (&onset, timestring); yyjson_mut_obj_add (&entry, &onset_key, &onset); } else { ms_log (2, "%s() Cannot create time string for %"PRId64"\n", __func__, eventdetection->onsettime); return MS_GENERROR; } } if (memcmp (eventdetection->medsnr, (uint8_t[]){0, 0, 0, 0, 0, 0}, 6)) { yyjson_mut_set_str (&medsnr_key, "MEDSNR"); yyjson_mut_set_arr (&medsnr); yyjson_mut_obj_add (&entry, &medsnr_key, &medsnr); /* Array containing the 6 SNR values */ for (idx = 0; idx < 6; idx++) { yyjson_mut_set_uint (&(medsnr_value[idx]), eventdetection->medsnr[idx]); yyjson_mut_arr_append (&medsnr, &(medsnr_value[idx])); } } if (eventdetection->medlookback >= 0) { yyjson_mut_set_str (&medlookback_key, "MEDLookback"); yyjson_mut_set_sint (&medlookback, eventdetection->medlookback); yyjson_mut_obj_add (&entry, &medlookback_key, &medlookback); } if (eventdetection->medpickalgorithm >= 0) { yyjson_mut_set_str (&medpickalg_key, "MEDPickAlgorithm"); yyjson_mut_set_sint (&medpickalg, eventdetection->medpickalgorithm); yyjson_mut_obj_add (&entry, &medpickalg_key, &medpickalg); } if (eventdetection->detector[0]) { yyjson_mut_set_str (&detector_key, "Detector"); yyjson_mut_set_str (&detector, eventdetection->detector); yyjson_mut_obj_add (&entry, &detector_key, &detector); } /* Add new object to array, created 'value' will be free'd on successful return */ if (mseh_set_ptr_r (msr, (ptr) ? ptr : "/FDSN/Event/Detection", &entry, 'A', parsestate)) { return MS_GENERROR; } return 0; } /* End of mseh_add_event_detection_r() */ /**********************************************************************/ /** * @brief Add calibration to the extra headers of the given record. * * If \a ptr is NULL, the default is \c '/FDSN/Calibration/Sequence'. * * @param[in] msr Parsed miniSEED record to query * @param[in] ptr Header value desired, specified in dot notation * @param[in] calibration Structure with calibration values * @param[in] parsestate Parsed state for multiple operations, can be NULL * * @returns 0 on success, otherwise a (negative) libmseed error code. * * \ref MessageOnError - this function logs a message on error ***************************************************************************/ int mseh_add_calibration_r (MS3Record *msr, const char *ptr, MSEHCalibration *calibration, LM_PARSED_JSON **parsestate) { yyjson_mut_val entry; yyjson_mut_val type_key, type; yyjson_mut_val begintime_key, begintime; yyjson_mut_val endtime_key, endtime; yyjson_mut_val steps_key, steps; yyjson_mut_val firstpp_key, firstpp; yyjson_mut_val altsign_key, altsign; yyjson_mut_val trigger_key, trigger; yyjson_mut_val continued_key, continued; yyjson_mut_val amplitude_key, amplitude; yyjson_mut_val inputunits_key, inputunits; yyjson_mut_val amprange_key, amprange; yyjson_mut_val duration_key, duration; yyjson_mut_val sineperiod_key, sineperiod; yyjson_mut_val stepbetween_key, stepbetween; yyjson_mut_val inputchannel_key, inputchannel; yyjson_mut_val refamp_key, refamp; yyjson_mut_val coupling_key, coupling; yyjson_mut_val rolloff_key, rolloff; yyjson_mut_val noise_key, noise; char beginstring[40]; char endstring[40]; if (!msr || !calibration) { ms_log (2, "%s() Required argument not defined: 'msr' or 'calibration'\n", __func__); return MS_GENERROR; } yyjson_mut_set_obj (&entry); /* Add elements to new object */ if (calibration->type[0]) { yyjson_mut_set_str (&type_key, "Type"); yyjson_mut_set_str (&type, calibration->type); yyjson_mut_obj_add (&entry, &type_key, &type); } if (calibration->begintime != NSTERROR && calibration->begintime != NSTUNSET) { if (ms_nstime2timestr (calibration->begintime, beginstring, ISOMONTHDAY_Z, NANO_MICRO_NONE)) { yyjson_mut_set_str (&begintime_key, "BeginTime"); yyjson_mut_set_str (&begintime, beginstring); yyjson_mut_obj_add (&entry, &begintime_key, &begintime); } else { ms_log (2, "%s() Cannot create time string for %" PRId64 "\n", __func__, calibration->begintime); return MS_GENERROR; } } if (calibration->endtime != NSTERROR && calibration->endtime != NSTUNSET) { if (ms_nstime2timestr (calibration->endtime, endstring, ISOMONTHDAY_Z, NANO_MICRO_NONE)) { yyjson_mut_set_str (&endtime_key, "EndTime"); yyjson_mut_set_str (&endtime, endstring); yyjson_mut_obj_add (&entry, &endtime_key, &endtime); } else { ms_log (2, "%s() Cannot create time string for %" PRId64 "\n", __func__, calibration->endtime); return MS_GENERROR; } } if (calibration->steps >= 0) { yyjson_mut_set_str (&steps_key, "Steps"); yyjson_mut_set_sint (&steps, calibration->steps); yyjson_mut_obj_add (&entry, &steps_key, &steps); } if (calibration->firstpulsepositive >= 0) { yyjson_mut_set_str (&firstpp_key, "StepFirstPulsePositive"); yyjson_mut_set_bool (&firstpp, calibration->firstpulsepositive); yyjson_mut_obj_add (&entry, &firstpp_key, &firstpp); } if (calibration->alternatesign >= 0) { yyjson_mut_set_str (&altsign_key, "StepAlternateSign"); yyjson_mut_set_bool (&altsign, calibration->alternatesign); yyjson_mut_obj_add (&entry, &altsign_key, &altsign); } if (calibration->trigger[0]) { yyjson_mut_set_str (&trigger_key, "Trigger"); yyjson_mut_set_str (&trigger, calibration->trigger); yyjson_mut_obj_add (&entry, &trigger_key, &trigger); } if (calibration->continued >= 0) { yyjson_mut_set_str (&continued_key, "Continued"); yyjson_mut_set_bool (&continued, calibration->continued); yyjson_mut_obj_add (&entry, &continued_key, &continued); } if (calibration->amplitude != 0.0) { yyjson_mut_set_str (&litude_key, "Amplitude"); yyjson_mut_set_real (&litude, calibration->amplitude); yyjson_mut_obj_add (&entry, &litude_key, &litude); } if (calibration->inputunits[0]) { yyjson_mut_set_str (&inputunits_key, "InputUnits"); yyjson_mut_set_str (&inputunits, calibration->inputunits); yyjson_mut_obj_add (&entry, &inputunits_key, &inputunits); } if (calibration->amplituderange[0]) { yyjson_mut_set_str (&range_key, "AmplitudeRange"); yyjson_mut_set_str (&range, calibration->amplituderange); yyjson_mut_obj_add (&entry, &range_key, &range); } if (calibration->duration != 0.0) { yyjson_mut_set_str (&duration_key, "Duration"); yyjson_mut_set_real (&duration, calibration->duration); yyjson_mut_obj_add (&entry, &duration_key, &duration); } if (calibration->sineperiod != 0.0) { yyjson_mut_set_str (&sineperiod_key, "SinePeriod"); yyjson_mut_set_real (&sineperiod, calibration->sineperiod); yyjson_mut_obj_add (&entry, &sineperiod_key, &sineperiod); } if (calibration->stepbetween != 0.0) { yyjson_mut_set_str (&stepbetween_key, "StepBetween"); yyjson_mut_set_real (&stepbetween, calibration->stepbetween); yyjson_mut_obj_add (&entry, &stepbetween_key, &stepbetween); } if (calibration->inputchannel[0]) { yyjson_mut_set_str (&inputchannel_key, "InputChannel"); yyjson_mut_set_str (&inputchannel, calibration->inputchannel); yyjson_mut_obj_add (&entry, &inputchannel_key, &inputchannel); } if (calibration->refamplitude != 0.0) { yyjson_mut_set_str (&refamp_key, "ReferenceAmplitude"); yyjson_mut_set_real (&refamp, calibration->refamplitude); yyjson_mut_obj_add (&entry, &refamp_key, &refamp);; } if (calibration->coupling[0]) { yyjson_mut_set_str (&coupling_key, "Coupling"); yyjson_mut_set_str (&coupling, calibration->coupling); yyjson_mut_obj_add (&entry, &coupling_key, &coupling); } if (calibration->rolloff[0]) { yyjson_mut_set_str (&rolloff_key, "Rolloff"); yyjson_mut_set_str (&rolloff, calibration->rolloff); yyjson_mut_obj_add (&entry, &rolloff_key, &rolloff); } if (calibration->noise[0]) { yyjson_mut_set_str (&noise_key, "Noise"); yyjson_mut_set_str (&noise, calibration->noise); yyjson_mut_obj_add (&entry, &noise_key, &noise); } /* Add new object to array, created 'value' will be free'd on successful return */ if (mseh_set_ptr_r (msr, (ptr) ? ptr : "/FDSN/Calibration/Sequence", &entry, 'A', parsestate)) { return MS_GENERROR; } return 0; } /* End of mseh_add_calibration_r() */ /**********************************************************************/ /** * @brief Add timing exception to the extra headers of the given record. * * If \a ptr is NULL, the default is \c '/FDSN/Time/Exception'. * * @param[in] msr Parsed miniSEED record to query * @param[in] ptr Header value desired, specified in dot notation * @param[in] exception Structure with timing exception values * @param[in] parsestate Parsed state for multiple operations, can be NULL * * @returns 0 on success, otherwise a (negative) libmseed error code. * * \ref MessageOnError - this function logs a message on error ***************************************************************************/ int mseh_add_timing_exception_r (MS3Record *msr, const char *ptr, MSEHTimingException *exception, LM_PARSED_JSON **parsestate) { yyjson_mut_val entry; yyjson_mut_val etime_key, etime; yyjson_mut_val vcocorr_key, vcocorr; yyjson_mut_val recqual_key, recqual; yyjson_mut_val count_key, count; yyjson_mut_val type_key, type; yyjson_mut_val clockstatus_key, clockstatus; char timestring[40]; if (!msr || !exception) { ms_log (2, "%s() Required argument not defined: 'msr' or 'exception'\n", __func__); return MS_GENERROR; } yyjson_mut_set_obj (&entry); /* Add elements to new object */ if (exception->time != NSTERROR && exception->time != NSTUNSET) { if (ms_nstime2timestr (exception->time, timestring, ISOMONTHDAY_Z, NANO_MICRO_NONE)) { yyjson_mut_set_str (&etime_key, "Time"); yyjson_mut_set_str (&etime, timestring); yyjson_mut_obj_add (&entry, &etime_key, &etime); } else { ms_log (2, "%s() Cannot create time string for %" PRId64 "\n", __func__, exception->time); return MS_GENERROR; } } if (exception->vcocorrection >= 0.0) { yyjson_mut_set_str (&vcocorr_key, "VCOCorrection"); yyjson_mut_set_real (&vcocorr, exception->vcocorrection); yyjson_mut_obj_add (&entry, &vcocorr_key, &vcocorr); } if (exception->receptionquality >= 0) { yyjson_mut_set_str (&recqual_key, "ReceptionQuality"); yyjson_mut_set_sint (&recqual, exception->receptionquality); yyjson_mut_obj_add (&entry, &recqual_key, &recqual); } if (exception->count > 0) { yyjson_mut_set_str (&count_key, "Count"); yyjson_mut_set_sint (&count, exception->count); yyjson_mut_obj_add (&entry, &count_key, &count); } if (exception->type[0]) { yyjson_mut_set_str (&type_key, "Type"); yyjson_mut_set_str (&type, exception->type); yyjson_mut_obj_add (&entry, &type_key, &type); } if (exception->clockstatus[0]) { yyjson_mut_set_str (&clockstatus_key, "ClockStatus"); yyjson_mut_set_str (&clockstatus, exception->clockstatus); yyjson_mut_obj_add (&entry, &clockstatus_key, &clockstatus); } /* Add new object to array, created 'value' will be free'd on successful return */ if (mseh_set_ptr_r (msr, (ptr) ? ptr : "/FDSN/Time/Exception", &entry, 'A', parsestate)) { return MS_GENERROR; } return 0; } /* End of mseh_add_timing_exception_r() */ /**********************************************************************/ /** * @brief Add recenter event to the extra headers of the given record. * * If \a ptr is NULL, the default is \c '/FDSN/Recenter/Sequence'. * * @param[in] msr Parsed miniSEED record to query * @param[in] ptr Header value desired, specified in dot notation * @param[in] recenter Structure with recenter values * @param[in] parsestate Parsed state for multiple operations, can be NULL * * @returns 0 on success, otherwise a (negative) libmseed error code. * * \ref MessageOnError - this function logs a message on error ***************************************************************************/ int mseh_add_recenter_r (MS3Record *msr, const char *ptr, MSEHRecenter *recenter, LM_PARSED_JSON **parsestate) { yyjson_mut_val entry; yyjson_mut_val type_key, type; yyjson_mut_val begin_key, begin; yyjson_mut_val end_key, end; yyjson_mut_val trigger_key, trigger; char beginstring[40]; char endstring[40]; if (!msr || !recenter) { ms_log (2, "%s() Required argument not defined: 'msr' or 'recenter'\n", __func__); return MS_GENERROR; } yyjson_mut_set_obj (&entry); /* Add elements to new object */ if (recenter->type[0]) { yyjson_mut_set_str (&type_key, "Type"); yyjson_mut_set_str (&type, recenter->type); yyjson_mut_obj_add (&entry, &type_key, &type); } if (recenter->begintime != NSTERROR && recenter->begintime != NSTUNSET) { if (ms_nstime2timestr (recenter->begintime, beginstring, ISOMONTHDAY_Z, NANO_MICRO_NONE)) { yyjson_mut_set_str (&begin_key, "BeginTime"); yyjson_mut_set_str (&begin, beginstring); yyjson_mut_obj_add (&entry, &begin_key, &begin); } else { ms_log (2, "%s() Cannot create time string for %" PRId64 "\n", __func__, recenter->begintime); return MS_GENERROR; } } if (recenter->endtime != NSTERROR && recenter->endtime != NSTUNSET) { if (ms_nstime2timestr (recenter->endtime, endstring, ISOMONTHDAY_Z, NANO_MICRO_NONE)) { yyjson_mut_set_str (&end_key, "EndTime"); yyjson_mut_set_str (&end, endstring); yyjson_mut_obj_add (&entry, &end_key, &end); } else { ms_log (2, "%s() Cannot create time string for %" PRId64 "\n", __func__, recenter->endtime); return MS_GENERROR; } } if (recenter->trigger[0]) { yyjson_mut_set_str (&trigger_key, "Trigger"); yyjson_mut_set_str (&trigger, recenter->trigger); yyjson_mut_obj_add (&entry, &trigger_key, &trigger); } /* Add new object to array, created 'value' will be free'd on successful return */ if (mseh_set_ptr_r (msr, (ptr) ? ptr : "/FDSN/Recenter/Sequence", &entry, 'A', parsestate)) { return MS_GENERROR; } return 0; } /* End of mseh_add_recenter_r() */ /**********************************************************************/ /** * @brief Generate extra headers string (serialize) from internal state * * Generate the extra headers JSON string from the internal parse state * created by mseh_set_ptr_r(). * * @param[in] msr ::MS3Record to generate extra headers for * @param[in] parsestate Internal parsed state associated with \a msr * * @returns Length of extra headers on success, otherwise a (negative) libmseed error code * * \sa mseh_set_ptr_r() ***************************************************************************/ int mseh_serialize (MS3Record *msr, LM_PARSED_JSON **parsestate) { yyjson_write_flag flg = YYJSON_WRITE_NOFLAG; yyjson_write_err err; yyjson_alc alc = {_priv_malloc, _priv_realloc, _priv_free, NULL}; LM_PARSED_JSON *parsed = NULL; char *serialized = NULL; size_t serialsize = 0; if (!msr || !parsestate) return MS_GENERROR; parsed = *parsestate; if (!parsed || !parsed->mut_doc) return 0; /* Serialize new JSON string */ serialized = yyjson_mut_write_opts (parsed->mut_doc, flg, &alc, &serialsize, &err); if (serialized == NULL) { ms_log (2, "%s() Cannot write extra header JSON: %s\n", __func__, (err.msg) ? err.msg : "Unknown error"); return MS_GENERROR; } if (serialsize > UINT16_MAX) { ms_log (2, "%s() New serialization size exceeds limit of %d bytes: %" PRIu64 "\n", __func__, UINT16_MAX, (uint64_t)serialsize); libmseed_memory.free(serialized); return MS_GENERROR; } /* Set new extra headers, replacing existing headers */ if (msr->extra) libmseed_memory.free (msr->extra); msr->extra = serialized; msr->extralength = (uint16_t)serialsize; return (int)serialsize; } /**********************************************************************/ /** * @brief Free internally parsed (deserialized) JSON data * * Free the memory associated with JSON data parsed by mseh_get_ptr_r() * or mseh_set_ptr_r(), specifically the data at the \a parsestate pointer. * * @param[in] parsestate Internal parsed state associated with \a msr * * \sa mseh_get_ptr_r() * \sa mseh_set_ptr_r() ***************************************************************************/ void mseh_free_parsestate (LM_PARSED_JSON **parsestate) { LM_PARSED_JSON *parsed = NULL; if (!parsestate || !*parsestate) return; parsed = *parsestate; if (parsed->doc) yyjson_doc_free (parsed->doc); if (parsed->mut_doc) yyjson_mut_doc_free (parsed->mut_doc); libmseed_memory.free(parsed); *parsestate = NULL; } /**********************************************************************/ /** * @brief Print the extra header structure for the specified MS3Record. * * Output is printed in a pretty, formatted form for readability and * the anonymous, root object container (the outer {}) is not printed. * * @param[in] msr ::MS3Record with extra headers to pring * @param[in] indent Number of spaces to indent output * * @returns 0 on success and a (negative) libmseed error code on error. ***************************************************************************/ int mseh_print (const MS3Record *msr, int indent) { char *extra; int idx; int instring = 0; if (!msr) return MS_GENERROR; if (!msr->extra || !msr->extralength) return MS_NOERROR; extra = msr->extra; if (extra[0] != '{' || extra[msr->extralength - 1] != '}') { ms_log (1, "%s() Warning, something is wrong, extra headers are not a clean {object}\n", __func__); } /* Print JSON character-by-character, inserting * indentation, spaces and newlines for readability. */ ms_log (0, "%*s", indent, ""); for (idx = 1; idx < (msr->extralength - 1); idx++) { /* Toggle "in string" flag for double quotes */ if (extra[idx] == '"') instring = (instring) ? 0 : 1; if (!instring) { if (extra[idx] == ':') { ms_log (0, ": "); } else if (extra[idx] == ',') { ms_log (0, ",\n%*s", indent, ""); } else if (extra[idx] == '{') { indent += 2; ms_log (0, "{\n%*s", indent, ""); } else if (extra[idx] == '[') { indent += 2; ms_log (0, "[\n%*s", indent, ""); } else if (extra[idx] == '}') { indent -= 2; ms_log (0, "\n%*s}", indent, ""); } else if (extra[idx] == ']') { indent -= 2; ms_log (0, "\n%*s]", indent, ""); } else { ms_log (0, "%c", extra[idx]); } } else { ms_log (0, "%c", extra[idx]); } } ms_log (0, "\n"); return MS_NOERROR; } /* End of mseh_print() */