diff --git a/src/core/libs/osnma_msg_receiver.h b/src/core/libs/osnma_msg_receiver.h index 412296f12..cda571321 100644 --- a/src/core/libs/osnma_msg_receiver.h +++ b/src/core/libs/osnma_msg_receiver.h @@ -146,12 +146,12 @@ private: // Provide access to inner functions to Gtest FRIEND_TEST(OsnmaMsgReceiverTest, TeslaKeyVerification); - FRIEND_TEST(OsnmaMsgReceiverTest, OsnmaTestVectorsSimulation); FRIEND_TEST(OsnmaMsgReceiverTest, TagVerification); FRIEND_TEST(OsnmaMsgReceiverTest, BuildTagMessageM0); FRIEND_TEST(OsnmaMsgReceiverTest, VerifyPublicKey); FRIEND_TEST(OsnmaMsgReceiverTest, ComputeBaseLeaf); FRIEND_TEST(OsnmaMsgReceiverTest, ComputeMerkleRoot); + FRIEND_TEST(OsnmaTestVectors, OsnmaTestVectorsSimulation); }; diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index d7bbab2ee..deca361e3 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -550,6 +550,18 @@ if(ENABLE_UNIT_TESTING_EXTRA) EXPECTED_HASH MD5=066d0d8434a8bc81e161778b7c34cc07 ) endif() + if(NOT EXISTS ${GNSSSDR_BINARY_DIR}/thirdparty/osnma_tests/Test_vectors.zip) + message(STATUS "Downloading file: Test_vectors.zip") + file(DOWNLOAD https://www.gsc-europa.eu/sites/default/files/sites/all/files/Test_vectors.zip + ${GNSSSDR_BINARY_DIR}/thirdparty/osnma_tests/Test_vectors.zip + SHOW_PROGRESS + EXPECTED_HASH MD5=8158aebee735652c9398e5bb6d944364 + ) + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xzf ${GNSSSDR_BINARY_DIR}/thirdparty/osnma_tests/Test_vectors.zip + WORKING_DIRECTORY ${GNSSSDR_BINARY_DIR}/thirdparty/osnma_tests/ + ) + endif() message(STATUS "Done.") if(ENABLE_INSTALL_TESTS) install(FILES ${GNSSSDR_BINARY_DIR}/thirdparty/signal_samples/gps_l2c_m_prn7_5msps.dat DESTINATION share/gnss-sdr/signal_samples) @@ -652,6 +664,7 @@ if(ENABLE_UNIT_TESTING) if(GNSSTK_OLDER_THAN_9) target_compile_definitions(run_tests PRIVATE -DGNSSTK_OLDER_THAN_9=1) endif() + target_compile_definitions(run_tests PRIVATE -DBASE_OSNMA_TEST_VECTORS="${GNSSSDR_BINARY_DIR}/thirdparty/osnma_tests/Test_vectors/") endif() xcode_remove_warning_duplicates(run_tests) if(ENABLE_STRIP) @@ -1356,53 +1369,6 @@ else() endif() endif() - -######################################################### osnma_msg_receiver_test - -if(NOT ENABLE_PACKAGING AND NOT ENABLE_FPGA) - set(OSNMA_MSG_RECEIVER_TEST_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/single_test_main.cc - ${CMAKE_CURRENT_SOURCE_DIR}/unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc - ) - - if(USE_CMAKE_TARGET_SOURCES) - add_executable(osnma_msg_receiver_test) - target_sources(osnma_msg_receiver_test PRIVATE ${OSNMA_MSG_RECEIVER_TEST_SOURCES}) - else() - add_executable(osnma_msg_receiver_test ${OSNMA_MSG_RECEIVER_TEST_SOURCES}) - endif() - - target_link_libraries(osnma_msg_receiver_test - PRIVATE - gnss_sdr_flags - Boost::thread - GTest::GTest - GTest::Main - core_libs - Gnuradio::blocks - Gnuradio::runtime - Gnuradio::filter # workaround for old systems - ) - if(ENABLE_GLOG_AND_GFLAGS) - target_link_libraries(osnma_msg_receiver_test PRIVATE Gflags::gflags Glog::glog) - target_compile_definitions(osnma_msg_receiver_test PRIVATE -DUSE_GLOG_AND_GFLAGS=1) - else() - target_link_libraries(osnma_msg_receiver_test PRIVATE absl::flags absl::flags_parse absl::log absl::log_initialize) - target_link_libraries(osnma_msg_receiver_test INTERFACE "$") - endif() - - xcode_remove_warning_duplicates(osnma_msg_receiver_test) # TODO - unsure if needed - - add_test(osnma_msg_receiver_test osnma_msg_receiver_test) - - set_property(TEST osnma_msg_receiver_test PROPERTY TIMEOUT 30) - - target_include_directories(osnma_msg_receiver_test - PRIVATE - ${GNSSSDR_SOURCE_DIR}/src/core/system_parameters - ) -endif() - if(ENABLE_BENCHMARKS) add_subdirectory(benchmarks) endif() diff --git a/src/tests/test_main.cc b/src/tests/test_main.cc index 7990ca258..bcd2b7018 100644 --- a/src/tests/test_main.cc +++ b/src/tests/test_main.cc @@ -113,7 +113,7 @@ private: #include "unit-tests/signal-processing-blocks/adapter/pass_through_test.cc" #include "unit-tests/signal-processing-blocks/libs/item_type_helpers_test.cc" #include "unit-tests/signal-processing-blocks/osnma/gnss_crypto_test.cc" -// #include "unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc" +#include "unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc" #include "unit-tests/signal-processing-blocks/pvt/geohash_test.cc" #include "unit-tests/signal-processing-blocks/pvt/nmea_printer_test.cc" #include "unit-tests/signal-processing-blocks/pvt/rinex_printer_test.cc" @@ -184,6 +184,7 @@ private: #include "unit-tests/signal-processing-blocks/acquisition/glonass_l1_ca_pcps_acquisition_test.cc" #include "unit-tests/signal-processing-blocks/acquisition/gps_l2_m_pcps_acquisition_test.cc" #include "unit-tests/signal-processing-blocks/tracking/gps_l2_m_dll_pll_tracking_test.cc" +#include "unit-tests/signal-processing-blocks/osnma/osnma_test_vectors.cc" #endif // #include "unit-tests/signal-processing-blocks/pvt/rtklib_solver_test.cc" #include "unit-tests/signal-processing-blocks/tracking/gps_l1_ca_dll_pll_tracking_test.cc" diff --git a/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc b/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc index 1d2c628ae..05da06b9d 100644 --- a/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc +++ b/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc @@ -1,5 +1,5 @@ /*! - * \file osmna_msg_receiver_testt.cc + * \file osmna_msg_receiver_test.cc * \brief Tests for the osnma_msg_receiver class. * \author Carles Fernandez, 2023-2024. cfernandez(at)cttc.es * Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de @@ -28,30 +28,12 @@ #if USE_GLOG_AND_GFLAGS #include // for LOG -#include #else #include #endif -struct TestVector -{ - int svId; - int numNavBits; - std::vector navBits; -}; - - -// TODO - parametrize class for different configurations (config_1, config_2, etc.. potentially 5 or 6 more) an make sure wont affect current TEST_F -// note: until the test is parametrized for configuration 1 and 2, in order to change between them you have to comment/uncomment the respective calls in this test, identified with comments // conf. 1/2 -// log_name, input_time, crtFilePath, merkleFilePath, testVectors class OsnmaMsgReceiverTest : public ::testing::Test { -public: - static std::vector parseNavBits(const std::string& hex); - static std::vector readTestVectorsFromFile(const std::string& filename); - std::string bytes_to_str(const std::vector& bytes); - std::vector extract_page_bytes(const TestVector& tv, const int byte_index, const int num_bytes); - protected: Osnma_Helper helper; osnma_msg_receiver_sptr osnma; @@ -63,22 +45,13 @@ protected: std::tm GST_START_EPOCH = {0, 0, 0, 22, 8 - 1, 1999 - 1900, 0}; // months start with 0 and years since 1900 in std::tm const uint32_t LEAP_SECONDS = 0; // 13 + 5; void set_time(std::tm& input); - // std::string log_name {"CONFIG1-2023-08-16-PKID1-OSNMA"}; - std::string log_name{"CONFIG2-2023-07-27-PKID2-MT2-OSNMA"}; // TODO - google::InitGoogleLogging(log_name.c_str()); but cannot be called twice - void initializeGoogleLog(); void SetUp() override { - initializeGoogleLog(); // std::tm input_time = {0, 0, 5, 16, 8 - 1, 2023 - 1900, 0}; // conf. 1 std::tm input_time = {0, 0, 0, 27, 7 - 1, 2023 - 1900, 0}; // conf. 2 set_time(input_time); - // std::string crtFilePath = "/home/cgm/CLionProjects/osnma/data/OSNMA_PublicKey_20230803105952_newPKID_1.crt"; // conf. 1 - // std::string merkleFilePath = "/home/cgm/CLionProjects/osnma/data/OSNMA_MerkleTree_20230803105953_newPKID_1.xml"; - std::string crtFilePath = "/home/cgm/CLionProjects/osnma/data/OSNMA_PublicKey_20230720113300_newPKID_2.crt"; // conf. 2 - std::string merkleFilePath = "/home/cgm/CLionProjects/osnma/data/OSNMA_MerkleTree_20230720113300_newPKID_2.xml"; - std::string rootKeyFilePath = ROOTKEYFILE_DEFAULT; - osnma = osnma_msg_receiver_make(crtFilePath, merkleFilePath, ROOTKEYFILE_DEFAULT); + osnma = osnma_msg_receiver_make("", ""); } }; @@ -220,15 +193,16 @@ TEST_F(OsnmaMsgReceiverTest, TagVerification) osnma->d_tesla_keys[TOW_Key_Tag0] = {0x69, 0xC0, 0x0A, 0xA7, 0x36, 0x42, 0x37, 0xA6, 0x5E, 0xBF, 0x00, 0x6A, 0xD8, 0xDD, 0xBC, 0x73}; // K4 osnma->d_osnma_data.d_dsm_kroot_message.mf = 0; osnma->d_nav_data_manager->add_navigation_data( - "000011101001011001000100000101000111010110100100100101100000000000" - "011101101011001111101110101010000001010000011011111100000011101011" - "011100101101011010101011011011001001110111101011110110111111001111" - "001000011111101110011000111111110111111010000011101011111111110000" - "110111000000100000001110110000110110001110000100001110101100010100" - "110100010001000110001110011010110000111010000010000000000001101000" - "000000000011100101100100010000000000000110110100110001111100000000" - "000000100110100000000101010010100000001011000010001001100000011111" - "110111111111000000000", PRNa, TOW_NavData); + "000011101001011001000100000101000111010110100100100101100000000000" + "011101101011001111101110101010000001010000011011111100000011101011" + "011100101101011010101011011011001001110111101011110110111111001111" + "001000011111101110011000111111110111111010000011101011111111110000" + "110111000000100000001110110000110110001110000100001110101100010100" + "110100010001000110001110011010110000111010000010000000000001101000" + "000000000011100101100100010000000000000110110100110001111100000000" + "000000100110100000000101010010100000001011000010001001100000011111" + "110111111111000000000", + PRNa, TOW_NavData); osnma->d_osnma_data.d_nma_header.nmas = 0b10; MACK_tag_and_info MTI; @@ -256,8 +230,8 @@ TEST_F(OsnmaMsgReceiverTest, TagVerification) osnma->d_osnma_data.d_dsm_kroot_message.mf = 0; osnma->d_nav_data_manager->add_navigation_data( "111111111111111111111111111111110000000000000000000000010001001001001000" - "111000001000100111100010010111111111011110111111111001001100000100000" - , PRNa, TOW_NavData); + "111000001000100111100010010111111111011110111111111001001100000100000", + PRNa, TOW_NavData); osnma->d_osnma_data.d_nma_header.nmas = 0b10; MTI.tag = static_cast(0x7BB238C883); @@ -297,325 +271,6 @@ TEST_F(OsnmaMsgReceiverTest, TeslaKeyVerification) } -TEST_F(OsnmaMsgReceiverTest, OsnmaTestVectorsSimulation) -{ - // Arrange - std::vector testVectors = readTestVectorsFromFile("/home/cgm/CLionProjects/osnma/data/27_JUL_2023_GST_00_00_01.csv"); // conf. 2 - if (testVectors.empty()) - { - ASSERT_TRUE(false); - } - - bool end_of_hex_stream{false}; - int offset_byte{0}; - int byte_index{0}; // index containing the last byte position of the hex stream that was retrieved. Takes advantage that all TVs have same size - const int SIZE_PAGE_BYTES{240 / 8}; // total bytes of a page - const int SIZE_SUBFRAME_PAGES{15}; // number of pages of a subframe - const int DURATION_SUBFRAME{30}; // duration of a subframe, in seconds - - const int DUMMY_PAGE{63}; - bool flag_dummy_page{false}; - std::cout << "OsnmaTestVectorsSimulation:" - << " d_GST_SIS= " << d_GST_SIS - << ", TOW=" << TOW - << ", WN=" << WN << std::endl; - - // Act - // loop over all bytes of data. Note: all TestVectors have same amount of data. - while (end_of_hex_stream == false) - { - // loop over all SVs, extract a subframe - for (const TestVector& tv : testVectors) - { // loop over all SVs, extract a subframe - std::cout << "OsnmaTestVectorsSimulation: SVID (PRN_a) " << tv.svId << std::endl; - auto osnmaMsg_sptr = std::make_shared(); - std::array hkroot{}; - std::array mack{}; - byte_index = offset_byte; // reset byte_index to the offset position for the next test vector. Offset is updated at the end of each Subframe (every 30 s or 450 Bytes) - std::map> words_for_OSNMA; // structure containing and - - for (int idx = 0; idx < SIZE_SUBFRAME_PAGES; ++idx) // extract all pages of a subframe - { - // extract bytes of complete page (odd+even) -- extract SIZE_PAGE from tv.navBits, starting from byte_index - std::vector page_bytes = extract_page_bytes(tv, byte_index, SIZE_PAGE_BYTES); - if (page_bytes.empty()) - { - std::cout << "OsnmaTestVectorsSimulation: end of TestVectors \n" - << "byte_index=" << byte_index << " expected= " << 432000 / 8 << std::endl; - end_of_hex_stream = true; - break; - } - // convert them to bitset representation using bytes_to_string - std::string page_bits = bytes_to_str(page_bytes); - // Extract the 40 OSNMA bits starting from the 18th bit - std::string even_page = page_bits.substr(0, page_bits.size() / 2); - std::string odd_page = page_bits.substr(page_bits.size() / 2); - if (even_page.size() < 120 || odd_page.size() < 120) - { - std::cout << "OsnmaTestVectorsSimulation: error parsing pages" << std::endl; - } - bool even_odd_OK = even_page[0] == '0' && odd_page[0] == '1'; - bool page_type_OK = even_page[1] == '0' && odd_page[1] == '0'; - bool tail_bits_OK = even_page.substr(even_page.size() - 6) == "000000" && odd_page.substr(odd_page.size() - 6) == "000000"; - if (!even_odd_OK || !page_type_OK || !tail_bits_OK) - std::cerr << "OsnmaTestVectorsSimulation: error parsing pages." << std::endl; - - std::bitset<112> data_k(even_page.substr(2, 112)); - std::bitset<16> data_j(odd_page.substr(2, 16)); - std::bitset<112> shifted_data_k = data_k; - uint8_t word_type = static_cast((shifted_data_k >>= 106).to_ulong()); // word type is the first 6 bits of the word - std::cout << "OsnmaTestVectorsSimulation: received Word " << static_cast(word_type) << std::endl; - if ((word_type >= 1 && word_type <= 5) || word_type == 6 || word_type == 10) - { - // store raw word - std::bitset<128> data_combined(data_k.to_string() + data_j.to_string()); - words_for_OSNMA[word_type] = data_combined; - } - if (word_type == DUMMY_PAGE) - flag_dummy_page = true; - - // place it into osnma object. - std::bitset<40> osnmaBits(odd_page.substr(18, 40)); - - // Extract bits for hkroot and mack - std::bitset<8> hkrootBits(osnmaBits.to_string().substr(0, 8)); - std::bitset<32> mackBits(osnmaBits.to_string().substr(8, 32)); - hkroot[idx] = static_cast(hkrootBits.to_ulong()); - mack[idx] = static_cast(mackBits.to_ulong()); - - byte_index += SIZE_PAGE_BYTES; - } - - std::cout << "----------" << std::endl; - if (end_of_hex_stream) - break; - if (flag_dummy_page) - { - flag_dummy_page = false; - continue; // skip this SV - } - - // Fill osnma object - osnmaMsg_sptr->hkroot = hkroot; - osnmaMsg_sptr->mack = mack; - - osnmaMsg_sptr->TOW_sf0 = d_GST_SIS & 0x000FFFFF; - osnmaMsg_sptr->WN_sf0 = (d_GST_SIS & 0xFFF00000) >> 20; - osnmaMsg_sptr->PRN = tv.svId; // PRNa - - // TODO - refactor this logic, currently it is split - - // check if words_for_OSNMA 1--> 5 words_for_OSNMA are received => fill EphClockStatus data vector - bool ephClockStatusWordsReceived = true; - for (int i = 1; i <= 5; ++i) - { - if (words_for_OSNMA.find(i) == words_for_OSNMA.end()) - { - ephClockStatusWordsReceived = false; - std::cerr << "OsnmaTestVectorsSimulation: error parsing words_for_OSNMA 1->5. " - "Word " - << i << " should be received for each subframe but was not." << std::endl; - } - } - // extract bits as needed by osnma block - if (ephClockStatusWordsReceived) - { - // Define the starting position and length of bits to extract for each word - std::map> extractionParams = { - {1, {6, 120}}, - {2, {6, 120}}, - {3, {6, 122}}, - {4, {6, 120}}, - {5, {6, 67}}, - }; - - // Fill NavData bits -- Iterate over the extraction parameters - std::string nav_data_ADKD_0_12 = ""; - for (const auto& param : extractionParams) - { - uint8_t wordKey = param.first; - uint8_t start = param.second.first; - uint8_t length = param.second.second; - - // Extract the required bits and fill osnma block - nav_data_ADKD_0_12 += words_for_OSNMA[wordKey].to_string().substr(start, length); - } - // send to osnma block - bool check_size_is_ok = nav_data_ADKD_0_12.size() == 549; - if (check_size_is_ok) - { - std::cout << "Galileo OSNMA: sending ADKD=0/12 navData, PRN_d (" << tv.svId << ") " - << "TOW_sf=" << osnmaMsg_sptr->TOW_sf0 << std::endl; - const auto tmp_obj_osnma = std::make_shared>( // < PRNd , navDataBits, TOW_Sosf> - tv.svId, - nav_data_ADKD_0_12, - osnmaMsg_sptr->TOW_sf0); - LOG(INFO) << "|---> Galileo OSNMA :: Telemetry Decoder NavData (PRN_d=" << static_cast(tv.svId) << ", TOW=" << static_cast(osnmaMsg_sptr->TOW_sf0) << "): 0b" << nav_data_ADKD_0_12; - osnma->msg_handler_osnma(pmt::make_any(tmp_obj_osnma)); - } - } - - // check w6 && w10 is received => fill TimingData data vector - bool timingWordsReceived = words_for_OSNMA.find(6) != words_for_OSNMA.end() && - words_for_OSNMA.find(10) != words_for_OSNMA.end(); - // extract bits as needed by osnma block - if (timingWordsReceived) - { - // Define the starting position and length of bits to extract for each word - std::map> extractionParams = { - {6, {6, 99}}, - {10, {86, 42}}}; - - std::string nav_data_ADKD_4 = ""; - // Fill NavData bits -- Iterate over the extraction parameters - for (const auto& param : extractionParams) - { - uint8_t wordKey = param.first; - uint8_t start = param.second.first; - uint8_t length = param.second.second; - - // Extract the required bits and fill osnma block - nav_data_ADKD_4 += words_for_OSNMA[wordKey].to_string().substr(start, length); - } - // send to osnma block - bool check_size_is_ok = nav_data_ADKD_4.size() == 141; - if (check_size_is_ok) - { - std::cout << "Galileo OSNMA: sending ADKD=04 navData, PRN_d (" << tv.svId << ") " - << "TOW_sf=" << osnmaMsg_sptr->TOW_sf0 << std::endl; - const auto tmp_obj_osnma = std::make_shared>( // < PRNd , navDataBits, TOW_Sosf> - tv.svId, - nav_data_ADKD_4, - osnmaMsg_sptr->TOW_sf0); - LOG(INFO) << "|---> Galileo OSNMA :: Telemetry Decoder NavData (PRN_d=" << static_cast(tv.svId) << ", TOW=" << static_cast(osnmaMsg_sptr->TOW_sf0) << "): 0b" << nav_data_ADKD_4; - osnma->msg_handler_osnma(pmt::make_any(tmp_obj_osnma)); - } - } - - // Call the handler, as if it came from telemetry decoder block - auto temp_obj = pmt::make_any(osnmaMsg_sptr); - - osnma->msg_handler_osnma(temp_obj); // osnma entry point - } - - if (!end_of_hex_stream) - { - offset_byte = byte_index; // update offset for the next subframe - d_GST_SIS += DURATION_SUBFRAME; - TOW = d_GST_SIS & 0x000FFFFF; - WN = (d_GST_SIS & 0xFFF00000) >> 20; - std::cout << "OsnmaTestVectorsSimulation:" - << " d_GST_SIS= " << d_GST_SIS - << ", TOW=" << TOW - << ", WN=" << WN << std::endl; - } - } - // Assert - - // TODO - create global vars with failed tags and compare to total tags (Tag Id for example) -} - - -// Auxiliary functions for the OsnmaTestVectorsSimulation test fixture. -// Essentially, they perform same work as the telemetry decoder block, but adapted to the osnma-test-vector files. -std::vector OsnmaMsgReceiverTest::readTestVectorsFromFile(const std::string& filename) -{ - std::ifstream file(filename); - std::vector testVectors; - if (!file.is_open()) - { - std::cerr << "Error reading the file \"" << filename << "\" \n"; - return testVectors; - } - - std::string line; - std::getline(file, line); - if (line != "SVID,NumNavBits,NavBitsHEX\r") - { - std::cerr << "Error parsing first line" - << "\n"; - } - - while (std::getline(file, line)) - { - std::stringstream ss(line); - TestVector tv; - - std::string val; - - std::getline(ss, val, ','); - tv.svId = std::stoi(val); - - std::getline(ss, val, ','); - tv.numNavBits = std::stoi(val); - - std::getline(ss, val, ','); - tv.navBits = OsnmaMsgReceiverTest::parseNavBits(val); - - testVectors.push_back(tv); - } - - return testVectors; -} - - -std::vector OsnmaMsgReceiverTest::parseNavBits(const std::string& hex) -{ - std::vector bytes; - - for (unsigned int i = 0; i < hex.length() - 1; i += 2) - { - std::string byteString = hex.substr(i, 2); - uint8_t byte = (uint8_t)strtol(byteString.c_str(), NULL, 16); - bytes.push_back(byte); - } - return bytes; -} - - -std::string OsnmaMsgReceiverTest::bytes_to_str(const std::vector& bytes) -{ - std::string bit_string; - bit_string.reserve(bytes.size() * 8); - for (const auto& byte : bytes) - { - std::bitset<8> bits(byte); - bit_string += bits.to_string(); - } - return bit_string; -} - - -/** - * @brief Extracts a range of bytes from a TestVector's navBits vector. - * - * This function extracts a extracts the bytes of complete page (odd+even) - * from the navBits vector of a TestVector object. - * - * - * @param tv The TestVector object from which to extract bytes. - * @param byte_index The index of the first byte to extract. - * @param num_bytes The number of bytes to extract. - * @return A vector containing the extracted bytes, or an empty vector if extraction is not possible. - */ -std::vector OsnmaMsgReceiverTest::extract_page_bytes(const TestVector& tv, const int byte_index, const int num_bytes) -{ - // Ensure we don't go beyond the end of tv.navBits - int num_bytes_to_extract = std::min(num_bytes, static_cast(tv.navBits.size() - byte_index)); - - // If byte_index is beyond the end of tv.navBits, return an empty vector - if (num_bytes_to_extract <= 0) - { - return std::vector(); - } - - // Use std::next to get an iterator to the range to extract - std::vector extracted_bytes(tv.navBits.begin() + byte_index, tv.navBits.begin() + byte_index + num_bytes_to_extract); - - return extracted_bytes; -} - - /** * @brief Sets the time based on the given input. * @@ -647,47 +302,3 @@ void OsnmaMsgReceiverTest::set_time(std::tm& input) // I am assuming that local realisation of receiver is identical to SIS GST time coming from W5 or W0 this->d_GST_SIS = (this->WN & 0x00000FFF) << 20 | (this->TOW & 0x000FFFFF); } - - -void OsnmaMsgReceiverTest::initializeGoogleLog() -{ - // google::InitGoogleLogging(log_name.c_str()); - FLAGS_minloglevel = 0; // INFO - FLAGS_logtostderr = 0; // add this line - // FLAGS_log_dir = "/home/cgm/CLionProjects/osnma/data/logs"; - if (FLAGS_log_dir.empty()) - { - std::cout << "Logging will be written at " - << std::filesystem::temp_directory_path() - << '\n' - << "Use gnss-sdr --log_dir=/path/to/log to change that.\n"; - } - else - { - try - { - const std::filesystem::path p(FLAGS_log_dir); - if (!std::filesystem::exists(p)) - { - std::cout << "The path " - << FLAGS_log_dir - << " does not exist, attempting to create it.\n"; - std::error_code ec; - if (!std::filesystem::create_directory(p, ec)) - { - std::cout << "Could not create the " << FLAGS_log_dir << " folder.\n"; - gflags::ShutDownCommandLineFlags(); - throw std::runtime_error("Could not create folder for logs"); - } - } - std::cout << "Logging will be written at " << FLAGS_log_dir << '\n'; - } - catch (const std::exception& e) - { - std::cerr << e.what() << '\n'; - std::cerr << "Could not create the " << FLAGS_log_dir << " folder.\n"; - gflags::ShutDownCommandLineFlags(); - throw; - } - } -} diff --git a/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_test_vectors.cc b/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_test_vectors.cc new file mode 100644 index 000000000..edb8b401f --- /dev/null +++ b/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_test_vectors.cc @@ -0,0 +1,423 @@ +/*! + * \file osmna_test_vectors.cc + * \brief Tests for the osnma_msg_receiver class. + * \author Carles Fernandez, 2023-2024. cfernandez(at)cttc.es + * Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de + * + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2024 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "gnss_crypto.h" +#include "osnma_helper.h" +#include "osnma_msg_receiver.h" +#include +#include +#include +#include +#include + +#if USE_GLOG_AND_GFLAGS +#include // for LOG +#else +#include +#endif + +struct TestVector +{ + int svId; + int numNavBits; + std::vector navBits; +}; + + +class OsnmaTestVectors : public ::testing::Test +{ +public: + static std::vector parseNavBits(const std::string& hex); + static std::vector readTestVectorsFromFile(const std::string& filename); + std::string bytes_to_str(const std::vector& bytes); + std::vector extract_page_bytes(const TestVector& tv, int byte_index, int num_bytes); + +protected: + Osnma_Helper helper; + osnma_msg_receiver_sptr osnma; + OSNMA_msg osnma_msg{}; + std::array nma_position_filled; + uint32_t d_GST_SIS{}; + uint32_t TOW{}; + uint32_t WN{}; + std::tm GST_START_EPOCH = {0, 0, 0, 22, 8 - 1, 1999 - 1900, 0}; // months start with 0 and years since 1900 in std::tm + const uint32_t LEAP_SECONDS = 0; // 13 + 5; + void set_time(std::tm& input); + + void SetUp() override + { + // std::tm input_time = {0, 0, 5, 16, 8 - 1, 2023 - 1900, 0}; // conf. 1 + std::tm input_time = {0, 0, 0, 27, 7 - 1, 2023 - 1900, 0}; // conf. 2 + set_time(input_time); + std::string crtFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/PublicKey/OSNMA_PublicKey_20230720113300_newPKID_2.crt"; // conf. 2 + std::string merkleFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/MerkleTree/OSNMA_MerkleTree_20230720113300_newPKID_2.xml"; + osnma = osnma_msg_receiver_make(crtFilePath, merkleFilePath); + } +}; + + +TEST_F(OsnmaTestVectors, OsnmaTestVectorsSimulation) +{ + // Arrange + std::vector testVectors = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/configuration_2/27_JUL_2023_GST_00_00_01.csv"); // conf. 2 + if (testVectors.empty()) + { + ASSERT_TRUE(false); + } + + bool end_of_hex_stream{false}; + int offset_byte{0}; + int byte_index{0}; // index containing the last byte position of the hex stream that was retrieved. Takes advantage that all TVs have same size + const int SIZE_PAGE_BYTES{240 / 8}; // total bytes of a page + const int SIZE_SUBFRAME_PAGES{15}; // number of pages of a subframe + const int DURATION_SUBFRAME{30}; // duration of a subframe, in seconds + + const int DUMMY_PAGE{63}; + bool flag_dummy_page{false}; + std::cout << "OsnmaTestVectorsSimulation:" + << " d_GST_SIS= " << d_GST_SIS + << ", TOW=" << TOW + << ", WN=" << WN << std::endl; + + // Act + // loop over all bytes of data. Note: all TestVectors have same amount of data. + while (end_of_hex_stream == false) + { + // loop over all SVs, extract a subframe + for (const TestVector& tv : testVectors) + { // loop over all SVs, extract a subframe + std::cout << "OsnmaTestVectorsSimulation: SVID (PRN_a) " << tv.svId << std::endl; + auto osnmaMsg_sptr = std::make_shared(); + std::array hkroot{}; + std::array mack{}; + byte_index = offset_byte; // reset byte_index to the offset position for the next test vector. Offset is updated at the end of each Subframe (every 30 s or 450 Bytes) + std::map> words_for_OSNMA; // structure containing and + + for (int idx = 0; idx < SIZE_SUBFRAME_PAGES; ++idx) // extract all pages of a subframe + { + // extract bytes of complete page (odd+even) -- extract SIZE_PAGE from tv.navBits, starting from byte_index + std::vector page_bytes = extract_page_bytes(tv, byte_index, SIZE_PAGE_BYTES); + if (page_bytes.empty()) + { + std::cout << "OsnmaTestVectorsSimulation: end of TestVectors \n" + << "byte_index=" << byte_index << " expected= " << 432000 / 8 << std::endl; + end_of_hex_stream = true; + break; + } + // convert them to bitset representation using bytes_to_string + std::string page_bits = bytes_to_str(page_bytes); + // Extract the 40 OSNMA bits starting from the 18th bit + std::string even_page = page_bits.substr(0, page_bits.size() / 2); + std::string odd_page = page_bits.substr(page_bits.size() / 2); + if (even_page.size() < 120 || odd_page.size() < 120) + { + std::cout << "OsnmaTestVectorsSimulation: error parsing pages" << std::endl; + } + bool even_odd_OK = even_page[0] == '0' && odd_page[0] == '1'; + bool page_type_OK = even_page[1] == '0' && odd_page[1] == '0'; + bool tail_bits_OK = even_page.substr(even_page.size() - 6) == "000000" && odd_page.substr(odd_page.size() - 6) == "000000"; + if (!even_odd_OK || !page_type_OK || !tail_bits_OK) + std::cerr << "OsnmaTestVectorsSimulation: error parsing pages." << std::endl; + + std::bitset<112> data_k(even_page.substr(2, 112)); + std::bitset<16> data_j(odd_page.substr(2, 16)); + std::bitset<112> shifted_data_k = data_k; + uint8_t word_type = static_cast((shifted_data_k >>= 106).to_ulong()); // word type is the first 6 bits of the word + std::cout << "OsnmaTestVectorsSimulation: received Word " << static_cast(word_type) << std::endl; + if ((word_type >= 1 && word_type <= 5) || word_type == 6 || word_type == 10) + { + // store raw word + std::bitset<128> data_combined(data_k.to_string() + data_j.to_string()); + words_for_OSNMA[word_type] = data_combined; + } + if (word_type == DUMMY_PAGE) + flag_dummy_page = true; + + // place it into osnma object. + std::bitset<40> osnmaBits(odd_page.substr(18, 40)); + + // Extract bits for hkroot and mack + std::bitset<8> hkrootBits(osnmaBits.to_string().substr(0, 8)); + std::bitset<32> mackBits(osnmaBits.to_string().substr(8, 32)); + hkroot[idx] = static_cast(hkrootBits.to_ulong()); + mack[idx] = static_cast(mackBits.to_ulong()); + + byte_index += SIZE_PAGE_BYTES; + } + + std::cout << "----------" << std::endl; + if (end_of_hex_stream) + break; + if (flag_dummy_page) + { + flag_dummy_page = false; + continue; // skip this SV + } + + // Fill osnma object + osnmaMsg_sptr->hkroot = hkroot; + osnmaMsg_sptr->mack = mack; + + osnmaMsg_sptr->TOW_sf0 = d_GST_SIS & 0x000FFFFF; + osnmaMsg_sptr->WN_sf0 = (d_GST_SIS & 0xFFF00000) >> 20; + osnmaMsg_sptr->PRN = tv.svId; // PRNa + + // TODO - refactor this logic, currently it is split + + // check if words_for_OSNMA 1--> 5 words_for_OSNMA are received => fill EphClockStatus data vector + bool ephClockStatusWordsReceived = true; + for (int i = 1; i <= 5; ++i) + { + if (words_for_OSNMA.find(i) == words_for_OSNMA.end()) + { + ephClockStatusWordsReceived = false; + std::cerr << "OsnmaTestVectorsSimulation: error parsing words_for_OSNMA 1->5. " + "Word " + << i << " should be received for each subframe but was not." << std::endl; + } + } + // extract bits as needed by osnma block + if (ephClockStatusWordsReceived) + { + // Define the starting position and length of bits to extract for each word + std::map> extractionParams = { + {1, {6, 120}}, + {2, {6, 120}}, + {3, {6, 122}}, + {4, {6, 120}}, + {5, {6, 67}}, + }; + + // Fill NavData bits -- Iterate over the extraction parameters + std::string nav_data_ADKD_0_12 = ""; + for (const auto& param : extractionParams) + { + uint8_t wordKey = param.first; + uint8_t start = param.second.first; + uint8_t length = param.second.second; + + // Extract the required bits and fill osnma block + nav_data_ADKD_0_12 += words_for_OSNMA[wordKey].to_string().substr(start, length); + } + // send to osnma block + bool check_size_is_ok = nav_data_ADKD_0_12.size() == 549; + if (check_size_is_ok) + { + std::cout << "Galileo OSNMA: sending ADKD=0/12 navData, PRN_d (" << tv.svId << ") " + << "TOW_sf=" << osnmaMsg_sptr->TOW_sf0 << std::endl; + const auto tmp_obj_osnma = std::make_shared>( // < PRNd , navDataBits, TOW_Sosf> + tv.svId, + nav_data_ADKD_0_12, + osnmaMsg_sptr->TOW_sf0); + LOG(INFO) << "|---> Galileo OSNMA :: Telemetry Decoder NavData (PRN_d=" << static_cast(tv.svId) << ", TOW=" << static_cast(osnmaMsg_sptr->TOW_sf0) << "): 0b" << nav_data_ADKD_0_12; + osnma->msg_handler_osnma(pmt::make_any(tmp_obj_osnma)); + } + } + + // check w6 && w10 is received => fill TimingData data vector + bool timingWordsReceived = words_for_OSNMA.find(6) != words_for_OSNMA.end() && + words_for_OSNMA.find(10) != words_for_OSNMA.end(); + // extract bits as needed by osnma block + if (timingWordsReceived) + { + // Define the starting position and length of bits to extract for each word + std::map> extractionParams = { + {6, {6, 99}}, + {10, {86, 42}}}; + + std::string nav_data_ADKD_4 = ""; + // Fill NavData bits -- Iterate over the extraction parameters + for (const auto& param : extractionParams) + { + uint8_t wordKey = param.first; + uint8_t start = param.second.first; + uint8_t length = param.second.second; + + // Extract the required bits and fill osnma block + nav_data_ADKD_4 += words_for_OSNMA[wordKey].to_string().substr(start, length); + } + // send to osnma block + bool check_size_is_ok = nav_data_ADKD_4.size() == 141; + if (check_size_is_ok) + { + std::cout << "Galileo OSNMA: sending ADKD=04 navData, PRN_d (" << tv.svId << ") " + << "TOW_sf=" << osnmaMsg_sptr->TOW_sf0 << std::endl; + const auto tmp_obj_osnma = std::make_shared>( // < PRNd , navDataBits, TOW_Sosf> + tv.svId, + nav_data_ADKD_4, + osnmaMsg_sptr->TOW_sf0); + LOG(INFO) << "|---> Galileo OSNMA :: Telemetry Decoder NavData (PRN_d=" << static_cast(tv.svId) << ", TOW=" << static_cast(osnmaMsg_sptr->TOW_sf0) << "): 0b" << nav_data_ADKD_4; + osnma->msg_handler_osnma(pmt::make_any(tmp_obj_osnma)); + } + } + + // Call the handler, as if it came from telemetry decoder block + auto temp_obj = pmt::make_any(osnmaMsg_sptr); + + osnma->msg_handler_osnma(temp_obj); // osnma entry point + } + + if (!end_of_hex_stream) + { + offset_byte = byte_index; // update offset for the next subframe + d_GST_SIS += DURATION_SUBFRAME; + TOW = d_GST_SIS & 0x000FFFFF; + WN = (d_GST_SIS & 0xFFF00000) >> 20; + std::cout << "OsnmaTestVectorsSimulation:" + << " d_GST_SIS= " << d_GST_SIS + << ", TOW=" << TOW + << ", WN=" << WN << std::endl; + } + } + // Assert + + // TODO - create global vars with failed tags and compare to total tags (Tag Id for example) +} + + +// Auxiliary functions for the OsnmaTestVectorsSimulation test fixture. +// Essentially, they perform same work as the telemetry decoder block, but adapted to the osnma-test-vector files. +std::vector OsnmaTestVectors::readTestVectorsFromFile(const std::string& filename) +{ + std::ifstream file(filename); + std::vector testVectors; + if (!file.is_open()) + { + std::cerr << "Error reading the file \"" << filename << "\" \n"; + return testVectors; + } + + std::string line; + std::getline(file, line); + if (line != "SVID,NumNavBits,NavBitsHEX\r") + { + std::cerr << "Error parsing first line" + << "\n"; + } + + while (std::getline(file, line)) + { + std::stringstream ss(line); + TestVector tv; + + std::string val; + + std::getline(ss, val, ','); + tv.svId = std::stoi(val); + + std::getline(ss, val, ','); + tv.numNavBits = std::stoi(val); + + std::getline(ss, val, ','); + tv.navBits = OsnmaTestVectors::parseNavBits(val); + + testVectors.push_back(tv); + } + + return testVectors; +} + + +std::vector OsnmaTestVectors::parseNavBits(const std::string& hexadecimal) +{ + std::vector bytes; + + for (unsigned int i = 0; i < hexadecimal.length() - 1; i += 2) + { + std::string byteString = hexadecimal.substr(i, 2); + uint8_t byte = static_cast(strtol(byteString.c_str(), nullptr, 16)); + bytes.push_back(byte); + } + return bytes; +} + + +std::string OsnmaTestVectors::bytes_to_str(const std::vector& bytes) +{ + std::string bit_string; + bit_string.reserve(bytes.size() * 8); + for (const auto& byte : bytes) + { + std::bitset<8> bits(byte); + bit_string += bits.to_string(); + } + return bit_string; +} + + +/** + * @brief Extracts a range of bytes from a TestVector's navBits vector. + * + * This function extracts a extracts the bytes of complete page (odd+even) + * from the navBits vector of a TestVector object. + * + * + * @param tv The TestVector object from which to extract bytes. + * @param byte_index The index of the first byte to extract. + * @param num_bytes The number of bytes to extract. + * @return A vector containing the extracted bytes, or an empty vector if extraction is not possible. + */ +std::vector OsnmaTestVectors::extract_page_bytes(const TestVector& tv, int byte_index, int num_bytes) +{ + // Ensure we don't go beyond the end of tv.navBits + int num_bytes_to_extract = std::min(num_bytes, static_cast(tv.navBits.size() - byte_index)); + + // If byte_index is beyond the end of tv.navBits, return an empty vector + if (num_bytes_to_extract <= 0) + { + return std::vector(); + } + + // Use std::next to get an iterator to the range to extract + std::vector extracted_bytes(tv.navBits.begin() + byte_index, tv.navBits.begin() + byte_index + num_bytes_to_extract); + + return extracted_bytes; +} + + +/** + * @brief Sets the time based on the given input. + * + * This function calculates the week number (WN) and time of week (TOW) + * based on the input time and the GST_START_EPOCH. It then stores the + * calculated values in the WN and TOW member variables. Finally, it + * combines the WN and TOW into a single 32-bit value and stores it in + * the d_GST_SIS member variable. + * + * @param input The input time as a tm struct. + */ +void OsnmaTestVectors::set_time(std::tm& input) +{ + auto epoch_time_point = std::chrono::system_clock::from_time_t(mktime(&GST_START_EPOCH)); + auto input_time_point = std::chrono::system_clock::from_time_t(mktime(&input)); + + // Get the duration from epoch in seconds + auto duration_sec = std::chrono::duration_cast(input_time_point - epoch_time_point); + + // Calculate the week number (WN) and time of week (TOW) + uint32_t sec_in_week = 7 * 24 * 60 * 60; + uint32_t week_number = duration_sec.count() / sec_in_week; + uint32_t time_of_week = duration_sec.count() % sec_in_week; + this->WN = week_number; + this->TOW = time_of_week + LEAP_SECONDS; + // Return the week number and time of week as a pair + + // TODO: d_GST_SIS or d_receiver_time? doubt + // I am assuming that local realisation of receiver is identical to SIS GST time coming from W5 or W0 + this->d_GST_SIS = (this->WN & 0x00000FFF) << 20 | (this->TOW & 0x000FFFFF); +}