1
0
mirror of https://github.com/gnss-sdr/gnss-sdr synced 2024-12-13 11:40:33 +00:00

Fix building of OSNMA tests

This commit is contained in:
Carles Fernandez 2024-07-26 18:09:25 +02:00
parent 06e0c4b63a
commit ec127089ab
No known key found for this signature in database
GPG Key ID: 4C583C52B0C3877D
5 changed files with 453 additions and 452 deletions

View File

@ -146,12 +146,12 @@ private:
// Provide access to inner functions to Gtest // Provide access to inner functions to Gtest
FRIEND_TEST(OsnmaMsgReceiverTest, TeslaKeyVerification); FRIEND_TEST(OsnmaMsgReceiverTest, TeslaKeyVerification);
FRIEND_TEST(OsnmaMsgReceiverTest, OsnmaTestVectorsSimulation);
FRIEND_TEST(OsnmaMsgReceiverTest, TagVerification); FRIEND_TEST(OsnmaMsgReceiverTest, TagVerification);
FRIEND_TEST(OsnmaMsgReceiverTest, BuildTagMessageM0); FRIEND_TEST(OsnmaMsgReceiverTest, BuildTagMessageM0);
FRIEND_TEST(OsnmaMsgReceiverTest, VerifyPublicKey); FRIEND_TEST(OsnmaMsgReceiverTest, VerifyPublicKey);
FRIEND_TEST(OsnmaMsgReceiverTest, ComputeBaseLeaf); FRIEND_TEST(OsnmaMsgReceiverTest, ComputeBaseLeaf);
FRIEND_TEST(OsnmaMsgReceiverTest, ComputeMerkleRoot); FRIEND_TEST(OsnmaMsgReceiverTest, ComputeMerkleRoot);
FRIEND_TEST(OsnmaTestVectors, OsnmaTestVectorsSimulation);
}; };

View File

@ -550,6 +550,18 @@ if(ENABLE_UNIT_TESTING_EXTRA)
EXPECTED_HASH MD5=066d0d8434a8bc81e161778b7c34cc07 EXPECTED_HASH MD5=066d0d8434a8bc81e161778b7c34cc07
) )
endif() 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.") message(STATUS "Done.")
if(ENABLE_INSTALL_TESTS) if(ENABLE_INSTALL_TESTS)
install(FILES ${GNSSSDR_BINARY_DIR}/thirdparty/signal_samples/gps_l2c_m_prn7_5msps.dat DESTINATION share/gnss-sdr/signal_samples) 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) if(GNSSTK_OLDER_THAN_9)
target_compile_definitions(run_tests PRIVATE -DGNSSTK_OLDER_THAN_9=1) target_compile_definitions(run_tests PRIVATE -DGNSSTK_OLDER_THAN_9=1)
endif() endif()
target_compile_definitions(run_tests PRIVATE -DBASE_OSNMA_TEST_VECTORS="${GNSSSDR_BINARY_DIR}/thirdparty/osnma_tests/Test_vectors/")
endif() endif()
xcode_remove_warning_duplicates(run_tests) xcode_remove_warning_duplicates(run_tests)
if(ENABLE_STRIP) if(ENABLE_STRIP)
@ -1356,53 +1369,6 @@ else()
endif() endif()
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 "$<LINK_LIBRARY:WHOLE_ARCHIVE,absl::log_flags>")
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) if(ENABLE_BENCHMARKS)
add_subdirectory(benchmarks) add_subdirectory(benchmarks)
endif() endif()

View File

@ -113,7 +113,7 @@ private:
#include "unit-tests/signal-processing-blocks/adapter/pass_through_test.cc" #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/libs/item_type_helpers_test.cc"
#include "unit-tests/signal-processing-blocks/osnma/gnss_crypto_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/geohash_test.cc"
#include "unit-tests/signal-processing-blocks/pvt/nmea_printer_test.cc" #include "unit-tests/signal-processing-blocks/pvt/nmea_printer_test.cc"
#include "unit-tests/signal-processing-blocks/pvt/rinex_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/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/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/tracking/gps_l2_m_dll_pll_tracking_test.cc"
#include "unit-tests/signal-processing-blocks/osnma/osnma_test_vectors.cc"
#endif #endif
// #include "unit-tests/signal-processing-blocks/pvt/rtklib_solver_test.cc" // #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" #include "unit-tests/signal-processing-blocks/tracking/gps_l1_ca_dll_pll_tracking_test.cc"

