// Copyright (2019-2023) Paul Scherrer Institute #include "CBORStream2Deserializer.h" #include "tinycbor/src/cbor.h" #include "CborErr.h" #include "CborUtil.h" #include inline std::string GetCBORString(CborValue &value) { if (!cbor_value_is_text_string(&value)) throw JFJochException(JFJochExceptionCategory::CBORError, "String expected"); std::vector s(16384); size_t len = s.size() - 1; cborErr(cbor_value_copy_text_string(&value, s.data(), &len, nullptr)); s[len+1] = 0; cborErr(cbor_value_advance(&value)); return {s.data()}; } inline int64_t GetCBORInt(CborValue &value) { if (!cbor_value_is_integer(&value) ) throw JFJochException(JFJochExceptionCategory::CBORError, "Integer expected"); int64_t tmp; cborErr(cbor_value_get_int64(&value, &tmp)); cborErr(cbor_value_advance(&value)); return tmp; } inline uint64_t GetCBORUInt(CborValue &value) { if (!cbor_value_is_unsigned_integer(&value) ) throw JFJochException(JFJochExceptionCategory::CBORError, "Unsigned integer expected"); uint64_t tmp; cborErr(cbor_value_get_uint64(&value, &tmp)); cborErr(cbor_value_advance(&value)); return tmp; } inline float GetCBORFloat(CborValue &value) { if (cbor_value_is_double(&value)) { double tmp; cborErr(cbor_value_get_double(&value, &tmp)); cborErr(cbor_value_advance(&value)); return static_cast(tmp); } else if (cbor_value_is_float(&value)) { float tmp; cborErr(cbor_value_get_float(&value, &tmp)); cborErr(cbor_value_advance(&value)); return tmp; } else if (cbor_value_is_half_float(&value)) { float tmp; cborErr(cbor_value_get_half_float_as_float(&value, &tmp)); cborErr(cbor_value_advance(&value)); return tmp; } throw JFJochException(JFJochExceptionCategory::CBORError, "Float expected"); } inline bool GetCBORBool(CborValue &value) { if (!cbor_value_is_boolean(&value) ) { throw JFJochException(JFJochExceptionCategory::CBORError, "Bool expected " + std::to_string(cbor_value_get_type(&value))); } bool tmp; cborErr(cbor_value_get_boolean(&value, &tmp)); cborErr(cbor_value_advance(&value)); return tmp; } inline CborTag GetCBORTag(CborValue &value) { if (!cbor_value_is_tag(&value) ) throw JFJochException(JFJochExceptionCategory::CBORError, "Tag expected"); CborTag tmp; cborErr(cbor_value_get_tag(&value, &tmp)); cborErr(cbor_value_advance(&value)); return tmp; } inline std::string GetCBORDateTime(CborValue &value) { auto tag = GetCBORTag(value); if (tag == CborDateTimeStringTag) return GetCBORString(value); else if (tag == CborUnixTime_tTag) { time_t t = GetCBORUInt(value); char buf1[255]; strftime(buf1, sizeof(buf1), "%FT%T", gmtime(&t)); return std::string(buf1) + "Z"; } else throw JFJochException(JFJochExceptionCategory::CBORError, "Time/date tag error"); } inline uint64_t GetCBORArrayLen(CborValue &value) { if (!cbor_value_is_array(&value) ) throw JFJochException(JFJochExceptionCategory::CBORError, "Array expected"); size_t array_len; cborErr(cbor_value_get_array_length(&value, &array_len)); return array_len; } inline uint64_t GetCBORMapLen(CborValue &value) { if (!cbor_value_is_map(&value) ) throw JFJochException(JFJochExceptionCategory::CBORError, "Map expected"); size_t map_len; cborErr(cbor_value_get_map_length(&value, &map_len)); return map_len; } inline std::pair GetRational(CborValue &value) { std::pair ret; if (GetCBORArrayLen(value) != 2) throw JFJochException(JFJochExceptionCategory::CBORError, "Rational number expects array of 2 elements"); CborValue array_value; cborErr(cbor_value_enter_container(&value, &array_value)); ret.first = GetCBORUInt(array_value); ret.second = GetCBORUInt(array_value); cborErr(cbor_value_leave_container(&value, &array_value)); return ret; } inline std::pair GetCBORDimension2DArray(CborValue &value) { std::pair ret; if (GetCBORArrayLen(value) != 2) throw JFJochException(JFJochExceptionCategory::CBORError, "Only 2D arrays allowed"); CborValue array_value; cborErr(cbor_value_enter_container(&value, &array_value)); ret.second = GetCBORUInt(array_value); ret.first = GetCBORUInt(array_value); cborErr(cbor_value_leave_container(&value, &array_value)); return ret; } inline std::pair GetCBORByteString(CborValue &value) { if (!cbor_value_is_byte_string(&value) ) throw JFJochException(JFJochExceptionCategory::CBORError, "Bool expected"); size_t len; cborErr(cbor_value_get_string_length(&value, &len)); // Code below from: https://github.com/dectris/documentation/blob/main/stream_v2/examples/stream2.c const uint8_t *ptr = cbor_value_get_next_byte(&value); uint8_t val = *ptr; if (val < 0x40) throw JFJochException(JFJochExceptionCategory::CBORError, "Something wrong with byte string tag"); val -= 0x40; if (val < 24) ptr += 1; else if (val == 24) ptr += 1 + 1; else if (val == 25) ptr += 1 + 2; else if (val == 26) ptr += 1 + 4; else if (val == 27) ptr += 1 + 8; else throw JFJochException(JFJochExceptionCategory::CBORError, "Something wrong with byte string tag"); cborErr(cbor_value_advance(&value)); return {ptr, len}; } inline ROIRectangle GetROIRectangle(CborValue &value) { ROIRectangle ret{}; CborValue map_value; if (GetCBORMapLen(value) != 4) throw JFJochException(JFJochExceptionCategory::CBORError, "ROI Rectangle must be 4 values"); cborErr(cbor_value_enter_container(&value, &map_value)); while (!cbor_value_at_end(&map_value)) { auto key = GetCBORString(map_value); if (key == "x_min") ret.x_min = GetCBORUInt(map_value); else if (key == "x_max") ret.x_max = GetCBORUInt(map_value); else if (key == "y_min") ret.y_min = GetCBORUInt(map_value); else if (key == "y_max") ret.y_max = GetCBORUInt(map_value); else throw JFJochException(JFJochExceptionCategory::CBORError, "Unexpected entry in ROI"); } cborErr(cbor_value_leave_container(&value, &map_value)); return ret; } inline void GetCBORFloatArray(CborValue &value, std::vector &v) { if (GetCBORTag(value) != TagFloatLE) throw JFJochException(JFJochExceptionCategory::CBORError, "Incorrect array type tag"); auto [ptr, len] = GetCBORByteString(value); if (len % sizeof(float)) throw JFJochException(JFJochExceptionCategory::CBORError, "Size mismatch"); v.resize(len / sizeof(float)); memcpy(v.data(), ptr, len); } inline void GetCBORStringArray(CborValue &value, std::vector &v) { if (!cbor_value_is_array(&value)) throw JFJochException(JFJochExceptionCategory::CBORError, "Array expected"); CborValue array_value; cborErr(cbor_value_enter_container(&value, &array_value)); while (! cbor_value_at_end(&array_value)) { v.emplace_back(GetCBORString(array_value)); } cborErr(cbor_value_leave_container(&value, &array_value)); } inline void GetCBORUInt64Array(CborValue &value, std::vector &v) { if (GetCBORTag(value) != TagUnsignedInt64BitLE) throw JFJochException(JFJochExceptionCategory::CBORError, "Incorrect array type tag"); auto [ptr, len] = GetCBORByteString(value); if (len % sizeof(uint64_t)) throw JFJochException(JFJochExceptionCategory::CBORError, "Size mismatch"); v.resize(len / sizeof(uint64_t)); memcpy(v.data(), ptr, len); } inline void GetCBORTypedArray(CompressedImage &v, CborValue &value) { CborTag tag = GetCBORTag(value); switch (tag) { case TagFloatLE: v.pixel_is_signed = true; v.pixel_depth_bytes = sizeof(float); v.pixel_is_float = true; break; case TagSignedInt8Bit: v.pixel_is_signed = true; v.pixel_depth_bytes = 1; v.pixel_is_float = false; break; case TagUnsignedInt8Bit: v.pixel_is_signed = false; v.pixel_depth_bytes = 1; v.pixel_is_float = false; break; case TagSignedInt16BitLE: v.pixel_is_signed = true; v.pixel_depth_bytes = 2; v.pixel_is_float = false; break; case TagUnsignedInt16BitLE: v.pixel_is_signed = false; v.pixel_depth_bytes = 2; v.pixel_is_float = false; break; case TagSignedInt32BitLE: v.pixel_is_signed = true; v.pixel_depth_bytes = 4; v.pixel_is_float = false; break; case TagUnsignedInt32BitLE: v.pixel_is_signed = false; v.pixel_depth_bytes = 4; v.pixel_is_float = false; break; default: throw JFJochException(JFJochExceptionCategory::CBORError, "Only int/uint arrays of 1, 2, 4 bytes per element allowed"); } if (cbor_value_is_tag(&value)) { if (GetCBORTag(value) != TagDECTRISCompression) throw JFJochException(JFJochExceptionCategory::CBORError, "Unsupported tag"); if (GetCBORArrayLen(value) != 3) throw JFJochException(JFJochExceptionCategory::CBORError, "Expected 3 element array"); CborValue array_value; cborErr(cbor_value_enter_container(&value, &array_value)); auto algorithm_text = GetCBORString(array_value); if (algorithm_text == "bslz4") v.algorithm = CompressionAlgorithm::BSHUF_LZ4; else if (algorithm_text == "bszstd") v.algorithm = CompressionAlgorithm::BSHUF_ZSTD; else throw JFJochException(JFJochExceptionCategory::CBORError, "Unsupported compression algorithm"); v.pixel_depth_bytes = GetCBORUInt(array_value); auto ret = GetCBORByteString(array_value); v.data = ret.first; v.size = ret.second; cborErr(cbor_value_leave_container(&value, &array_value)); } else if (cbor_value_is_byte_string(&value)) { v.algorithm = CompressionAlgorithm::NO_COMPRESSION; auto ret = GetCBORByteString(value); v.data = ret.first; v.size = ret.second; } else throw JFJochException(JFJochExceptionCategory::CBORError, "Byte string or compressed array expected"); } void GetCBORMultidimTypedArray(CompressedImage &v, CborValue &value) { if (GetCBORTag(value) != TagMultiDimArray) throw JFJochException(JFJochExceptionCategory::CBORError, "Multidim array expected"); if (GetCBORArrayLen(value) != 2) throw JFJochException(JFJochExceptionCategory::CBORError, "2 element array expected"); CborValue array_value; cborErr(cbor_value_enter_container(&value, &array_value)); auto [x,y] = GetCBORDimension2DArray(array_value); v.xpixel = x; v.ypixel = y; GetCBORTypedArray(v, array_value); cborErr(cbor_value_leave_container(&value, &array_value)); } bool CheckMagicNumber(CborValue &v) { auto key = GetCBORString(v); if (key != "magic_number") { cbor_value_advance(&v); return false; } else { return GetCBORUInt(v) == user_data_magic_number; } } inline SpotToSave GetSpot(CborValue& value) { SpotToSave s; CborValue map_value; cborErr(cbor_value_enter_container(&value, &map_value)); while (!cbor_value_at_end(&map_value)) { auto key = GetCBORString(map_value); if (key == "x") s.x = GetCBORFloat(map_value); else if (key == "y") s.y = GetCBORFloat(map_value); else if (key == "I") s.intensity = GetCBORFloat(map_value); else if (key == "indexed") s.indexed = GetCBORBool(map_value); else cbor_value_advance(&map_value); } cborErr(cbor_value_leave_container(&value, &map_value)); return s; } void CBORStream2Deserializer::GetCBORSpots(CborValue &value) { size_t array_len = GetCBORArrayLen(value); CborValue array_value; cborErr(cbor_value_enter_container(&value, &array_value)); for (int i = 0; i < array_len; i++) data_message.spots.push_back(GetSpot(array_value)); cborErr(cbor_value_leave_container(&value, &array_value)); } void CBORStream2Deserializer::DecodeType(CborValue &value) { if (cbor_value_at_end(&value)) throw JFJochException(JFJochExceptionCategory::CBORError, "Message empty"); auto key = GetCBORString(value); if (key != "type") throw JFJochException(JFJochExceptionCategory::CBORError, "First CBOR entry must by type"); if (cbor_value_at_end(&value)) throw JFJochException(JFJochExceptionCategory::CBORError, "Message empty"); auto type_str = GetCBORString(value); if (type_str == "start") msg_type = Type::START; else if (type_str == "end") msg_type = Type::END; else if (type_str == "image") msg_type = Type::IMAGE; else throw JFJochException(JFJochExceptionCategory::CBORError, "Unknown message type"); } void CBORStream2Deserializer::ProcessGoniometerMap(CborValue &value) { CborValue map_value; cborErr(cbor_value_enter_container(&value, &map_value)); while (! cbor_value_at_end(&map_value)) { auto key = GetCBORString(map_value); if (key == "omega") ProcessGoniometerOmega(map_value); } cborErr(cbor_value_leave_container(&value, &map_value)); } void CBORStream2Deserializer::ProcessAxis(CborValue &value, float v[3]) { if (GetCBORArrayLen(value) != 3) throw JFJochException(JFJochExceptionCategory::CBORError, "Array with 3 floats expected"); CborValue array_value; cborErr(cbor_value_enter_container(&value, &array_value)); for (int i = 0; i < 3; i++) v[i] = GetCBORFloat(array_value); cborErr(cbor_value_leave_container(&value, &array_value)); } void CBORStream2Deserializer::ProcessGoniometerOmega(CborValue &value) { CborValue map_value; GoniometerAxis ret{}; cborErr(cbor_value_enter_container(&value, &map_value)); while (! cbor_value_at_end(&map_value)) { auto key = GetCBORString(map_value); if (key == "increment") start_message.omega.increment = GetCBORFloat(map_value); else if (key == "start") start_message.omega.start = GetCBORFloat(map_value); else cbor_value_advance(&value); } cborErr(cbor_value_leave_container(&value, &map_value)); } bool CBORStream2Deserializer::ProcessImageMessageElement(CborValue &value) { if (cbor_value_at_end(&value)) return false; else { auto key = GetCBORString(value); try { if (key == "image_id") data_message.number = GetCBORInt(value); else if (key == "data") ProcessImageData(value); else if (key == "series_unique_id") data_message.series_unique_id = GetCBORString(value); else if (key == "series_id") data_message.series_id = GetCBORUInt(value); else if (key == "real_time") { auto r = GetRational(value); data_message.exptime = r.first; data_message.exptime_base = r.second; } else if (key == "start_time") { auto r = GetRational(value); data_message.timestamp = r.first; data_message.timestamp_base = r.second; } else if (key == "user_data") data_message.user_data = GetCBORString(value); else if (key == "spots") GetCBORSpots(value); else if (key == "az_int_profile") GetCBORFloatArray(value, data_message.az_int_profile); else if (key == "indexing_result") data_message.indexing_result = GetCBORUInt(value); else if (key == "indexing_lattice") GetCBORFloatArray(value, data_message.indexing_lattice); else if (key == "jf_info") data_message.jf_info = GetCBORUInt(value) & UINT32_MAX; else if (key == "receiver_aq_dev_delay") data_message.receiver_aq_dev_delay = GetCBORInt(value); else if (key == "storage_cell") data_message.storage_cell = GetCBORUInt(value) & UINT32_MAX; else if (key == "xfel_pulse_id") data_message.xfel_pulse_id = GetCBORUInt(value); else if (key == "xfel_event_code") data_message.xfel_event_code = GetCBORUInt(value); else if (key == "saturated_pixel_count") data_message.saturated_pixel_count = GetCBORUInt(value); else if (key == "error_pixel_count") data_message.error_pixel_count = GetCBORUInt(value); else if (key == "strong_pixel_count") data_message.strong_pixel_count = GetCBORUInt(value); else if (key == "data_collection_efficiency") data_message.image_collection_efficiency = GetCBORFloat(value); else if (key == "bkg_estimate") data_message.bkg_estimate = GetCBORFloat(value); else if (key == "adu_histogram") GetCBORUInt64Array(value, data_message.adu_histogram); else if (key == "roi_sum") data_message.roi_sum = GetCBORInt(value); else { if (cbor_value_is_tag(&value)) cbor_value_advance(&value); cbor_value_advance(&value); } return true; } catch (const JFJochException &e) { throw JFJochException(JFJochExceptionCategory::CBORError, "Error processing image message, key " + key + ":" + e.what()); } } } void CBORStream2Deserializer::ProcessCalibration(CborValue &value) { // For calibration and mask - image might be accessed later on - so it is saved in internal storage of the object // It allows the original start message to be lost CborValue map_value; cborErr(cbor_value_enter_container(&value, &map_value)); while (! cbor_value_at_end(&map_value)) { auto key = GetCBORString(map_value); CompressedImage image; image.channel = key; GetCBORMultidimTypedArray(image, map_value); image.Save(); start_message.calibration.emplace_back(std::move(image)); } cborErr(cbor_value_leave_container(&value, &map_value)); } void CBORStream2Deserializer::ProcessPixelMaskElement(CborValue &value) { // For calibration and mask - image might be accessed later on - so it is saved in internal storage of the object // It allows the original start message to be lost CborValue map_value; cborErr(cbor_value_enter_container(&value, &map_value)); while (! cbor_value_at_end(&map_value)) { auto key = GetCBORString(map_value); CompressedImage image; image.channel = key; GetCBORMultidimTypedArray(image, map_value); image.Save(); start_message.pixel_mask.emplace_back(std::move(image)); } cborErr(cbor_value_leave_container(&value, &map_value)); } void CBORStream2Deserializer::ProcessRadIntResultElement(CborValue &value) { CborValue map_value; cborErr(cbor_value_enter_container(&value, &map_value)); while (! cbor_value_at_end(&map_value)) { auto key = GetCBORString(map_value); std::vector val; GetCBORFloatArray(map_value, val); end_message.az_int_result[key] = val; } cborErr(cbor_value_leave_container(&value, &map_value)); } void CBORStream2Deserializer::ProcessADUHistogramElement(CborValue &value) { CborValue map_value; cborErr(cbor_value_enter_container(&value, &map_value)); while (! cbor_value_at_end(&map_value)) { auto key = GetCBORString(map_value); std::vector val; GetCBORUInt64Array(map_value, val); end_message.adu_histogram[key] = val; } cborErr(cbor_value_leave_container(&value, &map_value)); } void CBORStream2Deserializer::ProcessChannels(CborValue &value) { if (!cbor_value_is_array(&value)) throw JFJochException(JFJochExceptionCategory::CBORError, "Array expected"); CborValue array_value; cborErr(cbor_value_enter_container(&value, &array_value)); while (! cbor_value_at_end(&array_value)) { const std::string s = GetCBORString(array_value); start_message.channels.push_back(s); } cborErr(cbor_value_leave_container(&value, &array_value)); } void CBORStream2Deserializer::ProcessUnitCellElement(CborValue &value) { UnitCell unit_cell{}; CborValue map_value; cborErr(cbor_value_enter_container(&value, &map_value)); while (! cbor_value_at_end(&map_value)) { auto key = GetCBORString(map_value); if (key == "a") unit_cell.a = GetCBORFloat(map_value); else if (key == "b") unit_cell.b = GetCBORFloat(map_value); else if (key == "c") unit_cell.c = GetCBORFloat(map_value); else if (key == "alpha") unit_cell.alpha = GetCBORFloat(map_value); else if (key == "beta") unit_cell.beta = GetCBORFloat(map_value); else if (key == "gamma") unit_cell.gamma = GetCBORFloat(map_value); else cbor_value_advance(&map_value); } cborErr(cbor_value_leave_container(&value, &map_value)); start_message.unit_cell = unit_cell; } void CBORStream2Deserializer::ProcessStartUserData(CborValue &value) { try { const std::string s = GetCBORString(value); auto j = nlohmann::json::parse(s); if (!j.is_object()) return; if (j.contains("file_prefix")) start_message.file_prefix = j["file_prefix"]; if (j.contains("data_file_count")) start_message.data_file_count = j["data_file_count"]; if (j.contains("user")) start_message.user_data = j["user"]; if (j.contains("sample_name")) start_message.sample_name = j["sample_name"]; if (j.contains("source_name")) start_message.source_name = j["source_name"]; if (j.contains("source_name_short")) start_message.source_name_short = j["source_name_short"]; if (j.contains("instrument_name")) start_message.instrument_name = j["instrument_name"]; if (j.contains("instrument_name_short")) start_message.instrument_name_short = j["instrument_name_short"]; if (j.contains("total_flux")) start_message.total_flux = j["total_flux"]; if (j.contains("attenuator_transmission")) start_message.attenuator_transmission = j["attenuator_transmission"]; if (j.contains("omega_rotation_axis") && j["omega_rotation_axis"].is_array() && (j["omega_rotation_axis"].size() == 3)) { for (int i = 0; i < 3; i++) start_message.omega.axis[i] = j["omega_rotation_axis"][i]; } if (j.contains("space_group_number")) start_message.space_group_number = j["space_group_number"]; } catch (const std::exception &e) { throw JFJochException(JFJochExceptionCategory::CBORError, "Cannot parse user_data as valid JSON " + std::string(e.what())); } } bool CBORStream2Deserializer::ProcessStartMessageElement(CborValue &value) { if (cbor_value_at_end(&value)) return false; else { auto key = GetCBORString(value); try { if (key == "beam_center_x") start_message.beam_center_x = GetCBORFloat(value); else if (key == "beam_center_y") start_message.beam_center_y = GetCBORFloat(value); else if (key == "detector_distance") start_message.detector_distance = GetCBORFloat(value); else if (key == "number_of_images") start_message.number_of_images = GetCBORUInt(value); else if (key == "countrate_correction_enabled") start_message.countrate_correction_enabled = GetCBORBool(value); else if (key == "flatfield_enabled") start_message.flatfield_enabled = GetCBORBool(value); else if (key == "image_size_x") start_message.image_size_x = GetCBORUInt(value); else if (key == "image_size_y") start_message.image_size_y = GetCBORUInt(value); else if (key == "incident_energy") start_message.incident_energy = GetCBORFloat(value); else if (key == "incident_wavelength") start_message.incident_wavelength = GetCBORFloat(value); else if (key == "frame_time") start_message.frame_time = GetCBORFloat(value); else if (key == "count_time") start_message.count_time = GetCBORFloat(value); else if (key == "saturation_value") start_message.saturation_value = GetCBORInt(value); else if (key == "error_value") start_message.error_value = GetCBORInt(value); else if (key == "pixel_size_x") start_message.pixel_size_x = GetCBORFloat(value); else if (key == "pixel_size_y") start_message.pixel_size_y = GetCBORFloat(value); else if (key == "sensor_thickness") start_message.sensor_thickness = GetCBORFloat(value); else if (key == "sensor_material") start_message.sensor_material = GetCBORString(value); else if (key == "detector_description") start_message.detector_description = GetCBORString(value); else if (key == "detector_serial_number") start_message.detector_serial_number = GetCBORString(value); else if (key == "series_unique_id") start_message.series_unique_id = GetCBORString(value); else if (key == "series_id") start_message.series_id = GetCBORUInt(value); else if (key == "pixel_mask") ProcessPixelMaskElement(value); else if (key == "channels") GetCBORStringArray(value, start_message.channels); else if (key == "detector_translation") ProcessAxis(value, start_message.detector_translation); else if (key == "goniometer") ProcessGoniometerMap(value); else if (key == "pixel_mask_enabled") start_message.pixel_mask_enabled = GetCBORBool(value); else if (key == "arm_date") start_message.arm_date = GetCBORDateTime(value); else if (key == "user_data") ProcessStartUserData(value); else if (key == "unit_cell") ProcessUnitCellElement(value); else if (key == "max_spot_count") start_message.max_spot_count = GetCBORUInt(value); else if (key == "image_dtype") { auto val = GetCBORString(value); if (val == "uint8") { start_message.pixel_signed = false; start_message.pixel_bit_depth = 8; } else if (val == "int8") { start_message.pixel_signed = true; start_message.pixel_bit_depth = 8; } else if (val == "uint16") { start_message.pixel_signed = false; start_message.pixel_bit_depth = 16; } else if (val == "int16") { start_message.pixel_signed = true; start_message.pixel_bit_depth = 16; } else if (val == "uint32") { start_message.pixel_signed = false; start_message.pixel_bit_depth = 32; } else if (val == "int32") { start_message.pixel_signed = true; start_message.pixel_bit_depth = 32; } } else if (key == "az_int_bin_number") start_message.az_int_bin_number = GetCBORInt(value); else if (key == "az_int_bin_to_q") GetCBORFloatArray(value, start_message.az_int_bin_to_q); else if (key == "summation") start_message.summation = GetCBORUInt(value); else if (key == "storage_cell_number") start_message.storage_cell_number = GetCBORUInt(value); else if (key == "storage_cell_delay") start_message.storage_cell_delay_ns = GetRational(value).first; else if (key == "roi_sum_area") start_message.roi_summation_area = GetROIRectangle(value); else if (key == "gain_file_names") GetCBORStringArray(value, start_message.gain_file_names); else if (key == "calibration") ProcessCalibration(value); else { if (cbor_value_is_tag(&value)) cbor_value_advance(&value); cbor_value_advance(&value); } return true; } catch (const JFJochException &e) { throw JFJochException(JFJochExceptionCategory::CBORError, "Error processing start message, key " + key + ":" + e.what()); } } } bool CBORStream2Deserializer::ProcessEndMessageElement(CborValue &value) { if (cbor_value_at_end(&value)) return false; else { auto key = GetCBORString(value); try { if (key == "end_date") end_message.end_date = GetCBORString(value); else if (key == "series_unique_id") end_message.series_unique_id = GetCBORString(value); else if (key == "series_id") end_message.series_id = GetCBORUInt(value); else if (key == "max_image_number") end_message.max_image_number = GetCBORUInt(value); else if (key == "images_collected") end_message.images_collected_count = GetCBORUInt(value); else if (key == "images_sent_to_write") end_message.images_sent_to_write_count = GetCBORUInt(value); else if (key == "max_receiver_delay") end_message.max_receiver_delay = GetCBORUInt(value); else if (key == "data_collection_efficiency") end_message.efficiency = GetCBORFloat(value); else if (key == "write_master_file") end_message.write_master_file = GetCBORBool(value); else if (key == "az_int_result") ProcessRadIntResultElement(value); else if (key == "adu_histogram") ProcessADUHistogramElement(value); else if (key == "adu_histogram_bin_width") end_message.adu_histogram_bin_width = GetCBORUInt(value); else cbor_value_advance(&value); return true; } catch (const JFJochException &e) { throw JFJochException(JFJochExceptionCategory::CBORError, "Error processing end message, key " + key + ":" + e.what()); } } } void CBORStream2Deserializer::ProcessImageData(CborValue &value) { if (!cbor_value_is_map(&value)) throw JFJochException(JFJochExceptionCategory::CBORError, "Map expected"); if (GetCBORMapLen(value) != 1) throw JFJochException(JFJochExceptionCategory::CBORError, "Single channel images only supported at the moment"); CborValue map_value; cborErr(cbor_value_enter_container(&value, &map_value)); data_message.image.channel = GetCBORString(map_value); GetCBORMultidimTypedArray(data_message.image, map_value); cborErr(cbor_value_leave_container(&value, &map_value)); } void CBORStream2Deserializer::Process(const std::vector &buffer) { Process(buffer.data(), buffer.size()); } void CBORStream2Deserializer::Process(const uint8_t *msg, size_t msg_size) { std::unique_lock ul(m); data_message = DataMessage(); CborParser parser; CborValue value; try { cborErr(cbor_parser_init(msg, msg_size, 0, &parser, &value)); if (GetCBORTag(value) != CborSignatureTag) throw JFJochException(JFJochExceptionCategory::CBORError, "CBOR must start with dedicated tag"); if (cbor_value_is_map(&value)) { CborValue map_value; cborErr(cbor_value_enter_container(&value, &map_value)); DecodeType(map_value); switch (msg_type) { case Type::IMAGE: while (ProcessImageMessageElement(map_value)); break; case Type::START: start_message = StartMessage{ .data_file_count = 1 }; while (ProcessStartMessageElement(map_value)); break; case Type::END: end_message = EndMessage{}; while (ProcessEndMessageElement(map_value)); break; case Type::NONE: break; } cborErr(cbor_value_leave_container(&value, &map_value)); } else throw JFJochException(JFJochExceptionCategory::CBORError, "Serialized frame must be map in top level"); } catch (...) { msg_type = Type::NONE; throw; } } DataMessage CBORStream2Deserializer::GetDataMessage() const { std::unique_lock ul(m); return data_message; } CBORStream2Deserializer::Type CBORStream2Deserializer::GetType() const { std::unique_lock ul(m); return msg_type; } EndMessage CBORStream2Deserializer::GetEndMessage() const { std::unique_lock ul(m); return end_message; } StartMessage CBORStream2Deserializer::GetStartMessage() const { std::unique_lock ul(m); return start_message; }