Hardened the Galileo OSNMA protocol implementation (Fixes #968, #967)

This commit is contained in:
Carles Fernandez
2026-05-13 17:05:45 +02:00
parent d6e0fded84
commit e13730276b
26 changed files with 8262 additions and 1034 deletions
+12
View File
@@ -27,6 +27,18 @@ All notable changes to GNSS-SDR will be documented in this file.
robustness across dependency discovery, distro detection, and
cross-compilation handling.
### Improvements in Reliability
- Hardened the Galileo OSNMA protocol implementation, adding support for Chain
Renewal, Chain Revocation, Public Key Renewal, Public Key Revocation, Merkle
Tree Renewal, and OSNMA Alert Message events. Improved the management of OSNMA
cryptographic material and added unit tests to ensure compliance with the
OSNMA Receiver Guidelines v1.3, including edge-case handling. Added the new
configuration value `GNSS-SDR.osnma_mode=replay`, which disables the receiver
wall-clock GST alignment check for OSNMA tag processing, enabling replay of
previously captured Galileo signals while keeping all other OSNMA verification
steps active.
See the definitions of concepts and metrics at
https://gnss-sdr.org/design-forces/
@@ -1629,7 +1629,18 @@ void rtklib_pvt_gs::msg_handler_osnma(const pmt::pmt_t& msg)
if (msg_type_hash_code == typeid(std::shared_ptr<OSNMA_NavData>).hash_code())
{
const auto osnma_data = wht::any_cast<std::shared_ptr<OSNMA_NavData>>(pmt::any_ref(msg));
d_auth_nav_data_map[osnma_data->get_prn_d()].insert(osnma_data->get_IOD_nav());
if (!osnma_data->get_ephemeris_data().empty())
{
auto auth_wn = osnma_data->get_wn_sf0();
auto auth_tow = osnma_data->get_tow_sf0();
if (auth_wn == 0 && auth_tow == 0 &&
(osnma_data->get_last_received_WN() != 0 || osnma_data->get_last_received_TOW() != 0))
{
auth_wn = osnma_data->get_last_received_WN();
auth_tow = osnma_data->get_last_received_TOW();
}
d_auth_nav_data_map[osnma_data->get_prn_d()][osnma_data->get_IOD_nav()] = osnma::galileo_gst_seconds(auth_wn, auth_tow);
}
}
}
catch (const wht::bad_any_cast& e)
@@ -2017,16 +2028,38 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item
((std::string(in[i][epoch].Signal, 2) == std::string("5X")) && (d_use_unhealthy_sats || ((tmp_eph_iter_gal->second.E5a_DVS == false) && (tmp_eph_iter_gal->second.E5a_HS == 0)))) ||
((std::string(in[i][epoch].Signal, 2) == std::string("7X")) && (d_use_unhealthy_sats || ((tmp_eph_iter_gal->second.E5b_DVS == false) && (tmp_eph_iter_gal->second.E5b_HS == 0))))))
{
if (d_osnma_strict && ((std::string(in[i][epoch].Signal, 2) == std::string("1B")) || ((std::string(in[i][epoch].Signal, 2) == std::string("7X")))))
if (d_osnma_strict)
{
// Pick up only authenticated satellites
// Pick up only recently authenticated nav data. IOD_nav is 10 bits and can be reused.
const auto eph_gst = osnma::galileo_gst_seconds(osnma::galileo_week_to_uint(tmp_eph_iter_gal->second.WN),
osnma::galileo_tow_to_uint(tmp_eph_iter_gal->second.tow));
auto IOD_nav_list = d_auth_nav_data_map.find(tmp_eph_iter_gal->second.PRN);
if (IOD_nav_list != d_auth_nav_data_map.cend())
{
if (IOD_nav_list->second.find(tmp_eph_iter_gal->second.IOD_nav) != IOD_nav_list->second.cend())
for (auto auth_it = IOD_nav_list->second.begin(); auth_it != IOD_nav_list->second.end();)
{
if (osnma::auth_gst_is_stale(auth_it->second, eph_gst))
{
auth_it = IOD_nav_list->second.erase(auth_it);
}
else
{
++auth_it;
}
}
const auto IOD_nav = static_cast<uint32_t>(tmp_eph_iter_gal->second.IOD_nav);
const auto auth_it = IOD_nav_list->second.find(IOD_nav);
if (auth_it != IOD_nav_list->second.cend() &&
osnma::auth_gst_matches_nav_data(auth_it->second, eph_gst))
{
store_valid_observable = true;
}
if (IOD_nav_list->second.empty())
{
d_auth_nav_data_map.erase(IOD_nav_list);
}
}
}
else
@@ -39,7 +39,6 @@
#include <map> // for map
#include <memory> // for shared_ptr, unique_ptr
#include <queue> // for std::queue
#include <set> // for std::set
#include <string> // for string
#include <vector> // for vector
@@ -210,7 +209,7 @@ private:
std::map<int, Gnss_Synchro> d_gnss_observables_map;
std::map<int, Gnss_Synchro> d_gnss_observables_map_t0;
std::map<int, Gnss_Synchro> d_gnss_observables_map_t1;
std::map<uint32_t, std::set<uint32_t>> d_auth_nav_data_map;
std::map<uint32_t, std::map<uint32_t, uint64_t>> d_auth_nav_data_map;
std::queue<GnssTime> d_TimeChannelTagTimestamps;
@@ -31,6 +31,7 @@
#include "galileo_utc_model.h" // for Galileo_Utc_Model
#include "gnss_sdr_make_unique.h" // for std::make_unique in C++11
#include "gnss_synchro.h" // for Gnss_Synchro
#include "osnma_data.h" // for osnma::galileo_week_tow_with_offset
#include "tlm_conf.h"
#include "tlm_crc_stats.h" // for Tlm_CRC_Stats
#include "tlm_utils.h" // for save_tlm_matfile, tlm_remove_file
@@ -404,21 +405,25 @@ void galileo_telemetry_decoder_gs::decode_INAV_word(float *page_part_symbols, in
// extract OSNMA bits, reset container.
if (d_inav_nav.get_osnma_adkd_0_12_nav_bits().size() == 549 && d_band == '1')
{
DLOG(INFO) << "Galileo OSNMA: new ADKD=0/12 navData from " << d_satellite << " at TOW_sf=" << d_inav_nav.get_TOW5() - 25;
const auto tmp_obj_osnma = std::make_shared<std::tuple<uint32_t, std::string, uint32_t>>( // < PRNd , navDataBits, TOW_Sosf>
const auto osnma_nav_time = osnma::galileo_week_tow_with_offset(d_inav_nav.get_Galileo_week(), d_inav_nav.get_TOW5(), -25);
DLOG(INFO) << "Galileo OSNMA: new ADKD=0/12 navData from " << d_satellite << " at WN=" << osnma_nav_time.first << ", TOW_sf=" << osnma_nav_time.second;
const auto tmp_obj_osnma = std::make_shared<std::tuple<uint32_t, std::string, uint32_t, uint32_t>>( // < PRNd , navDataBits, WN, TOW_Sosf>
d_satellite.get_PRN(),
d_inav_nav.get_osnma_adkd_0_12_nav_bits(),
d_inav_nav.get_TOW5() - 25);
osnma_nav_time.first,
osnma_nav_time.second);
this->message_port_pub(pmt::mp("OSNMA_from_TLM"), pmt::make_any(tmp_obj_osnma));
d_inav_nav.reset_osnma_nav_bits_adkd0_12();
}
if (d_inav_nav.get_osnma_adkd_4_nav_bits().size() == 141 && d_band == '1')
{
DLOG(INFO) << "Galileo OSNMA: new ADKD=4 navData from " << d_satellite << " at TOW_sf=" << d_inav_nav.get_TOW6() - 5;
const auto tmp_obj = std::make_shared<std::tuple<uint32_t, std::string, uint32_t>>( // < PRNd , navDataBits, TOW_Sosf> // TODO conversion from W6 to W_Start_of_subframe
const auto osnma_nav_time = osnma::galileo_week_tow_with_offset(d_inav_nav.get_Galileo_week(), d_inav_nav.get_TOW6(), -5);
DLOG(INFO) << "Galileo OSNMA: new ADKD=4 navData from " << d_satellite << " at WN=" << osnma_nav_time.first << ", TOW_sf=" << osnma_nav_time.second;
const auto tmp_obj = std::make_shared<std::tuple<uint32_t, std::string, uint32_t, uint32_t>>( // < PRNd , navDataBits, WN, TOW_Sosf> // TODO conversion from W6 to W_Start_of_subframe
d_satellite.get_PRN(),
d_inav_nav.get_osnma_adkd_4_nav_bits(),
d_inav_nav.get_TOW6() - 5);
osnma_nav_time.first,
osnma_nav_time.second);
this->message_port_pub(pmt::mp("OSNMA_from_TLM"), pmt::make_any(tmp_obj));
d_inav_nav.reset_osnma_nav_bits_adkd4();
}
+2
View File
@@ -21,6 +21,7 @@ set(CORE_LIBS_SOURCES
INIReader.cc
nav_message_monitor.cc
nav_message_udp_sink.cc
osnma_crypto_material.cc
osnma_helper.cc
osnma_msg_receiver.cc
osnma_nav_data_manager.cc
@@ -40,6 +41,7 @@ set(CORE_LIBS_HEADERS
nav_message_monitor.h
nav_message_packet.h
nav_message_udp_sink.h
osnma_crypto_material.h
osnma_helper.h
osnma_msg_receiver.h
osnma_nav_data_manager.h
+353 -189
View File
@@ -1,7 +1,7 @@
/*!
* \file gnss_crypto.cc
* \brief Class for computing cryptographic functions
* \author Carles Fernandez, 2023-2024. cfernandez(at)cttc.es
* \author Carles Fernandez, 2023-2026. cfernandez(at)cttc.es
* Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de
*
*
@@ -10,7 +10,7 @@
* 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)
* Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* -----------------------------------------------------------------------------
@@ -18,7 +18,6 @@
#include "gnss_crypto.h"
#include "Galileo_OSNMA.h"
#include <pugixml.hpp>
#include <cstddef>
#include <cstring>
#include <fstream>
@@ -109,20 +108,9 @@ Gnss_Crypto::Gnss_Crypto(const std::string& certFilePath, const std::string& mer
Gnss_Crypto::~Gnss_Crypto()
{
clear_public_key();
#if USE_GNUTLS_FALLBACK
if (d_PublicKey != nullptr)
{
gnutls_pubkey_deinit(d_PublicKey);
d_PublicKey = nullptr;
}
gnutls_global_deinit();
#else // OpenSSL
#if !USE_OPENSSL_3
if (d_PublicKey != nullptr)
{
EC_KEY_free(d_PublicKey);
}
#endif
#endif
}
@@ -137,6 +125,33 @@ bool Gnss_Crypto::have_public_key() const
}
void Gnss_Crypto::clear_public_key()
{
#if USE_GNUTLS_FALLBACK
if (d_PublicKey != nullptr)
{
gnutls_pubkey_deinit(d_PublicKey);
d_PublicKey = nullptr;
}
#else // OpenSSL
#if USE_OPENSSL_3
if (d_PublicKey != nullptr)
{
EVP_PKEY_free(d_PublicKey);
d_PublicKey = nullptr;
}
#else // OpenSSL 1.x
if (d_PublicKey != nullptr)
{
EC_KEY_free(d_PublicKey);
d_PublicKey = nullptr;
}
#endif
#endif
d_PublicKeyType = std::string("Unknown");
}
bool Gnss_Crypto::store_public_key(const std::string& pubKeyFilePath) const
{
if (!have_public_key())
@@ -489,19 +504,19 @@ std::vector<uint8_t> Gnss_Crypto::compute_SHA_256(const std::vector<uint8_t>& in
EVP_MD_CTX* mdCtx = EVP_MD_CTX_new();
if (!EVP_DigestInit_ex(mdCtx, EVP_sha256(), OPENSSL_ENGINE))
{
// LOG(WARNING) << "OSNMA SHA-256: Message digest initialization failed.";
// LOG(WARNING) << "Galileo OSNMA SHA-256: Message digest initialization failed.";
EVP_MD_CTX_free(mdCtx);
return output;
}
if (!EVP_DigestUpdate(mdCtx, input.data(), input.size()))
{
// LOG(WARNING) << "OSNMA SHA-256: Message digest update failed.";
// LOG(WARNING) << "Galileo OSNMA SHA-256: Message digest update failed.";
EVP_MD_CTX_free(mdCtx);
return output;
}
if (!EVP_DigestFinal_ex(mdCtx, output.data(), &mdLen))
{
// LOG(WARNING) << "OSNMA SHA-256: Message digest finalization failed.";
// LOG(WARNING) << "Galileo OSNMA SHA-256: Message digest finalization failed.";
EVP_MD_CTX_free(mdCtx);
return output;
}
@@ -579,7 +594,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_HMAC_SHA_256(const std::vector<uint8_t
EVP_MAC* mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
if (!mac)
{
LOG(WARNING) << "OSNMA HMAC_SHA_256 computation failed to fetch HMAC";
LOG(WARNING) << "Galileo OSNMA HMAC_SHA_256 computation failed to fetch HMAC";
return output;
}
@@ -587,7 +602,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_HMAC_SHA_256(const std::vector<uint8_t
if (!ctx)
{
EVP_MAC_free(mac);
LOG(WARNING) << "OSNMA HMAC_SHA_256 computation failed to create HMAC context";
LOG(WARNING) << "Galileo OSNMA HMAC_SHA_256 computation failed to create HMAC context";
return output;
}
@@ -600,7 +615,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_HMAC_SHA_256(const std::vector<uint8_t
{
EVP_MAC_CTX_free(ctx);
EVP_MAC_free(mac);
LOG(WARNING) << "OSNMA HMAC_SHA_256 computation failed to initialize HMAC context";
LOG(WARNING) << "Galileo OSNMA HMAC_SHA_256 computation failed to initialize HMAC context";
return output;
}
@@ -609,7 +624,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_HMAC_SHA_256(const std::vector<uint8_t
{
EVP_MAC_CTX_free(ctx);
EVP_MAC_free(mac);
LOG(WARNING) << "OSNMA HMAC_SHA_256 computation failed to update HMAC context";
LOG(WARNING) << "Galileo OSNMA HMAC_SHA_256 computation failed to update HMAC context";
return output;
}
@@ -618,7 +633,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_HMAC_SHA_256(const std::vector<uint8_t
{
EVP_MAC_CTX_free(ctx);
EVP_MAC_free(mac);
LOG(WARNING) << "OSNMA HMAC_SHA_256 computation failed to finalize HMAC";
LOG(WARNING) << "Galileo OSNMA HMAC_SHA_256 computation failed to finalize HMAC";
return output;
}
@@ -632,7 +647,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_HMAC_SHA_256(const std::vector<uint8_t
unsigned char* result = HMAC(EVP_sha256(), key.data(), key.size(), input.data(), input.size(), output.data(), &outputLength);
if (result == nullptr)
{
LOG(WARNING) << "OSNMA HMAC_SHA_256 computation failed to compute HMAC-SHA256";
LOG(WARNING) << "Galileo OSNMA HMAC_SHA_256 computation failed to compute HMAC-SHA256";
return output;
}
@@ -655,7 +670,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_CMAC_AES(const std::vector<uint8_t>& k
int ret = gnutls_hmac_init(&hmac, GNUTLS_MAC_AES_CMAC_128, key.data(), key.size());
if (ret != GNUTLS_E_SUCCESS)
{
LOG(WARNING) << "OSNMA CMAC-AES: gnutls_hmac_init failed: " << gnutls_strerror(ret);
LOG(WARNING) << "Galileo OSNMA CMAC-AES: gnutls_hmac_init failed: " << gnutls_strerror(ret);
return output;
}
@@ -663,7 +678,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_CMAC_AES(const std::vector<uint8_t>& k
ret = gnutls_hmac(hmac, input.data(), input.size());
if (ret != GNUTLS_E_SUCCESS)
{
LOG(WARNING) << "OSNMA CMAC-AES: gnutls_hmac failed: " << gnutls_strerror(ret);
LOG(WARNING) << "Galileo OSNMA CMAC-AES: gnutls_hmac failed: " << gnutls_strerror(ret);
gnutls_hmac_deinit(hmac, nullptr);
return output;
}
@@ -692,14 +707,15 @@ std::vector<uint8_t> Gnss_Crypto::compute_CMAC_AES(const std::vector<uint8_t>& k
EVP_MAC* mac = EVP_MAC_fetch(nullptr, "CMAC", nullptr);
if (!mac)
{
LOG(WARNING) << "OSNMA CMAC-AES: Failed to fetch CMAC";
LOG(WARNING) << "Galileo OSNMA CMAC-AES: Failed to fetch CMAC";
return output;
}
EVP_MAC_CTX* ctx = EVP_MAC_CTX_new(mac);
if (!ctx)
{
LOG(WARNING) << "OSNMA CMAC-AES: Failed to create CMAC context";
EVP_MAC_free(mac);
LOG(WARNING) << "Galileo OSNMA CMAC-AES: Failed to create CMAC context";
return output;
}
@@ -713,7 +729,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_CMAC_AES(const std::vector<uint8_t>& k
{
EVP_MAC_CTX_free(ctx);
EVP_MAC_free(mac);
LOG(WARNING) << "OSNMA CMAC-AES: Failed to initialize CMAC context";
LOG(WARNING) << "Galileo OSNMA CMAC-AES: Failed to initialize CMAC context";
return output;
}
@@ -722,7 +738,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_CMAC_AES(const std::vector<uint8_t>& k
{
EVP_MAC_CTX_free(ctx);
EVP_MAC_free(mac);
LOG(WARNING) << "OSNMA CMAC-AES: Failed to update CMAC context";
LOG(WARNING) << "Galileo OSNMA CMAC-AES: Failed to update CMAC context";
return output;
}
@@ -731,7 +747,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_CMAC_AES(const std::vector<uint8_t>& k
{
EVP_MAC_CTX_free(ctx);
EVP_MAC_free(mac);
LOG(WARNING) << "OSNMA CMAC-AES: Failed to finalize CMAC";
LOG(WARNING) << "Galileo OSNMA CMAC-AES: Failed to finalize CMAC";
return output;
}
@@ -748,14 +764,14 @@ std::vector<uint8_t> Gnss_Crypto::compute_CMAC_AES(const std::vector<uint8_t>& k
CMAC_CTX* cmacCtx = CMAC_CTX_new();
if (!cmacCtx)
{
LOG(WARNING) << "OSNMA CMAC-AES: Failed to create CMAC context";
LOG(WARNING) << "Galileo OSNMA CMAC-AES: Failed to create CMAC context";
return output;
}
// Initialize the CMAC context with the key and cipher
if (CMAC_Init(cmacCtx, key.data(), key.size(), EVP_aes_128_cbc(), nullptr) != 1)
{
LOG(WARNING) << "OSNMA CMAC-AES: MAC_Init failed";
LOG(WARNING) << "Galileo OSNMA CMAC-AES: MAC_Init failed";
CMAC_CTX_free(cmacCtx);
return output;
}
@@ -763,7 +779,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_CMAC_AES(const std::vector<uint8_t>& k
// Compute the CMAC
if (CMAC_Update(cmacCtx, input.data(), input.size()) != 1)
{
LOG(WARNING) << "OSNMA CMAC-AES: CMAC_Update failed";
LOG(WARNING) << "Galileo OSNMA CMAC-AES: CMAC_Update failed";
CMAC_CTX_free(cmacCtx);
return output;
}
@@ -771,7 +787,7 @@ std::vector<uint8_t> Gnss_Crypto::compute_CMAC_AES(const std::vector<uint8_t>& k
// Finalize the CMAC computation and retrieve the output
if (CMAC_Final(cmacCtx, output.data(), &mac_length) != 1)
{
LOG(WARNING) << "OSNMA CMAC-AES: CMAC_Final failed";
LOG(WARNING) << "Galileo OSNMA CMAC-AES: CMAC_Final failed";
CMAC_CTX_free(cmacCtx);
return output;
}
@@ -803,6 +819,238 @@ std::string Gnss_Crypto::get_public_key_type() const
}
std::vector<uint8_t> Gnss_Crypto::get_public_key_compressed() const
{
std::vector<uint8_t> compressed_key;
if (!have_public_key())
{
return compressed_key;
}
#if USE_GNUTLS_FALLBACK
gnutls_ecc_curve_t curve = GNUTLS_ECC_CURVE_INVALID;
gnutls_datum_t x_coord = {nullptr, 0};
gnutls_datum_t y_coord = {nullptr, 0};
const int ret = gnutls_pubkey_export_ecc_raw(
d_PublicKey,
&curve,
&x_coord,
&y_coord);
if (ret != GNUTLS_E_SUCCESS)
{
LOG(WARNING) << "GnuTLS: Failed to export raw EC public key: "
<< gnutls_strerror(ret);
return compressed_key;
}
size_t coordinate_size = 0;
if (curve == GNUTLS_ECC_CURVE_SECP256R1)
{
coordinate_size = 32;
}
else if (curve == GNUTLS_ECC_CURVE_SECP521R1)
{
coordinate_size = 66;
}
else
{
LOG(WARNING) << "GnuTLS: Unsupported EC curve when exporting "
<< "compressed public key";
gnutls_free(x_coord.data);
gnutls_free(y_coord.data);
return compressed_key;
}
if (x_coord.data == nullptr || y_coord.data == nullptr ||
x_coord.size == 0 || y_coord.size == 0)
{
LOG(WARNING) << "GnuTLS: Invalid raw EC public key coordinates";
gnutls_free(x_coord.data);
gnutls_free(y_coord.data);
return compressed_key;
}
if (x_coord.size > coordinate_size)
{
bool only_leading_zeroes = true;
const size_t extra_bytes = x_coord.size - coordinate_size;
for (size_t i = 0; i < extra_bytes; ++i)
{
if (x_coord.data[i] != 0)
{
only_leading_zeroes = false;
break;
}
}
if (!only_leading_zeroes)
{
LOG(WARNING) << "GnuTLS: EC public key x-coordinate is "
<< "larger than expected";
gnutls_free(x_coord.data);
gnutls_free(y_coord.data);
return compressed_key;
}
}
compressed_key.assign(1 + coordinate_size, 0);
// Compressed EC point prefix: 0x02 for even y, 0x03 for odd y.
compressed_key[0] = ((y_coord.data[y_coord.size - 1] & 0x01U) != 0U)
? 0x03
: 0x02;
const size_t x_copy_size =
(x_coord.size > coordinate_size) ? coordinate_size : x_coord.size;
const size_t x_src_offset = x_coord.size - x_copy_size;
const size_t x_dst_offset = 1 + coordinate_size - x_copy_size;
std::memcpy(
&compressed_key[x_dst_offset],
&x_coord.data[x_src_offset],
x_copy_size);
gnutls_free(x_coord.data);
gnutls_free(y_coord.data);
#else
#if USE_OPENSSL_3
if (EVP_PKEY_base_id(d_PublicKey) != EVP_PKEY_EC)
{
LOG(WARNING) << "OpenSSL: public key is not an EC key";
return compressed_key;
}
char curve_name[256] = {};
size_t curve_name_len = 0;
if (EVP_PKEY_get_utf8_string_param(
d_PublicKey,
OSSL_PKEY_PARAM_GROUP_NAME,
curve_name,
sizeof(curve_name),
&curve_name_len) != 1)
{
LOG(WARNING) << "OpenSSL: unable to determine EC public key curve";
return compressed_key;
}
size_t coordinate_size = 0;
if (std::strcmp(curve_name, "prime256v1") == 0 ||
std::strcmp(curve_name, "secp256r1") == 0 ||
std::strcmp(curve_name, "P-256") == 0)
{
coordinate_size = 32;
}
else if (std::strcmp(curve_name, "secp521r1") == 0 ||
std::strcmp(curve_name, "P-521") == 0)
{
coordinate_size = 66;
}
else
{
LOG(WARNING) << "OpenSSL: unsupported EC curve when exporting "
<< "compressed public key: " << curve_name;
return compressed_key;
}
BIGNUM* x_coord = nullptr;
BIGNUM* y_coord = nullptr;
if (EVP_PKEY_get_bn_param(
d_PublicKey,
OSSL_PKEY_PARAM_EC_PUB_X,
&x_coord) != 1 ||
EVP_PKEY_get_bn_param(
d_PublicKey,
OSSL_PKEY_PARAM_EC_PUB_Y,
&y_coord) != 1)
{
LOG(WARNING) << "OpenSSL: unable to export raw EC public key "
<< "coordinates";
BN_free(x_coord);
BN_free(y_coord);
return compressed_key;
}
if (BN_num_bytes(x_coord) > static_cast<int>(coordinate_size))
{
LOG(WARNING) << "OpenSSL: EC public key x-coordinate is larger "
<< "than expected";
BN_free(x_coord);
BN_free(y_coord);
return compressed_key;
}
compressed_key.assign(1 + coordinate_size, 0);
// Compressed EC point prefix: 0x02 for even y, 0x03 for odd y.
compressed_key[0] = BN_is_odd(y_coord) ? 0x03 : 0x02;
if (BN_bn2binpad(
x_coord,
&compressed_key[1],
static_cast<int>(coordinate_size)) !=
static_cast<int>(coordinate_size))
{
LOG(WARNING) << "OpenSSL: unable to encode EC public key "
<< "x-coordinate";
compressed_key.clear();
}
BN_free(x_coord);
BN_free(y_coord);
#else
const EC_GROUP* group = EC_KEY_get0_group(d_PublicKey);
const EC_POINT* point = EC_KEY_get0_public_key(d_PublicKey);
if (group == nullptr || point == nullptr)
{
return compressed_key;
}
const size_t size = EC_POINT_point2oct(
group,
point,
POINT_CONVERSION_COMPRESSED,
nullptr,
0,
nullptr);
if (size == 0)
{
return compressed_key;
}
compressed_key.resize(size);
if (EC_POINT_point2oct(
group,
point,
POINT_CONVERSION_COMPRESSED,
compressed_key.data(),
compressed_key.size(),
nullptr) != compressed_key.size())
{
compressed_key.clear();
}
#endif
#endif
return compressed_key;
}
std::string Gnss_Crypto::get_merkle_tree_hash_function() const
{
if (d_merkle_tree_hash_function.empty())
{
return {"Unknown"};
}
return d_merkle_tree_hash_function;
}
void Gnss_Crypto::set_public_key(const std::vector<uint8_t>& publicKey)
{
d_PublicKeyType = std::string("Unknown");
@@ -971,7 +1219,7 @@ void Gnss_Crypto::set_public_key(const std::vector<uint8_t>& publicKey)
EC_GROUP_free(group);
#endif // OpenSSL 1.x
#endif
DLOG(INFO) << "OSNMA Public Key successfully set up.";
DLOG(INFO) << "Galileo OSNMA Public Key successfully set up.";
}
@@ -990,96 +1238,38 @@ void Gnss_Crypto::set_merkle_root(const std::vector<uint8_t>& v)
}
void Gnss_Crypto::read_merkle_xml(const std::string& merkleFilePath)
void Gnss_Crypto::set_merkle_tree_hash_function(const std::string& hash_function)
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(merkleFilePath.c_str());
if (!result)
if (hash_function == "SHA-256" || hash_function == "SHA256")
{
// XML file not found
// If it was not the default, maybe it is a configuration error, warn user
if (merkleFilePath != MERKLEFILE_DEFAULT && !merkleFilePath.empty())
{
LOG(WARNING) << "File " << merkleFilePath << " not found";
}
// fill default values
d_x_4_0 = convert_from_hex_str("832E15EDE55655EAC6E399A539477B7C034CCE24C3C93FFC904ACD9BF842F04E");
return;
d_merkle_tree_hash_function = "SHA-256";
}
try
else if (hash_function == "SHA3-256" || hash_function == "SHA3_256")
{
pugi::xml_node root = doc.child("signalData");
pugi::xml_node header = root.child("header");
pugi::xml_node body = root.child("body");
// Accessing data from the header
pugi::xml_node galHeader = header.child("GAL-header");
pugi::xml_node source = galHeader.child("source").child("GAL-EXT-GOC-SC-GLAd");
pugi::xml_node destination = galHeader.child("destination").child("GAL-EXT-GOC-SC-GLAd");
std::string issueDate = galHeader.child("issueDate").text().get();
std::string signalVersion = galHeader.child("signalVersion").text().get();
std::string dataVersion = galHeader.child("dataVersion").text().get();
LOG(INFO) << "OSNMA Merkletree - Source: " << source.child_value("mission") << " - " << source.child_value("segment") << " - " << source.child_value("element");
LOG(INFO) << "OSNMA Merkletree - Destination: " << destination.child_value("mission") << " - " << destination.child_value("segment") << " - " << destination.child_value("element");
LOG(INFO) << "OSNMA Merkletree - Issue Date: " << issueDate;
LOG(INFO) << "OSNMA Merkletree - Signal Version: " << signalVersion;
LOG(INFO) << "OSNMA Merkletree - Data Version: " << dataVersion;
// Accessing data from the body
pugi::xml_node merkleTree = body.child("MerkleTree");
int n = std::stoi(merkleTree.child_value("N"));
std::string hashFunction = merkleTree.child_value("HashFunction");
LOG(INFO) << "OSNMA Merkletree - N: " << n;
LOG(INFO) << "OSNMA Merkletree - Hash Function: " << hashFunction;
for (pugi::xml_node publicKey : merkleTree.children("PublicKey"))
{
int i = std::stoi(publicKey.child_value("i"));
std::string pkid = publicKey.child_value("PKID");
int lengthInBits = std::stoi(publicKey.child_value("lengthInBits"));
std::string point = publicKey.child_value("point");
std::string pkType = publicKey.child_value("PKType");
LOG(INFO) << "OSNMA Merkletree - Public Key: " << i;
LOG(INFO) << "OSNMA Merkletree - PKID: " << pkid;
LOG(INFO) << "OSNMA Merkletree - Length in Bits: " << lengthInBits;
LOG(INFO) << "OSNMA Merkletree - Point: " << point;
LOG(INFO) << "OSNMA Merkletree - PK Type: " << pkType;
if (pkType == "ECDSA P-256/SHA-256")
{
d_PublicKeyType = std::string("ECDSA P-256");
}
else if (pkType == "ECDSA P-521/SHA-512")
{
d_PublicKeyType = std::string("ECDSA P-521");
}
}
for (pugi::xml_node treeNode : merkleTree.children("TreeNode"))
{
int j = std::stoi(treeNode.child_value("j"));
int i = std::stoi(treeNode.child_value("i"));
int lengthInBits = std::stoi(treeNode.child_value("lengthInBits"));
LOG(INFO) << "OSNMA Merkletree - Node length (bits): " << lengthInBits;
std::string x_ji = treeNode.child_value("x_ji");
LOG(INFO) << "OSNMA Merkletree - Size string (bytes): " << x_ji.size();
LOG(INFO) << "OSNMA Merkletree - m_" << j << "_" << i << " = " << x_ji;
if (j == 4 && i == 0)
{
d_x_4_0 = convert_from_hex_str(x_ji);
}
}
d_merkle_tree_hash_function = "SHA3-256";
}
catch (const std::exception& e)
else
{
LOG(INFO) << "Exception raised reading the " << merkleFilePath << " file: " << e.what();
d_x_4_0 = convert_from_hex_str("832E15EDE55655EAC6E399A539477B7C034CCE24C3C93FFC904ACD9BF842F04E");
return;
d_merkle_tree_hash_function = "Unknown";
}
std::cout << "OSNMA Merkle Tree successfully read from file " << merkleFilePath << std::endl;
LOG(INFO) << "OSNMA Merkle Tree successfully read from file " << merkleFilePath;
}
Osnma_Merkle_Tree_Material Gnss_Crypto::read_merkle_xml(const std::string& merkleFilePath)
{
auto material = osnma_read_merkle_tree_xml(merkleFilePath);
if (!material.valid)
{
d_x_4_0.clear();
return material;
}
d_x_4_0 = material.root;
set_merkle_tree_hash_function(material.hash_function);
std::cout << "Galileo OSNMA Merkle Tree successfully read from file " << merkleFilePath << std::endl;
LOG(INFO) << "Galileo OSNMA Merkle Tree successfully read from file " << merkleFilePath;
return material;
}
@@ -1269,8 +1459,8 @@ void Gnss_Crypto::readPublicKeyFromPEM(const std::string& pemFilePath)
return;
}
#endif
std::cout << "OSNMA Public key successfully read from file " << pemFilePath << std::endl;
LOG(INFO) << "OSNMA Public key successfully read from file " << pemFilePath;
std::cout << "Galileo OSNMA Public key successfully read from file " << pemFilePath << std::endl;
LOG(INFO) << "Galileo OSNMA Public key successfully read from file " << pemFilePath;
}
@@ -1531,15 +1721,16 @@ bool Gnss_Crypto::readPublicKeyFromCRT(const std::string& crtFilePath)
BIO_free(bio);
X509_free(cert);
#endif
std::cout << "OSNMA Public key successfully read from file " << crtFilePath << std::endl;
LOG(INFO) << "OSNMA Public key successfully read from file " << crtFilePath;
std::cout << "Galileo OSNMA Public key successfully read from file " << crtFilePath << std::endl;
LOG(INFO) << "Galileo OSNMA Public key successfully read from file " << crtFilePath;
return true;
}
bool Gnss_Crypto::convert_raw_to_der_ecdsa(const std::vector<uint8_t>& raw_signature, std::vector<uint8_t>& der_signature) const
{
if (raw_signature.size() % 2 != 0)
der_signature.clear();
if (raw_signature.empty() || raw_signature.size() % 2 != 0)
{
LOG(WARNING) << "Invalid raw ECDSA signature size";
return false;
@@ -1553,17 +1744,24 @@ bool Gnss_Crypto::convert_raw_to_der_ecdsa(const std::vector<uint8_t>& raw_signa
std::vector<uint8_t> result;
result.push_back(0x02); // INTEGER tag
if (value[0] & 0x80)
size_t first_significant_byte = 0;
while (first_significant_byte + 1 < value.size() && value[first_significant_byte] == 0x00)
{
result.push_back(value.size() + 1); // Length byte
result.push_back(0x00); // Add leading zero byte to ensure positive integer
first_significant_byte++;
}
std::vector<uint8_t> encoded_value(value.begin() + first_significant_byte, value.end());
if (encoded_value[0] & 0x80)
{
result.push_back(encoded_value.size() + 1); // Length byte
result.push_back(0x00); // Add leading zero byte to ensure positive integer
}
else
{
result.push_back(value.size()); // Length byte
result.push_back(encoded_value.size()); // Length byte
}
result.insert(result.end(), value.begin(), value.end());
result.insert(result.end(), encoded_value.begin(), encoded_value.end());
return result;
};
@@ -1585,27 +1783,6 @@ bool Gnss_Crypto::convert_raw_to_der_ecdsa(const std::vector<uint8_t>& raw_signa
}
std::vector<uint8_t> Gnss_Crypto::convert_from_hex_str(const std::string& input) const
{
std::vector<uint8_t> result;
// Iterate over the input string in pairs
for (size_t i = 0; i < input.length(); i += 2)
{
// Extract two hexadecimal characters from the input string
std::string hexByte = input.substr(i, 2);
// Convert the hexadecimal string to an integer value
auto value = static_cast<uint8_t>(std::stoul(hexByte, nullptr, 16));
// Append the value to the result vector
result.push_back(value);
}
return result;
}
#if USE_GNUTLS_FALLBACK // GnuTLS-specific functions
bool Gnss_Crypto::pubkey_copy(gnutls_pubkey_t src, gnutls_pubkey_t* dest)
{
@@ -1718,6 +1895,18 @@ bool tonelli_shanks(mpz_t& res, const mpz_t& n, const mpz_t& p)
}
void export_mpz_fixed_width(const mpz_t& value, std::vector<uint8_t>& output, size_t width)
{
output.assign(width, 0);
const size_t byte_count = (mpz_sizeinbase(value, 2) + 7) / 8;
if (byte_count > width)
{
return;
}
mpz_export(output.data() + (width - byte_count), nullptr, 1, 1, 1, 0, value);
}
void Gnss_Crypto::decompress_public_key_secp256r1(const std::vector<uint8_t>& compressed_key, std::vector<uint8_t>& x, std::vector<uint8_t>& y) const
{
// Define curve parameters for secp256r1
@@ -1763,11 +1952,8 @@ void Gnss_Crypto::decompress_public_key_secp256r1(const std::vector<uint8_t>& co
mpz_sub(y_coord, p, y_coord); // y = p - y
}
// Export the x and y coordinates to vectors
x.resize(32);
y.resize(32);
mpz_export(x.data(), nullptr, 1, 1, 1, 0, x_coord);
mpz_export(y.data(), nullptr, 1, 1, 1, 0, y_coord);
export_mpz_fixed_width(x_coord, x, 32);
export_mpz_fixed_width(y_coord, y, 32);
mpz_clears(p, a, b, x_coord, y_coord, y_squared, tmp, nullptr);
}
@@ -1818,11 +2004,8 @@ void Gnss_Crypto::decompress_public_key_secp521r1(const std::vector<uint8_t>& co
mpz_sub(y_coord, p, y_coord); // y = p - y
}
// Export the x and y coordinates to vectors
x.resize(66, 0); // Ensure 66 bytes with leading zeros if necessary
y.resize(66, 0);
mpz_export(x.data() + 1, nullptr, 1, 1, 1, 0, x_coord);
mpz_export(y.data(), nullptr, 1, 1, 1, 0, y_coord);
export_mpz_fixed_width(x_coord, x, 66);
export_mpz_fixed_width(y_coord, y, 66);
mpz_clears(p, a, b, x_coord, y_coord, y_squared, tmp, nullptr);
}
@@ -1830,45 +2013,26 @@ void Gnss_Crypto::decompress_public_key_secp521r1(const std::vector<uint8_t>& co
#if USE_OPENSSL_3
bool Gnss_Crypto::pubkey_copy(EVP_PKEY* src, EVP_PKEY** dest)
{
// Open a memory buffer
BIO* mem_bio = BIO_new(BIO_s_mem());
if (mem_bio == nullptr)
if (src == nullptr || dest == nullptr)
{
return false;
}
// Export the public key from src into the memory buffer in PEM format
if (!PEM_write_bio_PUBKEY(mem_bio, src))
if (src == *dest)
{
return true;
}
if (EVP_PKEY_up_ref(src) != 1)
{
BIO_free(mem_bio);
return false;
}
// Read the data from the memory buffer
char* bio_data;
int64_t data_len = BIO_get_mem_data(mem_bio, &bio_data);
// Create a new memory buffer and load the data into it
BIO* mem_bio2 = BIO_new_mem_buf(bio_data, data_len);
if (mem_bio2 == nullptr)
if (*dest != nullptr)
{
BIO_free(mem_bio);
return false;
EVP_PKEY_free(*dest);
}
// Read the public key from the new memory buffer
*dest = PEM_read_bio_PUBKEY(mem_bio2, nullptr, nullptr, nullptr);
if (*dest == nullptr)
{
BIO_free(mem_bio);
BIO_free(mem_bio2);
return false;
}
// Clean up
BIO_free(mem_bio);
BIO_free(mem_bio2);
*dest = src;
return true;
}
#else // OpenSSL 1.x
+10 -5
View File
@@ -19,6 +19,7 @@
#ifndef GNSS_SDR_GNSS_CRYPTO_H
#define GNSS_SDR_GNSS_CRYPTO_H
#include "osnma_crypto_material.h"
#include <cstdint>
#include <string>
#include <vector>
@@ -52,6 +53,7 @@ public:
~Gnss_Crypto(); //!< Default destructor
bool have_public_key() const; //!< Returns true if the ECDSA Public Key is already loaded
void clear_public_key(); //!< Clears the loaded ECDSA Public Key
/*!
* Stores the ECDSA Public Key in a .pem file, which is read in a following run if the .crt file is not found
@@ -66,19 +68,21 @@ public:
std::vector<uint8_t> compute_HMAC_SHA_256(const std::vector<uint8_t>& key, const std::vector<uint8_t>& input) const; //!< Computes HMAC-SHA-256 message authentication code
std::vector<uint8_t> compute_CMAC_AES(const std::vector<uint8_t>& key, const std::vector<uint8_t>& input) const; //!< Computes CMAC-AES message authentication code
std::vector<uint8_t> get_merkle_root() const; //!< Gets the Merkle Tree root node (\f$ x_{4,0} \f$)
std::string get_public_key_type() const; //!< Gets the ECDSA Public Key type (ECDSA P-256 / ECDSA P-521 / Unknown)
std::vector<uint8_t> get_merkle_root() const; //!< Gets the Merkle Tree root node (\f$ x_{4,0} \f$)
std::string get_public_key_type() const; //!< Gets the ECDSA Public Key type (ECDSA P-256 / ECDSA P-521 / Unknown)
std::vector<uint8_t> get_public_key_compressed() const; //!< Gets the ECDSA Public Key in compressed format
std::string get_merkle_tree_hash_function() const;
void set_public_key(const std::vector<uint8_t>& publickey); //!< Sets the ECDSA Public Key (publickey compressed format)
void set_public_key_type(const std::string& public_key_type); //!< Sets the ECDSA Public Key type (ECDSA P-256 / ECDSA P-521)
void set_merkle_root(const std::vector<uint8_t>& v); //!< Sets the Merkle Tree root node x(\f$ x_{4,0} \f$)
void read_merkle_xml(const std::string& merkleFilePath); //!> Reads the XML file provided from the GSC OSNMA server
void set_merkle_tree_hash_function(const std::string& hash_function);
Osnma_Merkle_Tree_Material read_merkle_xml(const std::string& merkleFilePath); //!> Reads the XML file provided from the GSC OSNMA server
private:
void readPublicKeyFromPEM(const std::string& pemFilePath);
bool readPublicKeyFromCRT(const std::string& crtFilePath);
bool convert_raw_to_der_ecdsa(const std::vector<uint8_t>& raw_signature, std::vector<uint8_t>& der_signature) const;
std::vector<uint8_t> convert_from_hex_str(const std::string& input) const; // TODO - deprecate if OSNMA helper is to do this operation
#if USE_GNUTLS_FALLBACK
void decompress_public_key_secp256r1(const std::vector<uint8_t>& compressed_key, std::vector<uint8_t>& x, std::vector<uint8_t>& y) const;
void decompress_public_key_secp521r1(const std::vector<uint8_t>& compressed_key, std::vector<uint8_t>& x, std::vector<uint8_t>& y) const;
@@ -95,9 +99,10 @@ private:
#endif
std::vector<uint8_t> d_x_4_0;
std::string d_PublicKeyType;
std::string d_merkle_tree_hash_function{"SHA-256"};
};
/** \} */
/** \} */
#endif // GNSS_SDR_GNSS_CRYPTO_H
#endif // GNSS_SDR_GNSS_CRYPTO_H
+643
View File
@@ -0,0 +1,643 @@
/*!
* \file osnma_crypto_material.cc
* \brief OSNMA cryptographic material metadata and local cache manager.
* \author Carles Fernandez-Prades, 2026. cfernandez(at)cttc.es
*
* -----------------------------------------------------------------------------
*
* GNSS-SDR is a Global Navigation Satellite System software-defined receiver.
* This file is part of GNSS-SDR.
*
* Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* -----------------------------------------------------------------------------
*/
#include "osnma_crypto_material.h"
#include "Galileo_OSNMA.h"
#include "gnss_sdr_filesystem.h"
#include <pugixml.hpp>
#include <algorithm>
#include <cctype>
#include <exception>
#include <fstream>
#include <iterator>
#include <map>
#include <sstream>
#if USE_GLOG_AND_GFLAGS
#include <glog/logging.h>
#else
#include <absl/log/log.h>
#endif
namespace
{
std::string trim_copy(const std::string& input)
{
const auto first = std::find_if_not(input.cbegin(), input.cend(), [](unsigned char c) { return std::isspace(c) != 0; });
const auto last = std::find_if_not(input.crbegin(), input.crend(), [](unsigned char c) { return std::isspace(c) != 0; }).base();
if (first >= last)
{
return {};
}
return std::string(first, last);
}
std::string normalize_hash_function(const std::string& hash_function)
{
if (hash_function == "SHA-256" || hash_function == "SHA256")
{
return "SHA-256";
}
if (hash_function == "SHA3-256" || hash_function == "SHA3_256")
{
return "SHA3-256";
}
return "Unknown";
}
std::string normalize_public_key_type(const std::string& key_type)
{
if (key_type == "ECDSA P-256" || key_type == "ECDSA P-256/SHA-256")
{
return "ECDSA P-256";
}
if (key_type == "ECDSA P-521" || key_type == "ECDSA P-521/SHA-512")
{
return "ECDSA P-521";
}
return key_type.empty() ? std::string("Unknown") : key_type;
}
bool hex_nibble(char c, uint8_t& value)
{
if (c >= '0' && c <= '9')
{
value = static_cast<uint8_t>(c - '0');
return true;
}
if (c >= 'a' && c <= 'f')
{
value = static_cast<uint8_t>(c - 'a' + 10);
return true;
}
if (c >= 'A' && c <= 'F')
{
value = static_cast<uint8_t>(c - 'A' + 10);
return true;
}
return false;
}
bool convert_from_hex_string(const std::string& input, std::vector<uint8_t>& bytes)
{
bytes.clear();
const std::string trimmed_input = trim_copy(input);
if (trimmed_input.empty() || (trimmed_input.size() % 2) != 0)
{
return false;
}
bytes.reserve(trimmed_input.size() / 2);
for (size_t i = 0; i < trimmed_input.size(); i += 2)
{
uint8_t high = 0;
uint8_t low = 0;
if (!hex_nibble(trimmed_input[i], high) || !hex_nibble(trimmed_input[i + 1], low))
{
bytes.clear();
return false;
}
bytes.push_back(static_cast<uint8_t>((high << 4U) | low));
}
return true;
}
bool parse_uint4_value(const std::string& value, uint8_t& output)
{
try
{
size_t processed = 0;
const auto parsed = static_cast<unsigned long>(std::stoul(value, &processed, 0));
if (processed != value.size())
{
return false;
}
if (parsed > 15UL)
{
return false;
}
output = static_cast<uint8_t>(parsed);
return true;
}
catch (const std::exception&)
{
return false;
}
}
std::map<std::string, std::string> read_key_value_file(const std::string& path)
{
std::ifstream file(path);
std::map<std::string, std::string> values;
if (!file)
{
return values;
}
std::string line;
while (std::getline(file, line))
{
const auto comment_pos = line.find('#');
if (comment_pos != std::string::npos)
{
line.erase(comment_pos);
}
const auto separator = line.find('=');
if (separator == std::string::npos)
{
continue;
}
const auto key = trim_copy(line.substr(0, separator));
const auto value = trim_copy(line.substr(separator + 1));
if (!key.empty())
{
values[key] = value;
}
}
return values;
}
bool replace_file_with_temporary_file(const std::string& tmp_path, const std::string& destination_path)
{
const fs::path tmp(tmp_path);
const fs::path destination(destination_path);
const fs::path backup(destination_path + ".bak");
errorlib::error_code error;
fs::remove(backup, error);
if (error)
{
return false;
}
const bool destination_exists = fs::exists(destination);
if (destination_exists)
{
error.clear();
fs::rename(destination, backup, error);
if (error)
{
return false;
}
}
fs::rename(tmp, destination, error);
if (!error)
{
if (destination_exists)
{
errorlib::error_code remove_error;
fs::remove(backup, remove_error);
}
return true;
}
error.clear();
if (destination_exists)
{
fs::rename(backup, destination, error);
}
return false;
}
bool write_text_file_atomically(const std::string& path, const std::string& contents)
{
if (path.empty())
{
return false;
}
const std::string tmp_path = path + ".tmp";
{
std::ofstream file(tmp_path, std::ios::binary | std::ios::trunc);
if (!file)
{
return false;
}
file << contents;
if (!file.good())
{
return false;
}
}
return replace_file_with_temporary_file(tmp_path, path);
}
bool copy_file_atomically(const std::string& source_path, const std::string& destination_path)
{
if (source_path.empty() || destination_path.empty())
{
return false;
}
std::ifstream source(source_path, std::ios::binary);
if (!source)
{
return false;
}
const std::string tmp_path = destination_path + ".tmp";
{
std::ofstream destination(tmp_path, std::ios::binary | std::ios::trunc);
if (!destination)
{
return false;
}
destination << source.rdbuf();
if (!destination.good())
{
return false;
}
}
return replace_file_with_temporary_file(tmp_path, destination_path);
}
} // namespace
Osnma_Merkle_Tree_Material osnma_read_merkle_tree_xml(const std::string& merkle_file_path)
{
Osnma_Merkle_Tree_Material material;
material.xml_path = merkle_file_path;
material.source = merkle_file_path.empty() ? std::string() : std::string("configured-xml");
pugi::xml_document doc;
const pugi::xml_parse_result result = doc.load_file(merkle_file_path.c_str());
if (!result)
{
if (!merkle_file_path.empty())
{
LOG(WARNING) << "Galileo OSNMA: Merkle Tree XML file " << merkle_file_path
<< " could not be read: " << result.description();
}
return material;
}
try
{
const pugi::xml_node root = doc.child("signalData");
const pugi::xml_node header = root.child("header");
const pugi::xml_node body = root.child("body");
const pugi::xml_node gal_header = header.child("GAL-header");
const pugi::xml_node merkle_tree = body.child("MerkleTree");
material.uid = merkle_tree.child_value("UID");
material.applicability = merkle_tree.child_value("Applicability");
material.state = merkle_tree.child_value("State");
material.hash_function = normalize_hash_function(merkle_tree.child_value("HashFunction"));
const std::string issue_date = gal_header.child("issueDate").text().get();
const std::string signal_version = gal_header.child("signalVersion").text().get();
const std::string data_version = gal_header.child("dataVersion").text().get();
LOG(INFO) << "Galileo OSNMA Merkletree - Issue Date: " << issue_date;
LOG(INFO) << "Galileo OSNMA Merkletree - Signal Version: " << signal_version;
LOG(INFO) << "Galileo OSNMA Merkletree - Data Version: " << data_version;
LOG(INFO) << "Galileo OSNMA Merkletree - Hash Function: " << material.hash_function;
for (pugi::xml_node public_key : merkle_tree.children("PublicKey"))
{
Osnma_Merkle_Tree_Material::PublicKeyEntry entry;
entry.leaf_index = static_cast<uint32_t>(std::stoul(public_key.child_value("i")));
entry.length_bits = static_cast<uint32_t>(std::stoul(public_key.child_value("lengthInBits")));
entry.point = public_key.child_value("point");
entry.key_type = normalize_public_key_type(public_key.child_value("PKType"));
entry.pkid_valid = parse_uint4_value(public_key.child_value("PKID"), entry.pkid);
material.public_keys.push_back(entry);
LOG(INFO) << "Galileo OSNMA Merkletree - Public Key: " << entry.leaf_index;
LOG(INFO) << "Galileo OSNMA Merkletree - PKID: " << static_cast<uint32_t>(entry.pkid);
LOG(INFO) << "Galileo OSNMA Merkletree - PK Type: " << entry.key_type;
}
for (pugi::xml_node tree_node : merkle_tree.children("TreeNode"))
{
const int j = std::stoi(tree_node.child_value("j"));
const int i = std::stoi(tree_node.child_value("i"));
const std::string x_ji = tree_node.child_value("x_ji");
LOG(INFO) << "Galileo OSNMA Merkletree - m_" << j << "_" << i << " = " << x_ji;
if (j == 4 && i == 0)
{
if (!convert_from_hex_string(x_ji, material.root))
{
LOG(WARNING) << "Galileo OSNMA: invalid Merkle Tree root in " << merkle_file_path;
material.root.clear();
}
}
}
material.valid = !material.root.empty() && material.hash_function != "Unknown";
}
catch (const std::exception& e)
{
LOG(INFO) << "Exception raised reading the " << merkle_file_path << " file: " << e.what();
material = Osnma_Merkle_Tree_Material();
material.xml_path = merkle_file_path;
}
return material;
}
Osnma_Crypto_Material_Manager::Osnma_Crypto_Material_Manager(const std::string& cache_dir) : d_cache_dir(cache_dir)
{
}
Osnma_Public_Key_Material Osnma_Crypto_Material_Manager::load_active_public_key_cache(const std::string& expected_fingerprint) const
{
return load_public_key_metadata(PEMFILE_DEFAULT, expected_fingerprint);
}
Osnma_Public_Key_Material Osnma_Crypto_Material_Manager::load_public_key_metadata(const std::string& pem_path, const std::string& expected_fingerprint) const
{
Osnma_Public_Key_Material key;
key.pem_path = pem_path;
const auto values = read_key_value_file(metadata_path_for_key(pem_path));
if (values.empty())
{
return key;
}
auto it = values.find("version");
if (it == values.cend() || it->second != "1")
{
return key;
}
it = values.find("pkid");
if (it != values.cend())
{
key.pkid_valid = parse_uint4_value(it->second, key.pkid);
}
it = values.find("npkt");
if (it != values.cend())
{
key.npkt_valid = parse_uint4_value(it->second, key.npkt);
}
it = values.find("pk_type");
if (it != values.cend())
{
key.key_type = normalize_public_key_type(it->second);
}
it = values.find("source");
if (it != values.cend())
{
key.source = it->second;
}
it = values.find("public_key_sha256");
if (it != values.cend())
{
key.fingerprint_sha256 = it->second;
}
it = values.find("product_uid");
if (it != values.cend())
{
key.product_uid = it->second;
}
it = values.find("applicability");
if (it != values.cend())
{
key.applicability = it->second;
}
it = values.find("state");
if (it != values.cend())
{
key.state = it->second;
}
if (!expected_fingerprint.empty() && key.fingerprint_sha256 != expected_fingerprint)
{
LOG(WARNING) << "Galileo OSNMA: Public Key metadata fingerprint mismatch for " << pem_path;
return {};
}
key.valid = key.pkid_valid &&
(key.key_type == "ECDSA P-256" || key.key_type == "ECDSA P-521") &&
key.npkt_valid &&
!key.fingerprint_sha256.empty();
return key;
}
bool Osnma_Crypto_Material_Manager::store_active_public_key_cache(const Osnma_Public_Key_Material& key) const
{
Osnma_Public_Key_Material stored_key = key;
if (stored_key.pem_path.empty())
{
stored_key.pem_path = PEMFILE_DEFAULT;
}
return store_public_key_metadata(stored_key);
}
bool Osnma_Crypto_Material_Manager::store_public_key_metadata(const Osnma_Public_Key_Material& key) const
{
if (key.pem_path.empty() || !key.valid || !key.pkid_valid || !key.npkt_valid || key.fingerprint_sha256.empty())
{
return false;
}
std::ostringstream output;
output << "version=1\n";
output << "pkid=" << static_cast<uint32_t>(key.pkid) << "\n";
output << "npkt=" << static_cast<uint32_t>(key.npkt) << "\n";
output << "pk_type=" << key.key_type << "\n";
output << "public_key_sha256=" << key.fingerprint_sha256 << "\n";
if (!key.source.empty())
{
output << "source=" << key.source << "\n";
}
if (!key.product_uid.empty())
{
output << "product_uid=" << key.product_uid << "\n";
}
if (!key.applicability.empty())
{
output << "applicability=" << key.applicability << "\n";
}
if (!key.state.empty())
{
output << "state=" << key.state << "\n";
}
return write_text_file_atomically(metadata_path_for_key(key.pem_path), output.str());
}
Osnma_Merkle_Tree_Material Osnma_Crypto_Material_Manager::load_configured_merkle_tree(const std::string& path) const
{
return osnma_read_merkle_tree_xml(path);
}
Osnma_Merkle_Tree_Material Osnma_Crypto_Material_Manager::load_active_merkle_tree_cache() const
{
return load_configured_merkle_tree(cache_path("OSNMA_MerkleTree.xml"));
}
Osnma_Merkle_Tree_Material Osnma_Crypto_Material_Manager::load_future_merkle_tree_cache() const
{
return load_configured_merkle_tree(cache_path("OSNMA_MerkleTree_Future.xml"));
}
bool Osnma_Crypto_Material_Manager::store_active_merkle_tree_cache(const Osnma_Merkle_Tree_Material& tree) const
{
if (!tree.valid || tree.xml_path.empty())
{
return false;
}
return copy_file_atomically(tree.xml_path, cache_path("OSNMA_MerkleTree.xml"));
}
bool Osnma_Crypto_Material_Manager::store_future_merkle_tree_cache(const Osnma_Merkle_Tree_Material& tree) const
{
if (!tree.valid || tree.xml_path.empty())
{
return false;
}
return copy_file_atomically(tree.xml_path, cache_path("OSNMA_MerkleTree_Future.xml"));
}
bool Osnma_Crypto_Material_Manager::has_candidate_material_for_pkid(uint8_t pkid) const
{
return d_candidate_public_key.valid &&
d_candidate_public_key.pkid_valid &&
d_candidate_public_key.pkid == pkid;
}
Osnma_Public_Key_Material Osnma_Crypto_Material_Manager::candidate_public_key(uint8_t pkid) const
{
if (has_candidate_material_for_pkid(pkid))
{
return d_candidate_public_key;
}
return {};
}
Osnma_Merkle_Tree_Material Osnma_Crypto_Material_Manager::candidate_merkle_tree() const
{
return d_candidate_merkle_tree;
}
void Osnma_Crypto_Material_Manager::set_active_public_key(const Osnma_Public_Key_Material& key)
{
d_active_public_key = key;
}
void Osnma_Crypto_Material_Manager::set_active_merkle_tree(const Osnma_Merkle_Tree_Material& tree)
{
d_active_merkle_tree = tree;
}
void Osnma_Crypto_Material_Manager::set_candidate_public_key(const Osnma_Public_Key_Material& key)
{
d_candidate_public_key = key;
}
void Osnma_Crypto_Material_Manager::set_candidate_merkle_tree(const Osnma_Merkle_Tree_Material& tree)
{
d_candidate_merkle_tree = tree;
}
void Osnma_Crypto_Material_Manager::clear_candidate_public_key()
{
d_candidate_public_key = Osnma_Public_Key_Material();
}
void Osnma_Crypto_Material_Manager::clear_candidate_merkle_tree()
{
d_candidate_merkle_tree = Osnma_Merkle_Tree_Material();
}
Osnma_Public_Key_Material Osnma_Crypto_Material_Manager::active_public_key() const
{
return d_active_public_key;
}
Osnma_Merkle_Tree_Material Osnma_Crypto_Material_Manager::active_merkle_tree() const
{
return d_active_merkle_tree;
}
bool Osnma_Crypto_Material_Manager::promote_candidate_for_pkid(uint8_t pkid)
{
if (!has_candidate_material_for_pkid(pkid))
{
return false;
}
d_active_public_key = d_candidate_public_key;
d_candidate_public_key = Osnma_Public_Key_Material();
return true;
}
bool Osnma_Crypto_Material_Manager::promote_candidate_merkle_tree()
{
if (!d_candidate_merkle_tree.valid)
{
return false;
}
d_active_merkle_tree = d_candidate_merkle_tree;
d_candidate_merkle_tree = Osnma_Merkle_Tree_Material();
return true;
}
std::string Osnma_Crypto_Material_Manager::metadata_path_for_key(const std::string& pem_path) const
{
if (pem_path.empty())
{
return {};
}
return pem_path + ".meta";
}
std::string Osnma_Crypto_Material_Manager::cache_path(const std::string& file_name) const
{
if (d_cache_dir.empty() || d_cache_dir == ".")
{
return "./" + file_name;
}
if (d_cache_dir.back() == '/')
{
return d_cache_dir + file_name;
}
return d_cache_dir + "/" + file_name;
}
+111
View File
@@ -0,0 +1,111 @@
/*!
* \file osnma_crypto_material.h
* \brief OSNMA cryptographic material metadata and local cache manager.
* \author Carles Fernandez-Prades, 2026. cfernandez(at)cttc.es
*
* -----------------------------------------------------------------------------
*
* GNSS-SDR is a Global Navigation Satellite System software-defined receiver.
* This file is part of GNSS-SDR.
*
* Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* -----------------------------------------------------------------------------
*/
#ifndef GNSS_SDR_OSNMA_CRYPTO_MATERIAL_H
#define GNSS_SDR_OSNMA_CRYPTO_MATERIAL_H
#include <cstdint>
#include <string>
#include <vector>
struct Osnma_Public_Key_Material
{
bool valid{false};
bool pkid_valid{false};
bool npkt_valid{false};
uint8_t pkid{0};
uint8_t npkt{0};
std::string key_type;
std::string source;
std::string fingerprint_sha256;
std::string pem_path;
std::string product_uid;
std::string applicability;
std::string state;
std::vector<uint8_t> compressed_key;
};
struct Osnma_Merkle_Tree_Material
{
struct PublicKeyEntry
{
bool pkid_valid{false};
uint8_t pkid{0};
uint32_t leaf_index{0};
uint32_t length_bits{0};
std::string key_type;
std::string point;
};
bool valid{false};
std::string source;
std::string uid;
std::string applicability;
std::string state;
std::string hash_function;
std::vector<uint8_t> root;
std::string xml_path;
std::vector<PublicKeyEntry> public_keys;
};
Osnma_Merkle_Tree_Material osnma_read_merkle_tree_xml(const std::string& merkle_file_path);
class Osnma_Crypto_Material_Manager
{
public:
explicit Osnma_Crypto_Material_Manager(const std::string& cache_dir);
Osnma_Public_Key_Material load_active_public_key_cache(const std::string& expected_fingerprint = std::string()) const;
Osnma_Public_Key_Material load_public_key_metadata(const std::string& pem_path, const std::string& expected_fingerprint = std::string()) const;
Osnma_Public_Key_Material candidate_public_key(uint8_t pkid) const;
Osnma_Merkle_Tree_Material candidate_merkle_tree() const;
Osnma_Merkle_Tree_Material load_configured_merkle_tree(const std::string& path) const;
Osnma_Merkle_Tree_Material load_active_merkle_tree_cache() const;
Osnma_Merkle_Tree_Material load_future_merkle_tree_cache() const;
Osnma_Public_Key_Material active_public_key() const;
Osnma_Merkle_Tree_Material active_merkle_tree() const;
bool store_active_public_key_cache(const Osnma_Public_Key_Material& key) const;
bool store_public_key_metadata(const Osnma_Public_Key_Material& key) const;
bool store_active_merkle_tree_cache(const Osnma_Merkle_Tree_Material& tree) const;
bool store_future_merkle_tree_cache(const Osnma_Merkle_Tree_Material& tree) const;
bool has_candidate_material_for_pkid(uint8_t pkid) const;
void set_active_public_key(const Osnma_Public_Key_Material& key);
void set_active_merkle_tree(const Osnma_Merkle_Tree_Material& tree);
void set_candidate_public_key(const Osnma_Public_Key_Material& key);
void set_candidate_merkle_tree(const Osnma_Merkle_Tree_Material& tree);
void clear_candidate_public_key();
void clear_candidate_merkle_tree();
bool promote_candidate_for_pkid(uint8_t pkid);
bool promote_candidate_merkle_tree();
private:
std::string metadata_path_for_key(const std::string& pem_path) const;
std::string cache_path(const std::string& file_name) const;
std::string d_cache_dir;
Osnma_Public_Key_Material d_active_public_key;
Osnma_Public_Key_Material d_candidate_public_key;
Osnma_Merkle_Tree_Material d_active_merkle_tree;
Osnma_Merkle_Tree_Material d_candidate_merkle_tree;
};
#endif // GNSS_SDR_OSNMA_CRYPTO_MATERIAL_H
+31 -10
View File
@@ -52,17 +52,36 @@ uint32_t Osnma_Helper::compute_gst(tm& input)
}
uint32_t Osnma_Helper::compute_gst_now()
uint32_t Osnma_Helper::compute_gst_now() const
{
time_t now = time(nullptr);
struct tm local_tm = *std::localtime(&now);
struct tm utc_tm = *std::gmtime(&now);
auto timezone_offset = std::mktime(&utc_tm) - std::mktime(&local_tm);
auto epoch_time_point = std::chrono::system_clock::from_time_t(std::mktime(&GST_START_EPOCH) - timezone_offset) + std::chrono::seconds(13);
auto duration_sec = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - epoch_time_point);
const uint32_t sec_in_week = 604800;
const uint32_t week_number = duration_sec.count() / sec_in_week;
const uint32_t time_of_week = duration_sec.count() % sec_in_week;
constexpr int64_t seconds_per_week = 604800;
// Unix timestamp of 1999-08-22 00:00:00 UTC.
// GST week 0 starts at 1999-08-22 00:00:00 GST.
constexpr int64_t gst_epoch_unix_utc_s = 935280000;
// Current GST - UTC offset, in seconds.
// This must be updated if a future
// leap second changes the GNSS-UTC offset.
constexpr int64_t gst_minus_utc_s = 18;
const std::time_t now = std::time(nullptr);
if (now == static_cast<std::time_t>(-1))
{
return compute_gst(0, 0);
}
const int64_t gst_seconds =
static_cast<int64_t>(now) - gst_epoch_unix_utc_s + gst_minus_utc_s;
if (gst_seconds < 0)
{
return compute_gst(0, 0);
}
const auto week_number = static_cast<uint32_t>(gst_seconds / seconds_per_week);
const auto time_of_week = static_cast<uint32_t>(gst_seconds % seconds_per_week);
return compute_gst(week_number, time_of_week);
}
@@ -124,6 +143,8 @@ std::string Osnma_Helper::verification_status_str(int status) const
return "FAIL";
case 2:
return "UNVERIFIED";
case 3:
return "AUTHENTICATED_DONT_USE";
default:
return "UNKNOWN";
}
+1 -1
View File
@@ -35,7 +35,7 @@ public:
~Osnma_Helper() = default;
uint32_t compute_gst(uint32_t WN, uint32_t TOW) const;
uint32_t compute_gst(std::tm& input);
uint32_t compute_gst_now();
uint32_t compute_gst_now() const;
uint32_t get_WN(uint32_t GST) const;
uint32_t get_TOW(uint32_t GST) const;
std::vector<uint8_t> gst_to_uint8(uint32_t GST) const;
File diff suppressed because it is too large Load Diff
+233 -19
View File
@@ -3,7 +3,7 @@
* \brief GNU Radio block that processes Galileo OSNMA data received from
* Galileo E1B telemetry blocks. After successful decoding, sends the content to
* the PVT block.
* \author Carles Fernandez-Prades, 2023-2024. cfernandez(at)cttc.es
* \author Carles Fernandez-Prades, 2023-2026. cfernandez(at)cttc.es
* Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de
*
* -----------------------------------------------------------------------------
@@ -11,7 +11,7 @@
* 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)
* Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* -----------------------------------------------------------------------------
@@ -25,13 +25,16 @@
#include "galileo_inav_message.h" // for OSNMA_msg
#include "gnss_block_interface.h" // for gnss_shared_ptr
#include "osnma_crypto_material.h" // for OSNMA cryptographic material manager
#include "osnma_data.h" // for OSNMA_data structures
#include "osnma_nav_data_manager.h" // for OSNMA_NavDataManager
#include <gnuradio/block.h> // for gr::block
#include <pmt/pmt.h> // for pmt::pmt_t
#include <array> // for std::array
#include <cstddef> // for size_t
#include <cstdint> // for uint8_t
#include <ctime> // for std::time_t
#include <initializer_list> // for std::initializer_list
#include <map> // for std::map, std::multimap
#include <memory> // for std::shared_ptr
#include <string> // for std::string
@@ -50,7 +53,7 @@ class osnma_msg_receiver;
using osnma_msg_receiver_sptr = gnss_shared_ptr<osnma_msg_receiver>;
osnma_msg_receiver_sptr osnma_msg_receiver_make(const std::string& pemFilePath, const std::string& merkleFilePath, bool strict_mode = false);
osnma_msg_receiver_sptr osnma_msg_receiver_make(const std::string& pemFilePath, const std::string& merkleFilePath, bool strict_mode = false, bool replay_mode = false);
/*!
* \brief GNU Radio block that receives asynchronous OSNMA messages
@@ -63,13 +66,14 @@ class osnma_msg_receiver : public gr::block
public:
~osnma_msg_receiver() = default; //!< Default destructor
bool verify_dsm_pkr(const DSM_PKR_message& message) const; //!< Public for benchmarking purposes
void msg_handler_osnma(const pmt::pmt_t& msg); //!< For testing purposes
void read_merkle_xml(const std::string& merklepath); //!< Public for testing purposes
void set_merkle_root(const std::vector<uint8_t>& v); //!< Public for benchmarking purposes
bool verify_dsm_pkr(const DSM_PKR_message& message, const Osnma_Merkle_Tree_Material& merkle_tree) const;
void msg_handler_osnma(const pmt::pmt_t& msg); //!< For testing purposes
void read_merkle_xml(const std::string& merklepath); //!< Public for testing purposes
void set_merkle_root(const std::vector<uint8_t>& v); //!< Public for benchmarking purposes
private:
friend osnma_msg_receiver_sptr osnma_msg_receiver_make(const std::string& pemFilePath, const std::string& merkleFilePath, bool strict_mode);
osnma_msg_receiver(const std::string& crtFilePath, const std::string& merkleFilePath, bool strict_mode);
friend osnma_msg_receiver_sptr osnma_msg_receiver_make(const std::string& pemFilePath, const std::string& merkleFilePath, bool strict_mode, bool replay_mode);
osnma_msg_receiver(const std::string& crtFilePath, const std::string& merkleFilePath, bool strict_mode, bool replay_mode);
void process_osnma_message(const std::shared_ptr<OSNMA_msg>& osnma_msg);
void read_nma_header(uint8_t nma_header);
@@ -77,41 +81,166 @@ private:
void read_dsm_block(const std::shared_ptr<OSNMA_msg>& osnma_msg);
void process_dsm_block(const std::shared_ptr<OSNMA_msg>& osnma_msg);
void process_dsm_message(const std::vector<uint8_t>& dsm_msg, const uint8_t& nma_header);
void expire_dsm_accumulator_if_needed(uint8_t dsm_id, uint32_t current_gst);
void reset_dsm_accumulator(uint8_t dsm_id);
void reset_dsm_accumulators();
void reset_tesla_chain_state(bool preserve_deferred_mack_blocks = false);
void promote_verified_future_kroot_if_due(uint32_t current_gst);
void expire_verified_kroot_if_needed(uint32_t current_gst);
void handle_authenticated_revocation(bool chain_revocation, bool public_key_revocation, bool preserve_future_kroot, bool preserve_active_public_key);
void handle_verified_alert_message();
void handle_authenticated_dont_use_status();
void read_and_process_mack_block(const std::shared_ptr<OSNMA_msg>& osnma_msg);
void read_mack_header();
void read_mack_body();
void process_mack_message();
void try_verify_pending_tags(bool log_unavailable_tags);
void process_deferred_mack_blocks();
void store_deferred_mack_block(const std::shared_ptr<OSNMA_msg>& osnma_msg);
void remove_verified_tags();
void control_tags_awaiting_verify_size();
void send_data_to_pvt(const std::vector<OSNMA_NavData>& data);
void prune_old_tesla_keys(uint32_t reference_gst);
bool verify_tesla_key(std::vector<uint8_t>& key, uint32_t TOW);
bool read_mack_header();
bool read_mack_body();
bool process_mack_block(const std::shared_ptr<OSNMA_msg>& osnma_msg, const std::vector<uint8_t>& allowed_adkds, uint8_t nmas);
bool set_gst_sf_for_mack(uint32_t WN, uint32_t TOW);
bool verify_tesla_key(std::vector<uint8_t>& key, uint32_t key_gst);
bool verify_tag(Tag& tag) const;
bool tag_has_nav_data_available(const Tag& t) const;
bool tag_has_key_available(const Tag& t) const;
bool store_dsm_kroot(const std::vector<uint8_t>& dsm, const uint8_t nma_header) const;
bool tag_is_allowed_by_time_constraint(const Tag& tag) const;
bool kroot_parameters_are_supported(const DSM_KROOT_message& kroot) const;
bool verified_kroot_is_fresh(uint32_t current_gst) const;
bool load_pending_dsm_kroot_cache();
bool load_dsm_kroot_metadata();
bool store_dsm_kroot(const std::vector<uint8_t>& dsm, const uint8_t nma_header, const DSM_KROOT_message& kroot) const;
bool store_dsm_kroot_metadata(const std::vector<uint8_t>& dsm, const uint8_t nma_header, const DSM_KROOT_message& kroot) const;
bool store_public_key_metadata(uint8_t pkid, uint8_t npkt, const std::string& source);
bool npkt_from_public_key_type(const std::string& key_type, uint8_t& npkt) const;
bool pkid_from_certificate_subject_cn(const std::string& crt_file_path, uint8_t& pkid) const;
bool resolve_configured_public_key_identity(const std::string& key_path, const std::vector<uint8_t>& compressed_key, const std::string& key_type, Osnma_Public_Key_Material& key_material) const;
bool merkle_tree_differs_from_renewal_start() const;
bool merkle_tree_differs_from_renewal_start(const Osnma_Merkle_Tree_Material& merkle_tree) const;
bool ensure_new_merkle_tree_available();
bool mack_bits_available(size_t bit_offset, size_t bit_length) const;
bool read_mack_byte(size_t bit_offset, uint8_t& value) const;
bool merge_partial_tesla_key(uint32_t key_gst, const std::vector<uint8_t>& key_bytes, const std::vector<uint8_t>& key_byte_received, std::vector<uint8_t>& merged_key);
bool get_tesla_key(uint32_t WN, uint32_t TOW, int32_t offset_seconds, std::vector<uint8_t>& key) const;
bool get_tesla_key_for_adkd(uint32_t WN, uint32_t TOW, int32_t offset_seconds, uint8_t adkd, std::vector<uint8_t>& key) const;
bool has_tesla_key(uint32_t WN, uint32_t TOW, int32_t offset_seconds) const;
bool has_tesla_key_for_adkd(uint32_t WN, uint32_t TOW, int32_t offset_seconds, uint8_t adkd) const;
uint32_t gst_with_offset(uint32_t WN, uint32_t TOW, int32_t offset_seconds) const;
int64_t gst_delta_seconds(uint32_t lhs_gst, uint32_t rhs_gst) const;
int64_t gst_to_seconds(uint32_t gst) const;
uint64_t read_mack_bits(size_t bit_offset, size_t bit_length) const;
void evaluate_pending_dsm_kroot_cache(uint32_t current_gst);
void invalidate_verified_kroot();
std::pair<std::vector<uint8_t>, uint8_t> parse_dsm_kroot() const;
std::string active_public_key_fingerprint() const;
std::string public_key_fingerprint_sha256(uint8_t pkid, uint8_t npkt, const std::vector<uint8_t>& compressed_key) const;
std::vector<uint8_t> get_merkle_tree_leaves(const DSM_PKR_message& dsm_pkr_message) const;
std::vector<uint8_t> compute_merkle_root(const DSM_PKR_message& dsm_pkr_message, const std::vector<uint8_t>& m_i) const;
std::vector<uint8_t> compute_merkle_root(const DSM_PKR_message& dsm_pkr_message, const std::vector<uint8_t>& m_i, const std::string& hash_function) const;
std::vector<uint8_t> build_message(Tag& tag) const;
std::vector<uint8_t> hash_chain(uint32_t num_of_hashes_needed, const std::vector<uint8_t>& key, uint32_t GST_SFi, const uint8_t lk_bytes) const;
std::vector<MACK_tag_and_info> verify_macseq(const MACK_message& mack);
struct PartialTeslaKey
{
std::vector<uint8_t> bytes;
std::vector<uint8_t> received;
};
struct VerifiedTeslaKey
{
VerifiedTeslaKey() = default;
explicit VerifiedTeslaKey(std::vector<uint8_t> key_bytes) : key(std::move(key_bytes)) {}
explicit VerifiedTeslaKey(std::initializer_list<uint8_t> key_bytes) : key(key_bytes) {}
VerifiedTeslaKey(std::vector<uint8_t> key_bytes, std::vector<uint8_t> adkds) : key(std::move(key_bytes)), allowed_adkds(std::move(adkds)) {}
std::map<uint32_t, std::map<uint32_t, OSNMA_NavData>> d_satellite_nav_data; // map holding OSNMA_NavData sorted by SVID (first key) and TOW (second key).
std::map<uint32_t, std::vector<uint8_t>> d_tesla_keys; // tesla keys over time, sorted by TOW
std::multimap<uint32_t, Tag> d_tags_awaiting_verify; // container with tags to verify from arbitrary SVIDs, sorted by TOW
bool operator==(const std::vector<uint8_t>& key_bytes) const { return key == key_bytes; }
VerifiedTeslaKey& operator=(const std::vector<uint8_t>& key_bytes)
{
key = key_bytes;
allowed_adkds = {0, 4, 12};
return *this;
}
VerifiedTeslaKey& operator=(std::initializer_list<uint8_t> key_bytes)
{
key = key_bytes;
allowed_adkds = {0, 4, 12};
return *this;
}
std::vector<uint8_t> key;
std::vector<uint8_t> allowed_adkds{0, 4, 12};
};
struct DeferredMackBlock
{
std::array<uint32_t, 15> mack{};
std::array<uint8_t, 15> page_valid{};
std::vector<uint8_t> allowed_adkds{0, 4, 12};
uint32_t PRN{};
uint32_t WN_sf0{};
uint32_t TOW_sf0{};
uint8_t nmas{};
bool page_validity_available{false};
};
struct CachedDsmKroot
{
std::vector<uint8_t> dsm;
std::string public_key_fingerprint_sha256;
std::string public_key_type;
std::string raw_sha256;
std::string kroot_sha256;
uint32_t signature_verified_at_gst{0};
uint32_t gst0{0};
uint32_t kroot_gst{0};
uint16_t wn_k{0};
uint8_t nma_header{0};
uint8_t pkid{0};
uint8_t cidkr{0};
uint8_t nmas{0};
uint8_t cpks{0};
uint8_t hf{0};
uint8_t mf{0};
uint8_t ks{0};
uint8_t ts{0};
uint8_t maclt{0};
uint8_t towh_k{0};
uint8_t public_key_npkt{0};
bool valid{false};
bool metadata_valid{false};
bool public_key_npkt_valid{false};
};
std::map<uint32_t, VerifiedTeslaKey> d_tesla_keys; // TESLA keys over time, sorted by GST
std::map<uint32_t, PartialTeslaKey> d_partial_tesla_keys;
std::multimap<uint32_t, Tag> d_tags_awaiting_verify; // container with tags to verify from arbitrary SVIDs, sorted by TOW
std::vector<uint8_t> d_new_public_key;
std::vector<uint8_t> d_merkle_root_at_renewal_start;
std::vector<uint8_t> d_tags_to_verify{0, 4, 12};
std::vector<MACK_message> d_macks_awaiting_MACSEQ_verification;
std::vector<DeferredMackBlock> d_mack_blocks_awaiting_kroot;
std::string d_merkle_file_path;
std::string d_merkle_hash_function_at_renewal_start;
CachedDsmKroot d_pending_dsm_kroot_cache;
std::array<std::array<uint8_t, 256>, 16> d_dsm_message{}; // structure for recording DSM blocks, when filled it sends them to parse and resets itself.
std::array<std::array<uint8_t, 16>, 16> d_dsm_id_received{};
std::array<uint16_t, 16> d_number_of_blocks{};
std::array<uint8_t, 16> d_dsm_nma_header{};
std::array<std::array<uint8_t, 16>, 16> d_dsm_block_nma_header{};
std::array<uint32_t, 16> d_dsm_first_gst{};
std::array<bool, 16> d_dsm_first_gst_valid{};
std::array<uint8_t, 60> d_mack_message{}; // C: 480 b
std::array<uint8_t, 15> d_mack_page_received{};
bool d_mack_page_validity_available{false};
std::unique_ptr<Gnss_Crypto> d_crypto; // class for cryptographic functions
std::unique_ptr<Gnss_Crypto> d_crypto; // class for cryptographic functions
std::unique_ptr<Osnma_Crypto_Material_Manager> d_material_manager;
std::unique_ptr<OSNMA_DSM_Reader> d_dsm_reader; // osnma parameters parser
std::unique_ptr<Osnma_Helper> d_helper; // helper class with auxiliary functions
std::unique_ptr<OSNMA_NavDataManager> d_nav_data_manager; // refactor for holding and processing navigation data
@@ -122,12 +251,14 @@ private:
uint32_t d_GST_Sf{}; // Scaled GST time for cryptographic computations
uint32_t d_GST_Rx{0}; // local GST receiver time
uint32_t d_last_verified_key_GST{0}; // GST for the latest verified TESLA key
uint32_t d_GST_0{}; // Time of applicability GST (KROOT + 30 s)
uint32_t d_GST_SIS{}; // GST coming from W6 and W5 of SIS
uint32_t d_last_verified_kroot_GST{0};
uint32_t d_GST_0{}; // Time of applicability GST (KROOT + 30 s)
uint32_t d_GST_SIS{}; // GST coming from W6 and W5 of SIS
uint32_t d_GST_PKR_PKREV_start{};
uint32_t d_GST_PKR_AM_start{};
uint32_t d_GST_chain_renewal_start{};
uint32_t d_GST_chain_revocation_start{};
uint32_t d_GST_merkle_tree_renewal_start{};
uint32_t d_count_successful_tags{0};
uint32_t d_count_failed_tags{0};
@@ -137,27 +268,110 @@ private:
uint8_t const d_T_L{30}; // s RG Section 2.1
uint8_t d_new_public_key_id{};
uint8_t d_active_public_key_id{};
bool d_new_data{false};
bool d_public_key_verified{false};
bool d_kroot_verified{false};
bool d_tesla_key_verified{false};
bool d_strict_mode{false};
bool d_replay_mode{false};
bool d_flag_hot_start{false};
bool d_flag_PK_renewal{false};
bool d_flag_PK_revocation{false};
bool d_flag_NPK_set{false};
bool d_flag_alert_message{false};
bool d_flag_alert_message_verified{false};
bool d_flag_chain_renewal{false};
bool d_flag_chain_revocation{false};
bool d_flag_merkle_tree_renewal{false};
bool d_receiver_time_override{false};
bool d_active_public_key_id_valid{false};
bool d_new_merkle_tree_loaded{false};
bool d_time_constraint_verified{false};
bool d_kroot_loaded_from_cache{false};
// Provide access to inner functions to Gtest
FRIEND_TEST(OsnmaMsgReceiverTest, TeslaKeyVerification);
FRIEND_TEST(OsnmaMsgReceiverTest, TagVerification);
FRIEND_TEST(OsnmaMsgReceiverTest, TagVerification20Bit);
FRIEND_TEST(OsnmaMsgReceiverTest, TimeConstraintAllowsOnlySlowMac);
FRIEND_TEST(OsnmaMsgReceiverTest, TimeConstraintDecisionIsStoredWithTag);
FRIEND_TEST(OsnmaMsgReceiverTest, ReplayModeSkipsReceiverTimeConstraint);
FRIEND_TEST(OsnmaMsgReceiverTest, TeslaKeyRetainsTimeConstraintDecision);
FRIEND_TEST(OsnmaMsgReceiverTest, TeslaChainResetCanPreserveOrClearDeferredMackQueue);
FRIEND_TEST(OsnmaMsgReceiverTest, PruneOldTeslaKeysDropsExpiredDisclosureKeys);
FRIEND_TEST(OsnmaMsgReceiverTest, TimeConstraintUsesReceiverGuidelineBounds);
FRIEND_TEST(OsnmaMsgReceiverTest, UnverifiedTransitionHeadersDoNotStartTransitions);
FRIEND_TEST(OsnmaMsgReceiverTest, UnverifiedDontUseHeaderDoesNotSkipMackProcessing);
FRIEND_TEST(OsnmaMsgReceiverTest, VerifiedDontUseTagStopsNavigationAuthentication);
FRIEND_TEST(OsnmaMsgReceiverTest, AuthenticatedDontUseClearsDsmAndNavAccumulation);
FRIEND_TEST(OsnmaMsgReceiverTest, StaleKrootStatusSkipsMackProcessing);
FRIEND_TEST(OsnmaMsgReceiverTest, UnverifiedRevocationHeadersDoNotClearState);
FRIEND_TEST(OsnmaMsgReceiverTest, AuthenticatedCrevPreservesVerifiedFutureKroot);
FRIEND_TEST(OsnmaMsgReceiverTest, AuthenticatedRevocationPromotesPreservedFutureKrootAtApplicability);
FRIEND_TEST(OsnmaMsgReceiverTest, AuthenticatedPkrevPreservesVerifiedFutureKrootAndCurrentPublicKey);
FRIEND_TEST(OsnmaMsgReceiverTest, UnverifiedNominalHeaderDoesNotResetTransitionLatches);
FRIEND_TEST(OsnmaMsgReceiverTest, DsmKrootAccumulatorExpiresAfterOneHour);
FRIEND_TEST(OsnmaMsgReceiverTest, DsmPkrAccumulatorExpiresAfterThirteenHours);
FRIEND_TEST(OsnmaMsgReceiverTest, KrootApplicabilitySkipsMackBeforeGst0);
FRIEND_TEST(OsnmaMsgReceiverTest, KrootApplicabilityAcceptsTowhkZero);
FRIEND_TEST(OsnmaMsgReceiverTest, AllZeroPageValidityMaskMeansNoMackPagesAvailable);
FRIEND_TEST(OsnmaMsgReceiverTest, MackBlockWaitsForVerifiedKroot);
FRIEND_TEST(OsnmaMsgReceiverTest, FailedTeslaKeyDoesNotReturnPersistentSuccess);
FRIEND_TEST(OsnmaMsgReceiverTest, OutOfOrderTeslaKeyRejected);
FRIEND_TEST(OsnmaMsgReceiverTest, TeslaKeyLookupUsesWeek);
FRIEND_TEST(OsnmaMsgReceiverTest, FailedDsmPkrKeepsCurrentPublicKey);
FRIEND_TEST(OsnmaMsgReceiverTest, LowerPkidDsmPkrIsRejectedWhilePublicKeyInForce);
FRIEND_TEST(OsnmaMsgReceiverTest, VerifiedDsmPkrSetsActiveKeyIdOnColdStart);
FRIEND_TEST(OsnmaMsgReceiverTest, VerifiedDsmPkrUpdatesPendingKeyIdAcrossRollover);
FRIEND_TEST(OsnmaMsgReceiverTest, MalformedDsmPkrRejectedBeforeLengthCopies);
FRIEND_TEST(OsnmaMsgReceiverTest, KrootWithMismatchedActivePkidIsRejected);
FRIEND_TEST(OsnmaMsgReceiverTest, FailedKrootWithMatchingPkidKeepsActivePublicKey);
FRIEND_TEST(OsnmaMsgReceiverTest, DsmKrootPdkUsesSha256WhenHfSha3);
FRIEND_TEST(OsnmaMsgReceiverTest, VerifiedAlertMessageClearsMerkleMaterial);
FRIEND_TEST(OsnmaMsgReceiverTest, DsmBlockZeroResetsStaleAccumulator);
FRIEND_TEST(OsnmaMsgReceiverTest, DsmBlockZeroKeepsCompatibleUnanchoredBlocks);
FRIEND_TEST(OsnmaMsgReceiverTest, DsmBlockZeroDropsIncompatibleUnanchoredBlocks);
FRIEND_TEST(OsnmaMsgReceiverTest, AnchoredDsmBlockOutsideAnnouncedLengthIsIgnored);
FRIEND_TEST(OsnmaMsgReceiverTest, DsmNonzeroBlockWithDifferentNmaHeaderResetsAnchoredAccumulator);
FRIEND_TEST(OsnmaMsgReceiverTest, RepeatedDsmBlockZeroKeepsPartialAccumulator);
FRIEND_TEST(OsnmaMsgReceiverTest, DsmMessageUsesSavedNmaHeaderForChainRouting);
FRIEND_TEST(OsnmaMsgReceiverTest, FailedKrootDoesNotOverwriteActiveState);
FRIEND_TEST(OsnmaMsgReceiverTest, ChainRenewalDoesNotPromoteUnverifiedKroot);
FRIEND_TEST(OsnmaMsgReceiverTest, ChainRenewalPromotesVerifiedFutureKrootAtApplicability);
FRIEND_TEST(OsnmaMsgReceiverTest, UnverifiedNominalHeaderDoesNotPromoteVerifiedKroot);
FRIEND_TEST(OsnmaMsgReceiverTest, MalformedKrootRejectedBeforeLengthCopies);
FRIEND_TEST(OsnmaMsgReceiverTest, TeslaKeyVerificationAcrossWeekRollover);
FRIEND_TEST(OsnmaMsgReceiverTest, PendingMackQueueExpiresOldEntries);
FRIEND_TEST(OsnmaMsgReceiverTest, FastTagsWaitForNavDataUntilVerificationWindowExpires);
FRIEND_TEST(OsnmaMsgReceiverTest, GstDeltaSecondsIsSigned);
FRIEND_TEST(OsnmaMsgReceiverTest, UnverifiedNominalHeaderKeepsPublicKeyRenewalPending);
FRIEND_TEST(OsnmaMsgReceiverTest, BuildTagMessageM0);
FRIEND_TEST(OsnmaMsgReceiverTest, NavDataArrivalRetriesPendingTagsImmediately);
FRIEND_TEST(OsnmaMsgReceiverTest, MacseqVerificationUsesMackTime);
FRIEND_TEST(OsnmaMsgReceiverTest, MacseqVerificationMissingMacseqKeepsFixedTagsOnly);
FRIEND_TEST(OsnmaMsgReceiverTest, MacseqVerificationChecksFixedSelfAndCrossSlots);
FRIEND_TEST(OsnmaMsgReceiverTest, MacseqVerificationDiscardsReservedFlexibleTags);
FRIEND_TEST(OsnmaMsgReceiverTest, VerifyPublicKey);
FRIEND_TEST(OsnmaMsgReceiverTest, ComputeBaseLeaf);
FRIEND_TEST(OsnmaMsgReceiverTest, CanonicalPublicKeyFingerprintIncludesPkidNpktAndPoint);
FRIEND_TEST(OsnmaMsgReceiverTest, ComputeMerkleRoot);
FRIEND_TEST(OsnmaMsgReceiverTest, ComputeMerkleRootUsesConfiguredHashFunction);
FRIEND_TEST(OsnmaMsgReceiverTest, DummyCopZeroTagUsesZeroNavigationData);
FRIEND_TEST(OsnmaMsgReceiverTest, NmtDsmPkrWaitsForNewMerkleTree);
FRIEND_TEST(OsnmaMsgReceiverTest, MerkleXmlDoesNotChangeActivePublicKeyType);
FRIEND_TEST(OsnmaMsgReceiverTest, PublicKeyMetadataRestoresPkidForPemHotStart);
FRIEND_TEST(OsnmaMsgReceiverTest, PublicKeyMetadataRejectsMismatchedPemFingerprint);
FRIEND_TEST(OsnmaMsgReceiverTest, NmtDsmPkrUsesCandidateMerkleTree);
FRIEND_TEST(OsnmaMsgReceiverTest, UnsupportedKrootParametersAreRejected);
FRIEND_TEST(OsnmaMsgReceiverTest, UnsupportedKrootParametersSkipMackProcessing);
FRIEND_TEST(OsnmaMsgReceiverTest, MackWithMissingTag0StillParsesMackBody);
FRIEND_TEST(OsnmaMsgReceiverTest, PartialMackKeepsAvailableFixedTags);
FRIEND_TEST(OsnmaMsgReceiverTest, PartialTeslaKeyReconstructedAcrossSatellites);
FRIEND_TEST(OsnmaMsgReceiverTest, CachedKrootWithoutMetadataIsNotPromotedAtStartup);
FRIEND_TEST(OsnmaMsgReceiverTest, CachedKrootMetadataStoresFreshnessAndApplicability);
FRIEND_TEST(OsnmaMsgReceiverTest, StaleCachedKrootMetadataIsRejected);
friend class OsnmaTestVectors;
FRIEND_TEST(OsnmaTestVectors, NominalTestConf1);
FRIEND_TEST(OsnmaTestVectors, NominalTestConf2);
FRIEND_TEST(OsnmaTestVectors, PublicKeyRenewal);
+232 -90
View File
@@ -21,22 +21,107 @@
#include <absl/log/log.h>
#endif
namespace
{
constexpr int64_t seconds_per_week = 604800;
constexpr int64_t nav_data_retention_s = 4 * 3600 + 600;
uint64_t gst_seconds(uint32_t WN, uint32_t TOW)
{
return static_cast<uint64_t>(static_cast<int64_t>(WN) * seconds_per_week + static_cast<int64_t>(TOW));
}
uint64_t gst_seconds_with_offset(uint32_t WN, uint32_t TOW, int32_t offset_seconds)
{
int64_t absolute_seconds = static_cast<int64_t>(WN) * seconds_per_week + static_cast<int64_t>(TOW) + offset_seconds;
if (absolute_seconds < 0)
{
absolute_seconds = 0;
}
return static_cast<uint64_t>(absolute_seconds);
}
int64_t tag_seconds_minus_cop(const Tag& tag)
{
return static_cast<int64_t>(gst_seconds(tag.WN, tag.TOW)) - static_cast<int64_t>(30) * tag.cop;
}
bool nav_data_is_before_tag(uint64_t nav_gst, const Tag& tag)
{
return static_cast<int64_t>(nav_gst) < static_cast<int64_t>(gst_seconds(tag.WN, tag.TOW));
}
bool nav_data_is_in_cop_window(const OSNMA_NavData& nav_data, uint64_t nav_gst, const Tag& tag)
{
const int64_t oldest_gst = tag_seconds_minus_cop(tag);
const auto last_received_gst = static_cast<int64_t>(gst_seconds(nav_data.get_last_received_WN(), nav_data.get_last_received_TOW()));
return (oldest_gst <= static_cast<int64_t>(nav_gst) || oldest_gst <= last_received_gst) &&
nav_data_is_before_tag(nav_gst, tag);
}
std::string nav_data_for_tag(const OSNMA_NavData& nav_data, const Tag& tag)
{
if (tag.ADKD == 0 || tag.ADKD == 12)
{
return nav_data.get_ephemeris_data();
}
if (tag.ADKD == 4)
{
return nav_data.get_utc_data();
}
return "";
}
bool tag_gst_is_coherent_with_accumulation(const OSNMA_NavData& nav_data, const Tag& tag)
{
if (!nav_data.has_tag_accumulation())
{
return true;
}
const uint64_t tag_gst = gst_seconds(tag.WN, tag.TOW);
const uint64_t first_tag_gst = nav_data.get_first_accumulated_tag_gst();
const uint64_t last_tag_gst = nav_data.get_last_accumulated_tag_gst();
if (tag_gst < last_tag_gst)
{
return false;
}
if (((tag_gst - last_tag_gst) % 30) != 0)
{
return false;
}
return tag_seconds_minus_cop(tag) <= static_cast<int64_t>(first_tag_gst);
}
} // namespace
/**
* @brief Adds the navigation data bits to the container holding OSNMA_NavData objects.
*
* @param nav_bits The navigation bits.
* @param PRNd The satellite ID.
* @param TOW The TOW of the received data.
* @param WN The GST week number of the received data.
* @param TOW The GST time-of-week of the received data.
*/
void OSNMA_NavDataManager::add_navigation_data(const std::string& nav_bits, uint32_t PRNd, uint32_t TOW)
void OSNMA_NavDataManager::add_navigation_data(const std::string& nav_bits, uint32_t PRNd, uint32_t WN, uint32_t TOW)
{
if (not have_nav_data(nav_bits, PRNd, TOW))
if (not have_nav_data(nav_bits, PRNd, WN, TOW))
{
d_satellite_nav_data[PRNd][TOW].add_nav_data(nav_bits);
d_satellite_nav_data[PRNd][TOW].set_prn_d(PRNd);
d_satellite_nav_data[PRNd][TOW].set_tow_sf0(TOW);
d_satellite_nav_data[PRNd][TOW].set_last_received_TOW(TOW);
const uint64_t nav_gst = gst_seconds(WN, TOW);
auto& nav_data = d_satellite_nav_data[PRNd][nav_gst];
nav_data.add_nav_data(nav_bits);
nav_data.set_prn_d(PRNd);
nav_data.set_wn_sf0(WN);
nav_data.set_tow_sf0(TOW);
nav_data.set_last_received_WN(WN);
nav_data.set_last_received_TOW(TOW);
}
prune_old_navigation_data(WN, TOW);
}
@@ -45,39 +130,116 @@ void OSNMA_NavDataManager::add_navigation_data(const std::string& nav_bits, uint
*/
void OSNMA_NavDataManager::update_nav_data(const std::multimap<uint32_t, Tag>& tags_verified, uint8_t tag_size)
{
if (d_satellite_nav_data.empty())
if (d_satellite_nav_data.empty() || tag_size == 0)
{
return;
}
// loop through all tags
for (const auto& tag : tags_verified)
for (const auto& tag_entry : tags_verified)
{
// if tag status is verified, look for corresponding OSNMA_NavData and add increase verified tag bits.
if (tag.second.status == Tag::e_verification_status::SUCCESS)
const auto& tag = tag_entry.second;
if (tag.cop == 0 ||
(tag.status != Tag::e_verification_status::SUCCESS && tag.status != Tag::e_verification_status::FAIL))
{
auto sat_it = d_satellite_nav_data.find(tag.second.PRN_d);
if (sat_it == d_satellite_nav_data.end())
continue;
}
auto sat_it = d_satellite_nav_data.find(tag.PRN_d);
if (sat_it == d_satellite_nav_data.end())
{
continue;
}
auto& tow_map = sat_it->second;
for (auto& tow_it : tow_map) // note: starts with smallest (i.e. oldest) navigation dataset
{
const std::string nav_data = nav_data_for_tag(tow_it.second, tag);
if (nav_data.empty() ||
tag.nav_data != nav_data ||
!nav_data_is_in_cop_window(tow_it.second, tow_it.first, tag))
{
continue;
}
auto& tow_map = sat_it->second;
for (auto& tow_it : tow_map) // note: starts with smallest (i.e. oldest) navigation dataset
if (tag.status == Tag::e_verification_status::FAIL)
{
std::string nav_data;
if (tag.second.ADKD == 0 || tag.second.ADKD == 12)
if (tag_size < L_t_min &&
tow_it.second.has_tag_accumulation() &&
tow_it.second.get_accumulated_tag_adkd() == tag.ADKD &&
!tow_it.second.get_verified_status())
{
nav_data = tow_it.second.get_ephemeris_data();
}
else if (tag.second.ADKD == 4)
{
nav_data = tow_it.second.get_utc_data();
}
// find associated OSNMA_NavData
if (tag.second.nav_data == nav_data)
{
d_satellite_nav_data[tag.second.PRN_d][tow_it.first].set_update_verified_bits(tag_size);
tow_it.second.reset_tag_accumulation();
}
continue;
}
if (tag_size >= L_t_min)
{
tow_it.second.set_update_verified_bits(tag_size);
continue;
}
const uint64_t tag_gst = gst_seconds(tag.WN, tag.TOW);
if (!tow_it.second.has_tag_accumulation() ||
tow_it.second.get_accumulated_tag_adkd() != tag.ADKD ||
!tag_gst_is_coherent_with_accumulation(tow_it.second, tag))
{
tow_it.second.start_tag_accumulation(tag_size, tag.ADKD, tag_gst);
}
else
{
tow_it.second.continue_tag_accumulation(tag_size, tag_gst);
}
}
}
}
void OSNMA_NavDataManager::reset_tag_accumulations()
{
for (auto& satellite : d_satellite_nav_data)
{
for (auto& tow_navdata : satellite.second)
{
if (tow_navdata.second.get_verified_status())
{
tow_navdata.second.clear_tag_accumulation_metadata();
}
else
{
tow_navdata.second.reset_tag_accumulation();
}
}
}
}
void OSNMA_NavDataManager::prune_old_navigation_data(uint32_t WN, uint32_t TOW)
{
const auto current_gst = static_cast<int64_t>(gst_seconds(WN, TOW));
for (auto sat_it = d_satellite_nav_data.begin(); sat_it != d_satellite_nav_data.end();)
{
auto& tow_map = sat_it->second;
for (auto nav_it = tow_map.begin(); nav_it != tow_map.end();)
{
const auto last_received_gst = static_cast<int64_t>(gst_seconds(nav_it->second.get_last_received_WN(), nav_it->second.get_last_received_TOW()));
if (current_gst > last_received_gst &&
current_gst - last_received_gst > nav_data_retention_s)
{
nav_it = tow_map.erase(nav_it);
}
else
{
++nav_it;
}
}
if (tow_map.empty())
{
sat_it = d_satellite_nav_data.erase(sat_it);
}
else
{
++sat_it;
}
}
}
@@ -90,7 +252,7 @@ std::vector<OSNMA_NavData> OSNMA_NavDataManager::get_verified_data()
{
for (const auto& tow_navdata : prna.second)
{
if (tow_navdata.second.get_verified_bits() >= L_t_min)
if (tow_navdata.second.get_verified_bits() >= L_t_min && !tow_navdata.second.get_verified_status())
{
result.push_back(tow_navdata.second);
d_satellite_nav_data[prna.first][tow_navdata.first].set_verified_status(true);
@@ -101,33 +263,6 @@ std::vector<OSNMA_NavData> OSNMA_NavDataManager::get_verified_data()
}
bool OSNMA_NavDataManager::have_nav_data(uint32_t PRNd, uint32_t TOW, uint8_t ADKD) const
{
const auto sat_it = d_satellite_nav_data.find(PRNd);
if (sat_it == d_satellite_nav_data.cend())
{
return false;
}
const auto tow_it = sat_it->second.find(TOW);
if (tow_it == sat_it->second.cend())
{
return false;
}
switch (ADKD)
{
case 0:
case 12:
return !tow_it->second.get_ephemeris_data().empty();
case 4:
return !tow_it->second.get_utc_data().empty();
default:
return false;
}
}
std::string OSNMA_NavDataManager::get_navigation_data(const Tag& tag) const
{
// Check if Dummy Tag, navData is all zeros
@@ -148,7 +283,7 @@ std::string OSNMA_NavDataManager::get_navigation_data(const Tag& tag) const
return "";
}
// satellite was found, check if TOW exists in inner map
auto nav_data = prn_it->second.find(tag.TOW - 30);
auto nav_data = prn_it->second.find(gst_seconds_with_offset(tag.WN, tag.TOW, -30));
if (nav_data != prn_it->second.end())
{
if (tag.ADKD == 0 || tag.ADKD == 12)
@@ -166,27 +301,24 @@ std::string OSNMA_NavDataManager::get_navigation_data(const Tag& tag) const
}
}
}
else
for (auto rev_it = prn_it->second.rbegin(); rev_it != prn_it->second.rend(); ++rev_it) // NOLINT(modernize-loop-convert)
{
for (auto rev_it = prn_it->second.rbegin(); rev_it != prn_it->second.rend(); ++rev_it) // NOLINT(modernize-loop-convert)
// note: starts with largest (i.e. newest) navigation dataset
// Check if current key (TOW) fulfills condition
if (nav_data_is_in_cop_window(rev_it->second, rev_it->first, tag))
{
// note: starts with largest (i.e. newest) navigation dataset
// Check if current key (TOW) fulfills condition
if ((tag.TOW - 30 * tag.cop <= rev_it->first || tag.TOW - 30 * tag.cop <= rev_it->second.get_last_received_TOW()) && rev_it->first < tag.TOW)
if (tag.ADKD == 0 || tag.ADKD == 12)
{
if (tag.ADKD == 0 || tag.ADKD == 12)
if (!rev_it->second.get_ephemeris_data().empty())
{
if (!rev_it->second.get_ephemeris_data().empty())
{
return rev_it->second.get_ephemeris_data();
}
return rev_it->second.get_ephemeris_data();
}
else if (tag.ADKD == 4)
}
else if (tag.ADKD == 4)
{
if (!rev_it->second.get_utc_data().empty())
{
if (!rev_it->second.get_utc_data().empty())
{
return rev_it->second.get_utc_data();
}
return rev_it->second.get_utc_data();
}
}
}
@@ -200,18 +332,27 @@ std::string OSNMA_NavDataManager::get_navigation_data(const Tag& tag) const
* @remarks e.g.: a SV may repeat the bits over several subframes. In that case, need to save them only once.
* @param nav_bits
* @param PRNd
* @param WN
* @param TOW
* @return
*/
bool OSNMA_NavDataManager::have_nav_data(const std::string& nav_bits, uint32_t PRNd, uint32_t TOW)
bool OSNMA_NavDataManager::have_nav_data(const std::string& nav_bits, uint32_t PRNd, uint32_t WN, uint32_t TOW)
{
if (d_satellite_nav_data.find(PRNd) != d_satellite_nav_data.end())
{
const uint64_t nav_gst = gst_seconds(WN, TOW);
for (auto& data_timestamp : d_satellite_nav_data[PRNd])
{
if (nav_gst >= data_timestamp.first + seconds_per_week ||
data_timestamp.first >= nav_gst + seconds_per_week)
{
continue;
}
if (nav_bits.size() == EPH_SIZE)
{
if (data_timestamp.second.get_ephemeris_data() == nav_bits)
{
data_timestamp.second.set_last_received_WN(WN);
data_timestamp.second.set_last_received_TOW(TOW);
return true;
}
@@ -220,6 +361,7 @@ bool OSNMA_NavDataManager::have_nav_data(const std::string& nav_bits, uint32_t P
{
if (data_timestamp.second.get_utc_data() == nav_bits)
{
data_timestamp.second.set_last_received_WN(WN);
data_timestamp.second.set_last_received_TOW(TOW);
return true;
}
@@ -248,7 +390,7 @@ bool OSNMA_NavDataManager::have_nav_data(const Tag& t) const
}
// satellite was found, check if TOW exists in inner map
// try find target TOW directly first
auto nav_data = prn_it->second.find(t.TOW - 30);
auto nav_data = prn_it->second.find(gst_seconds_with_offset(t.WN, t.TOW, -30));
if (nav_data != prn_it->second.end())
{
if (t.ADKD == 0 || t.ADKD == 12)
@@ -266,29 +408,25 @@ bool OSNMA_NavDataManager::have_nav_data(const Tag& t) const
}
}
}
else
// iterate in reverse order to find matching TOW with Tag's COP value
for (auto rev_it = prn_it->second.rbegin(); rev_it != prn_it->second.rend(); ++rev_it) // NOLINT(modernize-loop-convert)
{
// iterate in reverse order to find matching TOW with Tag's COP value
std::map<uint32_t, OSNMA_NavData> tow_map = prn_it->second;
for (auto rev_it = tow_map.rbegin(); rev_it != tow_map.rend(); ++rev_it) // NOLINT(modernize-loop-convert)
// note: starts with largest (i.e. newest) navigation dataset
// Check if current key (TOW) fulfills cut-off point and is not received after the tag
if (nav_data_is_in_cop_window(rev_it->second, rev_it->first, t))
{
// note: starts with largest (i.e. newest) navigation dataset
// Check if current key (TOW) fulfills cut-off point and is not received after the tag
if ((t.TOW - 30 * t.cop <= rev_it->first || t.TOW - 30 * t.cop <= rev_it->second.get_last_received_TOW()) && rev_it->first < t.TOW)
if (t.ADKD == 0 || t.ADKD == 12)
{
if (t.ADKD == 0 || t.ADKD == 12)
if (!rev_it->second.get_ephemeris_data().empty())
{
if (!rev_it->second.get_ephemeris_data().empty())
{
return true;
}
return true;
}
else if (t.ADKD == 4)
}
else if (t.ADKD == 4)
{
if (!rev_it->second.get_utc_data().empty())
{
if (!rev_it->second.get_utc_data().empty())
{
return true;
}
return true;
}
}
}
@@ -309,8 +447,12 @@ void OSNMA_NavDataManager::log_status() const
<< std::bitset<10>(nav_data.second.get_IOD_nav())
<< ", TOW_start="
<< nav_data.second.get_tow_sf0()
<< ", WN_start="
<< nav_data.second.get_wn_sf0()
<< ", TOW_last="
<< nav_data.second.get_last_received_TOW()
<< ", WN_last="
<< nav_data.second.get_last_received_WN()
<< ", l_t="
<< nav_data.second.get_verified_bits()
<< ", PRNd="
+5 -4
View File
@@ -39,16 +39,17 @@ public:
void log_status() const;
bool have_nav_data(const Tag& t) const;
bool have_nav_data(uint32_t PRNd, uint32_t TOW, uint8_t ADKD) const;
std::string get_navigation_data(const Tag& t) const;
void add_navigation_data(const std::string& nav_bits, uint32_t PRNd, uint32_t TOW);
void add_navigation_data(const std::string& nav_bits, uint32_t PRNd, uint32_t WN, uint32_t TOW);
void update_nav_data(const std::multimap<uint32_t, Tag>& tags_verified, uint8_t tag_size);
bool have_nav_data(const std::string& nav_bits, uint32_t PRNd, uint32_t TOW);
void reset_tag_accumulations();
void prune_old_navigation_data(uint32_t WN, uint32_t TOW);
bool have_nav_data(const std::string& nav_bits, uint32_t PRNd, uint32_t WN, uint32_t TOW);
std::vector<OSNMA_NavData> get_verified_data();
private:
std::map<uint32_t, std::map<uint32_t, OSNMA_NavData>> d_satellite_nav_data{}; // NavData sorted by [PRNd][TOW_start]
std::map<uint32_t, std::map<uint64_t, OSNMA_NavData>> d_satellite_nav_data{}; // NavData sorted by [PRNd][GST_start_seconds]
const uint32_t L_t_min{40};
const uint16_t EPH_SIZE{549};
const uint16_t UTC_SIZE{141};
+16 -6
View File
@@ -52,6 +52,7 @@
#include <cstddef> // for size_t
#include <cstdlib> // for exit
#include <exception> // for exception
#include <fstream> // for std::ifstream
#include <iostream> // for operator<<
#include <iterator> // for insert_iterator, inserter
#include <memory> // for std::shared_ptr
@@ -147,14 +148,23 @@ void GNSSFlowgraph::init()
{
enable_osnma_rx_ = true;
const auto certFilePath = configuration_->property("GNSS-SDR.osnma_public_key", CRTFILE_DEFAULT);
const auto merKleTreePath = configuration_->property("GNSS-SDR.osnma_merkletree", MERKLEFILE_DEFAULT);
std::string osnma_mode = configuration_->property("GNSS-SDR.osnma_mode", std::string(""));
bool strict_mode = false;
if (osnma_mode == "strict")
auto merKleTreePath = configuration_->property("GNSS-SDR.osnma_merkletree", MERKLEFILE_DEFAULT);
if (!configuration_->is_present("GNSS-SDR.osnma_merkletree"))
{
strict_mode = true;
std::ifstream default_merkle_tree(MERKLEFILE_DEFAULT);
if (!default_merkle_tree.good())
{
merKleTreePath.clear();
}
}
osnma_rx_ = osnma_msg_receiver_make(certFilePath, merKleTreePath, strict_mode);
std::string osnma_mode = configuration_->property("GNSS-SDR.osnma_mode", std::string(""));
const bool strict_mode = osnma_mode == "strict";
const bool replay_mode = osnma_mode == "replay";
if (!osnma_mode.empty() && !strict_mode && !replay_mode)
{
LOG(WARNING) << "Unknown GNSS-SDR.osnma_mode=" << osnma_mode << ". Falling back to default mode.";
}
osnma_rx_ = osnma_msg_receiver_make(certFilePath, merKleTreePath, strict_mode, replay_mode);
}
else
{
@@ -46,6 +46,9 @@ constexpr int32_t GALILEO_PAGE_TYPE_BITS = 6;
constexpr int32_t GALILEO_DATA_JK_BITS = 128;
constexpr int32_t GALILEO_DATA_FRAME_BITS = 196;
constexpr int32_t GALILEO_DATA_FRAME_BYTES = 25;
constexpr size_t GALILEO_INAV_EVEN_PAGE_TYPE_BIT = 1;
constexpr size_t GALILEO_INAV_ODD_PAGE_TYPE_BIT = 115;
constexpr int32_t GALILEO_INAV_DUMMY_WORD_TYPE = 63;
constexpr char GALILEO_INAV_PREAMBLE[11] = "0101100000";
const std::vector<std::pair<int32_t, int32_t>> TYPE({{1, 6}});
+4 -4
View File
@@ -132,7 +132,7 @@ const std::unordered_map<uint8_t, uint16_t> OSNMA_TABLE_10 = {
{11, 0},
{12, 0},
{13, 0},
{15, 0},
{14, 0},
{15, 0}}; // key: ks, value: lk_bits
const std::unordered_map<uint8_t, uint8_t> OSNMA_TABLE_11 = {
@@ -159,8 +159,8 @@ const std::unordered_map<std::string, uint16_t> OSNMA_TABLE_15 = {
{std::string("ECDSA P-521"), 1056}}; // key: ECDSA Curve and hash function, value: {l_ds_bits}
const std::string PEMFILE_DEFAULT("./OSNMA_PublicKey.pem");
const std::string CRTFILE_DEFAULT("./OSNMA_PublicKey_20240115100000_newPKID_1.crt");
const std::string MERKLEFILE_DEFAULT("./OSNMA_MerkleTree_20240115100000_newPKID_1.xml");
const std::string CRTFILE_DEFAULT("./OSNMA_PublicKey.crt");
const std::string MERKLEFILE_DEFAULT("./OSNMA_MerkleTree.xml");
const std::string KROOTFILE_DEFAULT("./OSNMA_DSM_KROOT_NMAHeader.bin");
class Mack_lookup
@@ -196,4 +196,4 @@ const std::unordered_map<uint8_t, Mack_lookup> OSNMA_TABLE_16 = {
/** \} */
/** \} */
#endif // GNSS_SDR_GALILEO_OSNMA_H
#endif // GNSS_SDR_GALILEO_OSNMA_H
@@ -179,7 +179,16 @@ void Galileo_Inav_Message::split_page(std::string page_string, int32_t flag_even
flag_CRC_test = true;
// CRC correct: Decode word
const std::string Data_jk_ephemeris = Data_k + Data_j;
page_jk_decoder(Data_jk_ephemeris.c_str());
const int32_t word_type = page_jk_decoder(Data_jk_ephemeris.c_str());
const std::bitset<8> hkroot_bs(osnma_sis.substr(0, 8));
const std::bitset<32> mack_bs(osnma_sis.substr(8, 32));
const bool osnma_sis_available = hkroot_bs.any() || mack_bs.any();
const bool nominal_inav_page = page_INAV[GALILEO_INAV_EVEN_PAGE_TYPE_BIT] == '0' &&
page_INAV[GALILEO_INAV_ODD_PAGE_TYPE_BIT] == '0';
const bool admit_osnma_page = nominal_inav_page &&
word_type != GALILEO_INAV_DUMMY_WORD_TYPE &&
osnma_sis_available;
// Fill OSNMA data
if (page_position_in_inav_subframe != 255)
@@ -189,16 +198,17 @@ void Galileo_Inav_Message::split_page(std::string page_string, int32_t flag_even
nma_position_filled = std::array<int8_t, 15>{};
nma_msg.mack = std::array<uint32_t, 15>{};
nma_msg.hkroot = std::array<uint8_t, 15>{};
nma_msg.page_valid = std::array<uint8_t, 15>{};
nma_msg.page_validity_available = true;
}
std::bitset<8> hkroot_bs(osnma_sis.substr(0, 8));
std::bitset<32> mack_bs(osnma_sis.substr(8, 32));
if (hkroot_bs.count() != 0 && mack_bs.count() != 0)
if (admit_osnma_page)
{
nma_msg.page_valid[page_position_in_inav_subframe] = 1;
nma_position_filled[page_position_in_inav_subframe] = 1;
hkroot_sis = static_cast<uint8_t>(hkroot_bs.to_ulong());
mack_sis = static_cast<uint32_t>(mack_bs.to_ulong());
nma_msg.mack[page_position_in_inav_subframe] = mack_sis;
nma_msg.hkroot[page_position_in_inav_subframe] = hkroot_sis;
nma_position_filled[page_position_in_inav_subframe] = 1;
}
}
}
@@ -1450,6 +1460,8 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk)
nma_position_filled = std::array<int8_t, 15>{};
nma_msg.mack = std::array<uint32_t, 15>{};
nma_msg.hkroot = std::array<uint8_t, 15>{};
nma_msg.page_valid = std::array<uint8_t, 15>{};
nma_msg.page_validity_available = false;
reset_osnma_nav_bits_adkd4();
reset_osnma_nav_bits_adkd0_12();
}
@@ -1474,29 +1486,33 @@ Galileo_ISM Galileo_Inav_Message::get_galileo_ism() const
*/
OSNMA_msg Galileo_Inav_Message::get_osnma_msg()
{
auto msg = nma_msg;
nma_position_filled = std::array<int8_t, 15>{};
nma_msg.mack = std::array<uint32_t, 15>{};
nma_msg.hkroot = std::array<uint8_t, 15>{};
nma_msg.page_valid = std::array<uint8_t, 15>{};
nma_msg.page_validity_available = false;
// Fill TOW and WN
nma_msg.WN_sf0 = WN_0;
auto WN_sf0 = static_cast<uint32_t>(WN_0);
int32_t TOW_sf0 = TOW_5 - 25;
if (TOW_sf0 < 0)
{
TOW_sf0 += 604800;
if (WN_sf0 > 0)
{
WN_sf0--;
}
}
nma_msg.TOW_sf0 = static_cast<uint32_t>(TOW_sf0);
return nma_msg;
msg.WN_sf0 = WN_sf0;
msg.TOW_sf0 = static_cast<uint32_t>(TOW_sf0);
return msg;
}
bool Galileo_Inav_Message::have_new_nma()
{
if (std::all_of(nma_position_filled.begin(), nma_position_filled.end(), [](int8_t element) { return element == 1; }))
{
return true;
}
else
{
return false;
}
return page_position_in_inav_subframe == 14 &&
std::any_of(nma_position_filled.begin(), nma_position_filled.end(), [](int8_t element) { return element == 1; });
}
@@ -49,6 +49,8 @@ public:
OSNMA_msg() = default;
std::array<uint32_t, 15> mack{};
std::array<uint8_t, 15> hkroot{};
std::array<uint8_t, 15> page_valid{};
bool page_validity_available{false};
uint32_t PRN{}; // PRN_a authentication data PRN
uint32_t WN_sf0{}; // Week number at the start of OSNMA subframe
uint32_t TOW_sf0{}; // TOW at the start of OSNMA subframe
@@ -260,6 +262,8 @@ public:
nma_msg.PRN = prn;
nma_msg.mack = std::array<uint32_t, 15>{};
nma_msg.hkroot = std::array<uint8_t, 15>{};
nma_msg.page_valid = std::array<uint8_t, 15>{};
nma_msg.page_validity_available = false;
page_position_in_inav_subframe = 255;
nma_position_filled = std::array<int8_t, 15>{};
}
+71 -1
View File
@@ -15,6 +15,72 @@
*/
#include "osnma_data.h"
namespace osnma
{
uint64_t galileo_gst_seconds(uint32_t WN, uint32_t TOW)
{
return static_cast<uint64_t>(WN) * GALILEO_SECONDS_PER_WEEK + TOW;
}
std::pair<uint32_t, uint32_t> galileo_week_tow_with_offset(uint32_t WN, uint32_t TOW, int32_t offset_seconds)
{
const auto seconds_per_week = static_cast<int64_t>(GALILEO_SECONDS_PER_WEEK);
const int64_t absolute_seconds = static_cast<int64_t>(WN) * seconds_per_week + static_cast<int64_t>(TOW) + offset_seconds;
if (absolute_seconds <= 0)
{
return std::make_pair(0U, 0U);
}
return std::make_pair(
static_cast<uint32_t>(absolute_seconds / seconds_per_week),
static_cast<uint32_t>(absolute_seconds % seconds_per_week));
}
uint32_t galileo_week_to_uint(int32_t WN)
{
if (WN <= 0)
{
return 0;
}
return static_cast<uint32_t>(WN);
}
uint32_t galileo_tow_to_uint(int32_t TOW)
{
if (TOW <= 0)
{
return 0;
}
if (static_cast<uint32_t>(TOW) >= GALILEO_SECONDS_PER_WEEK)
{
return static_cast<uint32_t>(GALILEO_SECONDS_PER_WEEK - 1);
}
return static_cast<uint32_t>(TOW);
}
bool auth_gst_matches_nav_data(uint64_t auth_gst, uint64_t nav_data_gst)
{
if (nav_data_gst < auth_gst)
{
return (auth_gst - nav_data_gst) <=
OSNMA_AUTH_IOD_FUTURE_TOLERANCE_SECONDS;
}
return (nav_data_gst - auth_gst) <= OSNMA_AUTH_IOD_MAX_AGE_SECONDS;
}
bool auth_gst_is_stale(uint64_t auth_gst, uint64_t nav_data_gst)
{
return nav_data_gst > auth_gst &&
(nav_data_gst - auth_gst) > OSNMA_AUTH_IOD_MAX_AGE_SECONDS;
}
} // namespace osnma
uint32_t Tag::id_counter = 0;
uint32_t OSNMA_NavData::id_counter = 0;
@@ -25,7 +91,7 @@ bool OSNMA_NavData::add_nav_data(const std::string& nav_data)
{
d_ephemeris_iono = nav_data;
std::bitset<10> bits(nav_data.substr(0, 10));
IOD_nav = static_cast<uint8_t>(bits.to_ulong());
IOD_nav = static_cast<uint32_t>(bits.to_ulong());
return true;
}
else if (nav_data.size() == 141)
@@ -35,10 +101,14 @@ bool OSNMA_NavData::add_nav_data(const std::string& nav_data)
}
return false;
}
std::string OSNMA_NavData::get_utc_data() const
{
return d_utc;
}
std::string OSNMA_NavData::get_ephemeris_data() const
{
return d_ephemeris_iono;
+83 -11
View File
@@ -23,6 +23,7 @@
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
/** \addtogroup Core
@@ -30,6 +31,20 @@
/** \addtogroup System_Parameters
* \{ */
namespace osnma
{
constexpr uint64_t GALILEO_SECONDS_PER_WEEK = 604800;
constexpr uint64_t OSNMA_AUTH_IOD_MAX_AGE_SECONDS = 4 * 60 * 60;
constexpr uint64_t OSNMA_AUTH_IOD_FUTURE_TOLERANCE_SECONDS = 10 * 60;
uint64_t galileo_gst_seconds(uint32_t WN, uint32_t TOW);
std::pair<uint32_t, uint32_t> galileo_week_tow_with_offset(uint32_t WN, uint32_t TOW, int32_t offset_seconds);
uint32_t galileo_week_to_uint(int32_t WN);
uint32_t galileo_tow_to_uint(int32_t TOW);
bool auth_gst_matches_nav_data(uint64_t auth_gst, uint64_t nav_data_gst);
bool auth_gst_is_stale(uint64_t auth_gst, uint64_t nav_data_gst);
} // namespace osnma
class DSM_nma_header
{
public:
@@ -57,6 +72,8 @@ public:
uint64_t tag0{};
uint16_t macseq{};
uint8_t cop{};
bool tag0_valid{false};
bool macseq_valid{false};
};
@@ -74,9 +91,10 @@ class MACK_tag_and_info
{
public:
MACK_tag_and_info() = default;
uint64_t tag; // C: 20-40 bits
uint64_t tag{}; // C: 20-40 bits
MACK_tag_info tag_info;
uint32_t counter; // CTR
uint32_t counter{}; // CTR
bool valid{true};
};
@@ -127,9 +145,11 @@ public:
MACK_header header;
std::vector<MACK_tag_and_info> tag_and_info;
std::vector<uint8_t> key;
uint32_t TOW; // TODO duplicated variable, also in OSNMA_NavData
uint32_t WN;
uint32_t PRNa;
std::vector<uint8_t> allowed_adkds{0, 4, 12};
uint32_t TOW{}; // TODO duplicated variable, also in OSNMA_NavData
uint32_t WN{};
uint32_t PRNa{};
uint8_t nmas{};
};
@@ -143,30 +163,69 @@ public:
uint32_t get_verified_bits() const { return verified_bits; }
uint32_t get_prn_d() const { return PRNd; }
uint32_t get_IOD_nav() const { return IOD_nav; }
uint32_t get_wn_sf0() const { return d_WN_sf0; }
uint32_t get_last_received_WN() const { return d_last_received_WN; }
uint32_t get_last_received_TOW() const { return d_last_received_TOW; }
uint32_t get_tow_sf0() const { return d_TOW_sf0; }
uint64_t get_first_accumulated_tag_gst() const { return d_first_accumulated_tag_gst; }
uint64_t get_last_accumulated_tag_gst() const { return d_last_accumulated_tag_gst; }
uint8_t get_accumulated_tag_adkd() const { return d_accumulated_tag_adkd; }
bool have_this_bits(std::string nav_data);
bool get_verified_status() const { return verified; }
bool has_tag_accumulation() const { return d_has_tag_accumulation; }
bool add_nav_data(const std::string& nav_data);
void set_tow_sf0(int value) { d_TOW_sf0 = value; }
void set_wn_sf0(uint32_t WN) { d_WN_sf0 = WN; }
void set_ephemeris_data(std::string value) { d_ephemeris_iono = value; }
void set_utc_data(std::string value) { d_utc = value; }
void update_last_received_timestamp(uint32_t TOW);
void set_prn_d(uint32_t value) { PRNd = value; }
void set_last_received_WN(uint32_t WN) { d_last_received_WN = WN; }
void set_last_received_TOW(uint32_t TOW) { d_last_received_TOW = TOW; };
void set_update_verified_bits(uint32_t morebits) { verified_bits += morebits; }
void set_verified_status(bool value) { verified = value; }
void set_IOD_nav(uint32_t value) { IOD_nav = value; }
void start_tag_accumulation(uint32_t bits, uint8_t adkd, uint64_t tag_gst)
{
verified_bits = bits;
d_accumulated_tag_adkd = adkd;
d_first_accumulated_tag_gst = tag_gst;
d_last_accumulated_tag_gst = tag_gst;
d_has_tag_accumulation = true;
}
void continue_tag_accumulation(uint32_t morebits, uint64_t tag_gst)
{
verified_bits += morebits;
d_last_accumulated_tag_gst = tag_gst;
}
void clear_tag_accumulation_metadata()
{
d_accumulated_tag_adkd = 0;
d_first_accumulated_tag_gst = 0;
d_last_accumulated_tag_gst = 0;
d_has_tag_accumulation = false;
}
void reset_tag_accumulation()
{
verified_bits = 0;
clear_tag_accumulation_metadata();
}
private:
static uint32_t id_counter;
std::string d_ephemeris_iono{""};
std::string d_utc{""};
uint32_t d_WN_sf0{0};
uint32_t d_last_received_WN{0};
uint32_t d_TOW_sf0{0};
uint32_t d_last_received_TOW{0};
uint64_t d_first_accumulated_tag_gst{0};
uint64_t d_last_accumulated_tag_gst{0};
uint32_t PRNd{0};
uint32_t verified_bits{0};
uint32_t IOD_nav{0};
uint8_t d_accumulated_tag_adkd{0};
bool d_has_tag_accumulation{false};
bool verified{false};
};
@@ -196,36 +255,47 @@ public:
{
SUCCESS,
FAIL,
UNVERIFIED
UNVERIFIED,
AUTHENTICATED_DONT_USE
};
Tag(const MACK_tag_and_info& MTI, uint32_t TOW, uint32_t WN, uint32_t PRNa, uint8_t CTR) // standard tag constructor, for tags within Tag&Info field
Tag(const MACK_tag_and_info& MTI,
uint32_t TOW,
uint32_t WN,
uint32_t PRNa,
uint8_t CTR,
uint8_t nmas,
std::vector<uint8_t> allowed_adkds = {0, 4, 12}) // standard tag constructor, for tags within Tag&Info field
: tag_id(id_counter++),
TOW(TOW), // TODO missing for build_message WN for GST computation, CTR, NMAS, OSNMA_NavData missing
TOW(TOW),
WN(WN),
PRNa(PRNa),
CTR(CTR),
nmas(nmas),
status(UNVERIFIED),
received_tag(MTI.tag),
computed_tag(0),
PRN_d(MTI.tag_info.PRN_d),
ADKD(MTI.tag_info.ADKD),
cop(MTI.tag_info.cop),
skipped(0)
skipped(0),
allowed_adkds(std::move(allowed_adkds))
{
}
explicit Tag(const MACK_message& mack) // constructor for Tag0
: tag_id(id_counter++),
TOW(mack.TOW), // TODO missing for build_message WN for GST computation, CTR, NMAS, OSNMA_NavData missing
TOW(mack.TOW),
WN(mack.WN),
PRNa(mack.PRNa),
CTR(1),
nmas(mack.nmas),
status(UNVERIFIED),
received_tag(mack.header.tag0),
computed_tag(0),
PRN_d(mack.PRNa), // Tag0 are self-authenticating
ADKD(0),
cop(mack.header.cop),
skipped(0)
skipped(0),
allowed_adkds(mack.allowed_adkds)
{
}
const uint32_t tag_id;
@@ -234,6 +304,7 @@ public:
uint32_t WN;
uint32_t PRNa;
uint8_t CTR;
uint8_t nmas;
e_verification_status status;
uint64_t received_tag;
uint64_t computed_tag;
@@ -241,6 +312,7 @@ public:
uint8_t ADKD;
uint8_t cop;
uint32_t skipped;
std::vector<uint8_t> allowed_adkds{0, 4, 12};
std::string nav_data;
};
@@ -118,6 +118,64 @@ TEST(GnssCryptoTest, VerifyPublicKeyStorage)
}
TEST(GnssCryptoTest, VerifyPublicKeyCompressedExport)
{
const std::string f1("./osnma_test_compressed_p256.pem");
const std::string f2("./osnma_test_compressed_p521.pem");
auto d_crypto = std::make_unique<Gnss_Crypto>();
ASSERT_FALSE(d_crypto->have_public_key());
ASSERT_TRUE(d_crypto->get_public_key_compressed().empty());
// Input taken from RG 1.3 A.7.1.
// Compressed ECDSA P-256 format.
std::vector<uint8_t> publicKey_P256 = {
0x03, 0x03, 0xB2, 0xCE, 0x64, 0xBC, 0x20, 0x7B, 0xDD, 0x8B,
0xC4, 0xDF, 0x85, 0x91, 0x87, 0xFC, 0xB6, 0x86, 0x32, 0x0D,
0x63, 0xFF, 0xA0, 0x91, 0x41, 0x0F, 0xC1, 0x58, 0xFB, 0xB7,
0x79, 0x80, 0xEA};
d_crypto->set_public_key(publicKey_P256);
ASSERT_TRUE(d_crypto->have_public_key());
ASSERT_TRUE(d_crypto->get_public_key_type() == "ECDSA P-256");
ASSERT_EQ(publicKey_P256, d_crypto->get_public_key_compressed());
ASSERT_TRUE(d_crypto->store_public_key(f1));
auto d_crypto2 = std::make_unique<Gnss_Crypto>(f1, "");
ASSERT_TRUE(d_crypto2->have_public_key());
ASSERT_TRUE(d_crypto2->get_public_key_type() == "ECDSA P-256");
ASSERT_EQ(publicKey_P256, d_crypto2->get_public_key_compressed());
// P-521 public key in compressed format.
std::vector<uint8_t> publicKey_P521 = {
0x03, 0x00, 0x28, 0x35, 0xBB, 0xE9, 0x24, 0x59, 0x4E, 0xF0,
0xE3, 0xA2, 0xDB, 0xC0, 0x49, 0x30, 0x60, 0x7C, 0x61, 0x90,
0xE4, 0x03, 0xE0, 0xC7, 0xB8, 0xC2, 0x62, 0x37, 0xF7, 0x58,
0x56, 0xBE, 0x63, 0x5C, 0x97, 0xF7, 0x53, 0x64, 0x7E, 0xE1,
0x0C, 0x07, 0xD3, 0x97, 0x8D, 0x58, 0x46, 0xFD, 0x6E, 0x06,
0x44, 0x01, 0xA7, 0xAA, 0xC4, 0x95, 0x13, 0x5D, 0xC9, 0x77,
0x26, 0xE9, 0xF8, 0x72, 0x0C, 0xD3, 0x88};
d_crypto->set_public_key(publicKey_P521);
ASSERT_TRUE(d_crypto->have_public_key());
ASSERT_TRUE(d_crypto->get_public_key_type() == "ECDSA P-521");
ASSERT_EQ(publicKey_P521, d_crypto->get_public_key_compressed());
ASSERT_TRUE(d_crypto->store_public_key(f2));
auto d_crypto3 = std::make_unique<Gnss_Crypto>(f2, "");
ASSERT_TRUE(d_crypto3->have_public_key());
ASSERT_TRUE(d_crypto3->get_public_key_type() == "ECDSA P-521");
ASSERT_EQ(publicKey_P521, d_crypto3->get_public_key_compressed());
errorlib::error_code ec;
ASSERT_TRUE(fs::remove(fs::path(f1), ec));
ASSERT_TRUE(fs::remove(fs::path(f2), ec));
}
TEST(GnssCryptoTest, TestComputeSHA_256)
{
auto d_crypto = std::make_unique<Gnss_Crypto>();
@@ -345,3 +403,44 @@ TEST(GnssCryptoTest, VerifySignatureP521)
wrong_signature[1] = 1;
ASSERT_FALSE(d_crypto->verify_signature_ecdsa_p521(message, wrong_signature));
}
TEST(GnssCryptoTest, VerifySignatureP521WithLeadingZeroComponent)
{
std::unique_ptr<Gnss_Crypto> d_crypto = std::make_unique<Gnss_Crypto>();
// PKREV test vector KROOT signed with PKID 9. The raw fixed-width ECDSA S component
// starts with 0x00 and must be minimally encoded when converted to DER.
std::vector<uint8_t> message = {
0xA2, 0x90, 0x49, 0x22, 0x04, 0xEA, 0x9B, 0xB4, 0x7B, 0xD0,
0xBC, 0x52, 0xC2, 0x2E, 0x37, 0x93, 0x30, 0xEB, 0xB7, 0x2A,
0xEA, 0xC9, 0x98, 0x2B, 0x98, 0x23, 0x76, 0xC5, 0xED};
std::vector<uint8_t> publicKey = {
0x03, 0x00, 0x4B, 0xE2, 0xD8, 0x94, 0xFC, 0xA1, 0xC3, 0x58,
0x5D, 0xB7, 0xFC, 0x4D, 0x29, 0xEB, 0x69, 0x5E, 0x41, 0xE3,
0x66, 0xB1, 0x03, 0x9C, 0xC4, 0x63, 0x7E, 0x68, 0x14, 0x21,
0x6D, 0x34, 0xEA, 0xE8, 0x74, 0xFF, 0x66, 0x52, 0xC4, 0x9B,
0xE0, 0x54, 0xEF, 0x2E, 0x9D, 0x22, 0x9B, 0xDD, 0x03, 0x04,
0x80, 0x39, 0xB4, 0x0D, 0x23, 0x0F, 0xFC, 0x76, 0x71, 0xE7,
0x66, 0xB7, 0x42, 0x0A, 0xF2, 0x2E, 0xEE};
std::vector<uint8_t> signature = {
0x01, 0x6F, 0x79, 0xE7, 0x11, 0x39, 0xE5, 0x83, 0x66, 0xF7,
0xB0, 0x43, 0xD6, 0x50, 0xCD, 0x70, 0x58, 0xB4, 0x84, 0xEA,
0x26, 0x88, 0xE1, 0xF6, 0xE8, 0xBE, 0x13, 0xD0, 0x89, 0x6D,
0x74, 0x8B, 0xE6, 0x7B, 0x1C, 0xCA, 0x68, 0x2D, 0x49, 0xFF,
0x4E, 0xAD, 0xD0, 0xAA, 0x91, 0xBD, 0xFB, 0x04, 0xA9, 0x29,
0x69, 0x62, 0x68, 0x49, 0x76, 0x1C, 0x59, 0xDA, 0x81, 0xF3,
0x15, 0x08, 0xF5, 0xC6, 0x9D, 0x60, 0x00, 0x02, 0x7D, 0x02,
0x52, 0x32, 0xA0, 0xC1, 0x9B, 0x92, 0x12, 0x29, 0x43, 0x1A,
0x7C, 0xC3, 0x3F, 0xF7, 0x5A, 0x0B, 0x3C, 0x03, 0x99, 0xF4,
0x96, 0x38, 0x72, 0x67, 0xDD, 0x19, 0x9C, 0x28, 0x0C, 0x9F,
0xC6, 0x61, 0x65, 0x05, 0xC6, 0xEC, 0x48, 0x73, 0xFA, 0x1D,
0x44, 0x4F, 0x54, 0x92, 0x6D, 0xE1, 0xC1, 0x76, 0x8A, 0x7D,
0x64, 0xA9, 0x67, 0x78, 0x26, 0xEC, 0x38, 0xB9, 0x26, 0x1D,
0xF4, 0x38};
d_crypto->set_public_key(publicKey);
ASSERT_TRUE(d_crypto->verify_signature_ecdsa_p521(message, signature));
}
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,7 @@
/*!
* \file osmna_test_vectors.cc
* \brief Tests for the osnma_msg_receiver class.
* \author Carles Fernandez, 2023-2024. cfernandez(at)cttc.es
* \author Carles Fernandez, 2023-2026. cfernandez(at)cttc.es
* Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de
*
*
@@ -10,18 +10,23 @@
* 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)
* Copyright (C) 2010-2026 (see AUTHORS file for a list of contributors)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* -----------------------------------------------------------------------------
*/
#include "gnss_crypto.h"
#include "gnss_sdr_filesystem.h"
#include "osnma_msg_receiver.h"
#include <gtest/gtest.h>
#include <bitset>
#include <cerrno>
#include <chrono>
#include <fstream>
#include <iterator>
#include <string>
#include <tuple>
#include <vector>
#if USE_GLOG_AND_GFLAGS
@@ -30,6 +35,21 @@
#include <absl/log/log.h>
#endif
#if defined(GTEST_SKIP)
#define GNSSSDR_OSNMA_SKIP_OR_FAIL() GTEST_SKIP()
#else
#define GNSSSDR_OSNMA_SKIP_OR_FAIL() FAIL()
#endif
namespace
{
bool is_missing_file_error(const errorlib::error_code& ec)
{
return ec.value() == ENOENT || ec.value() == ENOTDIR;
}
} // namespace
struct TestVector
{
int svId;
@@ -49,6 +69,25 @@ protected:
void set_time(std::tm& input);
void SetUp() override
{
if (!save_default_file(KROOTFILE_DEFAULT, d_had_kroot_file, d_saved_kroot_file) ||
!save_default_file(KROOTFILE_DEFAULT + ".meta", d_had_kroot_metadata_file, d_saved_kroot_metadata_file) ||
!save_default_file(PEMFILE_DEFAULT, d_had_pem_file, d_saved_pem_file) ||
!save_default_file(PEMFILE_DEFAULT + ".meta", d_had_pem_metadata_file, d_saved_pem_metadata_file) ||
!remove_default_file(KROOTFILE_DEFAULT) ||
!remove_default_file(KROOTFILE_DEFAULT + ".meta") ||
!remove_default_file(PEMFILE_DEFAULT) ||
!remove_default_file(PEMFILE_DEFAULT + ".meta"))
{
GNSSSDR_OSNMA_SKIP_OR_FAIL() << "Unable to preserve existing OSNMA hot-start files";
}
}
void TearDown() override
{
restore_default_file(KROOTFILE_DEFAULT, d_had_kroot_file, d_saved_kroot_file);
restore_default_file(KROOTFILE_DEFAULT + ".meta", d_had_kroot_metadata_file, d_saved_kroot_metadata_file);
restore_default_file(PEMFILE_DEFAULT, d_had_pem_file, d_saved_pem_file);
restore_default_file(PEMFILE_DEFAULT + ".meta", d_had_pem_metadata_file, d_saved_pem_metadata_file);
}
uint32_t d_GST_SIS{};
@@ -61,6 +100,85 @@ protected:
const int DURATION_SUBFRAME{30}; // duration of a subframe, in seconds// 13 + 5;
bool d_flag_NPK{false}; // flag for NPK, new MT will be set when the new Kroot is received.
private:
bool save_default_file(const std::string& path, bool& existed, std::vector<char>& contents)
{
contents.clear();
errorlib::error_code ec;
existed = fs::exists(fs::path(path), ec);
if (ec)
{
if (is_missing_file_error(ec))
{
existed = false;
return true;
}
ADD_FAILURE() << "Unable to check " << path << ": " << ec.message();
return false;
}
if (!existed)
{
return true;
}
std::ifstream file(path, std::ios::binary);
if (!file)
{
ADD_FAILURE() << "Unable to read existing " << path;
return false;
}
contents.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
return true;
}
bool remove_default_file(const std::string& path)
{
errorlib::error_code ec;
fs::remove(fs::path(path), ec);
if (ec)
{
if (is_missing_file_error(ec))
{
return true;
}
ADD_FAILURE() << "Unable to remove " << path << ": " << ec.message();
return false;
}
return true;
}
void restore_default_file(const std::string& path, bool existed, const std::vector<char>& contents)
{
if (!remove_default_file(path))
{
return;
}
if (!existed)
{
return;
}
std::ofstream file(path, std::ios::binary | std::ios::trunc);
if (!file)
{
ADD_FAILURE() << "Unable to restore " << path;
return;
}
if (!contents.empty())
{
file.write(contents.data(), contents.size());
}
}
bool d_had_kroot_file{false};
bool d_had_kroot_metadata_file{false};
bool d_had_pem_file{false};
bool d_had_pem_metadata_file{false};
std::vector<char> d_saved_kroot_file;
std::vector<char> d_saved_kroot_metadata_file;
std::vector<char> d_saved_pem_file;
std::vector<char> d_saved_pem_metadata_file;
};
TEST_F(OsnmaTestVectors, NominalTestConf1)
@@ -474,9 +592,10 @@ bool OsnmaTestVectors::feedOsnmaWithTestVectors(osnma_msg_receiver_sptr osnma_ob
{
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>
const auto tmp_obj_osnma = std::make_shared<std::tuple<uint32_t, std::string, uint32_t, uint32_t>>( // < PRNd , navDataBits, WN, TOW_Sosf>
tv.svId,
nav_data_ADKD_0_12,
osnmaMsg_sptr->WN_sf0,
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_object->msg_handler_osnma(pmt::make_any(tmp_obj_osnma));
@@ -511,9 +630,10 @@ bool OsnmaTestVectors::feedOsnmaWithTestVectors(osnma_msg_receiver_sptr osnma_ob
{
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>
const auto tmp_obj_osnma = std::make_shared<std::tuple<uint32_t, std::string, uint32_t, uint32_t>>( // < PRNd , navDataBits, WN, TOW_Sosf>
tv.svId,
nav_data_ADKD_4,
osnmaMsg_sptr->WN_sf0,
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_object->msg_handler_osnma(pmt::make_any(tmp_obj_osnma));
@@ -523,6 +643,13 @@ bool OsnmaTestVectors::feedOsnmaWithTestVectors(osnma_msg_receiver_sptr osnma_ob
// Call the handler, as if it came from telemetry decoder block
auto temp_obj = pmt::make_any(osnmaMsg_sptr);
osnma_object->d_receiver_time_override = true;
const uint32_t gst_sis_tow = d_GST_SIS & 0x000FFFFF;
const uint32_t gst_sis_wn = (d_GST_SIS & 0xFFF00000) >> 20;
const uint32_t receiver_time_tow = gst_sis_tow + DURATION_SUBFRAME;
const uint32_t receiver_time_wn = gst_sis_wn + receiver_time_tow / static_cast<uint32_t>(osnma::GALILEO_SECONDS_PER_WEEK);
osnma_object->d_GST_Rx = (receiver_time_wn & 0x00000FFF) << 20 |
((receiver_time_tow % static_cast<uint32_t>(osnma::GALILEO_SECONDS_PER_WEEK)) & 0x000FFFFF);
osnma_object->msg_handler_osnma(temp_obj); // osnma entry point
}
if (!end_of_hex_stream)
@@ -20,15 +20,108 @@
#include "galileo_inav_message.h"
#include "gnss_sdr_make_unique.h" // for std::make_unique in C++11
#include "viterbi_decoder.h"
#include <boost/crc.hpp>
#include <boost/dynamic_bitset.hpp>
#include <gtest/gtest.h>
#include <algorithm> // for copy
#include <array>
#include <bitset>
#include <chrono>
#include <cstddef>
#include <exception>
#include <iterator> // for std::back_inserter
#include <string>
#include <unistd.h>
#include <utility>
#include <vector>
namespace
{
using CRC_Galileo_INAV_test_type = boost::crc_optimal<24, 0x1864CFBU, 0x0, 0x0, false, false>;
std::string to_bit_string(uint32_t value, size_t width)
{
std::string bits(width, '0');
for (size_t i = 0; i < width; i++)
{
if ((value & (uint32_t{1} << (width - i - 1))) != 0)
{
bits[i] = '1';
}
}
return bits;
}
uint32_t compute_galileo_inav_crc(const std::string& crc_data)
{
CRC_Galileo_INAV_test_type CRC_Galileo;
const std::bitset<GALILEO_DATA_FRAME_BITS> bits(crc_data);
boost::dynamic_bitset<unsigned char> frame_bits(bits.to_string());
std::vector<unsigned char> bytes;
boost::to_block_range(frame_bits, std::back_inserter(bytes));
std::reverse(bytes.begin(), bytes.end());
CRC_Galileo.process_bytes(bytes.data(), GALILEO_DATA_FRAME_BYTES);
return CRC_Galileo.checksum();
}
std::pair<std::string, std::string> build_inav_page(uint8_t word_type, char page_type, const std::string& osnma_sis)
{
std::string data_k = to_bit_string(word_type, 6);
data_k.append(106, '0');
std::string even_page;
even_page.reserve(114);
even_page.push_back('0');
even_page.push_back(page_type);
even_page += data_k;
std::string odd_page_without_crc;
odd_page_without_crc.reserve(82);
odd_page_without_crc.push_back('1');
odd_page_without_crc.push_back(page_type);
odd_page_without_crc.append(16, '0');
odd_page_without_crc += osnma_sis;
odd_page_without_crc.append(22, '0'); // SAR
odd_page_without_crc.append(2, '0'); // Spare
const std::string crc_data = even_page + odd_page_without_crc;
const uint32_t crc = compute_galileo_inav_crc(crc_data);
std::string odd_page = odd_page_without_crc + to_bit_string(crc, 24);
odd_page.append(8, '0'); // Reserved 2
odd_page.append(6, '0'); // Tail
return std::make_pair(even_page, odd_page);
}
std::string build_osnma_sis(uint8_t hkroot, uint32_t mack)
{
return to_bit_string(hkroot, 8) + to_bit_string(mack, 32);
}
void feed_inav_page(Galileo_Inav_Message& decoder, uint8_t word_type, char page_type, const std::string& osnma_sis)
{
const auto page = build_inav_page(word_type, page_type, osnma_sis);
decoder.split_page(page.first, 0);
decoder.split_page(page.second, 1);
}
OSNMA_msg decode_osnma_page(uint8_t word_type, char page_type, const std::string& osnma_sis)
{
Galileo_Inav_Message decoder;
feed_inav_page(decoder, word_type, page_type, osnma_sis);
return decoder.get_osnma_msg();
}
} // namespace
class Galileo_FNAV_INAV_test : public ::testing::Test
@@ -53,7 +146,7 @@ public:
std::unique_ptr<Viterbi_Decoder> viterbi_inav;
int32_t flag_even_word_arrived;
void deinterleaver(int32_t rows, int32_t cols, const float *in, float *out)
void deinterleaver(int32_t rows, int32_t cols, const float* in, float* out)
{
for (int32_t r = 0; r < rows; r++)
{
@@ -64,7 +157,7 @@ public:
}
}
bool decode_INAV_word(float *page_part_symbols, int32_t frame_length)
bool decode_INAV_word(float* page_part_symbols, int32_t frame_length)
{
// 1. De-interleave
std::vector<float> page_part_symbols_deint = std::vector<float>(frame_length / 2);
@@ -119,7 +212,7 @@ public:
return crc_ok;
}
bool decode_FNAV_word(float *page_symbols, int32_t frame_length)
bool decode_FNAV_word(float* page_symbols, int32_t frame_length)
{
// 1. De-interleave
std::vector<float> page_symbols_deint = std::vector<float>(frame_length);
@@ -165,6 +258,47 @@ public:
};
TEST(Galileo_INAV_Message_Test, OsnmaAdmissionAcceptsNominalNonZeroPage)
{
const auto msg = decode_osnma_page(2, '0', build_osnma_sis(0xA5, 0x12345678));
EXPECT_TRUE(msg.page_validity_available);
EXPECT_EQ(msg.page_valid[0], 1);
EXPECT_EQ(msg.hkroot[0], 0xA5);
EXPECT_EQ(msg.mack[0], 0x12345678U);
}
TEST(Galileo_INAV_Message_Test, OsnmaAdmissionRejectsZeroAndAlertPages)
{
const auto zero_osnma_msg = decode_osnma_page(2, '0', build_osnma_sis(0x00, 0x00000000));
EXPECT_EQ(zero_osnma_msg.page_valid[0], 0);
EXPECT_EQ(zero_osnma_msg.hkroot[0], 0);
EXPECT_EQ(zero_osnma_msg.mack[0], 0U);
const auto alert_page_msg = decode_osnma_page(2, '1', build_osnma_sis(0xA5, 0x12345678));
EXPECT_EQ(alert_page_msg.page_valid[0], 0);
EXPECT_EQ(alert_page_msg.hkroot[0], 0);
EXPECT_EQ(alert_page_msg.mack[0], 0U);
}
TEST(Galileo_INAV_Message_Test, OsnmaAdmissionRejectsDummyPagesInActiveSubframe)
{
Galileo_Inav_Message decoder;
feed_inav_page(decoder, 2, '0', build_osnma_sis(0xA5, 0x12345678));
feed_inav_page(decoder, 63, '0', build_osnma_sis(0x5A, 0x87654321));
const auto msg = decoder.get_osnma_msg();
EXPECT_EQ(msg.page_valid[0], 1);
EXPECT_EQ(msg.hkroot[0], 0xA5);
EXPECT_EQ(msg.mack[0], 0x12345678U);
EXPECT_EQ(msg.page_valid[1], 0);
EXPECT_EQ(msg.hkroot[1], 0);
EXPECT_EQ(msg.mack[1], 0U);
}
TEST_F(Galileo_FNAV_INAV_test, ValidationOfResults)
{
std::chrono::time_point<std::chrono::system_clock> start, end;