View File

@ -1,5 +1,5 @@
/*! /*!
* \file osmna_msg_receiver_testt.cc * \file osmna_msg_receiver_test.cc
* \brief Tests for the osnma_msg_receiver class. * \brief Tests for the osnma_msg_receiver class.
* \author Carles Fernandez, 2023-2024. cfernandez(at)cttc.es * \author Carles Fernandez, 2023-2024. cfernandez(at)cttc.es
* Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de * Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de
@ -28,30 +28,12 @@
#if USE_GLOG_AND_GFLAGS #if USE_GLOG_AND_GFLAGS
#include <glog/logging.h> // for LOG #include <glog/logging.h> // for LOG
#include <filesystem>
#else #else
#include <absl/log/log.h> #include <absl/log/log.h>
#endif #endif
struct TestVector
{
int svId;
int numNavBits;
std::vector<uint8_t> 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 class OsnmaMsgReceiverTest : public ::testing::Test
{ {
public:
static std::vector<uint8_t> parseNavBits(const std::string& hex);
static std::vector<TestVector> readTestVectorsFromFile(const std::string& filename);
std::string bytes_to_str(const std::vector<uint8_t>& bytes);
std::vector<uint8_t> extract_page_bytes(const TestVector& tv, const int byte_index, const int num_bytes);
protected: protected:
Osnma_Helper helper; Osnma_Helper helper;
osnma_msg_receiver_sptr osnma; 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 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; const uint32_t LEAP_SECONDS = 0; // 13 + 5;
void set_time(std::tm& input); 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 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, 5, 16, 8 - 1, 2023 - 1900, 0}; // conf. 1
std::tm input_time = {0, 0, 0, 27, 7 - 1, 2023 - 1900, 0}; // conf. 2 std::tm input_time = {0, 0, 0, 27, 7 - 1, 2023 - 1900, 0}; // conf. 2
set_time(input_time); set_time(input_time);
// std::string crtFilePath = "/home/cgm/CLionProjects/osnma/data/OSNMA_PublicKey_20230803105952_newPKID_1.crt"; // conf. 1 osnma = osnma_msg_receiver_make("", "");
// 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);
} }
}; };
@ -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_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_osnma_data.d_dsm_kroot_message.mf = 0;
osnma->d_nav_data_manager->add_navigation_data( osnma->d_nav_data_manager->add_navigation_data(
"000011101001011001000100000101000111010110100100100101100000000000" "000011101001011001000100000101000111010110100100100101100000000000"
"011101101011001111101110101010000001010000011011111100000011101011" "011101101011001111101110101010000001010000011011111100000011101011"
"011100101101011010101011011011001001110111101011110110111111001111" "011100101101011010101011011011001001110111101011110110111111001111"
"001000011111101110011000111111110111111010000011101011111111110000" "001000011111101110011000111111110111111010000011101011111111110000"
"110111000000100000001110110000110110001110000100001110101100010100" "110111000000100000001110110000110110001110000100001110101100010100"
"110100010001000110001110011010110000111010000010000000000001101000" "110100010001000110001110011010110000111010000010000000000001101000"
"000000000011100101100100010000000000000110110100110001111100000000" "000000000011100101100100010000000000000110110100110001111100000000"
"000000100110100000000101010010100000001011000010001001100000011111" "000000100110100000000101010010100000001011000010001001100000011111"
"110111111111000000000", PRNa, TOW_NavData); "110111111111000000000",
PRNa, TOW_NavData);
osnma->d_osnma_data.d_nma_header.nmas = 0b10; osnma->d_osnma_data.d_nma_header.nmas = 0b10;
MACK_tag_and_info MTI; 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_osnma_data.d_dsm_kroot_message.mf = 0;
osnma->d_nav_data_manager->add_navigation_data( osnma->d_nav_data_manager->add_navigation_data(
"111111111111111111111111111111110000000000000000000000010001001001001000" "111111111111111111111111111111110000000000000000000000010001001001001000"
"111000001000100111100010010111111111011110111111111001001100000100000" "111000001000100111100010010111111111011110111111111001001100000100000",
, PRNa, TOW_NavData); PRNa, TOW_NavData);
osnma->d_osnma_data.d_nma_header.nmas = 0b10; osnma->d_osnma_data.d_nma_header.nmas = 0b10;
MTI.tag = static_cast<uint64_t>(0x7BB238C883); MTI.tag = static_cast<uint64_t>(0x7BB238C883);
@ -297,325 +271,6 @@ TEST_F(OsnmaMsgReceiverTest, TeslaKeyVerification)
} }
TEST_F(OsnmaMsgReceiverTest, OsnmaTestVectorsSimulation)
{
// Arrange
std::vector<TestVector> 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<OSNMA_msg>();
std::array<uint8_t, 15> hkroot{};
std::array<uint32_t, 15> 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<uint8_t, std::bitset<128>> words_for_OSNMA; // structure containing <WORD_NUMBER> and <EXTRACTED_BITS>
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<uint8_t> 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<uint8_t>((shifted_data_k >>= 106).to_ulong()); // word type is the first 6 bits of the word
std::cout << "OsnmaTestVectorsSimulation: received Word " << static_cast<int>(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<uint8_t>(hkrootBits.to_ulong());
mack[idx] = static_cast<uint32_t>(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<uint8_t, std::pair<uint8_t, uint8_t>> 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<std::tuple<uint32_t, std::string, uint32_t>>( // < 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<int>(tv.svId) << ", TOW=" << static_cast<int>(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<uint8_t, std::pair<uint8_t, uint8_t>> 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<std::tuple<uint32_t, std::string, uint32_t>>( // < 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<int>(tv.svId) << ", TOW=" << static_cast<int>(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<TestVector> OsnmaMsgReceiverTest::readTestVectorsFromFile(const std::string& filename)
{
std::ifstream file(filename);
std::vector<TestVector> 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<uint8_t> OsnmaMsgReceiverTest::parseNavBits(const std::string& hex)
{
std::vector<uint8_t> 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<uint8_t>& 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<uint8_t> 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<int>(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<uint8_t>();
}
// Use std::next to get an iterator to the range to extract
std::vector<uint8_t> 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. * @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 // 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); 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;
}
}
}

View File

@ -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 <gtest/gtest.h>
#include <bitset>
#include <chrono>
#include <fstream>
#include <vector>
#if USE_GLOG_AND_GFLAGS
#include <glog/logging.h> // for LOG
#else
#include <absl/log/log.h>
#endif
struct TestVector
{
int svId;
int numNavBits;
std::vector<uint8_t> navBits;
};
class OsnmaTestVectors : public ::testing::Test
{
public:
static std::vector<uint8_t> parseNavBits(const std::string& hex);
static std::vector<TestVector> readTestVectorsFromFile(const std::string& filename);
std::string bytes_to_str(const std::vector<uint8_t>& bytes);
std::vector<uint8_t> 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<int8_t, 15> 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<TestVector> 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<OSNMA_msg>();
std::array<uint8_t, 15> hkroot{};
std::array<uint32_t, 15> 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<uint8_t, std::bitset<128>> words_for_OSNMA; // structure containing <WORD_NUMBER> and <EXTRACTED_BITS>
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<uint8_t> 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<uint8_t>((shifted_data_k >>= 106).to_ulong()); // word type is the first 6 bits of the word
std::cout << "OsnmaTestVectorsSimulation: received Word " << static_cast<int>(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<uint8_t>(hkrootBits.to_ulong());
mack[idx] = static_cast<uint32_t>(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<uint8_t, std::pair<uint8_t, uint8_t>> 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<std::tuple<uint32_t, std::string, uint32_t>>( // < 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<int>(tv.svId) << ", TOW=" << static_cast<int>(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<uint8_t, std::pair<uint8_t, uint8_t>> 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<std::tuple<uint32_t, std::string, uint32_t>>( // < 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<int>(tv.svId) << ", TOW=" << static_cast<int>(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<TestVector> OsnmaTestVectors::readTestVectorsFromFile(const std::string& filename)
{
std::ifstream file(filename);
std::vector<TestVector> 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<uint8_t> OsnmaTestVectors::parseNavBits(const std::string& hexadecimal)
{
std::vector<uint8_t> bytes;
for (unsigned int i = 0; i < hexadecimal.length() - 1; i += 2)
{
std::string byteString = hexadecimal.substr(i, 2);
uint8_t byte = static_cast<uint8_t>(strtol(byteString.c_str(), nullptr, 16));
bytes.push_back(byte);
}
return bytes;
}
std::string OsnmaTestVectors::bytes_to_str(const std::vector<uint8_t>& 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<uint8_t> 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<int>(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<uint8_t>();
}
// Use std::next to get an iterator to the range to extract
std::vector<uint8_t> 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<std::chrono::seconds>(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);
}