diff --git a/.gitignore b/.gitignore index 45e531e..44f5a7c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /cmake-build-release .idea /result +/.cache diff --git a/CMakeLists.txt b/CMakeLists.txt index 63df1b4..ffd4474 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,11 +12,15 @@ add_executable(tetra-decoder src/streaming_ordered_output_thread_pool_executor.cpp src/bit_stream_decoder.cpp src/iq_stream_decoder.cpp + src/prometheus.cpp + src/l2/access_assignment_channel.cpp + src/l2/broadcast_synchronization_channel.cpp + src/l2/logical_link_control.cpp src/l2/lower_mac.cpp src/l2/lower_mac_coding.cpp + src/l2/timebase_counter.cpp src/l2/upper_mac.cpp src/l2/upper_mac_fragmentation.cpp - src/l2/logical_link_control.cpp src/l3/mobile_link_entity.cpp src/l3/mobile_management.cpp src/l3/circuit_mode_control_entity.cpp @@ -44,6 +48,7 @@ find_package(cxxopts CONFIG REQUIRED) find_package(ZLIB REQUIRED) find_package(fmt REQUIRED) find_package(nlohmann_json REQUIRED) +find_package(prometheus-cpp CONFIG REQUIRED) include_directories(${CMAKE_SOURCE_DIR}/include) @@ -51,7 +56,7 @@ if (NOT NIX_BUILD) target_link_libraries(tetra-decoder cxxopts::cxxopts) endif() -target_link_libraries(tetra-decoder ZLIB::ZLIB fmt::fmt nlohmann_json::nlohmann_json viterbi) +target_link_libraries(tetra-decoder ZLIB::ZLIB fmt::fmt nlohmann_json::nlohmann_json viterbi prometheus-cpp::pull) target_link_libraries(tetra-viterbi viterbi) install(TARGETS tetra-decoder DESTINATION bin) diff --git a/derivation.nix b/derivation.nix index 2ed7d3f..2dff06b 100644 --- a/derivation.nix +++ b/derivation.nix @@ -5,6 +5,8 @@ , zlib , fmt , nlohmann_json +, prometheus-cpp +, curlFull }: clangStdenv.mkDerivation { name = "tetra-decoder"; @@ -13,7 +15,13 @@ clangStdenv.mkDerivation { src = ./.; nativeBuildInputs = [ cmake pkg-config fmt ]; - buildInputs = [ cxxopts zlib nlohmann_json ]; + buildInputs = [ + cxxopts + zlib + nlohmann_json + curlFull + prometheus-cpp + ]; cmakeFlags = [ "-DNIX_BUILD=ON" ]; diff --git a/include/decoder.hpp b/include/decoder.hpp index 7e64557..7d3dd15 100644 --- a/include/decoder.hpp +++ b/include/decoder.hpp @@ -40,7 +40,8 @@ class Decoder { public: Decoder(unsigned int receive_port, unsigned int send_port, bool packed, std::optional input_file, std::optional output_file, bool iq_or_bit_stream, - std::optional uplink_scrambling_code); + std::optional uplink_scrambling_code, + std::shared_ptr& prometheus_exporter); ~Decoder(); void main_loop(); diff --git a/include/l2/access_assignment_channel.hpp b/include/l2/access_assignment_channel.hpp new file mode 100644 index 0000000..32b24c0 --- /dev/null +++ b/include/l2/access_assignment_channel.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 Transit Live Mapping Solutionss + * All rights reserved. + * + * Authors: + * Marenz Schmidl + */ + +#pragma once + +#include "burst_type.hpp" +#include "l2/timebase_counter.hpp" +#include +#include +#include +#include + +enum DownlinkUsage { CommonControl, Unallocated, AssignedControl, CommonAndAssignedControl, Traffic }; + +struct AccessAssignmentChannel { + DownlinkUsage downlink_usage; + std::optional downlink_traffic_usage_marker; + + AccessAssignmentChannel() = delete; + AccessAssignmentChannel(BurstType burst_type, const TimebaseCounter& time, const std::vector& data); + + friend auto operator<<(std::ostream& stream, const AccessAssignmentChannel& aac) -> std::ostream&; +}; + +auto operator<<(std::ostream& stream, const AccessAssignmentChannel& aac) -> std::ostream&; \ No newline at end of file diff --git a/include/l2/broadcast_synchronization_channel.hpp b/include/l2/broadcast_synchronization_channel.hpp new file mode 100644 index 0000000..150ab1a --- /dev/null +++ b/include/l2/broadcast_synchronization_channel.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 Transit Live Mapping Solutions + * All rights reserved. + * + * Authors: + * Marenz Schmidl + */ + +#pragma once + +#include "burst_type.hpp" +#include "l2/timebase_counter.hpp" +#include +#include + +struct BroadcastSynchronizationChannel { + public: + uint8_t system_code = 0; + uint32_t color_code = 0; + TimebaseCounter time{}; + uint8_t sharing_mode = 0; + uint8_t time_slot_reserved_frames = 0; + uint8_t up_lane_dtx = 0; + uint8_t frame_18_extension = 0; + + uint32_t scrambling_code = 0; + + uint32_t mobile_country_code = 0; + uint32_t mobile_network_code = 0; + uint8_t dNwrk_broadcast_broadcast_supported = 0; + uint8_t dNwrk_broadcast_enquiry_supported = 0; + uint8_t cell_load_ca = 0; + uint8_t late_entry_supported = 0; + + BroadcastSynchronizationChannel() = default; + BroadcastSynchronizationChannel(const BurstType burst_type, const std::vector& data); + + friend auto operator<<(std::ostream& stream, const BroadcastSynchronizationChannel& bsc) -> std::ostream&; +}; + +auto operator<<(std::ostream& stream, const BroadcastSynchronizationChannel& bsc) -> std::ostream&; \ No newline at end of file diff --git a/include/l2/lower_mac.hpp b/include/l2/lower_mac.hpp index 204fd6e..3305ee3 100644 --- a/include/l2/lower_mac.hpp +++ b/include/l2/lower_mac.hpp @@ -9,33 +9,187 @@ #pragma once +#include "l2/broadcast_synchronization_channel.hpp" +#include "l2/timebase_counter.hpp" #include #include +#include #include #include #include +#include #include #include +/// The class to provide prometheus metrics to the lower mac +class LowerMacPrometheusCounters { + private: + /// The family of counters for received bursts + prometheus::Family& burst_received_count_family_; + /// The counter for the received ControlUplinkBurst + prometheus::Counter& control_uplink_burst_received_count_; + /// The counter for the received NormalUplinkBurst + prometheus::Counter& normal_uplink_burst_received_count_; + /// The counter for the received NormalUplinkBurstSplit + prometheus::Counter& normal_uplink_burst_split_received_count_; + /// The counter for the received NormalDownlinkBurst + prometheus::Counter& normal_downlink_burst_received_count_; + /// The counter for the received NormalDownlinkBurstSplit + prometheus::Counter& normal_downlink_burst_split_received_count_; + /// The counter for the received SynchronizationBurst + prometheus::Counter& synchronization_burst_received_count_; + + /// The family of counters for decoding errors on received bursts in the lower MAC + prometheus::Family& burst_lower_mac_decode_error_count_family_; + /// The counter for the received ControlUplinkBurst with decode errors + prometheus::Counter& control_uplink_burst_lower_mac_decode_error_count_; + /// The counter for the received NormalUplinkBurst with decode errors + prometheus::Counter& normal_uplink_burst_lower_mac_decode_error_count_; + /// The counter for the received NormalUplinkBurstSplit with decode errors + prometheus::Counter& normal_uplink_burst_split_lower_mac_decode_error_count_; + /// The counter for the received NormalDownlinkBurst with decode errors + prometheus::Counter& normal_downlink_burst_lower_mac_decode_error_count_; + /// The counter for the received NormalDownlinkBurstSplit with decode errors + prometheus::Counter& normal_downlink_burst_split_lower_mac_decode_error_count_; + /// The counter for the received SynchronizationBurst with decode errors + prometheus::Counter& synchronization_burst_lower_mac_decode_error_count_; + + /// The family of counters for mismatched number of bursts in the downlink lower MAC + prometheus::Family& burst_lower_mac_mismatch_count_family_; + /// The counter for the skipped bursts (lost bursts) in the downlink lower MAC + prometheus::Counter& lower_mac_burst_skipped_count_; + /// The counter for the too many bursts in the downlink lower MAC + prometheus::Counter& lower_mac_burst_too_many_count_; + + public: + LowerMacPrometheusCounters(std::shared_ptr& prometheus_exporter) + : burst_received_count_family_(prometheus_exporter->burst_received_count()) + , control_uplink_burst_received_count_(burst_received_count_family_.Add({{"burst_type", "ControlUplinkBurst"}})) + , normal_uplink_burst_received_count_(burst_received_count_family_.Add({{"burst_type", "NormalUplinkBurst"}})) + , normal_uplink_burst_split_received_count_( + burst_received_count_family_.Add({{"burst_type", "NormalUplinkBurstSplit"}})) + , normal_downlink_burst_received_count_( + burst_received_count_family_.Add({{"burst_type", "NormalDownlinkBurst"}})) + , normal_downlink_burst_split_received_count_( + burst_received_count_family_.Add({{"burst_type", "NormalDownlinkBurstSplit"}})) + , synchronization_burst_received_count_( + burst_received_count_family_.Add({{"burst_type", "SynchronizationBurst"}})) + , burst_lower_mac_decode_error_count_family_(prometheus_exporter->burst_lower_mac_decode_error_count()) + , control_uplink_burst_lower_mac_decode_error_count_( + burst_lower_mac_decode_error_count_family_.Add({{"burst_type", "ControlUplinkBurst"}})) + , normal_uplink_burst_lower_mac_decode_error_count_( + burst_lower_mac_decode_error_count_family_.Add({{"burst_type", "NormalUplinkBurst"}})) + , normal_uplink_burst_split_lower_mac_decode_error_count_( + burst_lower_mac_decode_error_count_family_.Add({{"burst_type", "NormalUplinkBurstSplit"}})) + , normal_downlink_burst_lower_mac_decode_error_count_( + burst_lower_mac_decode_error_count_family_.Add({{"burst_type", "NormalDownlinkBurst"}})) + , normal_downlink_burst_split_lower_mac_decode_error_count_( + burst_lower_mac_decode_error_count_family_.Add({{"burst_type", "NormalDownlinkBurstSplit"}})) + , synchronization_burst_lower_mac_decode_error_count_( + burst_lower_mac_decode_error_count_family_.Add({{"burst_type", "SynchronizationBurst"}})) + , burst_lower_mac_mismatch_count_family_(prometheus_exporter->burst_lower_mac_mismatch_count()) + , lower_mac_burst_skipped_count_(burst_lower_mac_mismatch_count_family_.Add({{"mismatch_type", "Skipped"}})) + , lower_mac_burst_too_many_count_( + burst_lower_mac_mismatch_count_family_.Add({{"mismatch_type", "Too many"}})){}; + + /// This function is called for every burst. It increments the counter associated to the burst type. + /// \param burst_type the type of the burst for which to increment the counter + /// \param decode_error true if there was an error decoding the packets on the lower mac (crc16) + auto increment(BurstType burst_type, bool decode_error) -> void { + switch (burst_type) { + case BurstType::ControlUplinkBurst: + control_uplink_burst_received_count_.Increment(); + break; + case BurstType::NormalUplinkBurst: + normal_uplink_burst_received_count_.Increment(); + break; + case BurstType::NormalUplinkBurstSplit: + normal_uplink_burst_split_received_count_.Increment(); + break; + case BurstType::NormalDownlinkBurst: + normal_downlink_burst_received_count_.Increment(); + break; + case BurstType::NormalDownlinkBurstSplit: + normal_downlink_burst_split_received_count_.Increment(); + break; + case BurstType::SynchronizationBurst: + synchronization_burst_received_count_.Increment(); + break; + } + + if (decode_error) { + switch (burst_type) { + case BurstType::ControlUplinkBurst: + control_uplink_burst_lower_mac_decode_error_count_.Increment(); + break; + case BurstType::NormalUplinkBurst: + normal_uplink_burst_lower_mac_decode_error_count_.Increment(); + break; + case BurstType::NormalUplinkBurstSplit: + normal_uplink_burst_split_lower_mac_decode_error_count_.Increment(); + break; + case BurstType::NormalDownlinkBurst: + normal_downlink_burst_lower_mac_decode_error_count_.Increment(); + break; + case BurstType::NormalDownlinkBurstSplit: + normal_downlink_burst_split_lower_mac_decode_error_count_.Increment(); + break; + case BurstType::SynchronizationBurst: + synchronization_burst_lower_mac_decode_error_count_.Increment(); + break; + } + } + } + + /// This function is called for Synchronization Bursts. It increments the counters for the missmatched received + /// burst counts. + /// \param current_timestamp the current timestamp of the synchronization burst + /// \param expected_timestamp the predicted timestamp of the synchronization burst + auto increment(const TimebaseCounter current_timestamp, const TimebaseCounter expected_timestamp) { + auto current_timestamp_count = current_timestamp.count(); + auto expected_timestamp_count = expected_timestamp.count(); + + if (current_timestamp_count > expected_timestamp_count) { + auto missed_bursts = current_timestamp_count - expected_timestamp_count; + lower_mac_burst_skipped_count_.Increment(missed_bursts); + } else if (expected_timestamp_count > current_timestamp_count) { + auto too_many_bursts = expected_timestamp_count - current_timestamp_count; + lower_mac_burst_too_many_count_.Increment(too_many_bursts); + } + } +}; + class LowerMac { public: - explicit LowerMac(std::shared_ptr reporter); + LowerMac() = delete; + LowerMac(std::shared_ptr reporter, std::shared_ptr& prometheus_exporter, + std::optional scrambling_code = std::nullopt); ~LowerMac() = default; - // does the signal processing and then returns a list of function that need to be executed for data to be passed to - // upper mac sequentially. - // TODO: this is currently only done for uplink bursts - // Downlink burst get processed and passed to upper mac directly + // does the signal processing and then returns a list of function that need to be executed for data to be passed + // to upper mac sequentially. + [[nodiscard]] auto processChannels(const std::vector& frame, BurstType burst_type, + const BroadcastSynchronizationChannel& bsc) + -> std::vector>; + + /// handles the decoding of the synchronization bursts and once synchronized passes the data to the decoding of the + /// channels. keeps track of the current network time [[nodiscard]] auto process(const std::vector& frame, BurstType burst_type) -> std::vector>; - void set_scrambling_code(unsigned int scrambling_code) { upper_mac_->set_scrambling_code(scrambling_code); }; private: std::shared_ptr reporter_{}; std::shared_ptr viter_bi_codec_1614_{}; std::shared_ptr upper_mac_{}; + std::unique_ptr metrics_; + + /// The last received synchronization burst. + /// This include the current scrambling code. Set by Synchronization Burst on downlink or injected from the side for + /// uplink processing, as we decouple it from the downlink for data/control packets. + std::optional sync_; + static auto descramble(const uint8_t* data, uint8_t* res, std::size_t len, uint32_t scramblingCode) noexcept -> void; static auto deinterleave(const uint8_t* data, uint8_t* res, std::size_t K, std::size_t a) noexcept -> void; diff --git a/include/l2/timebase_counter.hpp b/include/l2/timebase_counter.hpp new file mode 100644 index 0000000..6a6ba96 --- /dev/null +++ b/include/l2/timebase_counter.hpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 Transit Live Mapping Solutions + * All rights reserved. + * + * Authors: + * Marenz Schmidl + */ + +#pragma once + +#include +#include +#include + +class TimebaseCounter { + private: + uint16_t time_slot_ = 1; + uint16_t frame_number_ = 1; + uint16_t multi_frame_number_ = 1; + + public: + TimebaseCounter() = default; + TimebaseCounter(const uint16_t time_slot, const uint16_t frame_number, const uint16_t multi_frame_number) + : time_slot_(time_slot) + , frame_number_(frame_number) + , multi_frame_number_(multi_frame_number){}; + + [[nodiscard]] auto time_slot() const noexcept -> uint16_t { return time_slot_; }; + [[nodiscard]] auto frame_number() const noexcept -> uint16_t { return frame_number_; }; + [[nodiscard]] auto multi_frame_number() const noexcept -> uint16_t { return multi_frame_number_; }; + /// convert the slot and frame numbers into a single value + [[nodiscard]] auto count() const noexcept -> unsigned { + return (time_slot_ - 1) + 4 * (frame_number_ - 1) + 4 * 18 * (multi_frame_number_ - 1); + } + + [[nodiscard]] auto operator==(const TimebaseCounter& other) const noexcept -> bool { + return std::tie(time_slot_, frame_number_, multi_frame_number_) == + std::tie(other.time_slot_, other.frame_number_, other.multi_frame_number_); + }; + + [[nodiscard]] auto operator!=(const TimebaseCounter& other) const noexcept -> bool { return !(*this == other); } + + auto increment() noexcept -> void; + + friend auto operator<<(std::ostream& stream, const TimebaseCounter& tc) -> std::ostream&; +}; + +auto operator<<(std::ostream& stream, const TimebaseCounter& tc) -> std::ostream&; \ No newline at end of file diff --git a/include/l2/upper_mac.hpp b/include/l2/upper_mac.hpp index 522e5a8..887e895 100644 --- a/include/l2/upper_mac.hpp +++ b/include/l2/upper_mac.hpp @@ -22,24 +22,15 @@ #include #include -enum DownlinkUsage { CommonControl, Unallocated, AssignedControl, CommonAndAssignedControl, Traffic }; - class UpperMac { public: - UpperMac(std::shared_ptr reporter) + UpperMac() = delete; + UpperMac(std::shared_ptr reporter, bool is_downlink) : reporter_(std::move(reporter)) - , mobile_link_entity_(std::make_shared(reporter_)) + , mobile_link_entity_(std::make_shared(reporter_, is_downlink)) , logical_link_control_(std::make_unique(reporter_, mobile_link_entity_)){}; ~UpperMac() noexcept = default; - ; - - void incrementTn(); - void set_scrambling_code(unsigned int scrambling_code) { scrambling_code_ = scrambling_code; }; - // Access Assignment Channel - void process_AACH(BurstType burst_type, const std::vector& data); - // Broadcast Synchronization Channel - void process_BSCH(BurstType burst_type, const std::vector& data); // Signalling CHannel for mapping onto Half-bursts on the Downlink void process_SCH_HD(BurstType burst_type, const std::vector& data); // Signalling CHannel for mapping onto Half-bursts on the Uplink @@ -49,18 +40,10 @@ class UpperMac { // STealing CHannel void process_STCH(BurstType burst_type, const std::vector& data); - [[nodiscard]] auto scrambling_code() const noexcept -> uint32_t { return scrambling_code_; } - [[nodiscard]] auto color_code() const noexcept -> uint16_t { return color_code_; } [[nodiscard]] auto downlink_frequency() const noexcept -> int32_t { return downlink_frequency_; } [[nodiscard]] auto uplink_frequency() const noexcept -> int32_t { return uplink_frequency_; } - [[nodiscard]] auto downlink_usage() const noexcept -> DownlinkUsage { return downlink_usage_; } - [[nodiscard]] auto downlink_traffic_usage_marker() const noexcept -> int { return downlink_traffic_usage_marker_; } [[nodiscard]] auto second_slot_stolen() const noexcept -> bool { return second_slot_stolen_; } - [[nodiscard]] auto time_slot() const noexcept -> uint16_t { return time_slot_; } - [[nodiscard]] auto frame_number() const noexcept -> uint16_t { return frame_number_; } - [[nodiscard]] auto multi_frame_number() const noexcept -> uint16_t { return multi_frame_number_; } - friend auto operator<<(std::ostream& stream, const UpperMac& upperMac) -> std::ostream&; private: @@ -70,8 +53,6 @@ class UpperMac { bool isStolenChannel); void process_signalling_channel(BurstType burst_type, BitVector& vec, bool isHalfChannel, bool isStolenChannel); - void update_scrambling_code(); - void process_broadcast(BitVector& vec); void process_supplementary_mac_pdu(BurstType burst_type, BitVector& vec); @@ -106,23 +87,6 @@ class UpperMac { void remove_fill_bits(BitVector& vec); bool remove_fill_bits_{}; - // SYNC PDU - bool sync_received_ = false; - uint8_t system_code_{}; - uint32_t color_code_ = 0; - // time slot - uint16_t time_slot_ = 1; - // frame number - uint16_t frame_number_ = 1; - // multi frame number - uint16_t multi_frame_number_ = 1; - uint8_t sharing_mode_{}; - uint8_t time_slot_reserved_frames_{}; - uint8_t up_lane_dtx_{}; - uint8_t frame_18_extension_{}; - - uint32_t scrambling_code_ = 0; - // SYSINFO PDU bool system_info_received_ = false; int32_t downlink_frequency_ = 0; @@ -169,10 +133,6 @@ class UpperMac { uint8_t service_150Qam_ = 0; } extended_service_broadcast_; - // AACH - enum DownlinkUsage downlink_usage_; - int downlink_traffic_usage_marker_{}; - // STCH bool second_slot_stolen_{}; diff --git a/include/l3/mobile_link_entity.hpp b/include/l3/mobile_link_entity.hpp index 8296a15..fe446cd 100644 --- a/include/l3/mobile_link_entity.hpp +++ b/include/l3/mobile_link_entity.hpp @@ -20,16 +20,14 @@ class MobileLinkEntity { public: - explicit MobileLinkEntity(std::shared_ptr reporter) + MobileLinkEntity() = delete; + MobileLinkEntity(std::shared_ptr reporter, bool is_downlink) : reporter_(std::move(reporter)) , cmce_(std::make_unique(reporter_)) - , mm_(std::make_unique()){}; + , mm_(std::make_unique()) + , is_downlink_(is_downlink){}; ~MobileLinkEntity() noexcept = default; - [[nodiscard]] inline auto mobile_country_code() const noexcept -> uint32_t { return mobile_country_code_; } - [[nodiscard]] inline auto mobile_network_code() const noexcept -> uint32_t { return mobile_network_code_; } - - void service_DMle_sync(BitVector& vec); void service_DMle_system_info(BitVector& vec); void service_user_pdu(AddressType address, BitVector& vec); @@ -40,14 +38,6 @@ class MobileLinkEntity { void service_data_pdu(AddressType address, BitVector& vec); void service_d_network_broadcast(const AddressType address, BitVector& vec); - bool sync_received_ = false; - uint32_t mobile_country_code_ = 0; // mmc - uint32_t mobile_network_code_ = 0; // mobile_network_code - uint8_t dNwrk_broadcast_broadcast_supported_ = 0; - uint8_t dNwrk_broadcast_enquiry_supported_ = 0; - uint8_t cell_load_ca_ = 0; - uint8_t late_entry_supported_ = 0; - bool system_info_received_ = false; uint16_t location_area_ = 0; uint16_t subscriber_class_ = 0; @@ -63,12 +53,12 @@ class MobileLinkEntity { uint8_t air_interface_encryption_service_ = 0; uint8_t advanced_link_supported_ = 0; - // this variable is set to true if sync is received. this is not available in uplink - bool is_downlink_ = false; - std::shared_ptr reporter_{}; std::unique_ptr cmce_{}; std::unique_ptr mm_{}; + + // Wheather this MLE is for decoding downlink or uplink data + const bool is_downlink_; }; auto operator<<(std::ostream& stream, const MobileLinkEntity& mle) -> std::ostream&; \ No newline at end of file diff --git a/include/prometheus.h b/include/prometheus.h new file mode 100644 index 0000000..cf709d1 --- /dev/null +++ b/include/prometheus.h @@ -0,0 +1,28 @@ +#ifndef PROMETHEUS_H +#define PROMETHEUS_H + +#include + +#include +#include +#include + +class PrometheusExporter { + private: + std::shared_ptr registry_; + std::unique_ptr exposer_; + const std::string prometheus_name_; + + public: + PrometheusExporter(const std::string& prometheus_host, const std::string& prometheus_name) noexcept; + ~PrometheusExporter() noexcept = default; + + /// The family of counters for received bursts + auto burst_received_count() noexcept -> prometheus::Family&; + /// The family of counters for decoding errors on received bursts in the lower MAC + auto burst_lower_mac_decode_error_count() noexcept -> prometheus::Family&; + /// The family of counters for mismatched number of bursts in the downlink lower MAC + auto burst_lower_mac_mismatch_count() noexcept -> prometheus::Family&; +}; + +#endif // PROMETHEUS_H diff --git a/src/decoder.cpp b/src/decoder.cpp index 507fa89..d269255 100644 --- a/src/decoder.cpp +++ b/src/decoder.cpp @@ -25,22 +25,18 @@ Decoder::Decoder(unsigned receive_port, unsigned send_port, bool packed, std::optional input_file, std::optional output_file, bool iq_or_bit_stream, - std::optional uplink_scrambling_code) + std::optional uplink_scrambling_code, + std::shared_ptr& prometheus_exporter) : reporter_(std::make_shared(send_port)) , packed_(packed) , uplink_scrambling_code_(uplink_scrambling_code) , iq_or_bit_stream_(iq_or_bit_stream) { - lower_mac_ = std::make_shared(reporter_); + lower_mac_ = std::make_shared(reporter_, prometheus_exporter, uplink_scrambling_code); bit_stream_decoder_ = std::make_shared(lower_mac_, uplink_scrambling_code_.has_value()); iq_stream_decoder_ = std::make_unique(lower_mac_, bit_stream_decoder_, uplink_scrambling_code_.has_value()); - if (uplink_scrambling_code_.has_value()) { - // set scrambling_code for uplink - lower_mac_->set_scrambling_code(uplink_scrambling_code_.value()); - } - // read input file from file or from socket if (input_file.has_value()) { input_fd_ = open(input_file->c_str(), O_RDONLY); diff --git a/src/l2/access_assignment_channel.cpp b/src/l2/access_assignment_channel.cpp new file mode 100644 index 0000000..cc0b5b8 --- /dev/null +++ b/src/l2/access_assignment_channel.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Transit Live Mapping Solutionss + * All rights reserved. + * + * Authors: + * Marenz Schmidl + */ + +#include "l2/access_assignment_channel.hpp" +#include "utils/bit_vector.hpp" +#include + +AccessAssignmentChannel::AccessAssignmentChannel(const BurstType burst_type, const TimebaseCounter& time, + const std::vector& data) { + assert(data.size() == 14); + assert(is_downlink_burst(burst_type)); + + auto vec = BitVector(data); + + auto header = vec.take(2); + auto field1 = vec.take(6); + auto _field2 = vec.take(6); + + // TODO: parse uplink marker and some other things relevant for the uplink + if (time.frame_number() == 18) { + downlink_usage = DownlinkUsage::CommonControl; + } else { + if (header == 0b00) { + downlink_usage = DownlinkUsage::CommonControl; + } else { + switch (field1) { + case 0b00000: + downlink_usage = DownlinkUsage::Unallocated; + break; + case 0b00001: + downlink_usage = DownlinkUsage::AssignedControl; + break; + case 0b00010: + downlink_usage = DownlinkUsage::CommonControl; + break; + case 0b00011: + downlink_usage = DownlinkUsage::CommonAndAssignedControl; + break; + default: + downlink_usage = DownlinkUsage::Traffic; + downlink_traffic_usage_marker = field1; + break; + } + } + } +} + +auto operator<<(std::ostream& stream, const AccessAssignmentChannel& aac) -> std::ostream& { + stream << "[Channel] AACH downlink_usage: " << aac.downlink_usage; + if (aac.downlink_traffic_usage_marker) { + stream << " downlinkUsageTrafficMarker: " << *aac.downlink_traffic_usage_marker; + } + stream << std::endl; + return stream; +} \ No newline at end of file diff --git a/src/l2/broadcast_synchronization_channel.cpp b/src/l2/broadcast_synchronization_channel.cpp new file mode 100644 index 0000000..887899a --- /dev/null +++ b/src/l2/broadcast_synchronization_channel.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024 Transit Live Mapping Solutions + * All rights reserved. + * + * Authors: + * Marenz Schmidl + */ + +#include "l2/broadcast_synchronization_channel.hpp" +#include "l2/timebase_counter.hpp" +#include "utils/bit_vector.hpp" +#include +#include + +BroadcastSynchronizationChannel::BroadcastSynchronizationChannel(const BurstType burst_type, + const std::vector& data) { + assert(data.size() == 60); + assert(is_downlink_burst(burst_type)); + + auto vec = BitVector(data); + + assert(vec.bits_left() == 60); + + system_code = vec.take(4); + color_code = vec.take(6); + auto time_slot = vec.take(2) + 1; + auto frame_number = vec.take(5); + auto multi_frame_number = vec.take(6); + time = TimebaseCounter(time_slot, frame_number, multi_frame_number); + sharing_mode = vec.take(2); + time_slot_reserved_frames = vec.take(3); + up_lane_dtx = vec.take(1); + frame_18_extension = vec.take(1); + auto _reserved = vec.take(1); + + assert(vec.bits_left() == 29); + + mobile_country_code = vec.take(10); + mobile_network_code = vec.take(14); + dNwrk_broadcast_broadcast_supported = vec.take(1); + dNwrk_broadcast_enquiry_supported = vec.take(1); + cell_load_ca = vec.take(2); + late_entry_supported = vec.take(1); + + // 10 MSB of MCC + uint16_t lmcc = mobile_country_code & 0x03ff; + // 14 MSB of MNC + uint16_t lmnc = mobile_network_code & 0x3fff; + // 6 MSB of ColorCode + uint16_t lcolor_code = color_code & 0x003f; + + // 30 MSB bits + scrambling_code = lcolor_code | (lmnc << 6) | (lmcc << 20); + // scrambling initialized to 1 on bits 31-32 - 8.2.5.2 (54) + scrambling_code = (scrambling_code << 2) | 0x0003; +} + +auto operator<<(std::ostream& stream, const BroadcastSynchronizationChannel& bsc) -> std::ostream& { + stream << "[Channel] BSCH SYNC:" << std::endl; + stream << " System code: 0b" << std::bitset<4>(bsc.system_code) << std::endl; + stream << " Color code: " << std::to_string(bsc.color_code) << std::endl; + stream << " " << bsc.time << std::endl; + stream << " Scrambling code: " << std::to_string(bsc.scrambling_code) << std::endl; + std::string sharing_mode_map[] = {"Continuous transmission", "Carrier sharing", "MCCH sharing", + "Traffic carrier sharing"}; + stream << " Sharing mode: " << sharing_mode_map[bsc.sharing_mode] << std::endl; + uint8_t ts_reserved_frames_map[] = {1, 2, 3, 4, 6, 9, 12, 18}; + stream << " TS reserved frames: " << std::to_string(ts_reserved_frames_map[bsc.time_slot_reserved_frames]) + << " frames reserved per 2 multiframes" << std::endl; + stream << " " + << (bsc.up_lane_dtx ? "Discontinuous U-plane transmission is allowed" + : "Discontinuous U-plane transmission is not allowed") + << std::endl; + stream << " " << (bsc.frame_18_extension ? "Frame 18 extension allowed" : "No frame 18 extension") << std::endl; + + stream << "[Channel] BSCH D-MLE-SYNC:" << std::endl; + stream << " MCC: " << bsc.mobile_country_code << std::endl; + stream << " MNC: " << bsc.mobile_network_code << std::endl; + stream << " Neighbour cell broadcast: " + << (bsc.dNwrk_broadcast_broadcast_supported ? "supported" : "not supported") << std::endl; + stream << " Neighbour cell enquiry: " << (bsc.dNwrk_broadcast_enquiry_supported ? "supported" : "not supported") + << std::endl; + stream << " Cell load CA: "; + switch (bsc.cell_load_ca) { + case 0b00: + stream << "Cell load unknown"; + break; + case 0b01: + stream << "Low cell load"; + break; + case 0b10: + stream << "Medium cell load"; + break; + case 0b11: + stream << "High cell load"; + break; + default: + break; + } + stream << std::endl; + stream << " Late entry supported: " + << (bsc.late_entry_supported ? "Late entry available" : "Late entry not supported") << std::endl; + + return stream; +} \ No newline at end of file diff --git a/src/l2/lower_mac.cpp b/src/l2/lower_mac.cpp index 9aa3871..896e2d6 100644 --- a/src/l2/lower_mac.cpp +++ b/src/l2/lower_mac.cpp @@ -1,12 +1,28 @@ +#include "burst_type.hpp" +#include "l2/access_assignment_channel.hpp" +#include "l2/broadcast_synchronization_channel.hpp" #include #include #include -LowerMac::LowerMac(std::shared_ptr reporter) +LowerMac::LowerMac(std::shared_ptr reporter, std::shared_ptr& prometheus_exporter, + std::optional scrambling_code) : reporter_(reporter) { viter_bi_codec_1614_ = std::make_shared(); - upper_mac_ = std::make_shared(reporter_); + + // For decoupled uplink processing we need to inject a scrambling code. Inject it into the correct place that would + // normally be filled by a Synchronization Burst + if (scrambling_code.has_value()) { + sync_ = BroadcastSynchronizationChannel(); + sync_->scrambling_code = *scrambling_code; + } + + upper_mac_ = std::make_shared(reporter_, /*is_downlink=*/!scrambling_code.has_value()); + + if (prometheus_exporter) { + metrics_ = std::make_unique(prometheus_exporter); + } } static auto vectorAppend(const std::vector& vec, std::vector& res, std::size_t pos, @@ -14,16 +30,14 @@ static auto vectorAppend(const std::vector& vec, std::vector& std::copy(vec.begin() + pos, vec.begin() + pos + length, std::back_inserter(res)); } -auto LowerMac::process(const std::vector& frame, BurstType burst_type) -> std::vector> { - std::vector sb{}; +auto LowerMac::processChannels(const std::vector& frame, BurstType burst_type, + const BroadcastSynchronizationChannel& bsc) -> std::vector> { std::vector bkn1{}; std::vector bkn2{}; std::vector cb{}; std::vector> functions{}; - fmt::print("[Physical Channel] Decoding: {}\n", burst_type); - // The BLCH may be mapped onto block 2 of the downlink slots, when a SCH/HD, // SCH-P8/HD or a BSCH is mapped onto block 1. The number of BLCH occurrences // on one carrier shall not exceed one per 4 multiframe periods. @@ -34,32 +48,15 @@ auto LowerMac::process(const std::vector& frame, BurstType burst_type) // // The scrambling code has a special handling, see the specific comments where // it is used. - if (burst_type == BurstType::SynchronizationBurst) { - upper_mac_->incrementTn(); - - // sb contains BSCH - // ✅ done - uint8_t sb_desc[120]; - descramble(frame.data() + 94, sb_desc, 120, 0x0003); - uint8_t sb_dei[120]; - deinterleave(sb_desc, sb_dei, 120, 11); - sb = viter_bi_decode_1614(depuncture23(sb_dei, 120)); - if (check_crc_16_ccitt(sb.data(), 76)) { - sb = std::vector(sb.begin(), sb.begin() + 60); - functions.push_back(std::bind(&UpperMac::process_BSCH, upper_mac_, burst_type, sb)); - } + if (burst_type == BurstType::SynchronizationBurst) { // bb contains AACH // ✅ done uint8_t bb_desc[30]; - // XXX: upper_mac_->scrambling_code() may yield the wrong result!!! if the processing of BSCH - // is not done before this call, and it cannot! This means the following data of the first - // SynchronizationBurst is useless. However, the following SynchronizationBurst are decoded - // correctly since the scrambling code could, but proprobably will not change. - descramble(frame.data() + 252, bb_desc, 30, upper_mac_->scrambling_code()); + descramble(frame.data() + 252, bb_desc, 30, bsc.scrambling_code); std::vector bb_rm(14); reed_muller_3014_decode(bb_desc, bb_rm.data()); - functions.push_back(std::bind(&UpperMac::process_AACH, upper_mac_, burst_type, bb_rm)); + auto _aach = AccessAssignmentChannel(burst_type, bsc.time, bb_rm); // bkn2 block // ✅ done @@ -67,93 +64,67 @@ auto LowerMac::process(const std::vector& frame, BurstType burst_type) // see ETSI EN 300 392-2 V3.8.1 (2016-08) Figure 8.6: Error control // structure for π4DQPSK logical channels (part 2) uint8_t bkn2_desc[216]; - // XXX: upper_mac_->scrambling_code() may yield the wrong result!!! if the processing of BSCH - // is not done before this call, and it cannot! This means the following data of the first - // SynchronizationBurst is useless. However, the following SynchronizationBurst are decoded - // correctly since the scrambling code could, but proprobably will not change. - descramble(frame.data() + 282, bkn2_desc, 216, upper_mac_->scrambling_code()); + descramble(frame.data() + 282, bkn2_desc, 216, bsc.scrambling_code); uint8_t bkn2_dei[216]; deinterleave(bkn2_desc, bkn2_dei, 216, 101); bkn2 = viter_bi_decode_1614(depuncture23(bkn2_dei, 216)); - // if the crc does not work, then it might be a BLCH if (check_crc_16_ccitt(bkn2.data(), 140)) { bkn2 = std::vector(bkn2.begin(), bkn2.begin() + 124); // SCH/HD or BNCH mapped - functions.push_back(std::bind(&UpperMac::process_SCH_HD, upper_mac_, burst_type, bkn2)); + functions.emplace_back(std::bind(&UpperMac::process_SCH_HD, upper_mac_, burst_type, bkn2)); } } else if (burst_type == BurstType::NormalDownlinkBurst) { - upper_mac_->incrementTn(); - // bb contains AACH // ✅ done std::vector bb{}; vectorAppend(frame, bb, 230, 14); vectorAppend(frame, bb, 266, 16); uint8_t bb_des[30]; - // XXX: upper_mac_->scrambling_code() may yield the wrong result!!! if the processing of the - // SynchronizationBurst is not done before this call, this may mean that the first packets are - // not decoded correctly. - descramble(bb.data(), bb_des, 30, upper_mac_->scrambling_code()); + descramble(bb.data(), bb_des, 30, bsc.scrambling_code); std::vector bb_rm(14); reed_muller_3014_decode(bb_des, bb_rm.data()); - functions.push_back(std::bind(&UpperMac::process_AACH, upper_mac_, burst_type, bb_rm)); + auto aach = AccessAssignmentChannel(burst_type, bsc.time, bb_rm); // TCH or SCH/F vectorAppend(frame, bkn1, 14, 216); vectorAppend(frame, bkn1, 282, 216); uint8_t bkn1_desc[432]; - // XXX: upper_mac_->scrambling_code() may yield the wrong result!!! if the processing of the - // SynchronizationBurst is not done before this call, this may mean that the first packets are - // not decoded correctly. - descramble(bkn1.data(), bkn1_desc, 432, upper_mac_->scrambling_code()); - - // TODO: fix this decoding order + descramble(bkn1.data(), bkn1_desc, 432, bsc.scrambling_code); uint8_t bkn1_dei[432]; deinterleave(bkn1_desc, bkn1_dei, 432, 103); bkn1 = viter_bi_decode_1614(depuncture23(bkn1_dei, 432)); - auto bkn1_crc = check_crc_16_ccitt(bkn1.data(), 284); - bkn1 = std::vector(bkn1.begin(), bkn1.begin() + 268); - functions.push_back([this, burst_type, bkn1, bkn1_crc]() { - if (upper_mac_->downlink_usage() == DownlinkUsage::Traffic && upper_mac_->time_slot() <= 17) { - // TODO: handle TCH + if (aach.downlink_usage == DownlinkUsage::Traffic) { + // TODO: handle TCH + functions.emplace_back([aach]() { std::cout << "AACH indicated traffic with usagemarker: " - << std::to_string(upper_mac_->downlink_traffic_usage_marker()) << std::endl; - } else { - // control channel - // ✅done - if (bkn1_crc) { - upper_mac_->process_SCH_F(burst_type, bkn1); - } + << std::to_string(*aach.downlink_traffic_usage_marker) << std::endl; + }); + } else { + // control channel + // ✅done + if (check_crc_16_ccitt(bkn1.data(), 284)) { + bkn1 = std::vector(bkn1.begin(), bkn1.begin() + 268); + functions.emplace_back(std::bind(&UpperMac::process_SCH_F, upper_mac_, burst_type, bkn1)); } - }); + } } else if (burst_type == BurstType::NormalDownlinkBurstSplit) { - upper_mac_->incrementTn(); - // bb contains AACH // ✅ done std::vector bb{}; vectorAppend(frame, bb, 230, 14); vectorAppend(frame, bb, 266, 16); uint8_t bb_desc[30]; - // XXX: upper_mac_->scrambling_code() may yield the wrong result!!! if the processing of the - // SynchronizationBurst is not done before this call, this may mean that the first packets are - // not decoded correctly. - descramble(bb.data(), bb_desc, 30, upper_mac_->scrambling_code()); + descramble(bb.data(), bb_desc, 30, bsc.scrambling_code); std::vector bb_rm(14); reed_muller_3014_decode(bb_desc, bb_rm.data()); - functions.push_back(std::bind(&UpperMac::process_AACH, upper_mac_, burst_type, bb_rm)); + auto aach = AccessAssignmentChannel(burst_type, bsc.time, bb_rm); - // STCH + TCH - // STCH + STCH uint8_t bkn1_desc[216]; uint8_t bkn2_desc[216]; - // XXX: upper_mac_->scrambling_code() may yield the wrong result!!! if the processing of the - // SynchronizationBurst is not done before this call, this may mean that the first packets are - // not decoded correctly. - descramble(frame.data() + 14, bkn1_desc, 216, upper_mac_->scrambling_code()); - descramble(frame.data() + 282, bkn2_desc, 216, upper_mac_->scrambling_code()); + descramble(frame.data() + 14, bkn1_desc, 216, bsc.scrambling_code); + descramble(frame.data() + 282, bkn2_desc, 216, bsc.scrambling_code); // We precompute as much as possible. Only when all computations are done we schedule the task. uint8_t bkn1_dei[216]; @@ -165,40 +136,43 @@ auto LowerMac::process(const std::vector& frame, BurstType burst_type) if (check_crc_16_ccitt(bkn1.data(), 140)) { bkn1 = std::vector(bkn1.begin(), bkn1.begin() + 124); - functions.push_back([this, burst_type, bkn1]() { - if (upper_mac_->downlink_usage() == DownlinkUsage::Traffic && upper_mac_->time_slot() <= 17) { - upper_mac_->process_STCH(burst_type, bkn1); - } else { - // SCH/HD + SCH/HD - // SCH/HD + BNCH - // ✅done - upper_mac_->process_SCH_HD(burst_type, bkn1); - } - }); + if (aach.downlink_usage == DownlinkUsage::Traffic) { + // STCH + TCH + // STCH + STCH + functions.emplace_back(std::bind(&UpperMac::process_STCH, upper_mac_, burst_type, bkn1)); + } else { + // SCH/HD + SCH/HD + // SCH/HD + BNCH + // ✅done + functions.emplace_back(std::bind(&UpperMac::process_SCH_HD, upper_mac_, burst_type, bkn1)); + } } - if (check_crc_16_ccitt(bkn2.data(), 140)) { - bkn2 = std::vector(bkn2.begin(), bkn2.begin() + 124); - functions.push_back([this, burst_type, bkn2]() { - if (upper_mac_->downlink_usage() == DownlinkUsage::Traffic && upper_mac_->time_slot() <= 17) { - if (upper_mac_->second_slot_stolen()) { + auto bkn2_crc = check_crc_16_ccitt(bkn2.data(), 140); + bkn2 = std::vector(bkn2.begin(), bkn2.begin() + 124); + if (aach.downlink_usage == DownlinkUsage::Traffic) { + functions.emplace_back([this, burst_type, aach, bkn2, bkn2_crc]() { + if (upper_mac_->second_slot_stolen()) { + if (bkn2_crc) { upper_mac_->process_STCH(burst_type, bkn2); - } else { - // TODO: handle this TCH - std::cout << "AACH indicated traffic with usagemarker: " - << std::to_string(upper_mac_->downlink_traffic_usage_marker()) << std::endl; } } else { - // control channel - upper_mac_->process_SCH_HD(burst_type, bkn2); + // TODO: handle this TCH + std::cout << "AACH indicated traffic with usagemarker: " + << std::to_string(*aach.downlink_traffic_usage_marker) << std::endl; } }); + } else { + // control channel + if (bkn2_crc) { + functions.emplace_back(std::bind(&UpperMac::process_SCH_HD, upper_mac_, burst_type, bkn2)); + } } } else if (burst_type == BurstType::ControlUplinkBurst) { vectorAppend(frame, cb, 4, 84); vectorAppend(frame, cb, 118, 84); uint8_t cb_desc[168]; - descramble(cb.data(), cb_desc, 168, upper_mac_->scrambling_code()); + descramble(cb.data(), cb_desc, 168, bsc.scrambling_code); // XXX: assume to be control channel uint8_t cb_dei[168]; @@ -206,37 +180,41 @@ auto LowerMac::process(const std::vector& frame, BurstType burst_type) cb = viter_bi_decode_1614(depuncture23(cb_dei, 168)); if (check_crc_16_ccitt(cb.data(), 108)) { cb = std::vector(cb.begin(), cb.begin() + 92); - functions.push_back(std::bind(&UpperMac::process_SCH_HU, upper_mac_, burst_type, cb)); + functions.emplace_back(std::bind(&UpperMac::process_SCH_HU, upper_mac_, burst_type, cb)); } } else if (burst_type == BurstType::NormalUplinkBurst) { vectorAppend(frame, bkn1, 4, 216); vectorAppend(frame, bkn1, 242, 216); uint8_t bkn1_desc[432]; - descramble(bkn1.data(), bkn1_desc, 432, upper_mac_->scrambling_code()); + descramble(bkn1.data(), bkn1_desc, 432, bsc.scrambling_code); - // XXX: assume to be control channel + // TODO: this can either be a SCH_H or a TCH, depending on the uplink usage marker, but the uplink + // and downlink processing are seperated. We assume a SCH_H here. uint8_t bkn1_dei[432]; deinterleave(bkn1_desc, bkn1_dei, 432, 103); bkn1 = viter_bi_decode_1614(depuncture23(bkn1_dei, 432)); if (check_crc_16_ccitt(bkn1.data(), 284)) { bkn1 = std::vector(bkn1.begin(), bkn1.begin() + 268); // fmt::print("NUB Burst crc good\n"); - functions.push_back(std::bind(&UpperMac::process_SCH_F, upper_mac_, burst_type, bkn1)); + functions.emplace_back(std::bind(&UpperMac::process_SCH_F, upper_mac_, burst_type, bkn1)); + } else { + // Either we have a faulty burst or a TCH here. } } else if (burst_type == BurstType::NormalUplinkBurstSplit) { - // TODO: finish NormalUplinkBurstSplit implementation uint8_t bkn1_desc[216]; uint8_t bkn2_desc[216]; - descramble(frame.data() + 4, bkn1_desc, 216, upper_mac_->scrambling_code()); - descramble(frame.data() + 242, bkn2_desc, 216, upper_mac_->scrambling_code()); + descramble(frame.data() + 4, bkn1_desc, 216, bsc.scrambling_code); + descramble(frame.data() + 242, bkn2_desc, 216, bsc.scrambling_code); uint8_t bkn1_dei[216]; deinterleave(bkn1_desc, bkn1_dei, 216, 101); bkn1 = viter_bi_decode_1614(depuncture23(bkn1_dei, 216)); + // STCH + TCH + // STCH + STCH if (check_crc_16_ccitt(bkn1.data(), 140)) { bkn1 = std::vector(bkn1.begin(), bkn1.begin() + 124); // fmt::print("NUB_S 1 Burst crc good\n"); - functions.push_back(std::bind(&UpperMac::process_STCH, upper_mac_, burst_type, bkn1)); + functions.emplace_back(std::bind(&UpperMac::process_STCH, upper_mac_, burst_type, bkn1)); } uint8_t bkn2_dei[216]; @@ -244,10 +222,12 @@ auto LowerMac::process(const std::vector& frame, BurstType burst_type) bkn2 = viter_bi_decode_1614(depuncture23(bkn2_dei, 216)); if (check_crc_16_ccitt(bkn2.data(), 140)) { bkn2 = std::vector(bkn2.begin(), bkn2.begin() + 124); - functions.push_back([this, burst_type, bkn2]() { + functions.emplace_back([this, burst_type, bkn2]() { if (upper_mac_->second_slot_stolen()) { // fmt::print("NUB_S 2 Burst crc good\n"); upper_mac_->process_STCH(burst_type, bkn2); + } else { + // TODO: handle this TCH } }); } @@ -257,3 +237,73 @@ auto LowerMac::process(const std::vector& frame, BurstType burst_type) return functions; } + +auto LowerMac::process(const std::vector& frame, BurstType burst_type) -> std::vector> { + std::vector sb{}; + + // Set to true if there was some decoding error in the lower MAC + bool decode_error = false; + + fmt::print("[Physical Channel] Decoding: {}\n", burst_type); + + // Once we received the Synchronization on the downlink, increment the time counter for every received burst. + // We do not have any time handling for uplink processing. + if (sync_ && is_downlink_burst(burst_type)) { + sync_->time.increment(); + } + + if (burst_type == BurstType::SynchronizationBurst) { + std::optional current_sync; + + // sb contains BSCH + // ✅ done + uint8_t sb_desc[120]; + descramble(frame.data() + 94, sb_desc, 120, 0x0003); + uint8_t sb_dei[120]; + deinterleave(sb_desc, sb_dei, 120, 11); + sb = viter_bi_decode_1614(depuncture23(sb_dei, 120)); + if (check_crc_16_ccitt(sb.data(), 76)) { + sb = std::vector(sb.begin(), sb.begin() + 60); + current_sync = BroadcastSynchronizationChannel(burst_type, sb); + } else { + decode_error |= true; + } + + // Update the mismatching received number of bursts metrics + if (current_sync && sync_ && metrics_) { + metrics_->increment(/*current_timestamp=*/current_sync->time, /*expected_timestamp=*/sync_->time); + } + sync_ = current_sync; + } + + std::vector> callbacks{}; + // We got a sync, continue with further processing of channels + if (sync_) { + callbacks = processChannels(frame, burst_type, *sync_); + + // We assume to encountered an error decoding in the lower MAC if we do not get the correct number of callbacks + // back. For normal channels this is one. For split channels this is two. + std::size_t correct_number_of_callbacks; + switch (burst_type) { + case BurstType::ControlUplinkBurst: + case BurstType::NormalUplinkBurst: + case BurstType::NormalDownlinkBurst: + case BurstType::SynchronizationBurst: + correct_number_of_callbacks = 1; + break; + case BurstType::NormalDownlinkBurstSplit: + case BurstType::NormalUplinkBurstSplit: + correct_number_of_callbacks = 2; + break; + } + + decode_error |= callbacks.size() != correct_number_of_callbacks; + } + + // Update the received burst type metrics + if (metrics_) { + metrics_->increment(burst_type, decode_error); + } + + return callbacks; +} diff --git a/src/l2/timebase_counter.cpp b/src/l2/timebase_counter.cpp new file mode 100644 index 0000000..969d4ce --- /dev/null +++ b/src/l2/timebase_counter.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Transit Live Mapping Solutions + * All rights reserved. + * + * Authors: + * Marenz Schmidl + */ + +#include "l2/timebase_counter.hpp" + +auto TimebaseCounter::increment() noexcept -> void { + time_slot_++; + + // TODO: remove magic numbers + // time slot + if (time_slot_ > 4) { + frame_number_++; + time_slot_ = 1; + } + + // frame number + if (frame_number_ > 18) { + multi_frame_number_++; + frame_number_ = 1; + } + + // multi-frame number + if (multi_frame_number_ > 60) { + multi_frame_number_ = 1; + } +} + +auto operator<<(std::ostream& stream, const TimebaseCounter& tc) -> std::ostream& { + stream << "TN/FN/MN: " << std::to_string(tc.time_slot_) << "/" << std::to_string(tc.frame_number_) << "/" + << std::to_string(tc.multi_frame_number_); + return stream; +} \ No newline at end of file diff --git a/src/l2/upper_mac.cpp b/src/l2/upper_mac.cpp index dd8eb40..f49b0e7 100644 --- a/src/l2/upper_mac.cpp +++ b/src/l2/upper_mac.cpp @@ -4,110 +4,6 @@ #include #include -void UpperMac::incrementTn() { - time_slot_++; - - // TODO: remove magic numbers - // time slot - if (time_slot_ > 4) { - frame_number_++; - time_slot_ = 1; - } - - // frame number - if (frame_number_ > 18) { - multi_frame_number_++; - frame_number_ = 1; - } - - // multi-frame number - if (multi_frame_number_ > 60) { - multi_frame_number_ = 1; - } - - std::cout << "[TIME] TN: " << time_slot_ << " FN: " << frame_number_ << " MN: " << multi_frame_number_ << std::endl; -} - -/** - * @brief Process ACCESS-ASSIGN - see 21.4.7.2 - * - */ -void UpperMac::process_AACH(const BurstType burst_type, const std::vector& data) { - assert(data.size() == 14); - assert(is_downlink_burst(burst_type)); - - auto vec = BitVector(data); - - auto header = vec.take(2); - auto field1 = vec.take(6); - auto _field2 = vec.take(6); - - // TODO: parse uplink marker and some other things relevant for the uplink - if (frame_number_ == 18) { - downlink_usage_ = DownlinkUsage::CommonControl; - } else { - if (header == 0b00) { - downlink_usage_ = DownlinkUsage::CommonControl; - } else { - switch (field1) { - case 0b00000: - downlink_usage_ = DownlinkUsage::Unallocated; - break; - case 0b00001: - downlink_usage_ = DownlinkUsage::AssignedControl; - break; - case 0b00010: - downlink_usage_ = DownlinkUsage::CommonControl; - break; - case 0b00011: - downlink_usage_ = DownlinkUsage::CommonAndAssignedControl; - break; - default: - downlink_usage_ = DownlinkUsage::Traffic; - downlink_traffic_usage_marker_ = field1; - break; - } - } - } - - std::cout << "[Channel] AACH downlink_usage: " << downlink_usage_ - << " downlinkUsageTrafficMarker: " << downlink_traffic_usage_marker_ << std::endl; -} - -/** - * @brief Process SYNC - see 21.4.4.2 - * - */ -void UpperMac::process_BSCH(const BurstType burst_type, const std::vector& data) { - assert(data.size() == 60); - assert(is_downlink_burst(burst_type)); - - auto vec = BitVector(data); - - assert(vec.bits_left() == 60); - - system_code_ = vec.take(4); - color_code_ = vec.take(6); - time_slot_ = vec.take(2) + 1; - frame_number_ = vec.take(5); - multi_frame_number_ = vec.take(6); - sharing_mode_ = vec.take(2); - time_slot_reserved_frames_ = vec.take(3); - up_lane_dtx_ = vec.take(1); - frame_18_extension_ = vec.take(1); - auto _reserved = vec.take(1); - - assert(vec.bits_left() == 29); - - mobile_link_entity_->service_DMle_sync(vec); - update_scrambling_code(); - - sync_received_ = true; - - // std::cout << *this; - std::cout << "[Channel] BSCH" << std::endl; -} - void UpperMac::process_SCH_HD(const BurstType burst_type, const std::vector& data) { assert(is_downlink_burst(burst_type)); @@ -911,43 +807,7 @@ void UpperMac::remove_fill_bits(BitVector& vec) { remove_fill_bits_ = false; } -void UpperMac::update_scrambling_code() { - // 10 MSB of MCC - uint16_t lmcc = mobile_link_entity_->mobile_country_code() & 0x03ff; - // 14 MSB of MNC - uint16_t lmnc = mobile_link_entity_->mobile_network_code() & 0x3fff; - // 6 MSB of ColorCode - uint16_t lcolor_code = color_code_ & 0x003f; - - // 30 MSB bits - scrambling_code_ = lcolor_code | (lmnc << 6) | (lmcc << 20); - // scrambling initialized to 1 on bits 31-32 - 8.2.5.2 (54) - scrambling_code_ = (scrambling_code_ << 2) | 0x0003; -} - auto operator<<(std::ostream& stream, const UpperMac& upperMac) -> std::ostream& { - if (upperMac.sync_received_) { - stream << "SYNC:" << std::endl; - stream << " System code: 0b" << std::bitset<4>(upperMac.system_code_) << std::endl; - stream << " Color code: " << std::to_string(upperMac.color_code_) << std::endl; - stream << " TN/FN/MN: " << std::to_string(upperMac.time_slot_) << "/" << std::to_string(upperMac.frame_number_) - << "/" << std::to_string(upperMac.multi_frame_number_) << std::endl; - stream << " Scrambling code: " << std::to_string(upperMac.scrambling_code_) << std::endl; - std::string sharing_mode_map[] = {"Continuous transmission", "Carrier sharing", "MCCH sharing", - "Traffic carrier sharing"}; - stream << " Sharing mode: " << sharing_mode_map[upperMac.sharing_mode_] << std::endl; - uint8_t ts_reserved_frames_map[] = {1, 2, 3, 4, 6, 9, 12, 18}; - stream << " TS reserved frames: " - << std::to_string(ts_reserved_frames_map[upperMac.time_slot_reserved_frames_]) - << " frames reserved per 2 multiframes" << std::endl; - stream << " " - << (upperMac.up_lane_dtx_ ? "Discontinuous U-plane transmission is allowed" - : "Discontinuous U-plane transmission is not allowed") - << std::endl; - stream << " " << (upperMac.frame_18_extension_ ? "Frame 18 extension allowed" : "No frame 18 extension") - << std::endl; - } - if (upperMac.system_info_received_) { stream << "SYSINFO:" << std::endl; stream << " DL " << std::to_string(upperMac.downlink_frequency_) << "Hz UL " diff --git a/src/l3/mobile_link_entity.cpp b/src/l3/mobile_link_entity.cpp index 7cab1fe..e3213e2 100644 --- a/src/l3/mobile_link_entity.cpp +++ b/src/l3/mobile_link_entity.cpp @@ -3,22 +3,6 @@ #include -void MobileLinkEntity::service_DMle_sync(BitVector& vec) { - assert(vec.bits_left() == 29); - - mobile_country_code_ = vec.take(10); - mobile_network_code_ = vec.take(14); - dNwrk_broadcast_broadcast_supported_ = vec.take(1); - dNwrk_broadcast_enquiry_supported_ = vec.take(1); - cell_load_ca_ = vec.take(2); - late_entry_supported_ = vec.take(1); - - sync_received_ = true; - is_downlink_ = true; - - // std::cout << *this; -} - void MobileLinkEntity::service_DMle_system_info(BitVector& vec) { assert(vec.bits_left() == 42); @@ -110,36 +94,6 @@ void MobileLinkEntity::service_data_pdu(const AddressType address, BitVector& ve } auto operator<<(std::ostream& stream, const MobileLinkEntity& mle) -> std::ostream& { - if (mle.sync_received_) { - stream << "D-MLE-SYNC:" << std::endl; - stream << " MCC: " << mle.mobile_country_code_ << std::endl; - stream << " MNC: " << mle.mobile_network_code_ << std::endl; - stream << " Neighbour cell broadcast: " - << (mle.dNwrk_broadcast_broadcast_supported_ ? "supported" : "not supported") << std::endl; - stream << " Neighbour cell enquiry: " - << (mle.dNwrk_broadcast_enquiry_supported_ ? "supported" : "not supported") << std::endl; - stream << " Cell load CA: "; - switch (mle.cell_load_ca_) { - case 0b00: - stream << "Cell load unknown"; - break; - case 0b01: - stream << "Low cell load"; - break; - case 0b10: - stream << "Medium cell load"; - break; - case 0b11: - stream << "High cell load"; - break; - default: - break; - } - stream << std::endl; - stream << " Late entry supported: " - << (mle.late_entry_supported_ ? "Late entry available" : "Late entry not supported") << std::endl; - } - if (mle.system_info_received_) { stream << "D-MLE-SYSINFO:" << std::endl; stream << " Location Area (LA): " << mle.location_area_ << std::endl; diff --git a/src/main.cpp b/src/main.cpp index 3031ada..19432c2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,10 @@ auto main(int argc, char** argv) -> int { bool packed, iq_or_bit_stream; std::optional input_file, output_file; std::optional uplink_scrambling_code; + std::optional prometheus_address; + std::optional prometheus_name; + + std::shared_ptr prometheus_exporter; struct sigaction signal_action {}; signal_action.sa_handler = sigint_handler; @@ -38,6 +42,8 @@ auto main(int argc, char** argv) -> int { ("P,packed", "pack rx data (1 byte = 8 bits)", cxxopts::value()->default_value("false")) ("iq", "Receive IQ instead of bitstream", cxxopts::value()->default_value("false")) ("uplink", " enable uplink parsing with predefined scrambilng code", cxxopts::value>(uplink_scrambling_code)) + ("prometheus-address", " on which ip and port the webserver for prometheus should listen. example: 127.0.0.1:9010", cxxopts::value>(prometheus_address)) + ("prometheus-name", " the name which is included in the prometheus metrics", cxxopts::value>(prometheus_name)) ; // clang-format on @@ -55,13 +61,18 @@ auto main(int argc, char** argv) -> int { debug_level = result["d"].as(); packed = result["packed"].as(); iq_or_bit_stream = result["iq"].as(); + + if (prometheus_address) { + prometheus_exporter = std::make_shared( + *prometheus_address, prometheus_name.value_or("Unnamed Tetra Decoder")); + } } catch (std::exception& e) { std::cout << "error parsing options: " << e.what() << std::endl; return EXIT_FAILURE; } auto decoder = std::make_unique(receive_port, send_port, packed, input_file, output_file, iq_or_bit_stream, - uplink_scrambling_code); + uplink_scrambling_code, prometheus_exporter); if (input_file.has_value()) { std::cout << "Reading from input file " << *input_file << std::endl; diff --git a/src/prometheus.cpp b/src/prometheus.cpp new file mode 100644 index 0000000..efa35a7 --- /dev/null +++ b/src/prometheus.cpp @@ -0,0 +1,33 @@ +#include "prometheus.h" + +PrometheusExporter::PrometheusExporter(const std::string& prometheus_host, const std::string& prometheus_name) noexcept + : prometheus_name_(std::move(prometheus_name)) { + exposer_ = std::make_unique(prometheus_host); + registry_ = std::make_shared(); + + exposer_->RegisterCollectable(registry_); +} + +auto PrometheusExporter::burst_received_count() noexcept -> prometheus::Family& { + return prometheus::BuildCounter() + .Name("burst_received_count") + .Help("Incrementing counter of the received bursts") + .Labels({{"name", prometheus_name_}}) + .Register(*registry_); +} + +auto PrometheusExporter::burst_lower_mac_decode_error_count() noexcept -> prometheus::Family& { + return prometheus::BuildCounter() + .Name("burst_lower_mac_decode_error_count") + .Help("Incrementing counter of the decoding errors on received bursts in the lower MAC") + .Labels({{"name", prometheus_name_}}) + .Register(*registry_); +} + +auto PrometheusExporter::burst_lower_mac_mismatch_count() noexcept -> prometheus::Family& { + return prometheus::BuildCounter() + .Name("burst_lower_mac_mismatch_count") + .Help("Incrementing counter of the number of mismatched bursts in the lower MAC on downlink") + .Labels({{"name", prometheus_name_}}) + .Register(*registry_); +}