mirror of
https://github.com/gnss-sdr/gnss-sdr
synced 2026-09-19 23:50:43 +00:00
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+2845
-642
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
@@ -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="
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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}});
|
||||
|
||||
@@ -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>{};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
+137
-3
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user