diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 32dc29f9b..54a478cba 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,7 +34,9 @@ jobs: - name: check run: cd build && ninja check && ../install/volk_gnsssdr_profile && ../install/run_tests - name: default position_test - run: cd build && cmake -DENABLE_SYSTEM_TESTING_EXTRA=ON .. && ninja && ../install/position_test + run: | + cd build && cmake -DENABLE_SYSTEM_TESTING_EXTRA=ON -DENABLE_UNIT_TESTING_EXTRA=OFF .. && \ + ninja && ../install/position_test && ../install/run_tests --gtest_filter=Osnma* build-macos: runs-on: macos-latest @@ -66,7 +68,9 @@ jobs: - name: check run: cd build && ninja check && ../install/volk_gnsssdr_profile && ../install/run_tests - name: default position_test - run: cd build && cmake -DENABLE_SYSTEM_TESTING_EXTRA=ON .. && ninja && ../install/position_test + run: | + cd build && cmake -DENABLE_SYSTEM_TESTING_EXTRA=ON -DENABLE_UNIT_TESTING_EXTRA=OFF .. && \ + ninja && ../install/position_test && ../install/run_tests --gtest_filter=Osnma* build-macos-xcode: runs-on: macos-latest @@ -104,9 +108,10 @@ jobs: - name: default position_test run: | cd build - cmake -DENABLE_SYSTEM_TESTING_EXTRA=ON .. + cmake -DENABLE_SYSTEM_TESTING_EXTRA=ON -DENABLE_UNIT_TESTING_EXTRA=OFF .. xcodebuild -configuration Release -target position_test ../install/position_test + ../install/run_tests --gtest_filter=Osnma* clang-format: runs-on: ubuntu-latest diff --git a/AUTHORS b/AUTHORS index 9f305c3a3..00d9a4e75 100644 --- a/AUTHORS +++ b/AUTHORS @@ -33,41 +33,44 @@ Contact Information List of authors -------------------------------------------------------------------------------- -Carles Fernández-Prades carles.fernandez@cttc.cat Project manager -Javier Arribas javier.arribas@cttc.es Developer -Luis Esteve Elfau luis@epsilon-formacion.com Developer -Marc Majoral marc.majoral@cttc.cat Developer -Xavier Guerrero xavier.guerrero@cttc.es Developer -Jordi Vilà-Valls jordi.vila-valls@isae-supaero.fr Consultant -Pau Closas pau.closas@northeastern.edu Consultant -Álvaro Cebrián Juan acebrianjuan@gmail.com Contributor -Andres Cecilia Luque a.cecilia.luque@gmail.com Contributor -Anthony Arnold anthony.arnold@uqconnect.edu.au Contributor -Antonio Ramos antonio.ramosdet@gmail.com Contributor -Carlos Avilés carlos.avilesr@googlemail.com Contributor -Cillian O'Driscoll cillian.odriscoll@gmail.com Contributor -Damian Miralles dmiralles2009@gmail.com Contributor -Daniel Fehr daniel.co@bluewin.ch Contributor -David Pubill david.pubill@cttc.cat Contributor -En Shin seanstone5923@gmail.com Contributor -Fran Fabra fabra@ice.csic.es Contributor -Gabriel Araujo gabriel.araujo.5000@gmail.com Contributor -Gerald LaMountain gerald@gece.neu.edu Contributor -Into Pääkkönen into.paakkonen@aalto.fi Contributor -Irene Pérez Riega iperrie@inta.es Contributor -Jim Melton jim.melton@sncorp.com Contributor -Josh Schindehette jschindehette@geontech.com Contributor -Leonardo Tonetto tonetto.dev@gmail.com Contributor -Malte Lenhart malte.lenhart@mailbox.org Contributor -Mara Branzanti mara.branzanti@gmail.com Contributor -Marc Molina marc.molina.pena@gmail.com Contributor -Marc Sales marcsales92@gmail.com Contributor -Piyush Gupta piyush04111999@gmail.com Contributor -Rodrigo Muñoz rodrigo.munoz@proteinlab.cl Contributor -Stefan van der Linden spvdlinden@gmail.com Contributor -Víctor Castillo-Agüero victorcastilloaguero@gmail.com Contributor -Will Silberman wsilberm@google.com Contributor -Carlos Paniego carpanie@hotmail.com Artwork + +Carles Fernández-Prades carles.fernandez@cttc.cat Project manager +Javier Arribas javier.arribas@cttc.es Developer +Luis Esteve Elfau luis@epsilon-formacion.com Developer +Marc Majoral marc.majoral@cttc.cat Developer +Xavier Guerrero xavier.guerrero@cttc.es Developer +Jordi Vilà-Valls jordi.vila-valls@isae-supaero.fr Consultant +Pau Closas pau.closas@northeastern.edu Consultant +Álvaro Cebrián Juan acebrianjuan@gmail.com Contributor +Andres Cecilia Luque a.cecilia.luque@gmail.com Contributor +Anthony Arnold anthony.arnold@uqconnect.edu.au Contributor +Antonio Ramos antonio.ramosdet@gmail.com Contributor +Carlos Avilés carlos.avilesr@googlemail.com Contributor +Cesare Ghionoiu Martinez c.ghionoiu-martinez@tu-braunschweig.de Contributor +Cillian O'Driscoll cillian.odriscoll@gmail.com Contributor +Damian Miralles dmiralles2009@gmail.com Contributor +Daniel Fehr daniel.co@bluewin.ch Contributor +David Pubill david.pubill@cttc.cat Contributor +En Shin seanstone5923@gmail.com Contributor +Fran Fabra fabra@ice.csic.es Contributor +Gabriel Araujo gabriel.araujo.5000@gmail.com Contributor +Gerald LaMountain gerald@gece.neu.edu Contributor +Into Pääkkönen into.paakkonen@aalto.fi Contributor +Irene Pérez Riega iperrie@inta.es Contributor +Jim Melton jim.melton@sncorp.com Contributor +Josh Schindehette jschindehette@geontech.com Contributor +Leonardo Tonetto tonetto.dev@gmail.com Contributor +Malte Lenhart malte.lenhart@mailbox.org Contributor +Mara Branzanti mara.branzanti@gmail.com Contributor +Marc Molina marc.molina.pena@gmail.com Contributor +Marc Sales marcsales92@gmail.com Contributor +Piyush Gupta piyush04111999@gmail.com Contributor +Rodrigo Muñoz rodrigo.munoz@proteinlab.cl Contributor +Stefan van der Linden spvdlinden@gmail.com Contributor +Víctor Castillo-Agüero victorcastilloaguero@gmail.com Contributor +Will Silberman wsilberm@google.com Contributor +Carlos Paniego carpanie@hotmail.com Artwork + # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2011-2024 Carles Fernandez-Prades diff --git a/CITATION.cff b/CITATION.cff index 4851a8b1b..337e4d42a 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -61,6 +61,11 @@ authors: - email: daniel.co@bluewin.ch family-names: Fehr given-names: Daniel + - alias: cesaaargm + affiliation: "Technische Universität Braunschweig" + email: c.ghionoiu-martinez@tu-braunschweig.de + family-names: "Ghionoiu Martinez" + given-names: Cesare - alias: piyush0411 email: piyush04111999@gmail.com family-names: Gupta diff --git a/CMakeLists.txt b/CMakeLists.txt index aa4c671b0..adad30676 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,8 @@ option(ENABLE_STRIP "Create stripped binaries without debugging symbols (in Rele option(Boost_USE_STATIC_LIBS "Use Boost static libs" OFF) +option(ENABLE_GNUTLS "Forces linking against GnuTLS" OFF) + if(ENABLE_PACKAGING) set(ENABLE_ARMA_NO_DEBUG ON) set(CMAKE_VERBOSE_MAKEFILE ON) @@ -2217,97 +2219,9 @@ endif() ################################################################################ -# GnuTLS - https://www.gnutls.org/ +# OpenSSL https://www.openssl.org/ or GnuTLS - https://www.gnutls.org/ ################################################################################ -find_package(GnuTLS) -set_package_properties(GnuTLS PROPERTIES - URL "https://www.gnutls.org/" - PURPOSE "Used for the SUPL protocol implementation." - TYPE REQUIRED -) -if(GnuTLS_FOUND AND GNUTLS_VERSION_STRING) - set_package_properties(GnuTLS PROPERTIES - DESCRIPTION "Transport Layer Security Library (found: v${GNUTLS_VERSION_STRING})" - ) -else() - set_package_properties(GnuTLS PROPERTIES - DESCRIPTION "Transport Layer Security Library" - ) -endif() -find_library(GNUTLS_OPENSSL_LIBRARY - NAMES gnutls-openssl libgnutls-openssl.so.27 - PATHS - /usr/lib - /usr/lib64 - /usr/lib/x86_64-linux-gnu - /usr/lib/aarch64-linux-gnu - /usr/lib/arm-linux-gnueabihf - /usr/lib/arm-linux-gnueabi - /usr/lib/i386-linux-gnu - /usr/lib/alpha-linux-gnu - /usr/lib/hppa-linux-gnu - /usr/lib/i386-gnu - /usr/lib/i686-gnu - /usr/lib/i686-linux-gnu - /usr/lib/x86_64-kfreebsd-gnu - /usr/lib/i686-kfreebsd-gnu - /usr/lib/m68k-linux-gnu - /usr/lib/mips-linux-gnu - /usr/lib/mips64el-linux-gnuabi64 - /usr/lib/mipsel-linux-gnu - /usr/lib/powerpc-linux-gnu - /usr/lib/powerpc-linux-gnuspe - /usr/lib/powerpc64-linux-gnu - /usr/lib/powerpc64le-linux-gnu - /usr/lib/s390x-linux-gnu - /usr/lib/riscv64-linux-gnu - /usr/lib/sparc64-linux-gnu - /usr/lib/x86_64-linux-gnux32 - /usr/lib/sh4-linux-gnu - /usr/lib/loongarch64-linux-gnu - /usr/local/lib - /usr/local/lib64 - /opt/local/lib -) -if(NOT GNUTLS_OPENSSL_LIBRARY) - if(GnuTLS_FOUND) - message(STATUS " But it was not built with openssl compatibility.") - endif() - message(STATUS " Looking for OpenSSL instead...") - if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set(OPENSSL_ROOT_DIR /usr/local/opt/openssl) # Trick for Homebrew - endif() - find_package(OpenSSL) - set_package_properties(OpenSSL PROPERTIES - URL "https://www.openssl.org" - DESCRIPTION "Cryptography and SSL/TLS Toolkit (found: v${OPENSSL_VERSION})" - PURPOSE "Used for the SUPL protocol implementation." - TYPE REQUIRED - ) - if(OPENSSL_FOUND) - set_package_properties(GnuTLS PROPERTIES - PURPOSE "Not found, but OpenSSL can replace it." - ) - set(GNUTLS_INCLUDE_DIR ${OPENSSL_INCLUDE_DIR}) - set(GNUTLS_LIBRARIES "") - set(GNUTLS_OPENSSL_LIBRARY ${OPENSSL_SSL_LIBRARY}) - else() - message(" The GnuTLS library with openssl compatibility enabled has not been found.") - message(" You can try to install the required libraries by typing:") - if(${CMAKE_SYSTEM_NAME} MATCHES "Linux|kFreeBSD|GNU") - if(${LINUX_DISTRIBUTION} MATCHES "Fedora" OR ${LINUX_DISTRIBUTION} MATCHES "Red Hat") - message(" sudo yum install openssl-devel") - else() - message(" sudo apt-get install libgnutls28-dev") - endif() - endif() - if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - message(" 'sudo port install gnutls', if you are using Macports, or") - message(" 'brew install openssl', if you are using Homebrew.") - endif() - message(FATAL_ERROR "GnuTLS libraries with openssl compatibility are required to build gnss-sdr") - endif() -endif() +include(GnsssdrCrypto) @@ -3496,6 +3410,112 @@ endif() +##################################################################### +# Check signal sources related to FPGA only. +##################################################################### +if(ENABLE_MAX2771 AND NOT ENABLE_FPGA) + message(STATUS "The SPIdev driver is enabled, but the FPGA is not enabled. The FPGA is required when using the SPIdev driver.") + if(ENABLE_PACKAGING) + set(ENABLE_MAX2771 OFF) + else() + message(FATAL_ERROR "ENABLE_MAX2771 can only be set when ENABLE_FPGA is also set.") + endif() +endif() +if(ENABLE_DMA_PROXY AND NOT ENABLE_FPGA) + message(STATUS "The DMA Proxy driver is enabled, but the FPGA is not enabled. The FPGA is required when using the DMA Proxy driver.") + if(ENABLE_PACKAGING) + set(ENABLE_DMA_PROXY OFF) + else() + message(FATAL_ERROR "ENABLE_DMA_PROXY can only be set when ENABLE_FPGA is also set.") + endif() +endif() + + + +##################################################################### +# spidev driver - OPTIONAL +# Linux kernel driver that provides user-space access to Serial +# Peripheral Interface) +##################################################################### +if(ENABLE_MAX2771) + if(DEFINED ENV{SDKTARGETSYSROOT}) + set(TARGET_ROOTFS_PATH $ENV{SDKTARGETSYSROOT}) + else() + set(TARGET_ROOTFS_PATH "") + endif() + find_program(STRINGS_EXECUTABLE strings) + if(NOT STRINGS_EXECUTABLE) + message(STATUS "The 'strings' command could not be found. See https://www.gnu.org/software/binutils/") + message(STATUS " You can try to install it by typing:") + if(${CMAKE_SYSTEM_NAME} MATCHES "Linux|kFreeBSD|GNU") + if(${LINUX_DISTRIBUTION} MATCHES "Fedora" OR ${LINUX_DISTRIBUTION} MATCHES "Red Hat") + message(STATUS " sudo yum install binutils") + elseif(${LINUX_DISTRIBUTION} MATCHES "openSUSE") + message(STATUS " sudo zypper install binutils") + else() + message(STATUS " sudo apt-get install binutils") + endif() + endif() + message(FATAL_ERROR "Binutils are required to build GNSS-SDR for SoC FPGA devices using the MAX2771 option.") + endif() + set(DTB_FILE "${TARGET_ROOTFS_PATH}/boot/devicetree/system-top.dtb") + if(EXISTS "${DTB_FILE}") + message(STATUS "Found DTB file: ${DTB_FILE}") + # Run the strings command and grep for "spidev" + execute_process( + COMMAND ${STRINGS_EXECUTABLE} ${DTB_FILE} + COMMAND grep "spidev" + OUTPUT_VARIABLE GREP_OUTPUT + RESULT_VARIABLE GREP_RESULT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(GREP_RESULT EQUAL 0) + message(STATUS "Found spidev-compatible peripheral in ${DTB_FILE}.") + else() + message(STATUS "SPIdev driver not found, its installation is required.") + if(ENABLE_PACKAGING) + set(ENABLE_MAX2771 OFF) + else() + message(FATAL_ERROR "SPIdev driver is required for building gnss-sdr with -DENABLE_MAX2271=ON.") + endif() + endif() + else() + message(FATAL_ERROR "The device tree (DTB) file ${DTB_FILE} cannot be found.") + endif() +endif() + + + +##################################################################### +# DMA Proxy driver - OPTIONAL +# Simplified and efficient interface for user-space applications +# to leverage DMA capabilities for Xilinx FPGA and SoC systems +##################################################################### +if(ENABLE_DMA_PROXY) + if(DEFINED ENV{SDKTARGETSYSROOT}) + set(TARGET_ROOTFS_PATH $ENV{SDKTARGETSYSROOT}) + else() + string(REGEX MATCH "(.*/tmp-glibc)" MATCHED_PATH "${GNURADIO_RUNTIME_INCLUDE_DIRS}") + if(MATCHED_PATH) + set(TARGET_ROOTFS_PATH "${MATCHED_PATH}/sysroots-components") + else() + set(TARGET_ROOTFS_PATH "") + endif() + endif() + file(GLOB_RECURSE DMA_PROXY_FILE "${TARGET_ROOTFS_PATH}/*/dma-proxy.ko") + if(EXISTS "${DMA_PROXY_FILE}") + message(STATUS "Found dma-proxy.ko file: ${DMA_PROXY_FILE}") + else() + if(ENABLE_PACKAGING) + set(ENABLE_DMA_PROXY OFF) + else() + message(FATAL_ERROR "DMA Proxy driver is required for building gnss-sdr with -DENABLE_DMA_PROXY=ON.") + endif() + endif() +endif() + + + ############################################## # TELEORBIT FLEXIBAND FRONTEND - OPTIONAL ############################################## @@ -3721,6 +3741,7 @@ add_feature_info(ENABLE_OWN_GLOG ENABLE_OWN_GLOG "Forces the downloading and bui add_feature_info(ENABLE_GLOG_AND_GFLAGS ENABLE_GLOG_AND_GFLAGS "Forces the usage of Google glog and Gflags instead of Abseil.") add_feature_info(ENABLE_OWN_ABSEIL ENABLE_OWN_ABSEIL "Forces downloading and building Abseil. Supersedes ENABLE_OWN_GLOG.") add_feature_info(ENABLE_OWN_ARMADILLO ENABLE_OWN_ARMADILLO "Forces the downloading and building of Armadillo.") +add_feature_info(ENABLE_GNUTLS ENABLE_GNUTLS "Forces linking against GnuTLS instead of OpenSSL.") add_feature_info(ENABLE_LOG ENABLE_LOG "Enables runtime internal logging.") add_feature_info(ENABLE_ORC ENABLE_ORC "Use the Optimized Inner Loop Runtime Compiler (ORC) for building volk_gnsssdr.") add_feature_info(ENABLE_STRIP ENABLE_STRIP "Enables the generation of stripped binaries (without debugging symbols).") diff --git a/README.md b/README.md index cea038c48..d5cbacc94 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ information about this open-source, software-defined GNSS receiver. - [Install Armadillo, a C++ linear algebra library](#install-armadillo-a-c-linear-algebra-library) - [Install Gflags, a commandline flags processing module for C++](#install-gflags-a-commandline-flags-processing-module-for-c) - [Install Glog, a library that implements application-level logging](#install-glog-a-library-that-implements-application-level-logging) - - [Install the GnuTLS or OpenSSL libraries](#install-the-gnutls-or-openssl-libraries) + - [Install the OpenSSL libraries](#install-the-openssl-libraries) - [Install Matio, MATLAB MAT file I/O library](#install-matio-matlab-mat-file-io-library) - [Install Protocol Buffers, a portable mechanism for serialization of structured data](#install-protocol-buffers-a-portable-mechanism-for-serialization-of-structured-data) - [Install Pugixml, a light-weight C++ XML processing library](#install-pugixml-a-light-weight-c-xml-processing-library) @@ -167,16 +167,19 @@ $ sudo apt-get install build-essential cmake git pkg-config libboost-dev libboos libboost-system-dev libboost-filesystem-dev libboost-thread-dev libboost-chrono-dev \ libboost-serialization-dev liblog4cpp5-dev libuhd-dev gnuradio-dev gr-osmosdr \ libblas-dev liblapack-dev libarmadillo-dev libgflags-dev libgoogle-glog-dev \ - libgnutls-openssl-dev libpcap-dev libmatio-dev libpugixml-dev libgtest-dev \ - libprotobuf-dev protobuf-compiler python3-mako + libssl-dev libpcap-dev libmatio-dev libpugixml-dev libgtest-dev \ + libprotobuf-dev libcpu-features-dev protobuf-compiler python3-mako ``` Please note that the required files from `libgtest-dev` were named `googletest` in Debian 9 "stretch" and Ubuntu 18.04 "bionic", and renamed to `libgtest-dev` in Debian 10 "buster" and above. -Since Ubuntu 21.04 Hirsute / Debian 11, the package `libcpu-features-dev` is -also required. +In distributions older than Ubuntu 21.04 Hirsute / Debian 11, the package +`libcpu-features-dev` is not required. + +In distributions older than Ubuntu 22.04 Jammy / Debian 12, the package +`libssl-dev` must be replaced by `libgnutls-openssl-dev`. **Note for Ubuntu 14.04 LTS "trusty" users:** you will need to build from source and install GNU Radio manually, as explained below, since GNSS-SDR requires @@ -431,20 +434,15 @@ Please note that Glog is replaced by the [Abseil Logging Library](https://abseil.io/docs/cpp/guides/logging) if Abseil >= v20240116 is available in your system. -#### Install the GnuTLS or OpenSSL libraries +#### Install the OpenSSL libraries ``` -$ sudo apt-get install libgnutls-openssl-dev # For Debian/Ubuntu/LinuxMint -$ sudo yum install openssl-devel # For Fedora/RHEL -$ sudo zypper install openssl-devel # For OpenSUSE -$ sudo pacman -S openssl # For Arch Linux +$ sudo apt-get install libssl-dev # For Debian/Ubuntu/LinuxMint +$ sudo yum install openssl-devel # For Fedora/CentOS/RHEL +$ sudo zypper install openssl-devel # For OpenSUSE +$ sudo pacman -S openssl # For Arch Linux ``` -In case the [GnuTLS](https://www.gnutls.org/ "GnuTLS's Homepage") library with -openssl extensions package is not available in your GNU/Linux distribution, -GNSS-SDR can also work well with -[OpenSSL](https://www.openssl.org/ "OpenSSL's Homepage"). - #### Install [Matio](https://github.com/tbeu/matio "Matio's Homepage"), MATLAB MAT file I/O library ``` @@ -818,7 +816,7 @@ In a terminal, type: ``` $ sudo port selfupdate $ sudo port upgrade outdated -$ sudo port install armadillo cmake pkgconfig protobuf3-cpp pugixml gnutls +$ sudo port install armadillo cmake pkgconfig protobuf3-cpp pugixml openssl3 $ sudo port install gnuradio +uhd +grc +zeromq $ sudo port install boost matio libad9361-iio libiio $ sudo port install py311-mako diff --git a/cmake/Modules/FindGMP.cmake b/cmake/Modules/FindGMP.cmake new file mode 100644 index 000000000..3f4db49c6 --- /dev/null +++ b/cmake/Modules/FindGMP.cmake @@ -0,0 +1,79 @@ +# GNSS-SDR is a Global Navigation Satellite System software-defined receiver. +# This file is part of GNSS-SDR. +# +# SPDX-FileCopyrightText: 2024 C. Fernandez-Prades cfernandez(at)cttc.es +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT COMMAND feature_summary) + include(FeatureSummary) +endif() + +if(NOT GNSSSDR_LIB_PATHS) + include(GnsssdrLibPaths) +endif() + +if(NOT PKG_CONFIG_FOUND) + include(FindPkgConfig) +endif() +pkg_check_modules(PC_GMP "gmp") + +set(GMP_DEFINITIONS ${PC_GMP_CFLAGS_OTHER}) + +find_path(GMP_INCLUDE_DIR + NAMES gmpxx.h + HINTS ${PC_GMP_INCLUDEDIR} + PATHS ${CMAKE_INSTALL_PREFIX}/include + /usr/local/include + /usr/include + /opt/local/include +) + +set(GMP_INCLUDE_DIRS ${GMP_INCLUDE_DIR}) +set(GMP_PC_ADD_CFLAGS "-I${GMP_INCLUDE_DIR}") + +find_library(GMPXX_LIBRARY + NAMES gmpxx + HINTS ${PC_GMP_LIBDIR} + PATHS ${GNSSSDR_LIB_PATHS} + ${CMAKE_INSTALL_PREFIX}/lib + ${CMAKE_INSTALL_PREFIX}/lib64 +) + +find_library(GMP_LIBRARY + NAMES gmp + HINTS ${PC_GMP_LIBDIR} + PATHS ${GNSSSDR_LIB_PATHS} + ${CMAKE_INSTALL_PREFIX}/lib + ${CMAKE_INSTALL_PREFIX}/lib64 +) + +set(GMP_LIBRARIES ${GMPXX_LIBRARY} ${GMP_LIBRARY}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GMP DEFAULT_MSG GMPXX_LIBRARY GMP_LIBRARY GMP_INCLUDE_DIR) + +if(GMP_FOUND AND NOT TARGET Gmp::gmp) + add_library(Gmp::gmp SHARED IMPORTED) + set_target_properties(Gmp::gmp PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${GMPXX_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${GMP_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${GMP_LIBRARIES}" + ) +endif() + +set_package_properties(GMP PROPERTIES + URL "https://gmplib.org/" +) + +if(PC_GMP_VERSION) + set_package_properties(GMP PROPERTIES + DESCRIPTION "The GNU Multiple Precision Arithmetic Library (found: v.${PC_GMP_VERSION})" + ) +else() + set_package_properties(GMP PROPERTIES + DESCRIPTION "The GNU Multiple Precision Arithmetic Library" + ) +endif() + +mark_as_advanced(GMPXX_LIBRARY GMP_LIBRARY GMP_INCLUDE_DIR) diff --git a/cmake/Modules/FindGNURADIO.cmake b/cmake/Modules/FindGNURADIO.cmake index cfaf51764..d976d0e28 100644 --- a/cmake/Modules/FindGNURADIO.cmake +++ b/cmake/Modules/FindGNURADIO.cmake @@ -366,8 +366,10 @@ if(GNURADIO_RUNTIME_INCLUDE_DIRS) ) if(CMAKE_VERSION VERSION_GREATER 3.13) target_link_libraries(Gnuradio::filter INTERFACE Log4cpp::log4cpp) + target_link_libraries(Gnuradio::runtime INTERFACE Log4cpp::log4cpp) else() set_target_properties(Gnuradio::filter PROPERTIES INTERFACE_LINK_LIBRARIES Log4cpp::log4cpp) + set_target_properties(Gnuradio::runtime PROPERTIES INTERFACE_LINK_LIBRARIES Log4cpp::log4cpp) endif() endif() if(${_uses_spdlog}) diff --git a/cmake/Modules/GnsssdrCrypto.cmake b/cmake/Modules/GnsssdrCrypto.cmake new file mode 100644 index 000000000..a9d89e6be --- /dev/null +++ b/cmake/Modules/GnsssdrCrypto.cmake @@ -0,0 +1,191 @@ +# GNSS-SDR is a Global Navigation Satellite System software-defined receiver. +# This file is part of GNSS-SDR. +# +# SPDX-FileCopyrightText: 2024 C. Fernandez-Prades cfernandez(at)cttc.es +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT COMMAND feature_summary) + include(FeatureSummary) +endif() + +if(NOT GNSSSDR_LIB_PATHS) + include(GnsssdrLibPaths) +endif() + +################################################################################ +# OpenSSL https://www.openssl.org/ +################################################################################ +if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(OPENSSL_ROOT_DIR /usr/local/opt/openssl) # Trick for Homebrew +endif() +unset(OPENSSL_FOUND CACHE) +unset(GnuTLS_FOUND CACHE) +unset(GMP_FOUND CACHE) +if(NOT ENABLE_GNUTLS) + find_package(OpenSSL) +endif() +set_package_properties(OpenSSL + PROPERTIES + URL "https://www.openssl.org" + PURPOSE "Used for the OSNMA and SUPL protocol implementations." + TYPE REQUIRED +) +if(OPENSSL_FOUND) + set_package_properties(OpenSSL + PROPERTIES + DESCRIPTION "Cryptography and SSL/TLS Toolkit (found: v${OPENSSL_VERSION})" + ) +else() + set_package_properties(OpenSSL + PROPERTIES + DESCRIPTION "OpenSSL has not been found, but GnuTLS with openssl compatibility can replace it" + ) + ################################################################################ + # GnuTLS - https://www.gnutls.org/ + ################################################################################ + find_package(GnuTLS) + set_package_properties(GnuTLS PROPERTIES + URL "https://www.gnutls.org/" + PURPOSE "Used for the OSNMA and SUPL protocol implementations." + TYPE REQUIRED + ) + if(GnuTLS_FOUND AND GNUTLS_VERSION_STRING) + set_package_properties(GnuTLS PROPERTIES + DESCRIPTION "Transport Layer Security Library (found: v${GNUTLS_VERSION_STRING})" + ) + else() + set_package_properties(GnuTLS PROPERTIES + DESCRIPTION "Transport Layer Security Library" + ) + endif() + find_library(GNUTLS_OPENSSL_LIBRARY + NAMES gnutls-openssl libgnutls-openssl.so.27 + PATHS ${GNSSSDR_LIB_PATHS} + ) + + find_path(GNUTLS_INCLUDE_DIR NAMES gnutls/gnutls.h + PATHS + /usr/include + /usr/local/include + /opt/local/include # default location in Macports + /opt/homebrew/opt/gnutls/include/ + ${GNUTLS_ROOT_DIR}/include/ + ) + + if(NOT GNUTLS_OPENSSL_LIBRARY) + message(" The GnuTLS library with openssl compatibility enabled has not been found.") + message(" You can try to install the required libraries by typing:") + if(${CMAKE_SYSTEM_NAME} MATCHES "Linux|kFreeBSD|GNU") + if(${LINUX_DISTRIBUTION} MATCHES "Fedora" OR ${LINUX_DISTRIBUTION} MATCHES "Red Hat") + message(" sudo yum install openssl-devel") + else() + message(" sudo apt-get install libgnutls28-dev") + endif() + endif() + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + message(" 'sudo port install openssl3', if you are using Macports, or") + message(" 'brew install openssl', if you are using Homebrew.") + endif() + message(FATAL_ERROR "OpenSSL or the GnuTLS libraries with openssl compatibility are required to build gnss-sdr") + endif() + + # Test GnuTLS capabilities + file(READ "${GNUTLS_INCLUDE_DIR}/gnutls/gnutls.h" gnutls_gnutls_file_contents) + if("${gnutls_gnutls_file_contents}" MATCHES "GNUTLS_SIGN_ECDSA_SHA256") + set(GNUTLS_SIGN_ECDSA_SHA256 TRUE) + endif() + if("${gnutls_gnutls_file_contents}" MATCHES "GNUTLS_SIGN_ECDSA_SHA512") + set(GNUTLS_SIGN_ECDSA_SHA512 TRUE) + endif() + if("${gnutls_gnutls_file_contents}" MATCHES "GNUTLS_DIG_SHA3_256") + set(GNUTLS_DIG_SHA3_256 TRUE) + endif() + if("${gnutls_gnutls_file_contents}" MATCHES "#define GNUTLS_VERSION_MAJOR 2") + set(GNUTLS_HMAC_INIT_WITH_DIGEST TRUE) + endif() + if("${gnutls_gnutls_file_contents}" MATCHES "GNUTLS_MAC_AES_CMAC_128") + set(GNUTLS_MAC_AES_CMAC_128 TRUE) + endif() + file(READ "${GNUTLS_INCLUDE_DIR}/gnutls/abstract.h" gnutls_abstract_file_contents) + if("${gnutls_abstract_file_contents}" MATCHES "gnutls_pubkey_export2") + set(GNUTLS_PUBKEY_EXPORT2 TRUE) + endif() + + find_package(GMP) + set_package_properties(GMP PROPERTIES + PURPOSE "Required to decompress cryptographic keys." + TYPE REQUIRED + ) + if(NOT GMP_FOUND) + message(FATAL_ERROR "GMP is required by gnss-sdr if linking against GnuTLS") + endif() +endif() + +################################################################################ + +function(link_to_crypto_dependencies target) + if(OPENSSL_FOUND) + if(TARGET OpenSSL::SSL) + target_link_libraries(${target} + PUBLIC + OpenSSL::SSL + ) + if(TARGET OpenSSL::Crypto) + target_link_libraries(${target} + PUBLIC + OpenSSL::Crypto + ) + endif() + else() + target_link_libraries(${target} + PUBLIC + ${OPENSSL_LIBRARIES} + "${OPENSSL_CRYPTO_LIBRARIES}" + ) + target_include_directories(${target} + PUBLIC + ${OPENSSL_INCLUDE_DIR} + ) + endif() + if(OPENSSL_VERSION) + if(OPENSSL_VERSION VERSION_GREATER "3.0.0") + target_compile_definitions(${target} PUBLIC -DUSE_OPENSSL_3=1) + else() + if(NOT OPENSSL_VERSION VERSION_LESS "1.1.1") + target_compile_definitions(${target} PUBLIC -DUSE_OPENSSL_111=1) + endif() + endif() + endif() + else() # GnuTLS + target_link_libraries(${target} + PUBLIC + ${GNUTLS_LIBRARIES} + ${GNUTLS_OPENSSL_LIBRARY} + PRIVATE + Gmp::gmp + ) + target_include_directories(${target} + PUBLIC + ${GNUTLS_INCLUDE_DIR} + ) + target_compile_definitions(${target} PUBLIC -DUSE_GNUTLS_FALLBACK=1) + if(GNUTLS_SIGN_ECDSA_SHA256) + target_compile_definitions(${target} PRIVATE -DHAVE_GNUTLS_SIGN_ECDSA_SHA256=1) + endif() + if(GNUTLS_SIGN_ECDSA_SHA512) + target_compile_definitions(${target} PRIVATE -DHAVE_GNUTLS_SIGN_ECDSA_SHA512=1) + endif() + if(GNUTLS_DIG_SHA3_256) + target_compile_definitions(${target} PRIVATE -DHAVE_GNUTLS_DIG_SHA3_256=1) + endif() + if(GNUTLS_PUBKEY_EXPORT2) + target_compile_definitions(${target} PRIVATE -DHAVE_GNUTLS_PUBKEY_EXPORT2=1) + endif() + if(GNUTLS_HMAC_INIT_WITH_DIGEST) + target_compile_definitions(${target} PRIVATE -DHAVE_GNUTLS_HMAC_INIT_WITH_DIGEST=1) + endif() + if(GNUTLS_MAC_AES_CMAC_128) + target_compile_definitions(${target} PRIVATE -DHAVE_GNUTLS_MAC_AES_CMAC_128=1) + endif() + endif() +endfunction() diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index aa7bef842..d18c58ffe 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -81,6 +81,25 @@ All notable changes to GNSS-SDR will be documented in this file. This change has a downside in maintainability, since the source code becomes plagued with preprocessor directives required to maintain compatibility both with gflags and glog, and with Abseil. +- Historically, GNSS-SDR linked against the GnuTLS library for cryptographic + functions. If GnuTLS was not found, then the building system looked for and + linked against OpenSSL as a fallback. This was due to the OpenSSL 1.x dual + license scheme, which was incompatible with GPL v3.0 license, preventing it + from being a mandatory dependency for GNSS-SDR in most GNU/Linux + distributions. This issue was solved with the release of OpenSSL 3.0.0, which + transitioned to the Apache License 2.0, fully compatible with GPL v3.0. + Accordingly, the GNSS-SDR building system now looks for OpenSSL in the first + place and, if not found, then it looks for GnuTLS as a fallback. + +### Reliability + +- Implementation of the Galileo Open Service Navigation Message Authentication + (OSNMA), a data authentication function for the Galileo Open Service worldwide + users, freely accessible to all. OSNMA provides receivers with the assurance + that the received Galileo navigation message is coming from the system itself + and has not been modified. OSNMA is enabled by default if the receiver + configuration defines Galileo E1 OS channels. More details can be found in + [Introducing GNSS Navigation Message Authentication](https://gnss-sdr.org/osnma). ### Improvements in Usability: diff --git a/src/algorithms/PVT/adapters/rtklib_pvt.cc b/src/algorithms/PVT/adapters/rtklib_pvt.cc index bfdf6c4b8..529c330f3 100644 --- a/src/algorithms/PVT/adapters/rtklib_pvt.cc +++ b/src/algorithms/PVT/adapters/rtklib_pvt.cc @@ -919,6 +919,17 @@ Rtklib_Pvt::Rtklib_Pvt(const ConfigurationInterface* configuration, // Use unhealthy satellites pvt_output_parameters.use_unhealthy_sats = configuration->property(role + ".use_unhealthy_sats", pvt_output_parameters.use_unhealthy_sats); + // OSNMA + if (gal_1B_count > 0) + { + std::string osnma_mode = configuration->property("GNSS-SDR.osnma_mode", std::string("")); + bool enable_osnma = configuration->property("GNSS-SDR.osnma_enable", true); + if (enable_osnma && osnma_mode == "strict") + { + pvt_output_parameters.osnma_strict = true; + } + } + // make PVT object pvt_ = rtklib_make_pvt_gs(in_streams_, pvt_output_parameters, rtk); DLOG(INFO) << "pvt(" << pvt_->unique_id() << ")"; diff --git a/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc b/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc index bae417cbb..33db19222 100644 --- a/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc +++ b/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc @@ -52,6 +52,7 @@ #include "monitor_pvt.h" #include "monitor_pvt_udp_sink.h" #include "nmea_printer.h" +#include "osnma_data.h" #include "pvt_conf.h" #include "rinex_printer.h" #include "rtcm_printer.h" @@ -184,7 +185,8 @@ rtklib_pvt_gs::rtklib_pvt_gs(uint32_t nchannels, d_an_printer_enabled(conf_.an_output_enabled), d_log_timetag(conf_.log_source_timetag), d_use_has_corrections(conf_.use_has_corrections), - d_use_unhealthy_sats(conf_.use_unhealthy_sats) + d_use_unhealthy_sats(conf_.use_unhealthy_sats), + d_osnma_strict(conf_.osnma_strict) { // Send feedback message to observables block with the receiver clock offset this->message_port_register_out(pmt::mp("pvt_to_observables")); @@ -217,6 +219,19 @@ rtklib_pvt_gs::rtklib_pvt_gs(uint32_t nchannels, #else boost::bind(&rtklib_pvt_gs::msg_handler_has_data, this, _1)); #endif +#endif + + // Galileo OSNMA messages port in + this->message_port_register_in(pmt::mp("OSNMA_to_PVT")); + this->set_msg_handler(pmt::mp("OSNMA_to_PVT"), +#if HAS_GENERIC_LAMBDA + [this](auto&& PH1) { msg_handler_osnma(PH1); }); +#else +#if USE_BOOST_BIND_PLACEHOLDERS + boost::bind(&rtklib_pvt_gs::msg_handler_osnma, this, boost::placeholders::_1)); +#else + boost::bind(&rtklib_pvt_gs::msg_handler_osnma, this, _1)); +#endif #endif d_initial_carrier_phase_offset_estimation_rads = std::vector(nchannels, 0.0); @@ -1639,6 +1654,28 @@ void rtklib_pvt_gs::msg_handler_has_data(const pmt::pmt_t& msg) } +void rtklib_pvt_gs::msg_handler_osnma(const pmt::pmt_t& msg) +{ + try + { + // Still not sure about what we should receive here. + // It should be a structure with the list of PRNs authenticated (NavData and utcData, + // so with ADKD0 and ADKD12 validated), their corresponding TOW at the beginning + // of the authenticated subframe, and maybe the COP. + const size_t msg_type_hash_code = pmt::any_ref(msg).type().hash_code(); + if (msg_type_hash_code == typeid(std::shared_ptr).hash_code()) + { + const auto osnma_data = wht::any_cast>(pmt::any_ref(msg)); + d_auth_nav_data_map[osnma_data->get_prn_d()].insert(osnma_data->get_IOD_nav()); + } + } + catch (const wht::bad_any_cast& e) + { + LOG(WARNING) << "msg_handler_osnma Bad any_cast: " << e.what(); + } +} + + std::map rtklib_pvt_gs::get_gps_ephemeris_map() const { return d_internal_pvt_solver->gps_ephemeris_map; @@ -1997,7 +2034,7 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item bool store_valid_observable = false; - if (tmp_eph_iter_gps != d_internal_pvt_solver->gps_ephemeris_map.cend()) + if (!d_osnma_strict && tmp_eph_iter_gps != d_internal_pvt_solver->gps_ephemeris_map.cend()) { const uint32_t prn_aux = tmp_eph_iter_gps->second.PRN; if ((prn_aux == in[i][epoch].PRN) && (std::string(in[i][epoch].Signal, 2) == std::string("1C")) && (d_use_unhealthy_sats || (tmp_eph_iter_gps->second.SV_health == 0))) @@ -2013,10 +2050,25 @@ 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)))))) { - store_valid_observable = true; + if (d_osnma_strict && ((std::string(in[i][epoch].Signal, 2) == std::string("1B")) || ((std::string(in[i][epoch].Signal, 2) == std::string("7X"))))) + { + // Pick up only authenticated satellites + 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()) + { + store_valid_observable = true; + } + } + } + else + { + store_valid_observable = true; + } } } - if (tmp_eph_iter_cnav != d_internal_pvt_solver->gps_cnav_ephemeris_map.cend()) + if (!d_osnma_strict && tmp_eph_iter_cnav != d_internal_pvt_solver->gps_cnav_ephemeris_map.cend()) { const uint32_t prn_aux = tmp_eph_iter_cnav->second.PRN; if ((prn_aux == in[i][epoch].PRN) && (((std::string(in[i][epoch].Signal, 2) == std::string("2S")) || (std::string(in[i][epoch].Signal, 2) == std::string("L5"))))) @@ -2024,7 +2076,7 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item store_valid_observable = true; } } - if (tmp_eph_iter_glo_gnav != d_internal_pvt_solver->glonass_gnav_ephemeris_map.cend()) + if (!d_osnma_strict && tmp_eph_iter_glo_gnav != d_internal_pvt_solver->glonass_gnav_ephemeris_map.cend()) { const uint32_t prn_aux = tmp_eph_iter_glo_gnav->second.PRN; if ((prn_aux == in[i][epoch].PRN) && ((std::string(in[i][epoch].Signal, 2) == std::string("1G")) || (std::string(in[i][epoch].Signal, 2) == std::string("2G")))) @@ -2032,7 +2084,7 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item store_valid_observable = true; } } - if (tmp_eph_iter_bds_dnav != d_internal_pvt_solver->beidou_dnav_ephemeris_map.cend()) + if (!d_osnma_strict && tmp_eph_iter_bds_dnav != d_internal_pvt_solver->beidou_dnav_ephemeris_map.cend()) { const uint32_t prn_aux = tmp_eph_iter_bds_dnav->second.PRN; if ((prn_aux == in[i][epoch].PRN) && (((std::string(in[i][epoch].Signal, 2) == std::string("B1")) || (std::string(in[i][epoch].Signal, 2) == std::string("B3"))) && (d_use_unhealthy_sats || (tmp_eph_iter_bds_dnav->second.SV_health == 0)))) @@ -2042,7 +2094,14 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item } if (std::string(in[i][epoch].Signal, 2) == std::string("E6")) { - store_valid_observable = true; + if (d_osnma_strict) + { + // TODO + } + else + { + store_valid_observable = true; + } } if (store_valid_observable) diff --git a/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.h b/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.h index 83d1f830d..65f7bd548 100644 --- a/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.h +++ b/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.h @@ -20,6 +20,7 @@ #include "gnss_block_interface.h" #include "gnss_synchro.h" #include "gnss_time.h" +#include "osnma_data.h" #include "rtklib.h" #include #include @@ -34,6 +35,7 @@ #include // for map #include // for shared_ptr, unique_ptr #include // for std::queue +#include // for std::set #include // for string #include // for key_t #include // for vector @@ -144,6 +146,8 @@ private: void msg_handler_has_data(const pmt::pmt_t& msg); + void msg_handler_osnma(const pmt::pmt_t& msg); + void initialize_and_apply_carrier_phase_offset(); void apply_rx_clock_offset(std::map& observables_map, @@ -201,6 +205,7 @@ private: std::map d_gnss_observables_map; std::map d_gnss_observables_map_t0; std::map d_gnss_observables_map_t1; + std::map> d_auth_nav_data_map; std::queue d_TimeChannelTagTimestamps; @@ -278,6 +283,7 @@ private: bool d_log_timetag; bool d_use_has_corrections; bool d_use_unhealthy_sats; + bool d_osnma_strict; }; diff --git a/src/algorithms/PVT/libs/pvt_conf.h b/src/algorithms/PVT/libs/pvt_conf.h index b38cfe788..f014325ba 100644 --- a/src/algorithms/PVT/libs/pvt_conf.h +++ b/src/algorithms/PVT/libs/pvt_conf.h @@ -95,6 +95,7 @@ public: bool use_e6_for_pvt = true; bool use_has_corrections = true; bool use_unhealthy_sats = false; + bool osnma_strict = false; // PVT KF parameters bool enable_pvt_kf = false; diff --git a/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc b/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc index a2d6b40cf..6c72f1b8c 100644 --- a/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc +++ b/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc @@ -45,6 +45,7 @@ #include // for std::numeric_limits #include // for std::map #include // for std::out_of_range +#include // for std::tuple #include // for typeid #include // for std::pair @@ -111,6 +112,7 @@ galileo_telemetry_decoder_gs::galileo_telemetry_decoder_gs( d_enable_reed_solomon_inav(false), d_valid_timetag(false), d_E6_TOW_set(false), + d_there_are_e1_channels(conf.there_are_e1_channels), d_there_are_e6_channels(conf.there_are_e6_channels), d_use_ced(conf.use_ced) { @@ -121,12 +123,19 @@ galileo_telemetry_decoder_gs::galileo_telemetry_decoder_gs( // Control messages to tracking block this->message_port_register_out(pmt::mp("telemetry_to_trk")); + if (d_there_are_e1_channels) + { + // register OSM out + this->message_port_register_out(pmt::mp("OSNMA_from_TLM")); + } + if (d_there_are_e6_channels) { // register Gal E6 messages HAS out this->message_port_register_out(pmt::mp("E6_HAS_from_TLM")); // register TOW from map out this->message_port_register_out(pmt::mp("TOW_from_TLM")); + // register TOW to TLM input this->message_port_register_in(pmt::mp("TOW_to_TLM")); // handler for input port @@ -361,7 +370,6 @@ void galileo_telemetry_decoder_gs::decode_INAV_word(float *page_part_symbols, in // 1. De-interleave std::vector page_part_symbols_soft_value(frame_length); deinterleaver(GALILEO_INAV_INTERLEAVER_ROWS, GALILEO_INAV_INTERLEAVER_COLS, page_part_symbols, page_part_symbols_soft_value.data()); - // 2. Viterbi decoder // 2.1 Take into account the NOT gate in G2 polynomial (Galileo ICD Figure 13, FEC encoder) for (int32_t i = 0; i < frame_length; i++) @@ -431,7 +439,29 @@ void galileo_telemetry_decoder_gs::decode_INAV_word(float *page_part_symbols, in } // 4. Push the new navigation data to the queues - if (d_inav_nav.have_new_ephemeris() == true) + // extract OSNMA bits, reset container. + if (d_inav_nav.get_osnma_adkd_0_12_nav_bits().size() == 549) + { + 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>( // < PRNd , navDataBits, TOW_Sosf> + d_satellite.get_PRN(), + d_inav_nav.get_osnma_adkd_0_12_nav_bits(), + d_inav_nav.get_TOW5() - 25); + 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) + { + 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>( // < PRNd , navDataBits, 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); + this->message_port_pub(pmt::mp("OSNMA_from_TLM"), pmt::make_any(tmp_obj)); + d_inav_nav.reset_osnma_nav_bits_adkd4(); + } + + if (d_inav_nav.have_new_ephemeris() == true) // C: tells if W1-->W4 available from same blcok (and W5!) { // get object for this SV (mandatory) const std::shared_ptr tmp_obj = std::make_shared(d_inav_nav.get_ephemeris()); @@ -466,7 +496,7 @@ void galileo_telemetry_decoder_gs::decode_INAV_word(float *page_part_symbols, in else { // If we still do not have ephemeris, check if we have a reduced CED - if ((d_band == '1') && d_use_ced && !d_first_eph_sent && (d_inav_nav.have_new_reduced_ced() == true)) + if ((d_band == '1') && d_use_ced && !d_first_eph_sent && (d_inav_nav.have_new_reduced_ced() == true)) // C: W16 has some Eph. params, uneeded for OSNMa I guess { const std::shared_ptr tmp_obj = std::make_shared(d_inav_nav.get_reduced_ced()); this->message_port_pub(pmt::mp("telemetry"), pmt::make_any(tmp_obj)); @@ -482,7 +512,7 @@ void galileo_telemetry_decoder_gs::decode_INAV_word(float *page_part_symbols, in } } - if (d_inav_nav.have_new_iono_and_GST() == true) + if (d_inav_nav.have_new_iono_and_GST() == true) // C: W5 { // get object for this SV (mandatory) const std::shared_ptr tmp_obj = std::make_shared(d_inav_nav.get_iono()); @@ -513,7 +543,7 @@ void galileo_telemetry_decoder_gs::decode_INAV_word(float *page_part_symbols, in } } - if (d_inav_nav.have_new_utc_model() == true) + if (d_inav_nav.have_new_utc_model() == true) // C: tells if W6 is available { // get object for this SV (mandatory) const std::shared_ptr tmp_obj = std::make_shared(d_inav_nav.get_utc_model()); @@ -548,7 +578,7 @@ void galileo_telemetry_decoder_gs::decode_INAV_word(float *page_part_symbols, in DLOG(INFO) << "delta_t=" << d_delta_t << "[s]"; } - if (d_inav_nav.have_new_almanac() == true) + if (d_inav_nav.have_new_almanac() == true) // flag_almanac_4 tells if W10 available. { const std::shared_ptr tmp_obj = std::make_shared(d_inav_nav.get_almanac()); this->message_port_pub(pmt::mp("telemetry"), pmt::make_any(tmp_obj)); @@ -580,6 +610,12 @@ void galileo_telemetry_decoder_gs::decode_INAV_word(float *page_part_symbols, in DLOG(INFO) << "d_TOW_at_current_symbol_ms=" << d_TOW_at_current_symbol_ms; DLOG(INFO) << "d_nav.WN_0=" << d_inav_nav.get_Galileo_week(); } + auto newOSNMA = d_inav_nav.have_new_nma(); + if (d_band == '1' && newOSNMA) + { + const std::shared_ptr tmp_obj = std::make_shared(d_inav_nav.get_osnma_msg()); + this->message_port_pub(pmt::mp("OSNMA_from_TLM"), pmt::make_any(tmp_obj)); + } } @@ -742,7 +778,7 @@ void galileo_telemetry_decoder_gs::decode_CNAV_word(uint64_t time_stamp, float * std::cout << TEXT_MAGENTA << "Receiving Galileo E6 CNAV dummy pages in channel " << d_channel << " from satellite " << d_satellite << " with CN0=" - << std::setprecision(2) << cn0 << " dB-Hz" << std::setprecision(default_precision) + << std::setprecision(2) << cn0 << std::setprecision(default_precision) << " dB-Hz" << TEXT_RESET << std::endl; } } diff --git a/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.h b/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.h index 9cf5c81e0..a6391a2e0 100644 --- a/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.h +++ b/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.h @@ -152,6 +152,7 @@ private: bool d_enable_reed_solomon_inav; bool d_valid_timetag; bool d_E6_TOW_set; + bool d_there_are_e1_channels; bool d_there_are_e6_channels; bool d_use_ced; }; diff --git a/src/algorithms/telemetry_decoder/libs/tlm_conf.cc b/src/algorithms/telemetry_decoder/libs/tlm_conf.cc index 2b9b3c59d..cf4a248c9 100644 --- a/src/algorithms/telemetry_decoder/libs/tlm_conf.cc +++ b/src/algorithms/telemetry_decoder/libs/tlm_conf.cc @@ -30,6 +30,11 @@ void Tlm_Conf::SetFromConfiguration(const ConfigurationInterface *configuration, const std::string default_crc_stats_dumpname("telemetry_crc_stats"); dump_crc_stats_filename = configuration->property(role + ".dump_crc_stats_filename", default_crc_stats_dumpname); enable_navdata_monitor = configuration->property("NavDataMonitor.enable_monitor", false); + if (configuration->property("Channels_1B.count", 0) > 0) + { + there_are_e1_channels = true; + } + if (configuration->property("Channels_E6.count", 0) > 0) { there_are_e6_channels = true; diff --git a/src/algorithms/telemetry_decoder/libs/tlm_conf.h b/src/algorithms/telemetry_decoder/libs/tlm_conf.h index fccbb3f7b..ecddac37e 100644 --- a/src/algorithms/telemetry_decoder/libs/tlm_conf.h +++ b/src/algorithms/telemetry_decoder/libs/tlm_conf.h @@ -42,6 +42,7 @@ public: bool enable_reed_solomon{false}; // for INAV message in Galileo E1B bool dump_crc_stats{false}; // telemetry CRC statistics bool enable_navdata_monitor{false}; + bool there_are_e1_channels{false}; bool there_are_e6_channels{false}; bool use_ced{false}; }; diff --git a/src/core/libs/CMakeLists.txt b/src/core/libs/CMakeLists.txt index 7b4f4f703..cb77ec515 100644 --- a/src/core/libs/CMakeLists.txt +++ b/src/core/libs/CMakeLists.txt @@ -9,34 +9,42 @@ protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${GNSSSDR_SOURCE_DIR}/docs/protobuf/ add_subdirectory(supl) set(CORE_LIBS_SOURCES - ini.cc - INIReader.cc - string_converter.cc - gnss_sdr_supl_client.cc - gnss_sdr_sample_counter.cc - channel_status_msg_receiver.cc channel_event.cc + channel_status_msg_receiver.cc command_event.cc galileo_e6_has_msg_receiver.cc + galileo_tow_map.cc + gnss_crypto.cc + gnss_sdr_sample_counter.cc + gnss_sdr_supl_client.cc + ini.cc + INIReader.cc nav_message_monitor.cc nav_message_udp_sink.cc - galileo_tow_map.cc + osnma_helper.cc + osnma_msg_receiver.cc + osnma_nav_data_manager.cc + string_converter.cc ) set(CORE_LIBS_HEADERS + channel_event.h + channel_status_msg_receiver.h + command_event.h + galileo_tow_map.h + gnss_crypto.h + gnss_sdr_sample_counter.h + gnss_sdr_supl_client.h ini.h INIReader.h - string_converter.h - gnss_sdr_supl_client.h - gnss_sdr_sample_counter.h - channel_status_msg_receiver.h - channel_event.h - command_event.h + nav_message_monitor.h nav_message_packet.h nav_message_udp_sink.h + osnma_helper.h + osnma_msg_receiver.h + osnma_nav_data_manager.h serdes_nav_message.h - nav_message_monitor.h - galileo_tow_map.h + string_converter.h ) if(ENABLE_FPGA) @@ -91,10 +99,10 @@ target_link_libraries(core_libs core_libs_supl core_system_parameters pvt_libs - algorithms_libs PRIVATE algorithms_libs Boost::serialization + Boost::system Pugixml::pugixml ) @@ -122,6 +130,10 @@ target_include_directories(core_libs ${GNSSSDR_SOURCE_DIR}/src/core/interfaces ) +# links to the appropriate library and defines +# USE_GNUTLS_FALLBACK, USE_OPENSSL_3, or USE_OPENSSL_111 accordingly. +link_to_crypto_dependencies(core_libs) + if(USE_GENERIC_LAMBDAS) set(has_generic_lambdas HAS_GENERIC_LAMBDA=1) set(no_has_generic_lambdas HAS_GENERIC_LAMBDA=0) diff --git a/src/core/libs/gnss_crypto.cc b/src/core/libs/gnss_crypto.cc new file mode 100644 index 000000000..cbe4785ff --- /dev/null +++ b/src/core/libs/gnss_crypto.cc @@ -0,0 +1,1919 @@ +/*! + * \file gnss_crypto.cc + * \brief Class for computing cryptographic functions + * \author Carles Fernandez, 2023-2024. cfernandez(at)cttc.es + * Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de + * + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2024 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "gnss_crypto.h" +#include "Galileo_OSNMA.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if USE_GNUTLS_FALLBACK +#include +#include +#include +#include +#else // OpenSSL +#include +#include +#include +#include +#include +#if USE_OPENSSL_3 +#include +#include +#include +#include +#include +#include +#include +#include +#define OPENSSL_ENGINE nullptr +#else // OpenSSL 1.x +#include +#endif +#endif + +#if USE_GLOG_AND_GFLAGS +#include // for DLOG +#else +#include +#endif + + +Gnss_Crypto::Gnss_Crypto() +{ +#if USE_GNUTLS_FALLBACK + gnutls_global_init(); +#if !HAVE_GNUTLS_SIGN_ECDSA_SHA256 + LOG(WARNING) << "The GnuTLS library version you are linking against is too old for some OSNMA functions." + << " Please do not trust OSNMA ouputs or upgrade your system to a newer version of GnuTLS or OpenSSL" + << " and rebuild GNSS-SDR against it."; +#endif +#else // OpenSSL +#if !(USE_OPENSSL_3 || USE_OPENSSL_111) + LOG(WARNING) << "The OpenSSL library version you are linking against is too old for some OSNMA functions." + << " Please do not trust OSNMA ouputs or upgrade your system to a newer version of OpenSSL" + << " and rebuild GNSS-SDR against it."; +#endif +#endif +} + + +Gnss_Crypto::Gnss_Crypto(const std::string& certFilePath, const std::string& merkleTreePath) +{ +#if USE_GNUTLS_FALLBACK + gnutls_global_init(); +#if !HAVE_GNUTLS_SIGN_ECDSA_SHA256 + LOG(WARNING) << "The GnuTLS library version you are linking against is too old for some OSNMA functions." + << " Please do not trust OSNMA ouputs or upgrade your system to a newer version of GnuTLS or OpenSSL" + << " and rebuild GNSS-SDR against it."; +#endif +#else // OpenSSL +#if !(USE_OPENSSL_3 || USE_OPENSSL_111) + LOG(WARNING) << "The OpenSSL library version you are linking against is too old for some OSNMA functions." + << " Please do not trust OSNMA ouputs or upgrade your system to a newer version of OpenSSL" + << " and rebuild GNSS-SDR against it."; +#endif +#endif + if (!readPublicKeyFromCRT(certFilePath)) + { + readPublicKeyFromPEM(certFilePath); + if (!have_public_key()) + { + readPublicKeyFromPEM(PEMFILE_DEFAULT); + } + } + read_merkle_xml(merkleTreePath); +} + + +Gnss_Crypto::~Gnss_Crypto() +{ +#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 +} + + +bool Gnss_Crypto::have_public_key() const +{ +#if USE_GNUTLS_FALLBACK + return (d_PublicKey != gnutls_pubkey_t{}); +#else // OpenSSL + return (d_PublicKey != nullptr); +#endif +} + + +bool Gnss_Crypto::store_public_key(const std::string& pubKeyFilePath) const +{ + if (!have_public_key()) + { + return false; + } + std::ofstream pubKeyFile(pubKeyFilePath, std::ios::binary); + if (!pubKeyFile.is_open()) + { + LOG(WARNING) << "Unable to open file for storing the Public Key: " << pubKeyFilePath; + return false; + } +#if USE_GNUTLS_FALLBACK + gnutls_datum_t pem_data; +#if HAVE_GNUTLS_PUBKEY_EXPORT2 + int ret = gnutls_pubkey_export2(d_PublicKey, GNUTLS_X509_FMT_PEM, &pem_data); +#else + size_t output_stata_size; + int ret = gnutls_pubkey_export(d_PublicKey, GNUTLS_X509_FMT_PEM, &pem_data, &output_stata_size); +#endif + if (ret != GNUTLS_E_SUCCESS) + { + LOG(WARNING) << "GnuTLS: Failed to export public key: " << gnutls_strerror(ret); + return false; + } + + pubKeyFile.write(reinterpret_cast(pem_data.data), pem_data.size); + pubKeyFile.close(); + gnutls_free(pem_data.data); +#else // OpenSSL + BIO* bio = BIO_new(BIO_s_mem()); + if (!bio) + { + LOG(WARNING) << "OpenSSL: Failed to create BIO"; + return false; + } +#if USE_OPENSSL_3 + if (!PEM_write_bio_PUBKEY(bio, d_PublicKey)) +#else // OpenSSL 1.x + if (!PEM_write_bio_EC_PUBKEY(bio, d_PublicKey)) +#endif + { + LOG(WARNING) << "OpenSSL: Failed to write public key to BIO"; + BIO_free(bio); + return false; + } + + char* bio_data; + auto bio_len = BIO_get_mem_data(bio, &bio_data); + if (bio_len <= 0) + { + LOG(WARNING) << "OpenSSL: Failed to get BIO data"; + BIO_free(bio); + return false; + } + + pubKeyFile.write(bio_data, bio_len); + pubKeyFile.close(); + BIO_free(bio); +#endif + return true; +} + + +bool Gnss_Crypto::verify_signature_ecdsa_p256(const std::vector& message, const std::vector& signature) const +{ + if (!have_public_key()) + { + LOG(WARNING) << "Signature verification error: Public key is not available"; + return false; + } + std::vector digest = this->compute_SHA_256(message); + bool success = false; +#if USE_GNUTLS_FALLBACK +#if HAVE_GNUTLS_SIGN_ECDSA_SHA256 + // Convert signature to DER format + std::vector der_sig; + if (!convert_raw_to_der_ecdsa(signature, der_sig)) + { + LOG(WARNING) << "Failed to convert raw ECDSA signature to DER format"; + return false; + } + + // Prepare the digest datum + gnutls_datum_t digest_data = {const_cast(digest.data()), static_cast(digest.size())}; + gnutls_datum_t der_sig_data = {der_sig.data(), static_cast(der_sig.size())}; + + // Verify the DER-encoded signature + int ret = gnutls_pubkey_verify_hash2(d_PublicKey, GNUTLS_SIGN_ECDSA_SHA256, 0, &digest_data, &der_sig_data); + success = (ret >= 0); + if (success) + { + DLOG(INFO) << "GnuTLS: OSNMA signature authenticated successfully"; + } + else + { + LOG(WARNING) << "GnuTLS: OSNMA message authentication failed: " << gnutls_strerror(ret); + } +#else + if (signature.empty()) + { + // do nothing + } +#endif +#else // OpenSSL +#if USE_OPENSSL_3 + EVP_PKEY_CTX* ctx; + ctx = EVP_PKEY_CTX_new(d_PublicKey, nullptr); + bool do_operation = true; + + if (!ctx) + { + do_operation = false; + } + // convert raw signature into DER format + size_t half_size = signature.size() / 2; + std::vector raw_r(signature.begin(), signature.begin() + half_size); + std::vector raw_s(signature.begin() + half_size, signature.end()); + + // Convert raw R and S to BIGNUMs + BIGNUM* r = BN_bin2bn(raw_r.data(), raw_r.size(), nullptr); + BIGNUM* s = BN_bin2bn(raw_s.data(), raw_s.size(), nullptr); + + ECDSA_SIG* sig = ECDSA_SIG_new(); + if (r == nullptr || s == nullptr || sig == nullptr) + { + LOG(WARNING) << "OpenSSL: Failed to allocate memory for BIGNUMs or ECDSA_SIG"; + return false; + } + + if (ECDSA_SIG_set0(sig, r, s) != 1) + { + LOG(WARNING) << "OpenSSL: Failed to set R and S values in ECDSA_SIG"; + ECDSA_SIG_free(sig); // Free the ECDSA_SIG struct as it is no longer needed + return false; + } + + std::vector derSignature; + unsigned char* derSig = nullptr; + int derSigLength = i2d_ECDSA_SIG(sig, &derSig); + + if (derSigLength <= 0) + { + LOG(WARNING) << "OpenSSL: Failed to convert ECDSA_SIG to DER format"; + return false; + } + + derSignature.assign(derSig, derSig + derSigLength); + + if (EVP_PKEY_verify_init(ctx) <= 0) + { + do_operation = false; + } + if (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0) + { + do_operation = false; + } + int verification = 0; + if (do_operation) + { + verification = EVP_PKEY_verify(ctx, derSignature.data(), derSignature.size(), digest.data(), digest.size()); + } + EVP_PKEY_CTX_free(ctx); + OPENSSL_free(derSig); + ECDSA_SIG_free(sig); + if (verification == 1) + { + success = true; + DLOG(INFO) << "OpenSSL: OSNMA signature authenticated successfully"; + } + else + { + uint64_t errCode = ERR_get_error(); + char* err = ERR_error_string(errCode, nullptr); + LOG(WARNING) << "OpenSSL: OSNMA message authentication failed: " << err; + } +#else // OpenSSL 1.x + std::vector der_sig; + if (!convert_raw_to_der_ecdsa(signature, der_sig)) + { + LOG(WARNING) << "OpenSSL: Failed to convert raw ECDSA signature to DER format"; + return false; + } + int verification = ECDSA_verify(0, digest.data(), SHA256_DIGEST_LENGTH, der_sig.data(), static_cast(der_sig.size()), d_PublicKey); + if (verification == 1) + { + success = true; + DLOG(INFO) << "OpenSSL: OSNMA signature authenticated successfully"; + } + else if (verification == 0) + { + LOG(WARNING) << "OpenSSL: invalid signature found when verifying message"; + } + else + { + LOG(WARNING) << "OpenSSL: OSNMA message authentication failed"; + } +#endif +#endif + return success; +} + + +bool Gnss_Crypto::verify_signature_ecdsa_p521(const std::vector& message, const std::vector& signature) const +{ + if (!have_public_key()) + { + LOG(WARNING) << "Signature verification error: Public key is not available"; + return false; + } + + if (signature.size() != 132) + { + LOG(WARNING) << "Invalid signature length for P-521. Expected 132 bytes, got " << signature.size(); + return false; + } + + std::vector der_sig; + if (!convert_raw_to_der_ecdsa(signature, der_sig)) + { + LOG(WARNING) << "Failed to convert raw ECDSA signature to DER format"; + return false; + } + bool success = false; +#if USE_GNUTLS_FALLBACK +#if HAVE_GNUTLS_SIGN_ECDSA_SHA512 + std::vector digest(64); + gnutls_hash_hd_t hash; + int ret = gnutls_hash_init(&hash, GNUTLS_DIG_SHA512); + if (ret != GNUTLS_E_SUCCESS) + { + LOG(WARNING) << "GnuTLS: gnutls_hash_init failed: " << gnutls_strerror(ret); + return false; + } + + gnutls_hash(hash, message.data(), message.size()); + gnutls_hash_deinit(hash, digest.data()); + + gnutls_datum_t digest_data = {digest.data(), static_cast(digest.size())}; + gnutls_datum_t signature_data = {der_sig.data(), static_cast(der_sig.size())}; + + // Verify the ECDSA signature + ret = gnutls_pubkey_verify_hash2(d_PublicKey, GNUTLS_SIGN_ECDSA_SHA512, 0, &digest_data, &signature_data); + + if (ret >= 0) + { + DLOG(INFO) << "GnuTLS: OSNMA signature authenticated successfully"; + success = true; + } + else + { + LOG(WARNING) << "GnuTLS: OSNMA message authentication failed: " << gnutls_strerror(ret); + } +#else + if (message.empty()) + { + // do nothing + } +#endif +#else // OpenSSL + // Compute SHA-512 hash of the message + std::vector digest(SHA512_DIGEST_LENGTH); + if (!EVP_Digest(message.data(), message.size(), digest.data(), nullptr, EVP_sha512(), nullptr)) + { + LOG(WARNING) << "OpenSSL: EVP_Digest failed"; + return false; + } +#if USE_OPENSSL_3 + // Verify the signature + EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new(d_PublicKey, nullptr); + if (pctx == nullptr) + { + LOG(WARNING) << "OpenSSL: EVP_PKEY_CTX_new failed"; + return false; + } + + if (EVP_PKEY_verify_init(pctx) <= 0) + { + LOG(WARNING) << "OpenSSL: EVP_PKEY_verify_init failed"; + EVP_PKEY_CTX_free(pctx); + return false; + } + + if (EVP_PKEY_CTX_set_signature_md(pctx, EVP_sha512()) <= 0) + { + LOG(WARNING) << "OpenSSL: EVP_PKEY_CTX_set_signature_md failed"; + EVP_PKEY_CTX_free(pctx); + return false; + } + + int verification = EVP_PKEY_verify(pctx, der_sig.data(), der_sig.size(), digest.data(), digest.size()); + EVP_PKEY_CTX_free(pctx); + + if (verification == 1) + { + DLOG(INFO) << "OpenSSL: OSNMA signature authenticated successfully"; + success = true; + } + else if (verification == 0) + { + LOG(WARNING) << "OpenSSL: invalid signature found when verifying message"; + } + else + { + LOG(WARNING) << "OpenSSL: OSNMA message authentication failed"; + } +#else // OpenSSL 1.x + const unsigned char* sig_ptr = der_sig.data(); + ECDSA_SIG* ecdsa_sig = d2i_ECDSA_SIG(nullptr, &sig_ptr, der_sig.size()); + if (ecdsa_sig == nullptr) + { + LOG(WARNING) << "OpenSSL: d2i_ECDSA_SIG failed"; + return false; + } + int verification = ECDSA_do_verify(digest.data(), digest.size(), ecdsa_sig, d_PublicKey); + ECDSA_SIG_free(ecdsa_sig); + if (verification == 1) + { + DLOG(INFO) << "OpenSSL: OSNMA signature authenticated successfully"; + success = true; + } + else if (verification == 0) + { + LOG(WARNING) << "OpenSSL: invalid signature found when verifying message"; + } + else + { + LOG(WARNING) << "OpenSSL: OSNMA message authentication failed"; + } +#endif +#endif + return success; +} + + +std::vector Gnss_Crypto::compute_SHA_256(const std::vector& input) const +{ + std::vector output(32); // SHA256 hash size +#if USE_GNUTLS_FALLBACK + std::vector output_aux(32); + gnutls_hash_hd_t hashHandle; + gnutls_hash_init(&hashHandle, GNUTLS_DIG_SHA256); + gnutls_hash(hashHandle, input.data(), input.size()); + gnutls_hash_output(hashHandle, output_aux.data()); + output = output_aux; + gnutls_hash_deinit(hashHandle, output_aux.data()); +#else // OpenSSL +#if USE_OPENSSL_3 + unsigned int mdLen; + 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."; + EVP_MD_CTX_free(mdCtx); + return output; + } + if (!EVP_DigestUpdate(mdCtx, input.data(), input.size())) + { + // LOG(WARNING) << "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."; + EVP_MD_CTX_free(mdCtx); + return output; + } + EVP_MD_CTX_free(mdCtx); +#else // OpenSSL 1.x + SHA256_CTX sha256Context; + SHA256_Init(&sha256Context); + SHA256_Update(&sha256Context, input.data(), input.size()); + SHA256_Final(output.data(), &sha256Context); +#endif +#endif + return output; +} + + +std::vector Gnss_Crypto::compute_SHA3_256(const std::vector& input) const +{ + std::vector output(32); // SHA256 hash size +#if USE_GNUTLS_FALLBACK +#if HAVE_GNUTLS_DIG_SHA3_256 + std::vector output_aux(32); + gnutls_hash_hd_t hashHandle; + gnutls_hash_init(&hashHandle, GNUTLS_DIG_SHA3_256); + gnutls_hash(hashHandle, input.data(), input.size()); + gnutls_hash_output(hashHandle, output_aux.data()); + output = output_aux; + gnutls_hash_deinit(hashHandle, output_aux.data()); +#else + if (input.empty()) + { + // do nothing + } +#endif +#else // OpenSSL +#if USE_OPENSSL_3 || USE_OPENSSL_111 + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + const EVP_MD* md = EVP_sha3_256(); + + EVP_DigestInit_ex(mdctx, md, nullptr); + EVP_DigestUpdate(mdctx, input.data(), input.size()); + EVP_DigestFinal_ex(mdctx, output.data(), nullptr); + EVP_MD_CTX_free(mdctx); +#else // OpenSSL 1.x + // SHA3-256 not implemented in OpenSSL 1.0, it was introduced in OpenSSL 1.1.1 + if (!input.empty()) + { + // do nothing + } +#endif +#endif + return output; +} + + +std::vector Gnss_Crypto::compute_HMAC_SHA_256(const std::vector& key, const std::vector& input) const +{ + std::vector output(32); +#if USE_GNUTLS_FALLBACK + std::vector output_aux(32); + gnutls_hmac_hd_t hmac; +#if HAVE_GNUTLS_HMAC_INIT_WITH_DIGEST + gnutls_hmac_init(&hmac, GNUTLS_DIG_SHA256, key.data(), key.size()); +#else + gnutls_hmac_init(&hmac, GNUTLS_MAC_SHA256, key.data(), key.size()); +#endif + gnutls_hmac(hmac, input.data(), input.size()); + gnutls_hmac_output(hmac, output_aux.data()); + output = output_aux; + gnutls_hmac_deinit(hmac, output_aux.data()); +#else // OpenSSL +#if USE_OPENSSL_3 + std::vector hmac(EVP_MAX_MD_SIZE); + size_t output_length = 0; + // Create the context for the HMAC operation + EVP_MAC* mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr); + if (!mac) + { + LOG(WARNING) << "OSNMA HMAC_SHA_256 computation failed to fetch HMAC"; + return output; + } + + EVP_MAC_CTX* ctx = EVP_MAC_CTX_new(mac); + if (!ctx) + { + EVP_MAC_free(mac); + LOG(WARNING) << "OSNMA HMAC_SHA_256 computation failed to create HMAC context"; + return output; + } + + // Initialize the HMAC context with the key and the SHA-256 algorithm + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string(OSSL_ALG_PARAM_DIGEST, const_cast("SHA256"), 0), + OSSL_PARAM_construct_end()}; + + if (EVP_MAC_init(ctx, key.data(), key.size(), params) <= 0) + { + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(mac); + LOG(WARNING) << "OSNMA HMAC_SHA_256 computation failed to initialize HMAC context"; + return output; + } + + // Update the HMAC context with the input data + if (EVP_MAC_update(ctx, input.data(), input.size()) <= 0) + { + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(mac); + LOG(WARNING) << "OSNMA HMAC_SHA_256 computation failed to update HMAC context"; + return output; + } + + // Finalize the HMAC and retrieve the output + if (EVP_MAC_final(ctx, hmac.data(), &output_length, hmac.size()) <= 0) + { + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(mac); + LOG(WARNING) << "OSNMA HMAC_SHA_256 computation failed to finalize HMAC"; + return output; + } + + // Clean up the HMAC context + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(mac); + hmac.resize(output_length); + output = std::move(hmac); +#else // OpenSSL 1.x + unsigned int outputLength = EVP_MAX_MD_SIZE; + 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"; + return output; + } + + // Resize the output vector to the actual length of the HMAC-SHA256 output + output.resize(outputLength); +#endif +#endif + return output; +} + + +std::vector Gnss_Crypto::compute_CMAC_AES(const std::vector& key, const std::vector& input) const +{ + std::vector output(16); +#if USE_GNUTLS_FALLBACK +#if HAVE_GNUTLS_MAC_AES_CMAC_128 + gnutls_hmac_hd_t hmac; + + // Initialize the HMAC context with the CMAC algorithm and key + 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); + return output; + } + + // Update the HMAC context with the input data + ret = gnutls_hmac(hmac, input.data(), input.size()); + if (ret != GNUTLS_E_SUCCESS) + { + LOG(WARNING) << "OSNMA CMAC-AES: gnutls_hmac failed: " << gnutls_strerror(ret); + gnutls_hmac_deinit(hmac, nullptr); + return output; + } + + // Retrieve the HMAC output + gnutls_hmac_output(hmac, output.data()); + + // Clean up the HMAC context + gnutls_hmac_deinit(hmac, nullptr); +#else + if (!key.empty()) + { + // do nothing + } + if (!input.empty()) + { + // do nothing + } +#endif +#else // OpenSSL +#if USE_OPENSSL_3 + std::vector aux(EVP_MAX_MD_SIZE); // CMAC-AES output size + size_t output_length = 0; + + // Create the context for the CMAC operation + EVP_MAC* mac = EVP_MAC_fetch(nullptr, "CMAC", nullptr); + if (!mac) + { + LOG(WARNING) << "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"; + return output; + } + + // Initialize the CMAC context with the key and the AES algorithm + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_CIPHER, const_cast("AES-128-CBC"), 0), + OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY, const_cast(key.data()), key.size()), + OSSL_PARAM_construct_end()}; + + if (EVP_MAC_init(ctx, nullptr, 0, params) <= 0) + { + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(mac); + LOG(WARNING) << "OSNMA CMAC-AES: Failed to initialize CMAC context"; + return output; + } + + // Update the CMAC context with the input data + if (EVP_MAC_update(ctx, input.data(), input.size()) <= 0) + { + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(mac); + LOG(WARNING) << "OSNMA CMAC-AES: Failed to update CMAC context"; + return output; + } + + // Finalize the CMAC and retrieve the output + if (EVP_MAC_final(ctx, aux.data(), &output_length, aux.size()) <= 0) + { + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(mac); + LOG(WARNING) << "OSNMA CMAC-AES: Failed to finalize CMAC"; + return output; + } + + // Clean up the CMAC context + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(mac); + + aux.resize(output_length); + output = std::move(aux); +#else // OpenSSL 1.x + size_t mac_length = 0; // to hold the length of the MAC output + + // Create CMAC context + CMAC_CTX* cmacCtx = CMAC_CTX_new(); + if (!cmacCtx) + { + LOG(WARNING) << "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"; + CMAC_CTX_free(cmacCtx); + return output; + } + + // Compute the CMAC + if (CMAC_Update(cmacCtx, input.data(), input.size()) != 1) + { + LOG(WARNING) << "OSNMA CMAC-AES: CMAC_Update failed"; + CMAC_CTX_free(cmacCtx); + return output; + } + + // 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"; + CMAC_CTX_free(cmacCtx); + return output; + } + + // Clean up CMAC context + CMAC_CTX_free(cmacCtx); + + // Ensure the output vector is properly sized according to the actual MAC length + output.resize(mac_length); +#endif +#endif + return output; +} + + +std::vector Gnss_Crypto::get_merkle_root() const +{ + return d_x_4_0; +} + + +std::string Gnss_Crypto::get_public_key_type() const +{ + if (d_PublicKeyType.empty()) + { + return {"Unknown"}; + } + return d_PublicKeyType; +} + + +void Gnss_Crypto::set_public_key(const std::vector& publicKey) +{ + d_PublicKeyType = std::string("Unknown"); +#if USE_GNUTLS_FALLBACK + gnutls_pubkey_t pubkey{}; + gnutls_ecc_curve_t curve; + std::vector x; + std::vector y; + + gnutls_pubkey_init(&pubkey); + const size_t size_pk = publicKey.size(); + if (size_pk == 33) + { + curve = GNUTLS_ECC_CURVE_SECP256R1; + d_PublicKeyType = std::string("ECDSA P-256"); + decompress_public_key_secp256r1(publicKey, x, y); + } + else if (size_pk == 67) + { + curve = GNUTLS_ECC_CURVE_SECP521R1; + d_PublicKeyType = std::string("ECDSA P-521"); + decompress_public_key_secp521r1(publicKey, x, y); + } + else + { + LOG(WARNING) << "GnuTLS: Invalid public key size"; + gnutls_pubkey_deinit(pubkey); + return; + } + + gnutls_datum_t x_coord = {x.data(), static_cast(x.size())}; + gnutls_datum_t y_coord = {y.data(), static_cast(y.size())}; + + int ret = gnutls_pubkey_import_ecc_raw(pubkey, curve, &x_coord, &y_coord); + if (ret != GNUTLS_E_SUCCESS) + { + gnutls_pubkey_deinit(pubkey); + LOG(WARNING) << "GnuTLS: error setting the OSNMA public key: " << gnutls_strerror(ret); + d_PublicKeyType = std::string("Unknown"); + return; + } + pubkey_copy(pubkey, &d_PublicKey); + gnutls_pubkey_deinit(pubkey); +#else // OpenSSL + +#if USE_OPENSSL_3 + // Uses the new EVP_PKEY envelope as well as the parameter builder functions + // generate the uncompressed key, then add it into the EVP_PKEY* struct + EVP_PKEY* pkey = nullptr; + EVP_PKEY_CTX* ctx = nullptr; + OSSL_PARAM_BLD* param_bld; + OSSL_PARAM* params = nullptr; + const size_t public_key_size = publicKey.size(); + + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld != nullptr && + OSSL_PARAM_BLD_push_utf8_string(param_bld, "group", (public_key_size == 33) ? "prime256v1" : "secp521r1", 0) && + OSSL_PARAM_BLD_push_octet_string(param_bld, "pub", publicKey.data(), public_key_size)) + { + params = OSSL_PARAM_BLD_to_param(param_bld); + } + + if (public_key_size == 33) + { + d_PublicKeyType = std::string("ECDSA P-256"); + } + else if (public_key_size == 67) + { + d_PublicKeyType = std::string("ECDSA P-521"); + } + + ctx = EVP_PKEY_CTX_new_from_name(nullptr, "EC", nullptr); + if (ctx == nullptr || params == nullptr || EVP_PKEY_fromdata_init(ctx) <= 0 || EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + { + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(param_bld); + d_PublicKeyType = std::string("Unknown"); + return; + } + + if (!pubkey_copy(pkey, &d_PublicKey)) + { + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(param_bld); + d_PublicKeyType = std::string("Unknown"); + return; + } + + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(param_bld); +#else // OpenSSL 1.x + EC_KEY* ec_key = nullptr; // ECC Key pair + EC_POINT* point = nullptr; // Represents the point in the EC the public key belongs to + EC_GROUP* group = nullptr; // Defines the curve the public key belongs + if (publicKey.size() == 33) // ECDSA-P-256 + { + group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1); + d_PublicKeyType = std::string("ECDSA P-256"); + } + else // ECDSA-P-521 + { + group = EC_GROUP_new_by_curve_name(NID_secp521r1); + d_PublicKeyType = std::string("ECDSA P-521"); + } + if (!group) + { + d_PublicKeyType = std::string("Unknown"); + return; + } + + point = EC_POINT_new(group); + if (!point) + { + EC_GROUP_free(group); + d_PublicKeyType = std::string("Unknown"); + return; + } + + if (!EC_POINT_oct2point(group, point, publicKey.data(), publicKey.size(), nullptr)) + { + EC_GROUP_free(group); + EC_POINT_free(point); + d_PublicKeyType = std::string("Unknown"); + return; + } + + if (publicKey.size() == 33) // ECDSA-P-256 + { + ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + } + else // ECDSA-P-521 + { + ec_key = EC_KEY_new_by_curve_name(NID_secp521r1); + } + if (!ec_key) + { + EC_GROUP_free(group); + EC_POINT_free(point); + d_PublicKeyType = std::string("Unknown"); + return; + } + if (!EC_KEY_set_public_key(ec_key, point)) + { + EC_KEY_free(ec_key); + EC_POINT_free(point); + EC_GROUP_free(group); + d_PublicKeyType = std::string("Unknown"); + return; + } + if (!pubkey_copy(ec_key, &d_PublicKey)) + { + EC_KEY_free(ec_key); + EC_POINT_free(point); + EC_GROUP_free(group); + d_PublicKeyType = std::string("Unknown"); + return; + } + EC_KEY_free(ec_key); + EC_POINT_free(point); + EC_GROUP_free(group); +#endif // OpenSSL 1.x +#endif + DLOG(INFO) << "OSNMA Public Key successfully set up."; +} + + +void Gnss_Crypto::set_public_key_type(const std::string& public_key_type) +{ + if (public_key_type == "ECDSA P-256" || public_key_type == "ECDSA P-521") + { + d_PublicKeyType = public_key_type; + } +} + + +void Gnss_Crypto::set_merkle_root(const std::vector& v) +{ + d_x_4_0 = v; +} + + +void Gnss_Crypto::read_merkle_xml(const std::string& merkleFilePath) +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(merkleFilePath.c_str()); + if (!result) + { + // 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; + } + try + { + 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); + } + } + } + catch (const std::exception& e) + { + LOG(INFO) << "Exception raised reading the " << merkleFilePath << " file: " << e.what(); + d_x_4_0 = convert_from_hex_str("832E15EDE55655EAC6E399A539477B7C034CCE24C3C93FFC904ACD9BF842F04E"); + return; + } + std::cout << "OSNMA Merkle Tree successfully read from file " << merkleFilePath << std::endl; + LOG(INFO) << "OSNMA Merkle Tree successfully read from file " << merkleFilePath; +} + + +void Gnss_Crypto::readPublicKeyFromPEM(const std::string& pemFilePath) +{ + // Open the .pem file + std::ifstream pemFile(pemFilePath); + if (!pemFile) + { + return; + } + d_PublicKeyType = std::string("Unknown"); + std::string pemContent((std::istreambuf_iterator(pemFile)), std::istreambuf_iterator()); +#if USE_GNUTLS_FALLBACK + // Import the PEM data + gnutls_datum_t pemDatum = {const_cast(reinterpret_cast(const_cast(pemContent.data()))), static_cast(pemContent.size())}; + gnutls_pubkey_t pubkey; + gnutls_pubkey_init(&pubkey); + + int ret = gnutls_pubkey_import(pubkey, &pemDatum, GNUTLS_X509_FMT_PEM); + if (ret != GNUTLS_E_SUCCESS) + { + gnutls_pubkey_deinit(pubkey); + std::cerr << "GnuTLS: error reading the OSNMA Public Key from file " + << pemFilePath + << ". Aborting import" << std::endl; + LOG(WARNING) << "GnuTLS: error reading the OSNMA Public Key from file " + << pemFilePath << ". Aborting import. Error " << gnutls_strerror(ret); + return; + } + + // store the key type - needed for the Kroot in case no DSM-PKR available + gnutls_pk_algorithm_t pk_algorithm; + unsigned int bits; + + ret = gnutls_pubkey_get_pk_algorithm(pubkey, &bits); + if (ret < 0) + { + LOG(WARNING) << "GnuTLS: Failed to get public key algorithm from .pem file: " << gnutls_strerror(ret); + gnutls_pubkey_deinit(pubkey); + return; + } + + pk_algorithm = static_cast(ret); + if (pk_algorithm == GNUTLS_PK_ECC) + { + gnutls_ecc_curve_t curve; + ret = gnutls_pubkey_export_ecc_raw(pubkey, &curve, nullptr, nullptr); + if (ret < 0) + { + LOG(WARNING) << "GnuTLS: Failed to get EC curve from .pem file: " << gnutls_strerror(ret); + gnutls_pubkey_deinit(pubkey); + return; + } + + if (curve == GNUTLS_ECC_CURVE_SECP256R1) + { + d_PublicKeyType = std::string("ECDSA P-256"); + } + else if (curve == GNUTLS_ECC_CURVE_SECP521R1) + { + d_PublicKeyType = std::string("ECDSA P-521"); + } + else + { + LOG(WARNING) << "GnuTLS: Trying to read unknown EC curve from .pem file"; + gnutls_pubkey_deinit(pubkey); + return; + } + } + else + { + LOG(WARNING) << "GnuTLS: Trying to read unknown key type from .pem file"; + gnutls_pubkey_deinit(pubkey); + return; + } + pubkey_copy(pubkey, &d_PublicKey); + gnutls_pubkey_deinit(pubkey); +#else // OpenSSL + // Create a BIO object from the string data + BIO* bio = BIO_new_mem_buf(const_cast(pemContent.c_str()), pemContent.length()); + if (!bio) + { + LOG(WARNING) << "OpenSSL: error creating a BIO object with data read from file " << pemFilePath << ". Aborting import."; + return; + } +#if USE_OPENSSL_3 + EVP_PKEY* pubkey = nullptr; + pubkey = PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr); + + // store the key type - needed for the Kroot in case no DSM-PKR available + // Get the key type + int key_type = EVP_PKEY_base_id(pubkey); + if (key_type == EVP_PKEY_EC) + { + // It's an EC key, now we need to determine the curve + char curve_name[256]; + size_t curve_name_len = sizeof(curve_name); + + if (EVP_PKEY_get_utf8_string_param(pubkey, OSSL_PKEY_PARAM_GROUP_NAME, curve_name, curve_name_len, &curve_name_len) == 1) + { + if (strcmp(curve_name, "prime256v1") == 0 || strcmp(curve_name, "P-256") == 0) + { + d_PublicKeyType = std::string("ECDSA P-256"); + } + else if (strcmp(curve_name, "secp521r1") == 0 || strcmp(curve_name, "P-521") == 0) + { + d_PublicKeyType = std::string("ECDSA P-521"); + } + else + { + LOG(WARNING) << "OpenSSL: Trying to read an unknown EC curve from .pem file"; + BIO_free(bio); + EVP_PKEY_free(pubkey); + return; + } + } + else + { + LOG(WARNING) << "OpenSSL: Trying to read an unknown EC curve from .pem file"; + BIO_free(bio); + EVP_PKEY_free(pubkey); + return; + } + } + else + { + LOG(WARNING) << "OpenSSL: Trying to read an unknown key type from .pem file"; + BIO_free(bio); + EVP_PKEY_free(pubkey); + return; + } + pubkey_copy(pubkey, &d_PublicKey); + EVP_PKEY_free(pubkey); +#else // OpenSSL 1.x + EC_KEY* pubkey = nullptr; + pubkey = PEM_read_bio_EC_PUBKEY(bio, nullptr, nullptr, nullptr); + if (!pubkey) + { + LOG(WARNING) << "OpenSSL: Failed to extract the public key from .pem file"; + BIO_free(bio); + return; + } + const EC_GROUP* group = EC_KEY_get0_group(pubkey); + int nid = EC_GROUP_get_curve_name(group); + if (nid == 0) + { + BIGNUM* p = BN_new(); + if (EC_GROUP_get_curve_GFp(group, p, nullptr, nullptr, nullptr) == 1) + { + char* p_str = BN_bn2hex(p); + const std::string pcstr(p_str); + if (pcstr.size() == 64) + { + d_PublicKeyType = std::string("ECDSA P-256"); + } + else if (pcstr.size() == 132) + { + d_PublicKeyType = std::string("ECDSA P-521"); + } + OPENSSL_free(p_str); + } + BN_free(p); + } + else + { + const char* curve_name = OBJ_nid2sn(nid); + const std::string curve_str(curve_name); + if (curve_str == "prime256v1") + { + d_PublicKeyType = std::string("ECDSA P-256"); + } + else if (curve_str == "secp521r1") + { + d_PublicKeyType = std::string("ECDSA P-521"); + } + } + + pubkey_copy(pubkey, &d_PublicKey); + EC_KEY_free(pubkey); +#endif + BIO_free(bio); + if (d_PublicKey == nullptr) + { + std::cerr << "OpenSSL: error reading the OSNMA Public Key from file " << pemFilePath << ". Aborting import" << std::endl; + LOG(WARNING) << "OpenSSL: error reading the OSNMA Public Key from file " << pemFilePath << ". Aborting import."; + return; + } +#endif + std::cout << "OSNMA Public key successfully read from file " << pemFilePath << std::endl; + LOG(INFO) << "OSNMA Public key successfully read from file " << pemFilePath; +} + + +bool Gnss_Crypto::readPublicKeyFromCRT(const std::string& crtFilePath) +{ + d_PublicKeyType = std::string("Unknown"); + // Open the .crt file + std::ifstream crtFile(crtFilePath, std::ios::binary); + if (!crtFile.is_open()) + { + // CRT file not found + // If it was not the default, maybe it is a configuration error + if (crtFilePath != CRTFILE_DEFAULT && !crtFilePath.empty()) + { + std::cerr << "File " << crtFilePath << " not found" << std::endl; + } + return false; + } + + const std::vector buffer((std::istreambuf_iterator(crtFile)), std::istreambuf_iterator()); +#if USE_GNUTLS_FALLBACK + const gnutls_datum_t buffer_datum = {const_cast(buffer.data()), static_cast(buffer.size())}; + gnutls_x509_crt_t cert; + gnutls_x509_crt_init(&cert); + int ret = gnutls_x509_crt_import(cert, &buffer_datum, GNUTLS_X509_FMT_PEM); + if (ret < 0) + { + LOG(WARNING) << "GnuTLS: Failed to import certificate: " << gnutls_strerror(ret); + gnutls_x509_crt_deinit(cert); + return false; + } + + gnutls_pubkey_t pubkey; + gnutls_pubkey_init(&pubkey); + + ret = gnutls_pubkey_import_x509(pubkey, cert, 0); + if (ret < 0) + { + LOG(WARNING) << "GnuTLS: Failed to import public key: " << gnutls_strerror(ret); + gnutls_pubkey_deinit(pubkey); + gnutls_x509_crt_deinit(cert); + return false; + } + + // store the key type - needed for the Kroot in case no DSM-PKR available + gnutls_pk_algorithm_t pk_algorithm; + unsigned int bits; + + ret = gnutls_pubkey_get_pk_algorithm(pubkey, &bits); + if (ret < 0) + { + LOG(WARNING) << "GnuTLS: Failed to get public key algorithm: " << gnutls_strerror(ret); + gnutls_pubkey_deinit(pubkey); + gnutls_x509_crt_deinit(cert); + return false; + } + + pk_algorithm = static_cast(ret); + if (pk_algorithm == GNUTLS_PK_ECC) + { + gnutls_ecc_curve_t curve; + ret = gnutls_pubkey_export_ecc_raw(pubkey, &curve, nullptr, nullptr); + if (ret < 0) + { + LOG(WARNING) << "GnuTLS: Failed to get EC curve: " << gnutls_strerror(ret); + gnutls_pubkey_deinit(pubkey); + gnutls_x509_crt_deinit(cert); + return false; + } + + if (curve == GNUTLS_ECC_CURVE_SECP256R1) + { + d_PublicKeyType = std::string("ECDSA P-256"); + } + else if (curve == GNUTLS_ECC_CURVE_SECP521R1) + { + d_PublicKeyType = std::string("ECDSA P-521"); + } + else + { + LOG(WARNING) << "GnuTLS: Trying to read unknown EC curve"; + gnutls_x509_crt_deinit(cert); + gnutls_pubkey_deinit(pubkey); + return false; + } + } + else + { + LOG(WARNING) << "GnuTLS: Trying to read unknown key type"; + gnutls_x509_crt_deinit(cert); + gnutls_pubkey_deinit(pubkey); + return false; + } + + pubkey_copy(pubkey, &d_PublicKey); + gnutls_x509_crt_deinit(cert); + gnutls_pubkey_deinit(pubkey); +#else // OpenSSL + // Read certificate +#if !(USE_OPENSSL_3 || USE_OPENSSL_111) + BIO* bio = BIO_new_mem_buf(const_cast(buffer.data()), buffer.size()); +#else + BIO* bio = BIO_new_mem_buf(buffer.data(), buffer.size()); +#endif + if (!bio) + { + LOG(WARNING) << "OpenSSL: Unable to create BIO for file: " << crtFilePath; + return false; + } + X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + if (!cert) + { + LOG(WARNING) << "OpenSSL: Unable to read certificate from file: " << crtFilePath; + BIO_free(bio); + return false; + } + + // Read the public key from the certificate + EVP_PKEY* pubkey = X509_get_pubkey(cert); + if (!pubkey) + { + LOG(WARNING) << "OpenSSL: Failed to extract the public key"; + X509_free(cert); + BIO_free(bio); + return false; + } +#if USE_OPENSSL_3 + // store the key type - needed for the Kroot in case no DSM-PKR available + // Get the key type + int key_type = EVP_PKEY_base_id(pubkey); + if (key_type == EVP_PKEY_EC) + { + // It's an EC key, now we need to determine the curve + char curve_name[256]; + size_t curve_name_len = sizeof(curve_name); + + if (EVP_PKEY_get_utf8_string_param(pubkey, OSSL_PKEY_PARAM_GROUP_NAME, curve_name, curve_name_len, &curve_name_len) == 1) + { + if (strcmp(curve_name, "prime256v1") == 0 || strcmp(curve_name, "P-256") == 0) + { + d_PublicKeyType = std::string("ECDSA P-256"); + } + else if (strcmp(curve_name, "secp521r1") == 0 || strcmp(curve_name, "P-521") == 0) + { + d_PublicKeyType = std::string("ECDSA P-521"); + } + else + { + LOG(WARNING) << "OpenSSL: Trying to read an unknown EC curve"; + X509_free(cert); + BIO_free(bio); + return false; + } + } + else + { + LOG(WARNING) << "OpenSSL: Trying to read an unknown EC curve"; + X509_free(cert); + BIO_free(bio); + return false; + } + } + else + { + LOG(WARNING) << "OpenSSL: Trying to read an unknown key type"; + X509_free(cert); + BIO_free(bio); + return false; + } + pubkey_copy(pubkey, &d_PublicKey); + EVP_PKEY_free(pubkey); +#else // OpenSSL 1.x +#if USE_OPENSSL_111 + // store the key type - needed for the Kroot in case no DSM-PKR available + const auto ec_key = EVP_PKEY_get0_EC_KEY(pubkey); + const EC_GROUP* group = EC_KEY_get0_group(ec_key); + if (!group) + { + X509_free(cert); + EC_KEY_free(ec_key); + BIO_free(bio); + return false; + } + const int nid = EC_GROUP_get_curve_name(group); + if (nid == NID_X9_62_prime256v1) + { + d_PublicKeyType = std::string("ECDSA P-256"); + } + else if (nid == NID_secp521r1) + { + d_PublicKeyType = std::string("ECDSA P-521"); + } + EC_KEY_free(ec_key); +#else + EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(pubkey); + if (!ec_key) + { + X509_free(cert); + BIO_free(bio); + return false; + } + + // Get the EC_GROUP from the EC_KEY + const EC_GROUP* group = EC_KEY_get0_group(ec_key); + if (!group) + { + X509_free(cert); + EC_KEY_free(ec_key); + BIO_free(bio); + return false; + } + const int nid = EC_GROUP_get_curve_name(group); + if (nid == 0) + { + BIGNUM* p = BN_new(); + if (EC_GROUP_get_curve_GFp(group, p, nullptr, nullptr, nullptr) == 1) + { + char* p_str = BN_bn2hex(p); + const std::string pcstr(p_str); + if (pcstr.size() == 64) + { + d_PublicKeyType = std::string("ECDSA P-256"); + } + else if (pcstr.size() == 132) + { + d_PublicKeyType = std::string("ECDSA P-521"); + } + OPENSSL_free(p_str); + } + BN_free(p); + } + else + { + const char* curve_name = OBJ_nid2sn(nid); + const std::string curve_str(curve_name); + if (curve_str == "prime256v1") + { + d_PublicKeyType = std::string("ECDSA P-256"); + } + else if (curve_str == "secp521r1") + { + d_PublicKeyType = std::string("ECDSA P-521"); + } + } + EC_KEY_free(ec_key); +#endif + EC_KEY* ec_pubkey = EVP_PKEY_get1_EC_KEY(pubkey); + EVP_PKEY_free(pubkey); + if (!ec_pubkey) + { + LOG(WARNING) << "OpenSSL: Failed to extract the public key"; + X509_free(cert); + return false; + } + pubkey_copy(ec_pubkey, &d_PublicKey); + EC_KEY_free(ec_pubkey); +#endif + 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; + return true; +} + + +bool Gnss_Crypto::convert_raw_to_der_ecdsa(const std::vector& raw_signature, std::vector& der_signature) const +{ + if (raw_signature.size() % 2 != 0) + { + LOG(WARNING) << "Invalid raw ECDSA signature size"; + return false; + } + + size_t half_size = raw_signature.size() / 2; + std::vector raw_r(raw_signature.begin(), raw_signature.begin() + half_size); + std::vector raw_s(raw_signature.begin() + half_size, raw_signature.end()); + + auto encode_asn1_integer = [](const std::vector& value) -> std::vector { + std::vector result; + result.push_back(0x02); // INTEGER tag + + if (value[0] & 0x80) + { + result.push_back(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.insert(result.end(), value.begin(), value.end()); + return result; + }; + + std::vector der_r = encode_asn1_integer(raw_r); + std::vector der_s = encode_asn1_integer(raw_s); + + size_t total_length = der_r.size() + der_s.size(); + der_signature.push_back(0x30); // SEQUENCE tag + if (total_length > 127) + { + der_signature.push_back(0x81); // Long form length + } + der_signature.push_back(static_cast(total_length)); + + der_signature.insert(der_signature.end(), der_r.begin(), der_r.end()); + der_signature.insert(der_signature.end(), der_s.begin(), der_s.end()); + + return true; +} + + +std::vector Gnss_Crypto::convert_from_hex_str(const std::string& input) const +{ + std::vector 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(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) +{ + gnutls_datum_t key_datum; + + // Export the public key from src to memory +#if HAVE_GNUTLS_PUBKEY_EXPORT2 + int ret = gnutls_pubkey_export2(src, GNUTLS_X509_FMT_PEM, &key_datum); +#else + size_t output_stata_size; + int ret = gnutls_pubkey_export(src, GNUTLS_X509_FMT_PEM, &key_datum, &output_stata_size); +#endif + if (ret < 0) + { + gnutls_free(key_datum.data); + return false; + } + + // Initialize dest + ret = gnutls_pubkey_init(dest); + if (ret < 0) + { + gnutls_free(key_datum.data); + return false; + } + + // Import the public key data from key_datum to dest + ret = gnutls_pubkey_import(*dest, &key_datum, GNUTLS_X509_FMT_PEM); + gnutls_free(key_datum.data); + + if (ret < 0) + { + gnutls_pubkey_deinit(*dest); + return false; + } + + return true; +} + + +bool tonelli_shanks(mpz_t& res, const mpz_t& n, const mpz_t& p) +{ + if (mpz_legendre(n, p) != 1) + { + return false; + } + mpz_t q; + mpz_t s; + mpz_t z; + mpz_t m; + mpz_t c; + mpz_t t; + mpz_t r; + mpz_t b; + mpz_t two; + mpz_t p_minus_one; + mpz_inits(q, s, z, m, c, t, r, b, two, p_minus_one, nullptr); + + mpz_set_ui(two, 2); + + mpz_sub_ui(q, p, 1); + mpz_set_ui(s, 0); + while (mpz_even_p(q)) + { + mpz_add_ui(s, s, 1); + mpz_divexact_ui(q, q, 2); + } + + mpz_set_ui(z, 2); + while (mpz_legendre(z, p) != -1) + { + mpz_add_ui(z, z, 1); + } + + mpz_powm(c, z, q, p); + + mpz_add_ui(p_minus_one, p, 1); + mpz_divexact_ui(p_minus_one, p_minus_one, 4); + mpz_powm(r, n, p_minus_one, p); + mpz_powm(t, n, q, p); + mpz_set(m, s); + + while (mpz_cmp_ui(t, 1) != 0) + { + mpz_set(b, t); + unsigned int i; + for (i = 0; mpz_cmp_ui(b, 1) != 0; i++) + { + mpz_powm_ui(b, b, 2, p); + } + if (i == mpz_get_ui(m)) + { + mpz_clears(q, s, z, m, c, t, r, b, two, p_minus_one, nullptr); + return false; + } + + mpz_powm_ui(b, two, mpz_get_ui(m) - i - 1, p); + mpz_powm(c, c, b, p); + mpz_mul(r, r, c); + mpz_mod(r, r, p); + mpz_powm_ui(c, c, 2, p); + mpz_mul(t, t, c); + mpz_mod(t, t, p); + mpz_set_ui(m, mpz_get_ui(m) - i - 1); + } + + mpz_set(res, r); + mpz_clears(q, s, z, m, c, t, r, b, two, p_minus_one, nullptr); + return true; +} + + +void Gnss_Crypto::decompress_public_key_secp256r1(const std::vector& compressed_key, std::vector& x, std::vector& y) const +{ + // Define curve parameters for secp256r1 + const char* p_str = "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF"; + const char* a_str = "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC"; + const char* b_str = "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B"; + + mpz_t p; + mpz_t a; + mpz_t b; + mpz_t x_coord; + mpz_t y_coord; + mpz_t y_squared; + mpz_t tmp; + mpz_inits(p, a, b, x_coord, y_coord, y_squared, tmp, nullptr); + + // Initialize curve parameters + mpz_set_str(p, p_str, 16); + mpz_set_str(a, a_str, 16); + mpz_set_str(b, b_str, 16); + + // Set x coordinate + mpz_import(x_coord, 32, 1, 1, 1, 0, &compressed_key[1]); + + // Calculate y^2 = x^3 + ax + b (mod p) + mpz_powm_ui(y_squared, x_coord, 3, p); // y_squared = x^3 + mpz_mul(tmp, a, x_coord); // tmp = ax + mpz_add(y_squared, y_squared, tmp); // y_squared = x^3 + ax + mpz_add(y_squared, y_squared, b); // y_squared = x^3 + ax + b + mpz_mod(y_squared, y_squared, p); // y_squared = (x^3 + ax + b) % p + + // Calculate the square root of y_squared to get y + if (!tonelli_shanks(y_coord, y_squared, p)) + { + mpz_clears(p, a, b, x_coord, y_coord, y_squared, tmp, nullptr); + LOG(WARNING) << "GnuTLS: Failed to decompress public key: No valid y coordinate"; + return; + } + + // Select the correct y coordinate based on the parity bit + if ((compressed_key[0] & 1) != (mpz_tstbit(y_coord, 0) & 1)) + { + 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); + + mpz_clears(p, a, b, x_coord, y_coord, y_squared, tmp, nullptr); +} + + +void Gnss_Crypto::decompress_public_key_secp521r1(const std::vector& compressed_key, std::vector& x, std::vector& y) const +{ + // Define curve parameters for secp521r1 + const char* p_str = "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + const char* a_str = "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC"; + const char* b_str = "0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00"; + + mpz_t p; + mpz_t a; + mpz_t b; + mpz_t x_coord; + mpz_t y_coord; + mpz_t y_squared; + mpz_t tmp; + mpz_inits(p, a, b, x_coord, y_coord, y_squared, tmp, nullptr); + + // Initialize curve parameters + mpz_set_str(p, p_str, 16); + mpz_set_str(a, a_str, 16); + mpz_set_str(b, b_str, 16); + + // Set x coordinate + mpz_import(x_coord, 66, 1, 1, 1, 0, &compressed_key[1]); + + // Calculate y^2 = x^3 + ax + b (mod p) + mpz_powm_ui(y_squared, x_coord, 3, p); // y_squared = x^3 + mpz_mul(tmp, a, x_coord); // tmp = ax + mpz_add(y_squared, y_squared, tmp); // y_squared = x^3 + ax + mpz_add(y_squared, y_squared, b); // y_squared = x^3 + ax + b + mpz_mod(y_squared, y_squared, p); // y_squared = (x^3 + ax + b) % p + + // Calculate the square root of y_squared to get y + if (!tonelli_shanks(y_coord, y_squared, p)) + { + mpz_clears(p, a, b, x_coord, y_coord, y_squared, tmp, nullptr); + LOG(WARNING) << "GnuTLS: Failed to decompress public key: No valid y coordinate"; + return; + } + + // Select the correct y coordinate based on the parity bit + if ((compressed_key[0] & 1) != (mpz_tstbit(y_coord, 0) & 1)) + { + 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); + + mpz_clears(p, a, b, x_coord, y_coord, y_squared, tmp, nullptr); +} +#else // OpenSSL +#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) + { + return false; + } + + // Export the public key from src into the memory buffer in PEM format + if (!PEM_write_bio_PUBKEY(mem_bio, src)) + { + 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) + { + BIO_free(mem_bio); + return false; + } + + // 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); + + return true; +} +#else // OpenSSL 1.x +bool Gnss_Crypto::pubkey_copy(EC_KEY* src, EC_KEY** dest) +{ + // Open a memory buffer + BIO* mem_bio = BIO_new(BIO_s_mem()); + if (mem_bio == nullptr) + { + return false; + } + + // Export the public key from src into the memory buffer in PEM format + if (!PEM_write_bio_EC_PUBKEY(mem_bio, src)) + { + BIO_free(mem_bio); + return false; + } + + // Read the data from the memory buffer + char* bio_data; + long 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) + { + BIO_free(mem_bio); + return false; + } + + // Read the public key from the new memory buffer + *dest = PEM_read_bio_EC_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); + + return true; +} +#endif // OpenSSL +#endif diff --git a/src/core/libs/gnss_crypto.h b/src/core/libs/gnss_crypto.h new file mode 100644 index 000000000..9fb16f96e --- /dev/null +++ b/src/core/libs/gnss_crypto.h @@ -0,0 +1,103 @@ +/*! + * \file gnss_crypto.h + * \brief Class for computing cryptographic functions + * \author Carles Fernandez, 2023-2024. cfernandez(at)cttc.es + * Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de + * + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2024 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_GNSS_CRYPTO_H +#define GNSS_SDR_GNSS_CRYPTO_H + +#include +#include +#include +#if USE_GNUTLS_FALLBACK +#include +#include +#else // OpenSSL +#include +#endif + +/** \addtogroup Core + * \{ */ +/** \addtogroup Core_Receiver_Library + * \{ */ + +/*! + * \brief Class implementing cryptographic functions + * for Navigation Message Authentication + */ +class Gnss_Crypto +{ +public: + Gnss_Crypto(); //!< Default constructor + + /*! + * Constructor with a .crt or .pem file for the ECDSA Public Key + * and a XML file for the Merkle Tree root. + * Files can be downloaded by registering at https://www.gsc-europa.eu/ + */ + Gnss_Crypto(const std::string& certFilePath, const std::string& merkleTreePath); + ~Gnss_Crypto(); //!< Default destructor + + bool have_public_key() const; //!< Returns true if the ECDSA Public Key is already loaded + + /*! + * Stores the ECDSA Public Key in a .pem file, which is read in a following run if the .crt file is not found + */ + bool store_public_key(const std::string& pubKeyFilePath) const; + + bool verify_signature_ecdsa_p256(const std::vector& message, const std::vector& signature) const; //!< Verify ECDSA-P256 signature (message in plain hex, signature in raw format) + bool verify_signature_ecdsa_p521(const std::vector& message, const std::vector& signature) const; //!< Verify ECDSA-P521 signature (message in plain hex, signature in raw format) + + std::vector compute_SHA_256(const std::vector& input) const; //!< Computes SHA-256 hash + std::vector compute_SHA3_256(const std::vector& input) const; //!< Computes SHA3-256 hash + std::vector compute_HMAC_SHA_256(const std::vector& key, const std::vector& input) const; //!< Computes HMAC-SHA-256 message authentication code + std::vector compute_CMAC_AES(const std::vector& key, const std::vector& input) const; //!< Computes CMAC-AES message authentication code + + std::vector 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) + + void set_public_key(const std::vector& 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& 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 + +private: + void readPublicKeyFromPEM(const std::string& pemFilePath); + bool readPublicKeyFromCRT(const std::string& crtFilePath); + bool convert_raw_to_der_ecdsa(const std::vector& raw_signature, std::vector& der_signature) const; + std::vector 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& compressed_key, std::vector& x, std::vector& y) const; + void decompress_public_key_secp521r1(const std::vector& compressed_key, std::vector& x, std::vector& y) const; + bool pubkey_copy(gnutls_pubkey_t src, gnutls_pubkey_t* dest); + gnutls_pubkey_t d_PublicKey{}; +#else // OpenSSL +#if USE_OPENSSL_3 + bool pubkey_copy(EVP_PKEY* src, EVP_PKEY** dest); + EVP_PKEY* d_PublicKey{}; +#else // OpenSSL 1.x + bool pubkey_copy(EC_KEY* src, EC_KEY** dest); + EC_KEY* d_PublicKey = nullptr; +#endif +#endif + std::vector d_x_4_0; + std::string d_PublicKeyType; +}; + +/** \} */ +/** \} */ + +#endif // GNSS_SDR_GNSS_CRYPTO_H \ No newline at end of file diff --git a/src/core/libs/osnma_helper.cc b/src/core/libs/osnma_helper.cc new file mode 100644 index 000000000..23b67707a --- /dev/null +++ b/src/core/libs/osnma_helper.cc @@ -0,0 +1,175 @@ +/*! + * \file osnma_helper.h + * \brief Class for auxiliary osnma functions + * \author Carles Fernandez-Prades, 2024 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-2023 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "osnma_helper.h" +#include +#include +#include +#include +#include + + +Osnma_Helper::Osnma_Helper() +{ + GST_START_EPOCH.tm_mday = 22; + GST_START_EPOCH.tm_mon = 7; // August (0-based) + GST_START_EPOCH.tm_year = 1999 - 1900; +} + + +uint32_t Osnma_Helper::compute_gst(uint32_t WN, uint32_t TOW) const +{ + return (WN & 0x00000FFF) << 20 | (TOW & 0x000FFFFF); +} + + +uint32_t Osnma_Helper::compute_gst(tm& input) +{ + auto epoch_time_point = std::chrono::system_clock::from_time_t(mktime(&GST_START_EPOCH)); + auto input_time_point = std::chrono::system_clock::from_time_t(mktime(&input)); + + // Get the duration from epoch in seconds + auto duration_sec = std::chrono::duration_cast(input_time_point - epoch_time_point); + + // Calculate the week number (WN) and time of week (TOW) + 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; + return compute_gst(week_number, time_of_week); +} + + +uint32_t Osnma_Helper::compute_gst_now() +{ + 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::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; + return compute_gst(week_number, time_of_week); +} + + +std::vector Osnma_Helper::gst_to_uint8(uint32_t GST) const +{ + std::vector res; + + res.push_back(static_cast((GST & 0xFF000000) >> 24)); + res.push_back(static_cast((GST & 0x00FF0000) >> 16)); + res.push_back(static_cast((GST & 0x0000FF00) >> 8)); + res.push_back(static_cast(GST & 0x000000FF)); + return res; +} + + +/** + * @brief Convert a binary string to a vector of bytes. + * + * This function takes a binary string and converts it into a vector of uint8_t bytes. + * The binary string is padded with zeros if necessary to ensure that the total number + * of bits is a multiple of a byte. + * + * @param binaryString The binary string to be converted. + * @return The vector of bytes converted from the binary string. + */ +std::vector Osnma_Helper::bytes(const std::string& binaryString) const +{ + std::vector bytes; + + // Determine the size of the padding needed. + size_t padding_size = binaryString.size() % 8; + + std::string padded_binary = binaryString; + + if (padding_size != 0) + { + padding_size = 8 - padding_size; // Compute padding size + padded_binary.append(padding_size, '0'); // Append zeros to the binary string + } + + for (size_t i = 0; i < padded_binary.size(); i += 8) + { + uint8_t byte = std::bitset<8>(padded_binary.substr(i, 8)).to_ulong(); + bytes.push_back(byte); + } + + return bytes; +} + + +std::string Osnma_Helper::verification_status_str(int status) const +{ + switch (status) + { + case 0: + return "SUCCESS"; + case 1: + return "FAIL"; + case 2: + return "UNVERIFIED"; + default: + return "UNKNOWN"; + } +} + + +std::string Osnma_Helper::convert_to_hex_string(const std::vector& vector) const +{ + std::stringstream ss; + ss << std::hex << std::setfill('0'); + for (auto byte : vector) + { + ss << std::setw(2) << static_cast(byte); + } + return ss.str(); +} + + +std::vector Osnma_Helper::convert_from_hex_string(const std::string& hex_string) const +{ + std::vector result; + + std::string adjusted_hex_string = hex_string; + if (hex_string.length() % 2 != 0) + { + adjusted_hex_string = "0" + hex_string; + } + + for (std::size_t i = 0; i < adjusted_hex_string.length(); i += 2) + { + std::string byte_string = adjusted_hex_string.substr(i, 2); + auto byte = static_cast(std::stoul(byte_string, nullptr, 16)); + result.push_back(byte); + } + + return result; +} + + +uint32_t Osnma_Helper::get_WN(uint32_t GST) const +{ + return (GST & 0xFFF00000) >> 20; +} + + +uint32_t Osnma_Helper::get_TOW(uint32_t GST) const +{ + return GST & 0x000FFFFF; +} diff --git a/src/core/libs/osnma_helper.h b/src/core/libs/osnma_helper.h new file mode 100644 index 000000000..a7b2cc332 --- /dev/null +++ b/src/core/libs/osnma_helper.h @@ -0,0 +1,51 @@ +/*! + * \file osnma_helper.h + * \brief Class for auxiliary osnma functions + * \author Carles Fernandez-Prades, 2024 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-2023 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_OSNMA_HELPER_H +#define GNSS_SDR_OSNMA_HELPER_H + + +#include +#include +#include +#include + +/** \addtogroup Core + * \{ */ +/** \addtogroup Core_Receiver_Library + * \{ */ + +class Osnma_Helper +{ +public: + Osnma_Helper(); + ~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 get_WN(uint32_t GST) const; + uint32_t get_TOW(uint32_t GST) const; + std::vector gst_to_uint8(uint32_t GST) const; + std::vector bytes(const std::string& binaryString) const; + std::string verification_status_str(int status) const; + std::string convert_to_hex_string(const std::vector& vector) const; + std::vector convert_from_hex_string(const std::string& hex_string) const; // TODO remove similar function in gnss_crypto + std::tm GST_START_EPOCH{}; +}; + +/** \} */ +/** \} */ +#endif // GNSS_SDR_OSNMA_HELPER_H diff --git a/src/core/libs/osnma_msg_receiver.cc b/src/core/libs/osnma_msg_receiver.cc new file mode 100644 index 000000000..9e07f998c --- /dev/null +++ b/src/core/libs/osnma_msg_receiver.cc @@ -0,0 +1,2085 @@ +/*! + * \file osnma_msg_receiver.cc + * \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 + * Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2024 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#include "osnma_msg_receiver.h" +#include "Galileo_OSNMA.h" +#include "gnss_crypto.h" +#include "gnss_satellite.h" +#include "gnss_sdr_make_unique.h" // for std::make_unique in C++11 +#include "osnma_dsm_reader.h" // for OSNMA_DSM_Reader +#include "osnma_helper.h" // for Osnma_Helper +#include // for gr::io_signature::make +#include +#include +#include +#include // for std::ifstream and std::ofstream +#include // for std::setfill +#include // for std::hex, std::uppercase +#include +#include // for std::accumulate +#include // std::stringstream +#include +#include // for typeid +#include + + +#if USE_GLOG_AND_GFLAGS +#include // for DLOG +#else +#include +#endif + +#if HAS_GENERIC_LAMBDA +#else +#include +#endif + +#if PMT_USES_BOOST_ANY +#include +#include +namespace wht = boost; +#else +#include +namespace wht = std; +#endif + + +osnma_msg_receiver_sptr osnma_msg_receiver_make(const std::string& pemFilePath, const std::string& merkleFilePath, bool strict_mode) +{ + return osnma_msg_receiver_sptr(new osnma_msg_receiver(pemFilePath, merkleFilePath, strict_mode)); +} + + +osnma_msg_receiver::osnma_msg_receiver(const std::string& crtFilePath, + const std::string& merkleFilePath, + bool strict_mode) : gr::block("osnma_msg_receiver", + gr::io_signature::make(0, 0, 0), + gr::io_signature::make(0, 0, 0)), + d_strict_mode(strict_mode) +{ + d_dsm_reader = std::make_unique(); + d_crypto = std::make_unique(crtFilePath, merkleFilePath); + d_helper = std::make_unique(); + d_nav_data_manager = std::make_unique(); + + if (d_crypto->have_public_key()) + { // Hot start is enabled + LOG(WARNING) << "OSNMA Public Key available, trying to find DSM-KROOT saved"; + std::cout << "OSNMA Public Key available, trying to find DSM-KROOT saved" << std::endl; + d_public_key_verified = true; + + auto dsm_nmah = parse_dsm_kroot(); + if (!dsm_nmah.first.empty()) + { + LOG(WARNING) << "OSNMA DSM-KROOT and NMA Header successfully read from file " << KROOTFILE_DEFAULT; + std::cout << "OSNMA DSM-KROOT and NMA Header successfully read from file " << KROOTFILE_DEFAULT << std::endl; + d_flag_hot_start = true; + process_dsm_message(dsm_nmah.first, dsm_nmah.second); + LOG(WARNING) << "OSNMA DSM-KROOT available :: HOT START"; + std::cout << "OSNMA DSM-KROOT available :: HOT START" << std::endl; + } + else + { + LOG(WARNING) << "OSNMA DSM-KROOT not available :: WARM START"; + std::cout << "OSNMA DSM-KROOT not available :: WARM START" << std::endl; + } + } + + // register OSNMA input message port from telemetry blocks + this->message_port_register_in(pmt::mp("OSNMA_from_TLM")); + // register OSNMA output message port to PVT block + this->message_port_register_out(pmt::mp("OSNMA_to_PVT")); + + this->set_msg_handler(pmt::mp("OSNMA_from_TLM"), +#if HAS_GENERIC_LAMBDA + [this](auto&& PH1) { msg_handler_osnma(PH1); }); +#else +#if USE_BOOST_BIND_PLACEHOLDERS + boost::bind(&osnma_msg_receiver::msg_handler_osnma, this, boost::placeholders::_1)); +#else + boost::bind(&osnma_msg_receiver::msg_handler_osnma, this, _1)); +#endif +#endif + + if (d_strict_mode) + { + d_GST_Rx = d_helper->compute_gst_now(); + const auto WN = d_helper->get_WN(d_GST_Rx); + const auto TOW = d_helper->get_TOW(d_GST_Rx); + LOG(INFO) << "Galileo OSNMA: initial receiver time GST=[" << WN << " " << TOW << "]"; + std::cout << "Galileo OSNMA: initial receiver time GST=[" << WN << " " << TOW << "]" << std::endl; + } + else + { + LOG(WARNING) << "Galileo OSNMA: in non-strict mode, local system time is not checked."; + std::cout << "Galileo OSNMA: in non-strict mode, local system time is not checked." << std::endl; + } +} + + +void osnma_msg_receiver::msg_handler_osnma(const pmt::pmt_t& msg) +{ + // requires mutex with msg_handler_osnma function called by the scheduler + gr::thread::scoped_lock lock(d_setlock); + try + { + const size_t msg_type_hash_code = pmt::any_ref(msg).type().hash_code(); + if (msg_type_hash_code == typeid(std::shared_ptr).hash_code()) + { + const auto nma_msg = wht::any_cast>(pmt::any_ref(msg)); + const auto sat = Gnss_Satellite(std::string("Galileo"), nma_msg->PRN); // TODO remove if unneeded + + std::ostringstream output_message; + output_message << "Galileo OSNMA: data received starting at " + << "WN=" + << nma_msg->WN_sf0 + << ", TOW=" + << nma_msg->TOW_sf0 + << ", from satellite " + << sat; + LOG(INFO) << output_message.str(); + std::cout << output_message.str() << std::endl; + + // Receiver time update + d_GST_SIS = d_helper->compute_gst(nma_msg->WN_sf0, nma_msg->TOW_sf0); + if (d_last_verified_key_GST == 0) + { + d_last_received_GST = d_GST_SIS; + } + else if (d_GST_SIS > d_last_received_GST) + { + d_last_received_GST = d_GST_SIS; + } + if (d_strict_mode) + { + d_GST_Rx = d_helper->compute_gst_now(); + } + else + { + d_GST_Rx = d_last_received_GST; + } + LOG(INFO) << "Galileo OSNMA: Receiver Time GST=[" << d_helper->get_WN(d_GST_Rx) << " " << d_helper->get_TOW(d_GST_Rx) << "]"; + std::cout << "Galileo OSNMA: Receiver Time GST=[" << d_helper->get_WN(d_GST_Rx) << " " << d_helper->get_TOW(d_GST_Rx) << "]" << std::endl; + + // time constraint verification + std::time_t delta_T = std::abs(static_cast(d_GST_Rx - d_GST_SIS)); + if (delta_T <= d_T_L) + { + d_tags_to_verify = {0, 4, 12}; + LOG(INFO) << "Galileo OSNMA: time constraint OK (delta_T=" << delta_T << " s)"; + std::cout << "Galileo OSNMA: time constraint OK (delta_T=" << delta_T << " s)" << std::endl; + } + else if (delta_T > d_T_L && delta_T <= 10 * d_T_L) + { + d_tags_to_verify = {12}; + LOG(WARNING) << "Galileo OSNMA: time constraint allows only slow MACs to be verified"; + std::cout << "Galileo OSNMA: |local_t - GST_SIS| < T_L [ |" << static_cast(d_GST_Rx - d_GST_SIS) << " | < " << static_cast(d_T_L) << " ]" << std::endl; + LOG(WARNING) << "Galileo OSNMA: d_receiver_time: " << d_GST_Rx << " d_GST_SIS: " << d_GST_SIS; + LOG(WARNING) << "Galileo OSNMA: |local_t - GST_SIS| < T_L [ |" << static_cast(d_GST_Rx - d_GST_SIS) << " | < " << static_cast(d_T_L) << " ]"; + } + else + { + d_tags_to_verify = {}; + LOG(WARNING) << "Galileo OSNMA: time constraint violation"; + std::cerr << "Galileo OSNMA: time constraint violation" << std::endl; + std::cerr << "Galileo OSNMA: | local_t - GST_SIS | < T_L [ | " << static_cast(d_GST_Rx - d_GST_SIS) << " | < " << static_cast(d_T_L) << " ]" << std::endl; + LOG(WARNING) << "Galileo OSNMA: d_receiver_time: " << d_GST_Rx << " d_GST_SIS: " << d_GST_SIS; + LOG(WARNING) << "Galileo OSNMA: | local_t - GST_SIS | < T_L [ | " << static_cast(d_GST_Rx - d_GST_SIS) << " | < " << static_cast(d_T_L) << " ]"; + return; + } + + process_osnma_message(nma_msg); + } // OSNMA frame received + else if (msg_type_hash_code == typeid(std::shared_ptr>).hash_code()) // Navigation data bits for OSNMA received + { + const auto inav_data = wht::any_cast>>(pmt::any_ref(msg)); + uint32_t PRNd = std::get<0>(*inav_data); + std::string nav_data = std::get<1>(*inav_data); + uint32_t TOW = std::get<2>(*inav_data); + d_nav_data_manager->add_navigation_data(nav_data, PRNd, TOW); + } + else + { + LOG(WARNING) << "Galileo OSNMA: osnma_msg_receiver received an unknown object type!"; + } + } + catch (const wht::bad_any_cast& e) + { + LOG(WARNING) << "Galileo OSNMA: osnma_msg_receiver Bad any_cast: " << e.what(); + } + + // Send the resulting decoded NMA data (if available) to PVT + if (d_new_data) + { + auto osnma_data_ptr = std::make_shared(d_osnma_data); + this->message_port_pub(pmt::mp("OSNMA_to_PVT"), pmt::make_any(osnma_data_ptr)); + d_new_data = false; + // d_osnma_data = OSNMA_data(); + DLOG(INFO) << "Galileo OSNMA: NMA info sent to the PVT block through the OSNMA_to_PVT async message port"; + } +} + + +void osnma_msg_receiver::read_merkle_xml(const std::string& merklepath) +{ + d_crypto->read_merkle_xml(merklepath); +} + + +void osnma_msg_receiver::process_osnma_message(const std::shared_ptr& osnma_msg) +{ + if (d_flag_alert_message && (d_public_key_verified || d_kroot_verified)) + { + return; + } + read_nma_header(osnma_msg->hkroot[0]); + + // Check for corner cases: renewal, revocation, alert message + if (d_osnma_data.d_nma_header.nmas == 0 /* RES */) + { + LOG(WARNING) << "Galileo OSNMA: NMAS invalid (RES), skipping osnma message"; + return; + } + // TODO - trusting the NMAS and CPKS shall be done upon PKR verification or Tag verification. + // It's ok to activate the flags, but the final decision should happen after verifying it. + // For OAM is solved, but NPK and PKREV I think not yet + if (d_osnma_data.d_nma_header.nmas == 2 /* OP */ && d_osnma_data.d_nma_header.cpks == 4 /* NPK */ && d_GST_PKR_PKREV_start == 0) + { + d_flag_PK_renewal = true; + d_GST_PKR_PKREV_start = d_helper->compute_gst(osnma_msg->WN_sf0, osnma_msg->TOW_sf0); + LOG(INFO) << "Galileo OSNMA: Public Key Renewal :: Start at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]"; + std::cout << "Galileo OSNMA: Public Key Renewal :: Start at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]" << std::endl; + } + if (d_flag_PK_renewal && d_osnma_data.d_nma_header.nmas == 2 /* OP */ && d_osnma_data.d_nma_header.cpks == 1 /* Nominal */) + { + d_flag_PK_renewal = false; + uint32_t final_GST = d_helper->compute_gst(osnma_msg->WN_sf0, osnma_msg->TOW_sf0); + double duration_hours = (final_GST - d_GST_PKR_PKREV_start) / 3600.0; + LOG(INFO) << "Galileo OSNMA: Public Key Renewal :: Finished at GST=" << duration_hours << ", Duration=" << duration_hours << " h"; + std::cout << "Galileo OSNMA: Public Key Renewal :: Finished at GST=" << duration_hours << ", Duration=" << duration_hours << " h" << std::endl; + } + + if (d_osnma_data.d_nma_header.nmas == 3 /* DU */ && d_osnma_data.d_nma_header.cpks == 5 /* PKREV */ && d_GST_PKR_PKREV_start == 0) + { + d_flag_PK_revocation = true; + d_number_of_blocks[d_osnma_data.d_dsm_header.dsm_id] = 0; + d_public_key_verified = false; + d_kroot_verified = false; + d_tesla_key_verified = false; + d_GST_PKR_PKREV_start = d_helper->compute_gst(osnma_msg->WN_sf0, osnma_msg->TOW_sf0); + LOG(INFO) << "Galileo OSNMA: Public Key Revocation :: Start at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]"; + std::cout << "Galileo OSNMA: Public Key Revocation :: Start at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]" << std::endl; + } + if (d_flag_PK_revocation && d_osnma_data.d_nma_header.nmas == 2 /* OP */ && d_osnma_data.d_nma_header.cpks == 1 /* Nominal */) + { + // step 2 , start using new chain + d_flag_PK_revocation = false; + uint32_t final_GST = d_helper->compute_gst(osnma_msg->WN_sf0, osnma_msg->TOW_sf0); + double duration_hours = (final_GST - d_GST_PKR_PKREV_start) / 3600.0; + LOG(INFO) << "Galileo OSNMA: Public Key Revocation :: Finished at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]" + << ", Duration=" << duration_hours << "h"; + std::cout << "Galileo OSNMA: Public Key Revocation :: Finished at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]" + << ", Duration=" << duration_hours << "h" << std::endl; + } + + if (d_osnma_data.d_nma_header.nmas == 3 /* DU */ && d_osnma_data.d_nma_header.cpks == 7 /* AM */ && d_GST_PKR_AM_start == 0) + { + d_flag_alert_message = true; + d_GST_PKR_AM_start = d_helper->compute_gst(osnma_msg->WN_sf0, osnma_msg->TOW_sf0); + d_public_key_verified = false; + d_kroot_verified = false; + LOG(INFO) << "Galileo OSNMA: Alert message :: Start at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]"; + std::cout << "Galileo OSNMA: Alert message :: Start at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]" << std::endl; + } + + if (d_osnma_data.d_nma_header.nmas == 2 /* OP */ && d_osnma_data.d_nma_header.cpks == 2 /* EOC */ && d_GST_chain_renewal_start == 0) + { + d_flag_chain_renewal = true; + d_GST_chain_renewal_start = d_helper->compute_gst(osnma_msg->WN_sf0, osnma_msg->TOW_sf0); + LOG(INFO) << "Galileo OSNMA: Chain renewal :: Start at at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]"; + std::cout << "Galileo OSNMA: Chain renewal :: Start at at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]" << std::endl; + } + if (d_flag_chain_renewal && d_osnma_data.d_nma_header.nmas == 2 /* OP */ && d_osnma_data.d_nma_header.cpks == 1 /* Nominal */) + { + // Step 2, start using the new kroot + d_flag_chain_renewal = false; + uint32_t final_GST = d_helper->compute_gst(osnma_msg->WN_sf0, osnma_msg->TOW_sf0); + double duration_hours = (final_GST - d_GST_chain_renewal_start) / 3600.0; + LOG(INFO) << "Galileo OSNMA: Chain renewal :: Finished at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]" + << ", Duration=" << duration_hours << "h"; + std::cout << "Galileo OSNMA: Chain renewal :: Finished at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]" + << ", Duration=" << duration_hours << "h" << std::endl; + d_osnma_data.d_dsm_kroot_message = d_osnma_data.d_dsm_kroot_new_message; // set new kroot as the one to use from now on + d_tesla_key_verified = false; // force the verification up to the Kroot due to chain change + } + + if (d_osnma_data.d_nma_header.nmas == 3 /* DU */ && d_osnma_data.d_nma_header.cpks == 3 /* CREV */ && d_GST_chain_revocation_start == 0) + { + d_flag_chain_revocation = true; + d_number_of_blocks[d_osnma_data.d_dsm_header.dsm_id] = 0; // delete blocks received up to now, new chain must be received. + // d_public_key_verified = false; + d_kroot_verified = false; + d_tesla_key_verified = false; + d_GST_chain_revocation_start = d_helper->compute_gst(osnma_msg->WN_sf0, osnma_msg->TOW_sf0); + LOG(INFO) << "Galileo OSNMA: Chain revocation :: Start at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]"; + std::cout << "Galileo OSNMA: Chain revocation :: Start at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]" << std::endl; + } + if (d_flag_chain_revocation && d_osnma_data.d_nma_header.nmas == 2 /* OP */ && d_osnma_data.d_nma_header.cpks == 1 /* Nominal */) + { + d_flag_chain_revocation = false; + uint32_t final_GST = d_helper->compute_gst(osnma_msg->WN_sf0, osnma_msg->TOW_sf0); + double duration_hours = (final_GST - d_GST_chain_revocation_start) / 3600.0; + LOG(INFO) << "Galileo OSNMA: Chain revocation :: Finished at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]" + << ", Duration=" << duration_hours << "h"; + std::cout << "Galileo OSNMA: Chain revocation :: Finished at GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]" + << ", Duration=" << duration_hours << "h" << std::endl; + } + + read_dsm_header(osnma_msg->hkroot[1]); + read_dsm_block(osnma_msg); + process_dsm_block(osnma_msg); // will process dsm block if received a complete one, then will call mack processing upon re-setting the dsm block to 0 + if (d_osnma_data.d_dsm_kroot_message.towh_k != 0) + { + d_GST_0 = d_helper->compute_gst(d_osnma_data.d_dsm_kroot_message.wn_k, d_osnma_data.d_dsm_kroot_message.towh_k * 3600); + d_GST_Sf = d_GST_0 + 30 * std::floor((d_GST_SIS - d_GST_0) / 30); // Eq. 3 R.G. + } + read_and_process_mack_block(osnma_msg); // only process them if at least 3 available. +} + + +/** + * @brief Reads the NMA header from the given input and stores the values in the d_osnma_data structure. + * + * The NMA header consists of several fields: d_nma_header.nmas, d_nma_header.cid, d_nma_header.cpks, and d_nma_header.reserved. + * Each field is retrieved using the corresponding getter functions from the d_dsm_reader auxiliary object. + * + * @param nma_header The input containing the NMA header. + */ +void osnma_msg_receiver::read_nma_header(uint8_t nma_header) +{ + d_osnma_data.d_nma_header.nmas = d_dsm_reader->get_nmas(nma_header); + d_osnma_data.d_nma_header.cid = d_dsm_reader->get_cid(nma_header); + d_osnma_data.d_nma_header.cpks = d_dsm_reader->get_cpks(nma_header); + d_osnma_data.d_nma_header.reserved = d_dsm_reader->get_nma_header_reserved(nma_header); +} + + +/** + * @brief Read the DSM header from the given dsm_header and populate the d_osnma_data structure. + * + * @param dsm_header The DSM header. + */ +void osnma_msg_receiver::read_dsm_header(uint8_t dsm_header) +{ + d_osnma_data.d_dsm_header.dsm_id = d_dsm_reader->get_dsm_id(dsm_header); + d_osnma_data.d_dsm_header.dsm_block_id = d_dsm_reader->get_dsm_block_id(dsm_header); // BID + LOG(INFO) << "Galileo OSNMA: Received block DSM_BID=" << static_cast(d_osnma_data.d_dsm_header.dsm_block_id) + << " with DSM_ID " << static_cast(d_osnma_data.d_dsm_header.dsm_id); +} + +/* + * accumulates dsm messages + * */ +void osnma_msg_receiver::read_dsm_block(const std::shared_ptr& osnma_msg) +{ + // Fill d_dsm_message. dsm_block_id provides the offset within the dsm message. + size_t index = 0; + for (const auto* it = osnma_msg->hkroot.cbegin() + 2; it != osnma_msg->hkroot.cend(); ++it) + { + d_dsm_message[d_osnma_data.d_dsm_header.dsm_id][SIZE_DSM_BLOCKS_BYTES * d_osnma_data.d_dsm_header.dsm_block_id + index] = *it; + index++; + } + // First block indicates number of blocks in DSM message + if (d_osnma_data.d_dsm_header.dsm_block_id == 0) + { + uint8_t nb = d_dsm_reader->get_number_blocks_index(d_dsm_message[d_osnma_data.d_dsm_header.dsm_id][0]); + uint16_t number_of_blocks = 0; + if (d_osnma_data.d_dsm_header.dsm_id < 12) + { + // DSM-KROOT Table 7 + const auto it = OSNMA_TABLE_7.find(nb); + if (it != OSNMA_TABLE_7.cend()) + { + number_of_blocks = it->second.first; + } + } + else if (d_osnma_data.d_dsm_header.dsm_id >= 12 && d_osnma_data.d_dsm_header.dsm_id < 16) + { + // DSM-PKR Table 3 + const auto it = OSNMA_TABLE_3.find(nb); + if (it != OSNMA_TABLE_3.cend()) + { + number_of_blocks = it->second.first; + } + } + + d_number_of_blocks[d_osnma_data.d_dsm_header.dsm_id] = number_of_blocks; + LOG(INFO) << "Galileo OSNMA: number of blocks in this message: " << static_cast(number_of_blocks); + if (number_of_blocks == 0) + { + // Something is wrong, start over + LOG(WARNING) << "OSNMA: Wrong number of blocks, start over"; + d_dsm_message[d_osnma_data.d_dsm_header.dsm_id] = std::array{}; + d_dsm_id_received[d_osnma_data.d_dsm_header.dsm_id] = std::array{}; + } + } + // Annotate bid + d_dsm_id_received[d_osnma_data.d_dsm_header.dsm_id][d_osnma_data.d_dsm_header.dsm_block_id] = 1; + std::stringstream available_blocks; + available_blocks << "Galileo OSNMA: Available blocks for DSM_ID " << static_cast(d_osnma_data.d_dsm_header.dsm_id) << ": [ "; + if (d_number_of_blocks[d_osnma_data.d_dsm_header.dsm_id] == 0) // block 0 not received yet + { + for (auto id_received : d_dsm_id_received[d_osnma_data.d_dsm_header.dsm_id]) + { + if (id_received == 0) + { + available_blocks << "- "; + } + else + { + available_blocks << "X "; + } + } + } + else + { + for (uint16_t k = 0; k < d_number_of_blocks[d_osnma_data.d_dsm_header.dsm_id]; k++) + { + if (d_dsm_id_received[d_osnma_data.d_dsm_header.dsm_id][k] == 0) + { + available_blocks << "- "; + } + else + { + available_blocks << "X "; + } + } + } + available_blocks << "]"; + LOG(INFO) << available_blocks.str(); + std::cout << available_blocks.str() << std::endl; +} + +/** + * @brief Process DSM block of an OSNMA message. + * + * \details This function checks if all inner blocks of the DSM message are available and if so, calls process_dsm_message(). + * \post It creates a vector to hold the DSM message data, copies the data from the inner blocks into the vector, + * resets the inner block arrays to empty + * + * @param osnma_msg The OSNMA message. + */ +void osnma_msg_receiver::process_dsm_block(const std::shared_ptr& osnma_msg) +{ + // if all inner blocks available -> Process DSM message + if ((d_number_of_blocks[d_osnma_data.d_dsm_header.dsm_id] != 0) && + (d_number_of_blocks[d_osnma_data.d_dsm_header.dsm_id] == std::accumulate(d_dsm_id_received[d_osnma_data.d_dsm_header.dsm_id].cbegin(), d_dsm_id_received[d_osnma_data.d_dsm_header.dsm_id].cend(), 0))) + { + size_t len = std::size_t(d_number_of_blocks[d_osnma_data.d_dsm_header.dsm_id]) * SIZE_DSM_BLOCKS_BYTES; + std::vector dsm_msg(len, 0); + for (uint32_t i = 0; i < d_number_of_blocks[d_osnma_data.d_dsm_header.dsm_id]; i++) + { + for (size_t j = 0; j < SIZE_DSM_BLOCKS_BYTES; j++) + { + dsm_msg[i * SIZE_DSM_BLOCKS_BYTES + j] = d_dsm_message[d_osnma_data.d_dsm_header.dsm_id][i * SIZE_DSM_BLOCKS_BYTES + j]; + } + } + d_dsm_message[d_osnma_data.d_dsm_header.dsm_id] = std::array{}; + d_dsm_id_received[d_osnma_data.d_dsm_header.dsm_id] = std::array{}; + LOG(INFO) << "Galileo OSNMA: DSM message completed :: start processing, GST=[" << osnma_msg->WN_sf0 << " " << osnma_msg->TOW_sf0 << "]"; + process_dsm_message(dsm_msg, osnma_msg->hkroot[0]); + } +} + + +/* + * case DSM-Kroot: + * - computes the padding and compares with received message + * - if successful, tries to verify the digital signature + * case DSM-PKR: + * - calls verify_dsm_pkr to verify the public key + * */ +void osnma_msg_receiver::process_dsm_message(const std::vector& dsm_msg, const uint8_t& nma_header) +{ + // DSM-KROOT message + if ((d_osnma_data.d_dsm_header.dsm_id < 12 || d_flag_hot_start) && d_public_key_verified) + { + bool new_chain = (d_dsm_reader->get_cidkr(dsm_msg) != d_osnma_data.d_nma_header.cid) && d_flag_chain_renewal; + DSM_KROOT_message& applicable_kroot_msg = new_chain ? d_osnma_data.d_dsm_kroot_new_message : d_osnma_data.d_dsm_kroot_message; + + // Parse Kroot message + LOG(INFO) << "Galileo OSNMA: DSM-KROOT message received."; + applicable_kroot_msg.nb_dk = d_dsm_reader->get_number_blocks_index(dsm_msg[0]); + applicable_kroot_msg.pkid = d_dsm_reader->get_pkid(dsm_msg); + applicable_kroot_msg.cidkr = d_dsm_reader->get_cidkr(dsm_msg); + applicable_kroot_msg.reserved1 = d_dsm_reader->get_dsm_reserved1(dsm_msg); + applicable_kroot_msg.hf = d_dsm_reader->get_hf(dsm_msg); + applicable_kroot_msg.mf = d_dsm_reader->get_mf(dsm_msg); + applicable_kroot_msg.ks = d_dsm_reader->get_ks(dsm_msg); + applicable_kroot_msg.ts = d_dsm_reader->get_ts(dsm_msg); + applicable_kroot_msg.maclt = d_dsm_reader->get_maclt(dsm_msg); + applicable_kroot_msg.reserved = d_dsm_reader->get_dsm_reserved(dsm_msg); + applicable_kroot_msg.wn_k = d_dsm_reader->get_wn_k(dsm_msg); + applicable_kroot_msg.towh_k = d_dsm_reader->get_towh_k(dsm_msg); + applicable_kroot_msg.alpha = d_dsm_reader->get_alpha(dsm_msg); + // Kroot field + const uint16_t l_lk_bytes = d_dsm_reader->get_lk_bits(applicable_kroot_msg.ks) / 8; + applicable_kroot_msg.kroot = d_dsm_reader->get_kroot(dsm_msg, l_lk_bytes); + // DS field + uint16_t l_ds_bits = 0; + const auto it = OSNMA_TABLE_15.find(d_crypto->get_public_key_type()); + if (it != OSNMA_TABLE_15.cend()) + { + l_ds_bits = it->second; + } + const uint16_t l_ds_bytes = l_ds_bits / 8; + applicable_kroot_msg.ds = std::vector(l_ds_bytes, 0); // C: this accounts for padding in case needed. + for (uint16_t k = 0; k < l_ds_bytes; k++) + { + applicable_kroot_msg.ds[k] = dsm_msg[13 + l_lk_bytes + k]; + } + // Padding + const uint16_t l_dk_bits = d_dsm_reader->get_l_dk_bits(applicable_kroot_msg.nb_dk); + const uint16_t l_dk_bytes = l_dk_bits / 8; + const uint16_t l_pdk_bytes = (l_dk_bytes - 13 - l_lk_bytes - l_ds_bytes); + applicable_kroot_msg.p_dk = std::vector(l_pdk_bytes, 0); + for (uint16_t k = 0; k < l_pdk_bytes; k++) + { + applicable_kroot_msg.p_dk[k] = dsm_msg[13 + l_lk_bytes + l_ds_bytes + k]; + } + + const uint16_t check_l_dk = 104 * std::ceil(1.0 + static_cast((l_lk_bytes * 8.0) + l_ds_bits) / 104.0); + if (l_dk_bits != check_l_dk) + { + LOG(WARNING) << "Galileo OSNMA: Failed length reading of DSM-KROOT message"; + d_count_failed_Kroot++; + } + else + { + // validation of padding + const uint16_t size_m = 13 + l_lk_bytes; + std::vector MSG; + MSG.reserve(size_m + l_ds_bytes + 1); + MSG.push_back(nma_header); // NMA header + for (uint16_t i = 1; i < size_m; i++) + { + MSG.push_back(dsm_msg[i]); + } + std::vector message = MSG; // MSG = (M | DS) from ICD. Eq. 7 + for (uint16_t k = 0; k < l_ds_bytes; k++) + { + MSG.push_back(applicable_kroot_msg.ds[k]); + } + + std::vector hash; + if (applicable_kroot_msg.hf == 0) // Table 8. + { + hash = d_crypto->compute_SHA_256(MSG); + } + else if (applicable_kroot_msg.hf == 2) + { + hash = d_crypto->compute_SHA3_256(MSG); + } + else + { + hash = std::vector(32); + } + // truncate hash + std::vector p_dk_truncated; + p_dk_truncated.reserve(l_pdk_bytes); + for (uint16_t i = 0; i < l_pdk_bytes; i++) + { + p_dk_truncated.push_back(hash[i]); + } + // Check that the padding bits received match the computed values + if (applicable_kroot_msg.p_dk == p_dk_truncated) + { + LOG(INFO) << "Galileo OSNMA: DSM-KROOT message received ok."; + LOG(INFO) << "Galileo OSNMA: DSM-KROOT with CID=" << static_cast(d_osnma_data.d_nma_header.cid) + << ", PKID=" << static_cast(applicable_kroot_msg.pkid) + << ", WN=" << static_cast(applicable_kroot_msg.wn_k) + << ", TOW=" << static_cast(applicable_kroot_msg.towh_k) * 3600; + + // If new PK verified and the new KROOT arrived, set the new PK before attempting verification + if (d_flag_PK_renewal && applicable_kroot_msg.pkid == d_new_public_key_id && d_flag_NPK_set == false) + { + d_crypto->set_public_key(d_new_public_key); + d_crypto->store_public_key(PEMFILE_DEFAULT); + d_flag_NPK_set = true; + } + if (l_ds_bits == 512) + { + d_kroot_verified = d_crypto->verify_signature_ecdsa_p256(message, applicable_kroot_msg.ds); + } + else if (l_ds_bits == 1056) + { + d_kroot_verified = d_crypto->verify_signature_ecdsa_p521(message, applicable_kroot_msg.ds); + } + if (d_kroot_verified) + { + applicable_kroot_msg.verified = true; + std::cout << "Galileo OSNMA: DSM-KROOT authentication successful!" << std::endl; + LOG(INFO) << "Galileo OSNMA: DSM-KROOT authentication successful!"; + if (d_flag_alert_message) + { + LOG(WARNING) << "Galileo OSNMA: DSM-KROOT :: Alert message verification :: SUCCESS. "; + } + else + { + LOG(INFO) << "Galileo OSNMA: NMA Status is " << d_dsm_reader->get_nmas_status(d_osnma_data.d_nma_header.nmas) << ", " + << "Chain in force is " << static_cast(d_osnma_data.d_nma_header.cid) << ", " + << "Chain and Public Key Status is " << d_dsm_reader->get_cpks_status(d_osnma_data.d_nma_header.cpks); + } + // Save DSM-Kroot and NMA header into a permanent storage + if (d_flag_hot_start) + { + d_flag_hot_start = false; + return; + } + store_dsm_kroot(dsm_msg, nma_header); // TODO - store it only if DSM-KROOT is new + } + else + { + LOG(WARNING) << "Galileo OSNMA: DSM-KROOT authentication failed."; + std::cerr << "Galileo OSNMA: DSM-KROOT authentication failed." << std::endl; + if (d_flag_alert_message) + { + d_flag_alert_message = false; + } + d_count_failed_Kroot++; + } + } + else + { + LOG(WARNING) << "Galileo OSNMA: Error computing padding bits."; + // TODO - here will have to decide if perform the verification or not. Since this step is not mandatory, one could as well have skipped it. + d_count_failed_Kroot++; + } + } + } + // DSM-PKR message + else if (d_osnma_data.d_dsm_header.dsm_id >= 12 && d_osnma_data.d_dsm_header.dsm_id < 16) + { + LOG(INFO) << "Galileo OSNMA: DSM-PKR message received"; + // Save DSM-PKR message + d_osnma_data.d_dsm_pkr_message.nb_dp = d_dsm_reader->get_number_blocks_index(dsm_msg[0]); + d_osnma_data.d_dsm_pkr_message.mid = d_dsm_reader->get_mid(dsm_msg); + for (int k = 0; k < 128; k++) + { + d_osnma_data.d_dsm_pkr_message.itn[k] = dsm_msg[k + 1]; + } + d_osnma_data.d_dsm_pkr_message.npkt = d_dsm_reader->get_npkt(dsm_msg); + uint8_t npktid = d_dsm_reader->get_npktid(dsm_msg); + if (d_flag_PK_renewal && npktid > d_osnma_data.d_dsm_pkr_message.npktid) + { + d_new_public_key_id = npktid; + } + d_osnma_data.d_dsm_pkr_message.npktid = npktid; + + uint32_t l_npk_bytes = 0; + std::string PKT; + const auto it = OSNMA_TABLE_5.find(d_osnma_data.d_dsm_pkr_message.npkt); + if (it != OSNMA_TABLE_5.cend()) + { + PKT = it->second; + const auto it2 = OSNMA_TABLE_6.find(it->second); + if (it2 != OSNMA_TABLE_6.cend()) + { + l_npk_bytes = it2->second / 8; + } + } + uint32_t l_dp_bytes = dsm_msg.size(); + if (d_osnma_data.d_dsm_pkr_message.npkt == 4 && d_osnma_data.d_dsm_pkr_message.npktid == 0) + { + LOG(WARNING) << "Galileo OSNMA: DSM-PKR :: Alert message received. Verifying it."; + std::cout << "Galileo OSNMA: DSM-PKR :: Alert message received. Verifying it." << std::endl; + l_npk_bytes = l_dp_bytes - 130; // bytes + } + + d_osnma_data.d_dsm_pkr_message.npk = std::vector(l_npk_bytes, 0); // ECDSA Public Key + for (uint32_t k = 0; k < l_npk_bytes; k++) + { + d_osnma_data.d_dsm_pkr_message.npk[k] = dsm_msg[k + 130]; + } + + uint32_t l_pd_bytes = l_dp_bytes - 130 - l_npk_bytes; + uint32_t check_l_dp_bytes = 104 * std::ceil(static_cast(1040.0 + l_npk_bytes * 8.0) / 104.0) / 8; + if (l_dp_bytes != check_l_dp_bytes) + { + LOG(WARNING) << "Galileo OSNMA: Failed length reading of DSM-PKR message"; + d_flag_alert_message = false; + } + else + { + d_osnma_data.d_dsm_pkr_message.p_dp = std::vector(l_pd_bytes, 0); + for (uint32_t k = 0; k < l_pd_bytes; k++) + { + d_osnma_data.d_dsm_pkr_message.p_dp[k] = dsm_msg[l_dp_bytes - l_pd_bytes + k]; + } + // TODO: kroot fields are 0 in case no DSM-KROOT received yet, need to take this into account. + // std::vector mi; // (NPKT + NPKID + NPK) + LOG(INFO) << "Galileo OSNMA: DSM-PKR with CID=" << static_cast(d_osnma_data.d_nma_header.cid) + << ", PKID=" << static_cast(d_osnma_data.d_dsm_pkr_message.npktid) << " received"; + // Public key verification against Merkle tree root. + bool verification = verify_dsm_pkr(d_osnma_data.d_dsm_pkr_message); + if (verification) + { + LOG(INFO) << "Galileo OSNMA: DSM-PKR verification :: SUCCESS"; + d_public_key_verified = true; + if (d_flag_PK_renewal) + { + d_new_public_key = d_osnma_data.d_dsm_pkr_message.npk; + } + else if (d_flag_alert_message) + { + LOG(WARNING) << "Galileo OSNMA: DSM-PKR verification :: Alert message verification :: SUCCESS. OSNMA disabled. Contact Galileo Service Centre"; + std::cout << "Galileo OSNMA: DSM-PKR verification :: Alert message verification :: SUCCESS. OSNMA disabled. Contact Galileo Service Centre" << std::endl; + } + else + { + d_crypto->set_public_key_type(PKT); + d_crypto->set_public_key(d_osnma_data.d_dsm_pkr_message.npk); + d_crypto->store_public_key(PEMFILE_DEFAULT); + } + } + else + { + LOG(ERROR) << "Galileo OSNMA: DSM-PKR verification :: FAILURE"; + d_public_key_verified = false; + d_count_failed_pubKey++; + if (d_flag_alert_message) + { + d_flag_alert_message = false; // disregard message as its authenticity could not be verified. + } + } + } + } + else + { + // Reserved message? + LOG(WARNING) << "Galileo OSNMA: Reserved message received"; + std::cerr << "Galileo OSNMA: Reserved message received" << std::endl; + } + d_number_of_blocks[d_osnma_data.d_dsm_header.dsm_id] = 0; // TODO - reset during header parsing in PKREV? +} + + +/** + * @brief Reads the Mack message from the given OSNMA_msg object + * + * @details Conditions for MACK processing: + * @param osnma_msg The OSNMA_msg object containing the Mack message. + */ +void osnma_msg_receiver::read_and_process_mack_block(const std::shared_ptr& osnma_msg) +{ + // Retrieve Mack message + uint32_t index = 0; + for (uint32_t value : osnma_msg->mack) + { + d_mack_message[index] = static_cast((value & 0xFF000000) >> 24); + d_mack_message[index + 1] = static_cast((value & 0x00FF0000) >> 16); + d_mack_message[index + 2] = static_cast((value & 0x0000FF00) >> 8); + d_mack_message[index + 3] = static_cast(value & 0x000000FF); + index = index + 4; + } + + d_osnma_data.d_nav_data.set_tow_sf0(osnma_msg->TOW_sf0); + bool can_process_mack_block = (d_osnma_data.d_nma_header.nmas != 3 && d_kroot_verified) || // NMAS different than DU + (d_osnma_data.d_nma_header.nmas == 3 && !d_kroot_verified); // NMAS is DU, but must be disregarded + bool can_verify_tesla_key = d_kroot_verified || d_tesla_key_verified; // Either of those suffices for verifying the incoming TESLA key + bool can_parse_tag_fields = d_osnma_data.d_dsm_kroot_message.ts != 0; // calculating the number of tags is based on the TS of the DSM-KROOT. + if (can_verify_tesla_key && can_parse_tag_fields && can_process_mack_block) + { + read_mack_header(); + d_osnma_data.d_mack_message.PRNa = osnma_msg->PRN; // FIXME this is ugly. + d_osnma_data.d_mack_message.TOW = osnma_msg->TOW_sf0; + d_osnma_data.d_mack_message.WN = osnma_msg->WN_sf0; + read_mack_body(); + process_mack_message(); + // TODO - shorten the MACK processing for the cases where no TK verified or no Kroot verified (warm and cold start) + // still, for instance the OSNMA_NavData and Mack storage (within process_mack_message) makes sense. + } + else + { + // TODO - MACKs should be saved because once Kroot available, they could be verified. + LOG(WARNING) << "Galileo OSNMA: Cannot process MACK block. Skipping it."; + std::cout << "Galileo OSNMA: Cannot process MACK block. Skipping it." << std::endl; + } +} + + +/** + * \brief Reads the MACk header from the d_mack_message array and updates the d_osnma_data structure. + * \details This function reads the message MACK header from the d_mack_message array and updates the d_osnma_data structure with the parsed data. The header consists of three fields + *: tag0, macseq, and cop. The size of the fields is determined by the number of tag length (lt) bits specified in OSNMA_TABLE_11 for the corresponding tag size in d_osnma_data.d_dsm_k + *root_message.ts. The lt_bits value is used to calculate tag0, MACSEQ, and COP. + * \pre The d_mack_message array and d_osnma_data.d_dsm_kroot_message.ts field must be properly populated. + * \post The d_osnma_data.d_mack_message.header.tag0, d_osnma_data.d_mack_message.header.macseq, and d_osnma_data.d_mack_message.header.cop fields are updated with the parsed values + *. + * \returns None. + */ +void osnma_msg_receiver::read_mack_header() +{ + uint8_t lt_bits = 0; + const auto it = OSNMA_TABLE_11.find(d_osnma_data.d_dsm_kroot_message.ts); + if (it != OSNMA_TABLE_11.cend()) + { + lt_bits = it->second; + } + if (lt_bits < 16) + { + return; // The 16 is to avoid negative shifts if shorter tags were defined + } + uint16_t macseq = 0; + uint8_t cop = 0; + uint64_t first_lt_bits = static_cast(d_mack_message[0]) << (lt_bits - 8); + first_lt_bits += (static_cast(d_mack_message[1]) << (lt_bits - 16)); + if (lt_bits == 20) + { + first_lt_bits += (static_cast(d_mack_message[2] & 0xF0) >> 4); + macseq += (static_cast(d_mack_message[2] & 0x0F) << 8); + macseq += static_cast(d_mack_message[3]); + cop += ((d_mack_message[4] & 0xF0) >> 4); + } + else if (lt_bits == 24) + { + first_lt_bits += static_cast(d_mack_message[2]); + macseq += (static_cast(d_mack_message[3]) << 4); + macseq += (static_cast(d_mack_message[4] & 0xF0) >> 4); + cop += (d_mack_message[4] & 0x0F); + } + else if (lt_bits == 28) + { + first_lt_bits += (static_cast(d_mack_message[2]) << 4); + first_lt_bits += (static_cast(d_mack_message[3] & 0xF0) >> 4); + macseq += (static_cast(d_mack_message[3] & 0x0F) << 8); + macseq += (static_cast(d_mack_message[4])); + cop += ((d_mack_message[5] & 0xF0) >> 4); + } + else if (lt_bits == 32) + { + first_lt_bits += (static_cast(d_mack_message[2]) << 8); + first_lt_bits += static_cast(d_mack_message[3]); + macseq += (static_cast(d_mack_message[4]) << 4); + macseq += (static_cast(d_mack_message[5] & 0xF0) >> 4); + cop += (d_mack_message[5] & 0x0F); + } + else if (lt_bits == 40) + { + first_lt_bits += (static_cast(d_mack_message[2]) << 16); + first_lt_bits += (static_cast(d_mack_message[3]) << 8); + first_lt_bits += static_cast(d_mack_message[4]); + macseq += (static_cast(d_mack_message[5]) << 4); + macseq += (static_cast(d_mack_message[6] & 0xF0) >> 4); + cop += (d_mack_message[6] & 0x0F); + } + d_osnma_data.d_mack_message.header.tag0 = first_lt_bits; + d_osnma_data.d_mack_message.header.macseq = macseq; + d_osnma_data.d_mack_message.header.cop = cop; +} + + +/** + * @brief Reads the MACK message body + * + * \details It retrieves all the tags and tag-info associated, as well as the TESLA key. + * \post populates d_osnma_data.d_mack_message with all tags and tag_info associated of MACK message, as well as the TESLA key into d_osnma_data.d_mack_message.key + * @return None + */ +void osnma_msg_receiver::read_mack_body() +{ + // retrieve tag length + uint8_t lt_bits = 0; + const auto it = OSNMA_TABLE_11.find(d_osnma_data.d_dsm_kroot_message.ts); + if (it != OSNMA_TABLE_11.cend()) + { + lt_bits = it->second; + } + if (lt_bits == 0) + { + return; + } + // retrieve key length + const uint16_t lk_bits = d_dsm_reader->get_lk_bits(d_osnma_data.d_dsm_kroot_message.ks); + // compute number of tags in the given Mack message as per Eq. 8 ICD + uint16_t nt = std::floor((480.0 - float(lk_bits)) / (float(lt_bits) + 16.0)); + d_osnma_data.d_mack_message.tag_and_info = std::vector(nt - 1); + // retrieve tags and tag-info associated with the tags + for (uint16_t k = 0; k < (nt - 1); k++) + { + uint64_t tag = 0; + uint8_t PRN_d = 0; + uint8_t ADKD = 0; + uint8_t cop = 0; + if (lt_bits == 20) + { + const uint16_t step = std::ceil(4.5 * k); + if (k % 2 == 0) + { + tag += (static_cast((d_mack_message[4 + step] & 0x0F)) << 16); + tag += (static_cast(d_mack_message[5 + step]) << 8); + tag += static_cast(d_mack_message[6 + step]); + PRN_d += d_mack_message[7 + step]; + ADKD += ((d_mack_message[8 + step] & 0xF0) >> 4); + cop += (d_mack_message[8 + step] & 0x0F); + if (k == (nt - 2)) + { + d_osnma_data.d_mack_message.key = std::vector(d_osnma_data.d_dsm_kroot_message.kroot.size()); + for (size_t j = 0; j < d_osnma_data.d_dsm_kroot_message.kroot.size(); j++) + { + d_osnma_data.d_mack_message.key[j] = d_mack_message[9 + step + j]; + } + } + } + else + { + tag += (static_cast(d_mack_message[4 + step]) << 12); + tag += (static_cast(d_mack_message[5 + step]) << 4); + tag += (static_cast((d_mack_message[6 + step] & 0xF0)) >> 4); + PRN_d += (d_mack_message[6 + step] & 0x0F) << 4; + PRN_d += (d_mack_message[7 + step] & 0xF0) >> 4; + ADKD += (d_mack_message[7 + step] & 0x0F); + cop += (d_mack_message[8 + step] & 0xF0) >> 4; + if (k == (nt - 2)) + { + d_osnma_data.d_mack_message.key = std::vector(d_osnma_data.d_dsm_kroot_message.kroot.size()); + for (size_t j = 0; j < d_osnma_data.d_dsm_kroot_message.kroot.size(); j++) + { + d_osnma_data.d_mack_message.key[j] = ((d_mack_message[8 + step + j] & 0x0F) << 4) + ((d_mack_message[9 + step + j] & 0xF0) >> 4); + } + } + } + } + else if (lt_bits == 24) + { + tag += (static_cast((d_mack_message[5 + k * 5])) << 16); + tag += (static_cast((d_mack_message[6 + k * 5])) << 8); + tag += static_cast(d_mack_message[7 + k * 5]); + PRN_d += d_mack_message[8 + k * 5]; + ADKD += ((d_mack_message[9 + k * 5] & 0xF0) >> 4); + cop += (d_mack_message[9 + k * 5] & 0x0F); + if (k == (nt - 2)) + { + d_osnma_data.d_mack_message.key = std::vector(d_osnma_data.d_dsm_kroot_message.kroot.size()); + for (size_t j = 0; j < d_osnma_data.d_dsm_kroot_message.kroot.size(); j++) + { + d_osnma_data.d_mack_message.key[j] = d_mack_message[10 + k * 5 + j]; + } + } + } + else if (lt_bits == 28) + { + const uint16_t step = std::ceil(5.5 * k); + if (k % 2 == 0) + { + tag += (static_cast((d_mack_message[5 + step] & 0x0F)) << 24); + tag += (static_cast(d_mack_message[6 + step]) << 16); + tag += (static_cast(d_mack_message[7 + step]) << 8); + tag += static_cast(d_mack_message[8 + step]); + PRN_d += d_mack_message[9 + step]; + ADKD += ((d_mack_message[10 + step] & 0xF0) >> 4); + cop += (d_mack_message[10 + step] & 0x0F); + if (k == (nt - 2)) + { + d_osnma_data.d_mack_message.key = std::vector(d_osnma_data.d_dsm_kroot_message.kroot.size()); + for (size_t j = 0; j < d_osnma_data.d_dsm_kroot_message.kroot.size(); j++) + { + d_osnma_data.d_mack_message.key[j] = d_mack_message[11 + step + j]; + } + } + } + else + { + tag += (static_cast((d_mack_message[5 + step])) << 20); + tag += (static_cast((d_mack_message[6 + step])) << 12); + tag += (static_cast((d_mack_message[7 + step])) << 4); + tag += (static_cast((d_mack_message[8 + step] & 0xF0)) >> 4); + PRN_d += ((d_mack_message[8 + step] & 0x0F) << 4); + PRN_d += ((d_mack_message[9 + step] & 0xF0) >> 4); + ADKD += (d_mack_message[9 + step] & 0x0F); + cop += ((d_mack_message[10 + step] & 0xF0) >> 4); + if (k == (nt - 2)) + { + d_osnma_data.d_mack_message.key = std::vector(d_osnma_data.d_dsm_kroot_message.kroot.size()); + for (size_t j = 0; j < d_osnma_data.d_dsm_kroot_message.kroot.size(); j++) + { + d_osnma_data.d_mack_message.key[j] = ((d_mack_message[10 + step + j] & 0x0F) << 4) + ((d_mack_message[11 + step + j] & 0xF0) >> 4); + } + } + } + } + else if (lt_bits == 32) + { + tag += (static_cast((d_mack_message[6 + k * 6])) << 24); + tag += (static_cast((d_mack_message[7 + k * 6])) << 16); + tag += (static_cast((d_mack_message[8 + k * 6])) << 8); + tag += static_cast(d_mack_message[9 + k * 6]); + PRN_d += d_mack_message[10 + k * 6]; + ADKD += ((d_mack_message[11 + k * 6] & 0xF0) >> 4); + cop += (d_mack_message[11 + k * 6] & 0x0F); + if (k == (nt - 2)) + { + d_osnma_data.d_mack_message.key = std::vector(d_osnma_data.d_dsm_kroot_message.kroot.size()); + for (size_t j = 0; j < d_osnma_data.d_dsm_kroot_message.kroot.size(); j++) + { + d_osnma_data.d_mack_message.key[j] = d_mack_message[12 + k * 6 + j]; + } + } + } + else if (lt_bits == 40) + { + tag += (static_cast((d_mack_message[7 /* bytes of MACK header */ + k * 7 /* offset of k-th tag */])) << 32); + tag += (static_cast((d_mack_message[8 + k * 7])) << 24); + tag += (static_cast((d_mack_message[9 + k * 7])) << 16); + tag += (static_cast((d_mack_message[10 + k * 7])) << 8); + tag += static_cast(d_mack_message[11 + k * 7]); + PRN_d += d_mack_message[12 + k * 7]; + ADKD += ((d_mack_message[13 + k * 7] & 0xF0) >> 4); + cop += (d_mack_message[13 + k * 7] & 0x0F); + if (k == (nt - 2)) // end of Tag&Info + { + d_osnma_data.d_mack_message.key = std::vector(d_osnma_data.d_dsm_kroot_message.kroot.size()); + for (size_t j = 0; j < d_osnma_data.d_dsm_kroot_message.kroot.size(); j++) + { + d_osnma_data.d_mack_message.key[j] = d_mack_message[14 + k * 7 + j]; + } + } + } + d_osnma_data.d_mack_message.tag_and_info[k].tag = tag; + d_osnma_data.d_mack_message.tag_and_info[k].counter = k + 2; // CTR==1 for Tag0, increases subsequently for all other tags. + d_osnma_data.d_mack_message.tag_and_info[k].tag_info.PRN_d = PRN_d; + d_osnma_data.d_mack_message.tag_and_info[k].tag_info.ADKD = ADKD; + d_osnma_data.d_mack_message.tag_and_info[k].tag_info.cop = cop; + } + // rest are padding bits, used for anything ? +} + + +/** + * @brief Verifies the tags transmitted in the past. + * + * \details This function is responsible for processing the MACK message received (480 bits) at time SF(i). + * It stores the last 10 MACK messages and the last 11 OSNMA_NavData messages. + * Then attempts to verify the Tesla Key by computing the number of hashes of distance between the key-to-verify and the + * Kroot and iteratively hashing the result, until the required number of hashes is achieved. + * The result is then compared with the Kroot. If the two values match, the Tesla key is verified. + * It also performs MACSEQ validation and compares the ADKD of Mack tags with MACLT defined ADKDs. + * Finally, it verifies the tags. + * \pre Kroot or already a TESLA key shall be available. Depending on the ADKD of the tag, OSNMA_NavData of SF(i-2)...SF(i-11) + * \post Number of tags bits verified for each ADKD. MACSEQ verification success + * @param osnma_msg A reference to OSNMA_msg containing the MACK message to be processed. + */ +void osnma_msg_receiver::process_mack_message() +{ + if (!d_kroot_verified && !d_tesla_key_verified) + { + LOG(WARNING) << "Galileo OSNMA: MACK cannot be processed, " + << "no Kroot nor TESLA key available."; + return; // early return, cannot proceed further without one of the two verified. this equals to having Kroot but no TESLa key yet. + } + // verify tesla key and add it to the container of verified keys if successful + if (d_tesla_keys.find(d_osnma_data.d_nav_data.get_tow_sf0()) == d_tesla_keys.end()) // check if already available => no need to verify + { + bool retV = verify_tesla_key(d_osnma_data.d_mack_message.key, d_osnma_data.d_nav_data.get_tow_sf0()); + if (retV) + { + d_tesla_keys.insert(std::pair>(d_osnma_data.d_nav_data.get_tow_sf0(), d_osnma_data.d_mack_message.key)); + } + } + + // MACSEQ - verify current macks, then add current retrieved mack to the end. + auto mack = d_macks_awaiting_MACSEQ_verification.begin(); + while (mack != d_macks_awaiting_MACSEQ_verification.end()) + { + if (d_tesla_keys.find(mack->TOW + 30) != d_tesla_keys.end()) + { + // add tag0 first + Tag tag0(*mack); + d_tags_awaiting_verify.insert(std::pair(mack->TOW, tag0)); + LOG(INFO) << "Galileo OSNMA: Add Tag0 Id= " + << tag0.tag_id + << ", value=0x" << std::setfill('0') << std::setw(10) << std::hex << std::uppercase + << tag0.received_tag << std::dec + << ", TOW=" + << tag0.TOW + << ", ADKD=" + << static_cast(tag0.ADKD) + << ", PRNa=" + << static_cast(tag0.PRNa) + << ", PRNd=" + << static_cast(tag0.PRN_d); + std::vector macseq_verified_tags = verify_macseq_new(*mack); + for (auto& tag_and_info : macseq_verified_tags) + { + // add tags of current mack to the verification queue + Tag t(tag_and_info, mack->TOW, mack->WN, mack->PRNa, tag_and_info.counter); + d_tags_awaiting_verify.insert(std::pair(mack->TOW, t)); + LOG(INFO) << "Galileo OSNMA: Add Tag Id= " + << t.tag_id + << ", value=0x" << std::setfill('0') << std::setw(10) << std::hex << std::uppercase + << t.received_tag << std::dec + << ", TOW=" + << t.TOW + << ", ADKD=" + << static_cast(t.ADKD) + << ", PRNa=" + << static_cast(t.PRNa) + << ", PRNd=" + << static_cast(t.PRN_d); + } + LOG(INFO) << "Galileo OSNMA: d_tags_awaiting_verify :: size: " << d_tags_awaiting_verify.size(); + mack = d_macks_awaiting_MACSEQ_verification.erase(mack); + } + else + { + // key not yet available - keep in container until then -- might be deleted if container size exceeds max allowed + ++mack; + } + } + // add current received MACK to the container to be verified in the next iteration (on this one no key available) + d_macks_awaiting_MACSEQ_verification.push_back(d_osnma_data.d_mack_message); + + // Tag verification + for (auto& it : d_tags_awaiting_verify) + { + bool ret; + if (tag_has_key_available(it.second) && d_nav_data_manager->have_nav_data(it.second)) // tag_has_nav_data_available(it.second)) + { + ret = verify_tag(it.second); + /* TODO - take into account: + * - COP: if + * - ADKD type + * - OSNMA_NavData the tag verifies (min. number of bits verified to consider OSNMA_NavData OK) + * */ + if (ret) + { + d_count_successful_tags++; + it.second.status = Tag::SUCCESS; + LOG(INFO) << "Galileo OSNMA: Tag verification :: SUCCESS for tag Id=" + << it.second.tag_id + << ", value=0x" << std::setfill('0') << std::setw(10) << std::hex << std::uppercase + << it.second.received_tag << std::dec + << ", TOW=" + << it.second.TOW + << ", ADKD=" + << static_cast(it.second.ADKD) + << ", PRNa=" + << static_cast(it.second.PRNa) + << ", PRNd=" + << static_cast(it.second.PRN_d); + std::cout << "Galileo OSNMA: Tag verification :: SUCCESS for tag ADKD=" + << static_cast(it.second.ADKD) + << ", PRNa=" + << static_cast(it.second.PRNa) + << ", PRNd=" + << static_cast(it.second.PRN_d) << std::endl; + } + /* TODO notify PVT via pmt + * have_new_data() true + * signal which one is verified + * communicate to PVT*/ + else + { + d_count_failed_tags++; + it.second.status = Tag::FAIL; + LOG(WARNING) << "Galileo OSNMA: Tag verification :: FAILURE for tag Id=" + << it.second.tag_id + << ", value=0x" << std::setfill('0') << std::setw(10) << std::hex << std::uppercase + << it.second.received_tag << std::dec + << ", TOW=" + << it.second.TOW + << ", ADKD=" + << static_cast(it.second.ADKD) + << ", PRNa=" + << static_cast(it.second.PRNa) + << ", PRNd=" + << static_cast(it.second.PRN_d); + std::cerr << "Galileo OSNMA: Tag verification :: FAILURE for tag ADKD=" + << static_cast(it.second.ADKD) + << ", PRNa=" + << static_cast(it.second.PRNa) + << ", PRNd=" + << static_cast(it.second.PRN_d) << std::endl; + } + } + else if (it.second.TOW > d_osnma_data.d_nav_data.get_tow_sf0()) + { + // TODO - I dont understand logic. This needs to be reviewed. + // case 1: adkd=12 and t.Tow + 300 < current TOW + // case 2: adkd=0/4 and t.Tow + 30 < current TOW + // case 3: any adkd and t.Tow > current TOW + it.second.skipped++; + LOG(WARNING) << "Galileo OSNMA: Tag verification :: SKIPPED (x" << it.second.skipped << ")for Tag Id= " + << it.second.tag_id + << ", value=0x" << std::setfill('0') << std::setw(10) << std::hex << std::uppercase + << it.second.received_tag << std::dec + << ", TOW=" + << it.second.TOW + << ", ADKD=" + << static_cast(it.second.ADKD) + << ", PRNa=" + << static_cast(it.second.PRNa) + << ", PRNd=" + << static_cast(it.second.PRN_d) + << ". Key available (" << tag_has_key_available(it.second) << "), navData (" << tag_has_nav_data_available(it.second) << "). "; + } + } + uint8_t tag_size = 0; + const auto it = OSNMA_TABLE_11.find(d_osnma_data.d_dsm_kroot_message.ts); + if (it != OSNMA_TABLE_11.cend()) + { + tag_size = it->second; + } + d_nav_data_manager->update_nav_data(d_tags_awaiting_verify, tag_size); + auto data_to_send = d_nav_data_manager->get_verified_data(); + d_nav_data_manager->log_status(); + send_data_to_pvt(data_to_send); + + remove_verified_tags(); + + control_tags_awaiting_verify_size(); // remove the oldest tags if size is too big. +} + + +/** + * @brief Verify received DSM-PKR message + * + * \details This method provides the functionality to verify the DSM-PKR message. The verification includes generating the base leaf + * and intermediate leafs, and comparing the computed merkle root leaf with the received one. + * \pre DSM_PKR_message correctly filled in, especially the 1024-bit intermediate tree nodes fields + * \returns true if computed merkle root matches received one, false otherwise + */ +bool osnma_msg_receiver::verify_dsm_pkr(const DSM_PKR_message& message) const +{ + const auto base_leaf = get_merkle_tree_leaves(message); // m_i + const auto computed_merkle_root = compute_merkle_root(message, base_leaf); // x_4_0 + const auto msg_id = static_cast(message.mid); + LOG(INFO) << "Galileo OSNMA: DSM-PKR verification :: leaf provided for Message ID " << msg_id; + + if (computed_merkle_root == d_crypto->get_merkle_root()) + { + LOG(INFO) << "Galileo OSNMA: DSM-PKR verification for Message ID " << msg_id << " :: SUCCESS. PKID=" << static_cast(message.npktid); + std::cout << "Galileo OSNMA: DSM-PKR verification for Message ID " << msg_id << " :: SUCCESS. PKID=" << static_cast(message.npktid) << std::endl; + return true; + } + else + { + LOG(WARNING) << "Galileo OSNMA: DSM-PKR verification for Message ID " << msg_id << " :: FAILURE."; + std::cerr << "Galileo OSNMA: DSM-PKR verification for Message ID " << msg_id << " :: FAILURE." << std::endl; + return false; + } +} + + +std::vector osnma_msg_receiver::compute_merkle_root(const DSM_PKR_message& dsm_pkr_message, const std::vector& m_i) const +{ + std::vector x_next; + std::vector x_current = d_crypto->compute_SHA_256(m_i); + for (size_t i = 0; i < 4; i++) + { + x_next.clear(); + bool leaf_is_on_right = ((dsm_pkr_message.mid / (1 << (i))) % 2) == 1; + + if (leaf_is_on_right) + { + // Leaf is on the right -> first the itn, then concatenate the leaf + x_next.insert(x_next.end(), &dsm_pkr_message.itn[32 * i], &dsm_pkr_message.itn[32 * i + 32]); + x_next.insert(x_next.end(), x_current.begin(), x_current.end()); + } + else + { + // Leaf is on the left -> first the leaf, then concatenate the itn + x_next.insert(x_next.end(), x_current.begin(), x_current.end()); + x_next.insert(x_next.end(), &dsm_pkr_message.itn[32 * i], &dsm_pkr_message.itn[32 * i + 32]); + } + + // Compute the next node. + x_current = d_crypto->compute_SHA_256(x_next); + } + return x_current; +} + + +/** + * @brief Get the Merkle tree base leave from a DSM_PKR_message. + * + * @param dsm_pkr_message The DSM_PKR_message object from which to retrieve the Merkle tree leave. + * @return std::vector The Merkle tree base leave from the DSM_PKR_message object. + */ +std::vector osnma_msg_receiver::get_merkle_tree_leaves(const DSM_PKR_message& dsm_pkr_message) const +{ + // build base leaf m_i according to OSNMA SIS ICD v1.1, section 6.2 DSM-PKR Verification + std::vector m_i; + const size_t size_npk = dsm_pkr_message.npk.size(); + m_i.reserve(1 + size_npk); + m_i.push_back((dsm_pkr_message.npkt << 4) + dsm_pkr_message.npktid); + for (size_t i = 0; i < size_npk; i++) + { + m_i.push_back(dsm_pkr_message.npk[i]); + } + return m_i; +} + + +bool osnma_msg_receiver::verify_tag(Tag& tag) const +{ + // Debug + // LOG(INFO) << "Galileo OSNMA: Tag verification :: Start for tag Id= " + // << tag.tag_id + // << ", value=0x" << std::setfill('0') << std::setw(10) << std::hex << std::uppercase + // << tag.received_tag << std::dec; + // build message + std::vector m = build_message(tag); + + std::vector mac; + std::vector applicable_key; + if (tag.ADKD == 0 || tag.ADKD == 4) + { + const auto it = d_tesla_keys.find(tag.TOW + 30); + if (it != d_tesla_keys.cend()) + { + applicable_key = it->second; + } + else + { + return false; + } + // LOG(INFO) << "|---> Galileo OSNMA :: applicable key: 0x" << d_helper->convert_to_hex_string(applicable_key) << "TOW="<(tag.TOW + 30); + } + else // ADKD 12 + { + const auto it = d_tesla_keys.find(tag.TOW + 330); + if (it != d_tesla_keys.cend()) + { + applicable_key = it->second; + } + else + { + return false; + } + // LOG(INFO) << "|---> Galileo OSNMA :: applicable key: 0x" << d_helper->convert_to_hex_string(applicable_key) << "TOW="<(tag.TOW + 330); + } + + if (d_osnma_data.d_dsm_kroot_message.mf == 0) // C: HMAC-SHA-256 + { + mac = d_crypto->compute_HMAC_SHA_256(applicable_key, m); + } + else if (d_osnma_data.d_dsm_kroot_message.mf == 1) // C: CMAC-AES + { + mac = d_crypto->compute_CMAC_AES(applicable_key, m); + } + + // truncate the computed mac: trunc(l_t, mac(K,m)) Eq. 23 ICD + uint8_t lt_bits = 0; // TODO - remove this duplication of code. + const auto it2 = OSNMA_TABLE_11.find(d_osnma_data.d_dsm_kroot_message.ts); + if (it2 != OSNMA_TABLE_11.cend()) + { + lt_bits = it2->second; + } + if (lt_bits < 16) + { + return false; + } + uint64_t computed_mac = static_cast(mac[0]) << (lt_bits - 8); + computed_mac += (static_cast(mac[1]) << (lt_bits - 16)); + if (lt_bits == 20) + { + computed_mac += (static_cast(mac[1] & 0xF0) >> 4); + } + else if (lt_bits == 24) + { + computed_mac += static_cast(mac[2]); + } + else if (lt_bits == 28) + { + computed_mac += (static_cast(mac[2]) << 4); + computed_mac += (static_cast(mac[3] & 0xF0) >> 4); + } + else if (lt_bits == 32) + { + computed_mac += (static_cast(mac[2]) << 8); + computed_mac += static_cast(mac[3]); + } + else if (lt_bits == 40) + { + computed_mac += (static_cast(mac[2]) << 16); + computed_mac += (static_cast(mac[3]) << 8); + computed_mac += static_cast(mac[4]); + } + + tag.computed_tag = computed_mac; // update with computed value + // Compare computed tag with received one truncated + if (tag.received_tag == computed_mac) + { + return true; + } + return false; +} + + +/** + * \brief generates the message for computing the tag + * \remarks It also sets some parameters to the Tag object, based on the verification process. + * + * \param tag The tag containing the information to be included in the message. + * + * \return The built OSNMA message as a vector of uint8_t. + */ +std::vector osnma_msg_receiver::build_message(Tag& tag) const +{ + std::vector m; + if (tag.CTR != 1) + { + m.push_back(static_cast(tag.PRN_d)); + } + m.push_back(static_cast(tag.PRNa)); + // TODO: maybe here I have to use d_receiver_time instead of d_GST_SIS which is what I am computing + uint32_t GST = d_helper->compute_gst(tag.WN, tag.TOW); + std::vector GST_uint8 = d_helper->gst_to_uint8(GST); + m.insert(m.end(), GST_uint8.begin(), GST_uint8.end()); + m.push_back(tag.CTR); + // Extracts only two bits from d_osnma_data.d_nma_header.nmas + uint8_t two_bits_nmas = d_osnma_data.d_nma_header.nmas & 0b00000011; + two_bits_nmas = two_bits_nmas << 6; + m.push_back(two_bits_nmas); + + // Add applicable NavData bits to message + std::string applicable_nav_data = d_nav_data_manager->get_navigation_data(tag); + std::vector applicable_nav_data_bytes = d_helper->bytes(applicable_nav_data); + tag.nav_data = std::move(applicable_nav_data); // update tag with applicable data + + // Convert and add OSNMA_NavData bytes into the message, taking care of that NMAS has only 2 bits + for (uint8_t byte : applicable_nav_data_bytes) + { + m.back() |= (byte >> 2); // First take the 6 MSB bits of byte and add to m + m.push_back(byte << 6); // Then take the last 2 bits of byte, shift them to MSB position and insert the new element into m + } + if (m.back() == 0) + { + m.pop_back(); // Remove the last element if its value is 0 (only padding was added) + } + else + { + // Pad with zeros if the last element wasn't full + for (int bits = 2; bits < 8; bits += 2) + { + // Check if the last element in the vector has 2 '00' bits in its LSB position + if ((m.back() & 0b00000011) == 0) + { + m.back() <<= 2; // Shift the existing bits to make room for new 2 bits + } + else + { + break; // If it does not have 2 '00' bits in its LSB position, then the padding is complete + } + } + } + return m; +} + + +void osnma_msg_receiver::display_data() +{ + // if(d_satellite_nav_data.empty()) + // return; + // + // for(const auto& satellite : d_satellite_nav_data) { + // std::cout << "SV_ID: " << satellite.first << std::endl; + // for(const auto& towData : satellite.second) { + // std::cout << "\tTOW: " << towData.first << " key: "; + // for(size_t i = 0; i < towData.second.d_mack_message.key.size(); i++) { + // std::cout << std::hex << std::setfill('0') << std::setw(2) + // << static_cast(towData.second.d_mack_message.key[i]) << " "; + // } + // } + // } +} + + +bool osnma_msg_receiver::verify_tesla_key(std::vector& key, uint32_t TOW) +{ + uint32_t num_of_hashes_needed; + uint32_t GST_SFi = d_GST_Sf - 30; // GST of target key is to be used. + std::vector hash; + const uint8_t lk_bytes = d_dsm_reader->get_lk_bits(d_osnma_data.d_dsm_kroot_message.ks) / 8; + std::vector validated_key; + if (d_tesla_key_verified) + { // have to go up to last verified key + validated_key = d_tesla_keys.rbegin()->second; + num_of_hashes_needed = (d_GST_Sf - d_last_verified_key_GST) / 30; // Eq. 19 ICD modified + LOG(INFO) << "Galileo OSNMA: TESLA verification (" << num_of_hashes_needed << " hashes) need to be performed up to closest verified TESLA key"; + + hash = hash_chain(num_of_hashes_needed, key, GST_SFi, lk_bytes); + } + else + { // have to go until Kroot + validated_key = d_osnma_data.d_dsm_kroot_message.kroot; + num_of_hashes_needed = (d_GST_Sf - d_GST_0) / 30 + 1; // Eq. 19 IC + LOG(INFO) << "Galileo OSNMA: TESLA verification (" << num_of_hashes_needed << " hashes) need to be performed up to Kroot"; + + hash = hash_chain(num_of_hashes_needed, key, GST_SFi, lk_bytes); + } + // truncate hash + std::vector computed_key; + computed_key.reserve(key.size()); + for (size_t i = 0; i < key.size(); i++) + { + computed_key.push_back(hash[i]); + } + if (computed_key == validated_key && num_of_hashes_needed > 0) + { + LOG(INFO) << "Galileo OSNMA: TESLA key verification :: SUCCESS!"; + std::cout << "Galileo OSNMA: TESLA key verification :: SUCCESS!" << std::endl; + d_tesla_keys.insert(std::pair>(TOW, key)); + d_tesla_key_verified = true; + d_last_verified_key_GST = d_GST_Sf; + } + else if (num_of_hashes_needed > 0) + { + LOG(WARNING) << "Galileo OSNMA: TESLA key verification :: FAILED"; + std::cerr << "Galileo OSNMA: TESLA key verification :: FAILED" << std::endl; + } + return d_tesla_key_verified; +} + + +/** + * @brief Removes the tags that have been verified from the multimap d_tags_awaiting_verify. + * + * This function iterates through the multimap d_tags_awaiting_verify, and removes the tags that have a status of SUCCESS or FAIL. + * \remarks it also prints the current unverified tags + */ +void osnma_msg_receiver::remove_verified_tags() +{ + for (auto it = d_tags_awaiting_verify.begin(); it != d_tags_awaiting_verify.end();) + { + if (it->second.status == Tag::SUCCESS || it->second.status == Tag::FAIL) + { + LOG(INFO) << "Galileo OSNMA: Tag verification :: DELETE tag Id=" + << it->second.tag_id + << ", value=0x" << std::setfill('0') << std::setw(10) << std::hex << std::uppercase + << it->second.received_tag << std::dec + << ", TOW=" + << it->second.TOW + << ", ADKD=" + << static_cast(it->second.ADKD) + << ", PRNa=" + << static_cast(it->second.PRNa) + << ", PRNd=" + << static_cast(it->second.PRN_d) + << ", status=" + << d_helper->verification_status_str(it->second.status); + it = d_tags_awaiting_verify.erase(it); + } + else if ((it->second.ADKD != 12 && !d_nav_data_manager->have_nav_data(it->second)) || (it->second.ADKD == 12 && (it->second.TOW + 30 * 11 < d_helper->get_TOW(d_last_verified_key_GST)))) + { + LOG(INFO) << "Galileo OSNMA: Tag verification :: DELETE tag Id=" + << it->second.tag_id + << ", value=0x" << std::setfill('0') << std::setw(10) << std::hex << std::uppercase + << it->second.received_tag << std::dec + << ", TOW=" + << it->second.TOW + << ", ADKD=" + << static_cast(it->second.ADKD) + << ", PRNa=" + << static_cast(it->second.PRNa) + << ", PRNd=" + << static_cast(it->second.PRN_d) + << ", status=" + << d_helper->verification_status_str(it->second.status) + << ". SV out of sight / NavData unavailable."; + it = d_tags_awaiting_verify.erase(it); + } + else + { + ++it; + } + } + LOG(INFO) << "Galileo OSNMA: d_tags_awaiting_verify :: size: " << d_tags_awaiting_verify.size(); + for (const auto& it : d_tags_awaiting_verify) + { + LOG(INFO) << "Galileo OSNMA: Tag verification :: status tag Id=" + << it.second.tag_id + << ", value=0x" << std::setfill('0') << std::setw(10) << std::hex << std::uppercase + << it.second.received_tag << std::dec + << ", TOW=" + << it.second.TOW + << ", ADKD=" + << static_cast(it.second.ADKD) + << ", PRNa=" + << static_cast(it.second.PRNa) + << ", PRNd=" + << static_cast(it.second.PRN_d) + << ", status=" + << d_helper->verification_status_str(it.second.status); + } +} + + +/** + * @brief Control the size of the tags awaiting verification multimap. + * + * This function checks the size of the multimap `d_tags_awaiting_verify` and removes + * elements from the beginning until the size is no longer greater than 60. + * The purpose is to limit the size of the multimap and prevent it from consuming + * excessive memory. + */ +void osnma_msg_receiver::control_tags_awaiting_verify_size() +{ + while (d_tags_awaiting_verify.size() > 500) + { + auto it = d_tags_awaiting_verify.begin(); + LOG(INFO) << "Galileo OSNMA: Tag verification :: DELETED tag due to exceeding buffer size. " + << "Tag Id= " << it->second.tag_id + << ", TOW=" << it->first + << ", ADKD=" << static_cast(it->second.ADKD) + << ", from satellite " << it->second.PRNa; + d_tags_awaiting_verify.erase(it); + } +} + + +// TODO - remove this method +/** + * @brief Verifies the MACSEQ of a received MACK_message. + * + * \details checks for each tag in the retrieved mack message if its flexible (MACSEQ) or not (MACSEQ/MACLT depending on configuration param, and + * verifies according to Eqs. 20, 21 SIS ICD. + * @param message The MACK_message to verify. + * @return True if the MACSEQ is valid, false otherwise. + */ +bool osnma_msg_receiver::verify_macseq(const MACK_message& mack) +{ + // MACSEQ verification + uint32_t GST_SFi = d_GST_Sf - 30; // time of the start of SF containing MACSEQ + std::vector applicable_key = d_tesla_keys[mack.TOW + 30]; // current tesla key ie transmitted in the next subframe + std::vector sq1{}; + std::vector sq2{}; + std::vector applicable_sequence; + const auto it = OSNMA_TABLE_16.find(d_osnma_data.d_dsm_kroot_message.maclt); + // TODO as per RG example appears that the seq. q shall also be validated against either next or former Sf (depending on GST) + if (it != OSNMA_TABLE_16.cend()) + { + sq1 = it->second.sequence1; + sq2 = it->second.sequence2; + } + // Assign relevant sequence based on subframe time + if (mack.TOW % 60 < 30) // tried GST_Sf and it does not support the data present. + { + applicable_sequence = std::move(sq1); + } + else if (mack.TOW % 60 >= 30) + { + applicable_sequence = std::move(sq2); + } + if (mack.tag_and_info.size() != applicable_sequence.size() - 1) + { + LOG(WARNING) << "Galileo OSNMA: Number of retrieved tags does not match MACLT sequence size!"; + return false; + } + std::vector flxTags{}; + std::string tempADKD; + // MACLT verification + for (size_t i = 0; i < mack.tag_and_info.size(); i++) + { + tempADKD = applicable_sequence[i + 1]; + if (tempADKD == "FLX") + { + flxTags.push_back(i); // C: just need to save the index in the sequence + } + else if (mack.tag_and_info[i].tag_info.ADKD != std::stoi(applicable_sequence[i + 1])) + { + // fill index of tags failed + LOG(WARNING) << "Galileo OSNMA: MACSEQ verification :: FAILURE :: ADKD mismatch against MAC Look-up table."; + return false; // TODO macseq shall be individual to each tag, a wrongly verified macseq should not discard the whole MACK tags + } + } + + if (flxTags.empty()) + { + LOG(INFO) << "Galileo OSNMA: MACSEQ verification :: SUCCESS :: ADKD matches MAC Look-up table."; + return true; + } + // Fixed as well as FLX Tags share first part - Eq. 22 ICD + std::vector m(5 + 2 * flxTags.size()); // each flx tag brings two bytes + m[0] = static_cast(mack.PRNa); // PRN_A - SVID of the satellite transmiting the tag + m[1] = static_cast((GST_SFi & 0xFF000000) >> 24); + m[2] = static_cast((GST_SFi & 0x00FF0000) >> 16); + m[3] = static_cast((GST_SFi & 0x0000FF00) >> 8); + m[4] = static_cast(GST_SFi & 0x000000FF); + // Case tags flexible - Eq. 21 ICD + for (size_t i = 0; i < flxTags.size(); i++) + { + m[2 * i + 5] = mack.tag_and_info[flxTags[i]].tag_info.PRN_d; + m[2 * i + 6] = mack.tag_and_info[flxTags[i]].tag_info.ADKD << 4 | + mack.tag_and_info[flxTags[i]].tag_info.cop; + } + // compute mac + std::vector mac; + if (d_osnma_data.d_dsm_kroot_message.mf == 0) // C: HMAC-SHA-256 + { + mac = d_crypto->compute_HMAC_SHA_256(applicable_key, m); + } + else if (d_osnma_data.d_dsm_kroot_message.mf == 1) // C: CMAC-AES + { + mac = d_crypto->compute_CMAC_AES(applicable_key, m); + } + // Truncate the twelve MSBits and compare with received MACSEQ + uint16_t mac_msb = 0; + if (!mac.empty()) + { + mac_msb = (mac[0] << 8) + mac[1]; + } + uint16_t computed_macseq = (mac_msb & 0xFFF0) >> 4; + if (computed_macseq == mack.header.macseq) + { + LOG(INFO) << "Galileo OSNMA: MACSEQ verification :: SUCCESS :: FLX tags verification OK"; + return true; + } + else + { + LOG(WARNING) << "Galileo OSNMA: MACSEQ verification :: FAILURE :: FLX tags verification failed"; + return false; + } +} + + +bool osnma_msg_receiver::tag_has_nav_data_available(const Tag& t) const +{ + auto prn_it = d_satellite_nav_data.find(t.PRN_d); + if (prn_it != d_satellite_nav_data.end()) + { + // PRN was found, check if TOW exists in inner map + // LOG(INFO) << "Galileo OSNMA: hasData = true " << std::endl; + std::map tow_map = prn_it->second; + auto tow_it = tow_map.find(t.TOW - 30); + if (tow_it != tow_map.end()) + { + return true; + } + else + { + // TOW not found + return false; + } + } + else + { + // PRN was not found + // LOG(INFO) << "Galileo OSNMA: hasData = false " << std::endl; + return false; + } + return false; +} + + +bool osnma_msg_receiver::tag_has_key_available(const Tag& t) const +{ + // check adkd of tag + // if adkd = 0 or 4 => look for d_tesla_keys[t.TOW+30] + // if adkd = 12 => look for d_tesla_keys[t.TOW+300] + // return true if available, otherwise false + + if (t.ADKD == 0 || t.ADKD == 4) + { + auto it = d_tesla_keys.find(t.TOW + 30); + if (it != d_tesla_keys.end()) + { + // LOG(INFO) << "Galileo OSNMA: hasKey = true " << std::endl; + return true; + } + } + else if (t.ADKD == 12) + { + auto it = d_tesla_keys.find(t.TOW + 330); + if (it != d_tesla_keys.end()) + { + // LOG(INFO) << "Galileo OSNMA: hasKey = true " << std::endl; + return true; + } + } + // LOG(INFO) << "Galileo OSNMA: hasKey = false "; + return false; +} + + +std::vector osnma_msg_receiver::hash_chain(uint32_t num_of_hashes_needed, const std::vector& key, uint32_t GST_SFi, const uint8_t lk_bytes) const +{ + std::vector K_II = key; + std::vector K_I; // result of the recursive hash operations + std::vector msg; + // compute the tesla key for current SF (GST_SFi and K_II change in each iteration) + for (uint32_t i = 1; i <= num_of_hashes_needed; i++) + { + // build message digest m = (K_I+1 || GST_SFi || alpha) + msg.reserve(K_II.size() + sizeof(GST_SFi) + sizeof(d_osnma_data.d_dsm_kroot_message.alpha)); + std::copy(K_II.begin(), K_II.end(), std::back_inserter(msg)); + + msg.push_back((GST_SFi & 0xFF000000) >> 24); + msg.push_back((GST_SFi & 0x00FF0000) >> 16); + msg.push_back((GST_SFi & 0x0000FF00) >> 8); + msg.push_back(GST_SFi & 0x000000FF); + // extract alpha + // d_osnma_data.d_dsm_kroot_message.alpha = 0xa06221261ad9; + for (int k = 5; k >= 0; k--) + { + // TODO: static extracts the MSB in case from larger to shorter int? + msg.push_back(static_cast((d_osnma_data.d_dsm_kroot_message.alpha >> (k * 8)) & 0xFF)); // extract first 6 bytes of alpha. + } + // compute hash + std::vector hash; + if (d_osnma_data.d_dsm_kroot_message.hf == 0) // Table 8. + { + hash = d_crypto->compute_SHA_256(msg); + } + else if (d_osnma_data.d_dsm_kroot_message.hf == 2) + { + hash = d_crypto->compute_SHA3_256(msg); + } + else + { + hash = std::vector(32); + } + // truncate hash + K_I.reserve(lk_bytes); // TODO - case hash function has 512 bits + for (int k = 0; k < lk_bytes; k++) + { + K_I.push_back(hash[k]); + } + // set parameters for next iteration + GST_SFi -= 30; // next SF time is the actual minus 30 seconds + K_II = K_I; // next key is the actual one + K_I.clear(); // empty the actual one for a new computation + msg.clear(); + } + + // check that the final time matches the Kroot time + bool check; + if (!d_tesla_key_verified) + { + check = GST_SFi + 30 == d_GST_0 - 30; + } + else + { + check = GST_SFi + 30 == d_last_verified_key_GST; + } + if (!check) + { + LOG(WARNING) << "Galileo OSNMA: TESLA key chain verification error: KROOT time mismatch!"; // ICD. Eq. 18 + std::cerr << "Galileo OSNMA: TESLA key chain verification error: KROOT time mismatch!" << std::endl; + } + else + { + LOG(INFO) << "Galileo OSNMA: TESLA key chain verification: KROOT time matches."; // ICD. Eq. 18 + } + return K_II; +} + + +/** + * @brief Verifies the MAC sequence of a received MACK message. + * + * This function is responsible for verifying the MAC sequence of a received MACK message. + * It takes a reference to a constant MACK_message object as input and returns a vector containing + * the tags for which the MACSEQ verification was successful + * + * @param mack The MACK message object to verify the MAC sequence for. + * @return vector MACK_tag_and_info for which the MACSEQ was successful + */ +std::vector osnma_msg_receiver::verify_macseq_new(const MACK_message& mack) +{ + std::vector verified_tags{}; + + // MACSEQ verification + uint32_t GST_Sfi = d_GST_Sf - 30; // time of the start of SF containing MACSEQ + std::vector applicable_key; + const auto key_it = d_tesla_keys.find(mack.TOW + 30); // current tesla key ie transmitted in the next subframe + if (key_it != d_tesla_keys.cend()) + { + applicable_key = key_it->second; + } + std::vector sq1{}; + std::vector sq2{}; + std::vector applicable_sequence; + const auto it = OSNMA_TABLE_16.find(d_osnma_data.d_dsm_kroot_message.maclt); + if (it != OSNMA_TABLE_16.cend()) + { + sq1 = it->second.sequence1; + sq2 = it->second.sequence2; + } + + // Assign relevant sequence based on subframe time + if (mack.TOW % 60 < 30) // tried GST_Sf and it does not support the data present. + { + applicable_sequence = std::move(sq1); + } + else if (mack.TOW % 60 >= 30) + { + applicable_sequence = std::move(sq2); + } + if (mack.tag_and_info.size() != applicable_sequence.size() - 1) + { + LOG(WARNING) << "Galileo OSNMA: Number of retrieved tags does not match MACLT sequence size!"; + d_count_failed_macseq += mack.tag_and_info.size(); + return verified_tags; + } + std::vector flxTags{}; + std::string tempADKD; + // MACLT verification + for (size_t i = 0; i < mack.tag_and_info.size(); i++) + { + tempADKD = applicable_sequence[i + 1]; + if (tempADKD == "FLX") + { + flxTags.push_back(i); // C: just need to save the index in the sequence + } + else if (mack.tag_and_info[i].tag_info.ADKD == std::stoi(applicable_sequence[i + 1])) + { + // fill index of tags failed + LOG(INFO) << "Galileo OSNMA: MACSEQ verification :: SUCCESS :: ADKD match against MAC Look-up table for Tag=0x" + << std::setfill('0') << std::setw(10) << std::hex << std::uppercase + << mack.tag_and_info[i].tag << std::dec; + verified_tags.push_back(mack.tag_and_info[i]); + } + else + { + // discard tag + LOG(WARNING) << "Galileo OSNMA: MACSEQ verification :: FAILURE :: ADKD mismatch against MAC Look-up table for Tag=0x" + << std::setfill('0') << std::setw(10) << std::hex << std::uppercase + << mack.tag_and_info[i].tag << std::dec; + d_count_failed_macseq++; + } + } + + if (flxTags.empty() /*TODO add check d_flag_check_mackseq_fixed_tags*/) + { + LOG(INFO) << "Galileo OSNMA: MACSEQ verification :: No FLX tags to verify."; + return verified_tags; + } + // Fixed as well as FLX Tags share first part - Eq. 22 ICD + std::vector m(5 + 2 * flxTags.size()); // each flx tag brings two bytes + m[0] = static_cast(mack.PRNa); // PRN_A - SVID of the satellite transmiting the tag + m[1] = static_cast((GST_Sfi & 0xFF000000) >> 24); + m[2] = static_cast((GST_Sfi & 0x00FF0000) >> 16); + m[3] = static_cast((GST_Sfi & 0x0000FF00) >> 8); + m[4] = static_cast(GST_Sfi & 0x000000FF); + // Case tags flexible - Eq. 21 ICD + for (size_t i = 0; i < flxTags.size(); i++) + { + m[2 * i + 5] = mack.tag_and_info[flxTags[i]].tag_info.PRN_d; + m[2 * i + 6] = mack.tag_and_info[flxTags[i]].tag_info.ADKD << 4 | + mack.tag_and_info[flxTags[i]].tag_info.cop; + } + // compute mac + std::vector mac; + if (d_osnma_data.d_dsm_kroot_message.mf == 0) // C: HMAC-SHA-256 + { + mac = d_crypto->compute_HMAC_SHA_256(applicable_key, m); + } + else if (d_osnma_data.d_dsm_kroot_message.mf == 1) // C: CMAC-AES + { + mac = d_crypto->compute_CMAC_AES(applicable_key, m); + } + // Truncate the twelve MSBits and compare with received MACSEQ + uint16_t mac_msb = 0; + if (!mac.empty()) + { + mac_msb = (mac[0] << 8) + mac[1]; + } + uint16_t computed_macseq = (mac_msb & 0xFFF0) >> 4; + if (computed_macseq == mack.header.macseq) + { + LOG(INFO) << "Galileo OSNMA: MACSEQ verification :: SUCCESS :: FLX tags verification OK"; + for (uint8_t flxTag : flxTags) + { + verified_tags.push_back(mack.tag_and_info[flxTag]); + } + return verified_tags; + } + else + { + LOG(WARNING) << "Galileo OSNMA: MACSEQ verification :: FAILURE :: FLX tags verification failed"; + d_count_failed_macseq += flxTags.size(); + return verified_tags; + } +} + + +void osnma_msg_receiver::send_data_to_pvt(const std::vector& data) +{ + if (!data.empty()) + { + for (const auto& i : data) + { + const auto tmp_obj = std::make_shared(i); + this->message_port_pub(pmt::mp("OSNMA_to_PVT"), pmt::make_any(tmp_obj)); + } + } +} + + +bool osnma_msg_receiver::store_dsm_kroot(const std::vector& dsm, const uint8_t nma_header) const +{ + std::ofstream file(KROOTFILE_DEFAULT, std::ios::binary | std::ios::out); + + if (!file.is_open()) + { + return false; + } + + // NMA header + file.write(reinterpret_cast(&nma_header), 1); + + // Then writing the entire dsm_msg vector to the file + file.write(reinterpret_cast(dsm.data()), dsm.size()); + + return file.good(); +} + + +std::pair, uint8_t> osnma_msg_receiver::parse_dsm_kroot() const +{ + std::ifstream file(KROOTFILE_DEFAULT, std::ios::binary | std::ios::in); + if (!file) + { + return {std::vector(), 0}; + } + + // Read the first byte into hkroot[0] + uint8_t nma_header; + file.read(reinterpret_cast(&nma_header), 1); + + // Read the remaining file content into dsm_msg + std::vector dsm_msg((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + file.close(); + + if (file.bad()) + { + return {std::vector(), 0}; + } + + return {dsm_msg, nma_header}; +} + +void osnma_msg_receiver::set_merkle_root(const std::vector& v) +{ + d_crypto->set_merkle_root(v); +} diff --git a/src/core/libs/osnma_msg_receiver.h b/src/core/libs/osnma_msg_receiver.h new file mode 100644 index 000000000..4240d7df8 --- /dev/null +++ b/src/core/libs/osnma_msg_receiver.h @@ -0,0 +1,176 @@ +/*! + * \file osnma_msg_receiver.h + * \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 + * Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2024 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_OSNMA_MSG_RECEIVER_H +#define GNSS_SDR_OSNMA_MSG_RECEIVER_H + +#define FRIEND_TEST(test_case_name, test_name) \ + friend class test_case_name##_##test_name##_Test + +#include "galileo_inav_message.h" // for OSNMA_msg +#include "gnss_block_interface.h" // for gnss_shared_ptr +#include "osnma_data.h" // for OSNMA_data structures +#include "osnma_nav_data_manager.h" // for OSNMA_NavDataManager +#include // for gr::block +#include // for pmt::pmt_t +#include // for std::array +#include // for uint8_t +#include // for std::time_t +#include // for std::map, std::multimap +#include // for std::shared_ptr +#include // for std::string +#include // for std::pair +#include // for std::vector + +/** \addtogroup Core + * \{ */ +/** \addtogroup Core_Receiver_Library + * \{ */ + +class OSNMA_DSM_Reader; +class Gnss_Crypto; +class Osnma_Helper; +class osnma_msg_receiver; + +using osnma_msg_receiver_sptr = gnss_shared_ptr; + +osnma_msg_receiver_sptr osnma_msg_receiver_make(const std::string& pemFilePath, const std::string& merkleFilePath, bool strict_mode = false); + +/*! + * \brief GNU Radio block that receives asynchronous OSNMA messages + * from the telemetry blocks, stores them in memory, and decodes OSNMA info + * when enough data have been received. + * The decoded OSNMA data is sent to the PVT block. + */ +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& 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); + + void process_osnma_message(const std::shared_ptr& osnma_msg); + void read_nma_header(uint8_t nma_header); + void read_dsm_header(uint8_t dsm_header); + void read_dsm_block(const std::shared_ptr& osnma_msg); + void process_dsm_block(const std::shared_ptr& osnma_msg); + void process_dsm_message(const std::vector& dsm_msg, const uint8_t& nma_header); + void read_and_process_mack_block(const std::shared_ptr& osnma_msg); + void read_mack_header(); + void read_mack_body(); + void process_mack_message(); + void remove_verified_tags(); + void control_tags_awaiting_verify_size(); + void display_data(); + void send_data_to_pvt(const std::vector& data); + + bool verify_tesla_key(std::vector& key, uint32_t TOW); + 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 verify_macseq(const MACK_message& mack); + + bool store_dsm_kroot(const std::vector& dsm, const uint8_t nma_header) const; + + std::pair, uint8_t> parse_dsm_kroot() const; + std::vector get_merkle_tree_leaves(const DSM_PKR_message& dsm_pkr_message) const; + std::vector compute_merkle_root(const DSM_PKR_message& dsm_pkr_message, const std::vector& m_i) const; + std::vector build_message(Tag& tag) const; + std::vector hash_chain(uint32_t num_of_hashes_needed, const std::vector& key, uint32_t GST_SFi, const uint8_t lk_bytes) const; + std::vector verify_macseq_new(const MACK_message& mack); + + std::map> d_satellite_nav_data; // map holding OSNMA_NavData sorted by SVID (first key) and TOW (second key). + std::map> d_tesla_keys; // tesla keys over time, sorted by TOW + std::multimap d_tags_awaiting_verify; // container with tags to verify from arbitrary SVIDs, sorted by TOW + + std::vector d_new_public_key; + std::vector d_tags_to_verify{0, 4, 12}; + std::vector d_macks_awaiting_MACSEQ_verification; + + std::array, 16> d_dsm_message{}; // structure for recording DSM blocks, when filled it sends them to parse and resets itself. + std::array, 16> d_dsm_id_received{}; + std::array d_number_of_blocks{}; + std::array d_mack_message{}; // C: 480 b + + std::unique_ptr d_crypto; // class for cryptographic functions + std::unique_ptr d_dsm_reader; // osnma parameters parser + std::unique_ptr d_helper; // helper class with auxiliary functions + std::unique_ptr d_nav_data_manager; // refactor for holding and processing navigation data + + OSNMA_data d_osnma_data{}; + + uint32_t d_last_received_GST{0}; // latest GST received + 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_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_count_successful_tags{0}; + uint32_t d_count_failed_tags{0}; + uint32_t d_count_failed_Kroot{0}; + uint32_t d_count_failed_pubKey{0}; // failed public key verifications against Merkle root + uint32_t d_count_failed_macseq{0}; + + uint8_t const d_T_L{30}; // s RG Section 2.1 + uint8_t d_new_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_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_chain_renewal{false}; + bool d_flag_chain_revocation{false}; + + // Provide access to inner functions to Gtest + FRIEND_TEST(OsnmaMsgReceiverTest, TeslaKeyVerification); + FRIEND_TEST(OsnmaMsgReceiverTest, TagVerification); + FRIEND_TEST(OsnmaMsgReceiverTest, BuildTagMessageM0); + FRIEND_TEST(OsnmaMsgReceiverTest, VerifyPublicKey); + FRIEND_TEST(OsnmaMsgReceiverTest, ComputeBaseLeaf); + FRIEND_TEST(OsnmaMsgReceiverTest, ComputeMerkleRoot); + FRIEND_TEST(OsnmaTestVectors, NominalTestConf1); + FRIEND_TEST(OsnmaTestVectors, NominalTestConf2); + FRIEND_TEST(OsnmaTestVectors, PublicKeyRenewal); + FRIEND_TEST(OsnmaTestVectors, PublicKeyRevocation); + FRIEND_TEST(OsnmaTestVectors, ChainRenewal); + FRIEND_TEST(OsnmaTestVectors, ChainRevocation); + FRIEND_TEST(OsnmaTestVectors, AlertMessage); +}; + + +/** \} */ +/** \} */ +#endif // GNSS_SDR_OSNMA_MSG_RECEIVER_H diff --git a/src/core/libs/osnma_nav_data_manager.cc b/src/core/libs/osnma_nav_data_manager.cc new file mode 100644 index 000000000..73392db5b --- /dev/null +++ b/src/core/libs/osnma_nav_data_manager.cc @@ -0,0 +1,322 @@ +/*! + * \file osnma_nav_data_manager.cc + * \brief Class for Galileo OSNMA navigation data management + * \author Cesare Ghionoiu-Martinez, 2020-2023 cesare.martinez(at)proton.me + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2023 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "osnma_nav_data_manager.h" +#if USE_GLOG_AND_GFLAGS +#include // for DLOG +#else +#include +#endif + +/** + * @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. + */ +void OSNMA_NavDataManager::add_navigation_data(const std::string& nav_bits, uint32_t PRNd, uint32_t TOW) +{ + if (not have_nav_data(nav_bits, PRNd, 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); + } +} + + +/** + * @brief loops over the verified tags and updates the navigation data tag length + */ +void OSNMA_NavDataManager::update_nav_data(const std::multimap& tags_verified, uint8_t tag_size) +{ + if (d_satellite_nav_data.empty()) + { + return; + } + // loop through all tags + for (const auto& tag : 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) + { + auto sat_it = d_satellite_nav_data.find(tag.second.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 + { + std::string nav_data; + if (tag.second.ADKD == 0 || tag.second.ADKD == 12) + { + 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); + } + } + } + } +} + + +std::vector OSNMA_NavDataManager::get_verified_data() +{ + std::vector result; + for (const auto& prna : d_satellite_nav_data) + { + for (const auto& tow_navdata : prna.second) + { + if (tow_navdata.second.get_verified_bits() >= L_t_min) + { + result.push_back(tow_navdata.second); + d_satellite_nav_data[prna.first][tow_navdata.first].set_verified_status(true); + } + } + } + return result; +} + + +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 + if (tag.cop == 0) + { + if (tag.ADKD == 0 || tag.ADKD == 12) + { + return {std::string(549, '0')}; + } + else if (tag.ADKD == 4) + { + return {std::string(141, '0')}; + } + } + auto prn_it = d_satellite_nav_data.find(tag.PRN_d); + if (prn_it == d_satellite_nav_data.end()) + { + return ""; + } + // satellite was found, check if TOW exists in inner map + auto nav_data = prn_it->second.find(tag.TOW - 30); + if (nav_data != prn_it->second.end()) + { + if (tag.ADKD == 0 || tag.ADKD == 12) + { + if (!nav_data->second.get_ephemeris_data().empty()) + { + return nav_data->second.get_ephemeris_data(); + } + } + else if (tag.ADKD == 4) + { + if (!nav_data->second.get_utc_data().empty()) + { + return nav_data->second.get_utc_data(); + } + } + } + else + { + 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 ((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 (!rev_it->second.get_ephemeris_data().empty()) + { + return rev_it->second.get_ephemeris_data(); + } + } + else if (tag.ADKD == 4) + { + if (!rev_it->second.get_utc_data().empty()) + { + return rev_it->second.get_utc_data(); + } + } + } + } + } + return ""; +} + + +/** + * @brief Checks if the OSNMA_NavData bits are already present. In case affirmative, it updates the OSNMA_NavData 'last received' timestamp + * @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 + * @return + */ +bool OSNMA_NavDataManager::have_nav_data(const std::string& nav_bits, uint32_t PRNd, uint32_t TOW) +{ + if (d_satellite_nav_data.find(PRNd) != d_satellite_nav_data.end()) + { + for (auto& data_timestamp : d_satellite_nav_data[PRNd]) + { + if (nav_bits.size() == EPH_SIZE) + { + if (data_timestamp.second.get_ephemeris_data() == nav_bits) + { + data_timestamp.second.set_last_received_TOW(TOW); + return true; + } + } + else if (nav_bits.size() == UTC_SIZE) + { + if (data_timestamp.second.get_utc_data() == nav_bits) + { + data_timestamp.second.set_last_received_TOW(TOW); + return true; + } + } + } + } + return false; +} + + +/** + * @brief Checks if there is a OSNMA_NavData element within the COP time interval for a Tag t + * @param t Tag object + * @return True if the needed navigation data for the tag is available (oldest possible OSNMA_NavData available) + */ +bool OSNMA_NavDataManager::have_nav_data(const Tag& t) const +{ + if (t.cop == 0) + { + return true; + } + auto prn_it = d_satellite_nav_data.find(t.PRN_d); + if (prn_it == d_satellite_nav_data.end()) + { + return false; + } + // 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); + if (nav_data != prn_it->second.end()) + { + if (t.ADKD == 0 || t.ADKD == 12) + { + if (!nav_data->second.get_ephemeris_data().empty()) + { + return true; + } + } + else if (t.ADKD == 4) + { + if (!nav_data->second.get_utc_data().empty()) + { + return true; + } + } + } + else + { + // iterate in reverse order to find matching TOW with Tag's COP value + std::map 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 ((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 (!rev_it->second.get_ephemeris_data().empty()) + { + return true; + } + } + else if (t.ADKD == 4) + { + if (!rev_it->second.get_utc_data().empty()) + { + return true; + } + } + } + } + } + return false; +} + + +void OSNMA_NavDataManager::log_status() const +{ + for (const auto& satellite : d_satellite_nav_data) + { + LOG(INFO) << "Galileo OSNMA: NavData status :: SVID=" << satellite.first; + const auto& tow_data = satellite.second; + for (const auto& nav_data : tow_data) + { + LOG(INFO) << "Galileo OSNMA: IOD_nav=0b" << std::uppercase + << std::bitset<10>(nav_data.second.get_IOD_nav()) + << ", TOW_start=" + << nav_data.second.get_tow_sf0() + << ", TOW_last=" + << nav_data.second.get_last_received_TOW() + << ", l_t=" + << nav_data.second.get_verified_bits() + << ", PRNd=" + << nav_data.second.get_prn_d() + << ", verified=" + << nav_data.second.get_verified_status(); + } + } +} diff --git a/src/core/libs/osnma_nav_data_manager.h b/src/core/libs/osnma_nav_data_manager.h new file mode 100644 index 000000000..6beaa907d --- /dev/null +++ b/src/core/libs/osnma_nav_data_manager.h @@ -0,0 +1,59 @@ +/*! + * \file osnma_nav_data_manager.h + * \brief Class for Galileo OSNMA navigation data management + * \author Cesare Ghionoiu-Martinez, 2020-2023 cesare.martinez(at)proton.me + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2023 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_OSNMA_NAV_DATA_MANAGER_H +#define GNSS_SDR_OSNMA_NAV_DATA_MANAGER_H + +#include "osnma_data.h" // for OSNMA_NavData, Tag +#include // for uint32_t +#include +#include +#include + +/** \addtogroup Core + * \{ */ +/** \addtogroup Core_Receiver_Library + * \{ */ + +/** + * @class OSNMA_NavDataManager + * @brief Class for managing OSNMA navigation data + */ +class OSNMA_NavDataManager +{ +public: + OSNMA_NavDataManager() = default; + + 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 update_nav_data(const std::multimap& tags_verified, uint8_t tag_size); + bool have_nav_data(const std::string& nav_bits, uint32_t PRNd, uint32_t TOW); + std::vector get_verified_data(); + +private: + std::map> d_satellite_nav_data{}; // NavData sorted by [PRNd][TOW_start] + const uint32_t L_t_min{40}; + const uint16_t EPH_SIZE{549}; + const uint16_t UTC_SIZE{141}; +}; + +/** \} */ +/** \} */ +#endif // GNSS_SDR_OSNMA_NAV_DATA_MANAGER_H diff --git a/src/core/libs/supl/CMakeLists.txt b/src/core/libs/supl/CMakeLists.txt index a08631e91..78ab2b80e 100644 --- a/src/core/libs/supl/CMakeLists.txt +++ b/src/core/libs/supl/CMakeLists.txt @@ -46,15 +46,9 @@ if(CMAKE_C_COMPILER_ID MATCHES "Clang|GNU" AND (CMAKE_VERSION VERSION_GREATER "3 target_compile_options(core_libs_supl PUBLIC $<$:${MY_C_FLAGS}>) endif() -if(OPENSSL_FOUND) - target_compile_definitions(core_libs_supl PUBLIC -DUSE_OPENSSL_FALLBACK=1) -endif() - -target_link_libraries(core_libs_supl - PUBLIC - ${GNUTLS_LIBRARIES} - ${GNUTLS_OPENSSL_LIBRARY} -) +# links to the appropriate library and defines +# USE_GNUTLS_FALLBACK, USE_OPENSSL_3, or USE_OPENSSL_111 accordingly. +link_to_crypto_dependencies(core_libs_supl) target_include_directories(core_libs_supl PUBLIC diff --git a/src/core/libs/supl/supl.h b/src/core/libs/supl/supl.h index c879112f6..8c9bef533 100644 --- a/src/core/libs/supl/supl.h +++ b/src/core/libs/supl/supl.h @@ -24,18 +24,18 @@ #define EXPORT #endif // clang-format off -#if USE_OPENSSL_FALLBACK -#include -#include -#include -#include -#include -#else +#if USE_GNUTLS_FALLBACK #include #include #include #include #include +#else +#include +#include +#include +#include +#include #endif // clang-format on #include diff --git a/src/core/receiver/gnss_flowgraph.cc b/src/core/receiver/gnss_flowgraph.cc index 2963b03a5..785672670 100644 --- a/src/core/receiver/gnss_flowgraph.cc +++ b/src/core/receiver/gnss_flowgraph.cc @@ -27,6 +27,7 @@ #include "Galileo_E5a.h" #include "Galileo_E5b.h" #include "Galileo_E6.h" +#include "Galileo_OSNMA.h" #include "channel.h" #include "channel_fsm.h" #include "channel_interface.h" @@ -82,6 +83,7 @@ GNSSFlowgraph::GNSSFlowgraph(std::shared_ptr configurati connected_(false), running_(false), multiband_(GNSSFlowgraph::is_multiband()), + enable_osnma_rx_(false), enable_e6_has_rx_(false) { enable_fpga_offloading_ = configuration_->property("GNSS-SDR.enable_FPGA", false); @@ -120,6 +122,24 @@ void GNSSFlowgraph::init() galileo_tow_map_ = nullptr; } + if (configuration_->property("Channels_1B.count", 0) > 0 && configuration_->property("GNSS-SDR.osnma_enable", true)) + { + 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") + { + strict_mode = true; + } + osnma_rx_ = osnma_msg_receiver_make(certFilePath, merKleTreePath, strict_mode); + } + else + { + osnma_rx_ = nullptr; + } + // 1. read the number of RF front-ends available (one file_source per RF front-end) int sources_count_deprecated = configuration_->property("Receiver.sources_count", 1); sources_count_ = configuration_->property("GNSS-SDR.num_sources", sources_count_deprecated); @@ -520,6 +540,14 @@ int GNSSFlowgraph::connect_desktop_flowgraph() } } + if (enable_osnma_rx_) + { + if (connect_osnma() != 0) + { + return 1; + } + } + // Activate acquisition in enabled channels std::lock_guard lock(signal_list_mutex_); for (int i = 0; i < channels_count_; i++) @@ -640,6 +668,14 @@ int GNSSFlowgraph::connect_fpga_flowgraph() } } + if (enable_osnma_rx_) + { + if (connect_osnma() != 0) + { + return 1; + } + } + check_desktop_conf_in_fpga_env(); LOG(INFO) << "The GNU Radio flowgraph for the current GNSS-SDR configuration with FPGA off-loading has been successfully connected"; @@ -1365,6 +1401,42 @@ int GNSSFlowgraph::connect_monitors() } +int GNSSFlowgraph::connect_osnma() +{ + try + { + bool gal_e1_channels = false; + for (int i = 0; i < channels_count_; i++) + { + const std::string gnss_signal = channels_.at(i)->get_signal().get_signal_str(); + switch (mapStringValues_[gnss_signal]) + { + case evGAL_1B: + top_block_->msg_connect(channels_.at(i)->get_right_block(), pmt::mp("OSNMA_from_TLM"), osnma_rx_, pmt::mp("OSNMA_from_TLM")); + gal_e1_channels = true; + break; + + default: + break; + } + } + + if (gal_e1_channels == true) + { + top_block_->msg_connect(osnma_rx_, pmt::mp("OSNMA_to_PVT"), pvt_->get_left_block(), pmt::mp("OSNMA_to_PVT")); + } + } + catch (const std::exception& e) + { + LOG(ERROR) << "Can't connect Galileo OSNMA msg ports: " << e.what(); + top_block_->disconnect_all(); + return 1; + } + DLOG(INFO) << "Galileo OSNMA message ports connected"; + return 0; +} + + int GNSSFlowgraph::connect_gal_e6_has() { try diff --git a/src/core/receiver/gnss_flowgraph.h b/src/core/receiver/gnss_flowgraph.h index be7c5e8bf..2616c8025 100644 --- a/src/core/receiver/gnss_flowgraph.h +++ b/src/core/receiver/gnss_flowgraph.h @@ -30,6 +30,7 @@ #include "galileo_tow_map.h" #include "gnss_sdr_sample_counter.h" #include "gnss_signal.h" +#include "osnma_msg_receiver.h" #include "pvt_interface.h" #include // for null_sink #include // for basic_block_sptr, top_block_sptr @@ -179,6 +180,7 @@ private: int connect_channels_to_observables(); int connect_observables_to_pvt(); int connect_monitors(); + int connect_osnma(); int connect_gal_e6_has(); int connect_gnss_synchro_monitor(); int connect_acquisition_monitor(); @@ -234,6 +236,7 @@ private: channel_status_msg_receiver_sptr channels_status_; // class that receives and stores the current status of the receiver channels galileo_e6_has_msg_receiver_sptr gal_e6_has_rx_; galileo_tow_map_sptr galileo_tow_map_; + osnma_msg_receiver_sptr osnma_rx_; gnss_sdr_sample_counter_sptr ch_out_sample_counter_; #if ENABLE_FPGA @@ -290,6 +293,7 @@ private: bool enable_tracking_monitor_; bool enable_navdata_monitor_; bool enable_fpga_offloading_; + bool enable_osnma_rx_; bool enable_e6_has_rx_; }; diff --git a/src/core/system_parameters/CMakeLists.txt b/src/core/system_parameters/CMakeLists.txt index fa0fa9983..f2c5efce1 100644 --- a/src/core/system_parameters/CMakeLists.txt +++ b/src/core/system_parameters/CMakeLists.txt @@ -28,6 +28,8 @@ set(SYSTEM_PARAMETERS_SOURCES glonass_gnav_utc_model.cc glonass_gnav_navigation_message.cc reed_solomon.cc + osnma_data.cc + osnma_dsm_reader.cc ) set(SYSTEM_PARAMETERS_HEADERS @@ -89,6 +91,9 @@ set(SYSTEM_PARAMETERS_HEADERS MATH_CONSTANTS.h reed_solomon.h galileo_has_page.h + Galileo_OSNMA.h + osnma_data.h + osnma_dsm_reader.h ) list(SORT SYSTEM_PARAMETERS_HEADERS) @@ -114,6 +119,8 @@ target_link_libraries(core_system_parameters PUBLIC Boost::date_time Boost::serialization + PRIVATE + Pugixml::pugixml ) if(ENABLE_GLOG_AND_GFLAGS) diff --git a/src/core/system_parameters/Galileo_OSNMA.h b/src/core/system_parameters/Galileo_OSNMA.h new file mode 100644 index 000000000..b394781cd --- /dev/null +++ b/src/core/system_parameters/Galileo_OSNMA.h @@ -0,0 +1,199 @@ +/*! + * \file Galileo_OSNMA.h + * \brief Galileo OSNMA mesage constants + * \author Carles Fernandez, 2023. 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-2023 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_GALILEO_OSNMA_H +#define GNSS_SDR_GALILEO_OSNMA_H + +#include +#include +#include +#include +#include +#include + +/** \addtogroup Core + * \{ */ +/** \addtogroup System_Parameters + * \{ */ + +constexpr size_t SIZE_DSM_BLOCKS_BYTES = 13; + +// OSNMA User ICD, Issue 1.1, Table 1 +const std::unordered_map OSNMA_TABLE_1 = { + {0, std::string("Reserved")}, + {1, std::string("Test")}, + {2, std::string("Operational")}, + {3, std::string("Don't use")}}; // key: nmas, value: nmas status + + +// OSNMA User ICD, Issue 1.1, Table 2 +const std::unordered_map OSNMA_TABLE_2 = { + {0, std::string("Reserved")}, + {1, std::string("Nominal")}, + {2, std::string("End of Chain (EOC)")}, + {3, std::string("Chain Revoked (CREV)")}, + {4, std::string("New Publick Key (NPK)")}, + {5, std::string("Public Key Revoked (PKREV)")}, + {6, std::string("New Merkle Tree (NMT)")}, + {7, std::string("Alert Message (AM)")}}; // key: cpks, value: cpks status + +// OSNMA User ICD for the Test Phase, Issue 1.0, Table 3 +const std::unordered_map> OSNMA_TABLE_3 = { + {0, {0, 0}}, + {1, {0, 0}}, + {2, {0, 0}}, + {3, {0, 0}}, + {4, {0, 0}}, + {5, {0, 0}}, + {6, {0, 0}}, + {7, {13, 1352}}, + {8, {14, 1456}}, + {9, {15, 1560}}, + {10, {16, 1664}}, + {11, {0, 0}}, + {12, {0, 0}}, + {13, {0, 0}}, + {14, {0, 0}}, + {15, {0, 0}}}; // key: nb_dp, value: {num_blocks, l_dp_bits} + +const std::unordered_map OSNMA_TABLE_5 = { + {0, std::string("Reserved")}, + {1, std::string("ECDSA P-256")}, + {2, std::string("Reserved")}, + {3, std::string("ECDSA P-521")}, + {4, std::string("OAM")}, + {5, std::string("Reserved")}, + {6, std::string("Reserved")}, + {7, std::string("Reserved")}, + {8, std::string("Reserved")}, + {9, std::string("Reserved")}, + {10, std::string("Reserved")}, + {11, std::string("Reserved")}, + {12, std::string("Reserved")}, + {13, std::string("Reserved")}, + {14, std::string("Reserved")}, + {15, std::string("Reserved")}}; // key: nptk, value: message + +const std::unordered_map OSNMA_TABLE_6 = { + {std::string("ECDSA P-256"), 264}, + {std::string("ECDSA P-521"), 536}}; + +// OSNMA User ICD, Issue 1.1, Table 7 +const std::unordered_map> OSNMA_TABLE_7 = { + {0, {0, 0}}, + {1, {7, 728}}, + {2, {8, 832}}, + {3, {9, 936}}, + {4, {10, 1040}}, + {5, {11, 1144}}, + {6, {12, 1248}}, + {7, {13, 1352}}, + {8, {14, 1456}}, + {9, {0, 0}}, + {10, {0, 0}}, + {11, {0, 0}}, + {12, {0, 0}}, + {13, {0, 0}}, + {14, {0, 0}}, + {15, {0, 0}}}; // key: nb_dk, value: {num_blocks, l_dk_bits} + +const std::unordered_map OSNMA_TABLE_8 = { + {0, std::string("SHA-256")}, + {1, std::string("Reserved")}, + {2, std::string("SHA3-256")}, + {3, std::string("Reserved")}}; // key: hs, value: hash_function + +const std::unordered_map OSNMA_TABLE_10 = { + {0, 96}, + {1, 104}, + {2, 112}, + {3, 120}, + {4, 128}, + {5, 160}, + {6, 192}, + {7, 224}, + {8, 256}, + {9, 0}, + {10, 0}, + {11, 0}, + {12, 0}, + {13, 0}, + {15, 0}, + {15, 0}}; // key: ks, value: lk_bits + +const std::unordered_map OSNMA_TABLE_11 = { + {0, 0}, + {1, 0}, + {2, 0}, + {3, 0}, + {4, 0}, + {5, 20}, + {6, 24}, + {7, 28}, + {8, 32}, + {9, 40}, + {10, 0}, + {11, 0}, + {12, 0}, + {13, 0}, + {14, 0}, + {15, 0}, +}; + +const std::unordered_map OSNMA_TABLE_15 = { + {std::string("ECDSA P-256"), 512}, + {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 KROOTFILE_DEFAULT("./OSNMA_DSM_KROOT_NMAHeader.bin"); + +class Mack_lookup +{ +public: + Mack_lookup() = default; + Mack_lookup(uint8_t msg_, + uint8_t nt_, + const std::vector& s1_, + const std::vector& s2_) : msg(msg_), + nt(nt_), + sequence1(s1_), + sequence2(s2_){}; + uint8_t msg{}; + uint8_t nt{}; + std::vector sequence1; + std::vector sequence2; +}; + +const std::unordered_map OSNMA_TABLE_16 = { + {27, {2, 6, {"00S", "00E", "00E", "00E", "12S", "00E"}, {"00S ", "00E", "00E", "04S", "12S", "00E"}}}, + {28, {2, 10, {"00S", "00E", "00E", "00E", "00S", "00E", "00E", "12S", "00E", "00E"}, {"00S", "00E", "00E", "00S", "00E", "00E", "04S", "12S", "00E", "00E"}}}, + {31, {2, 5, {"00S", "00E", "00E", "12S", "00E"}, {"00S", "00E", "00E", "12S", "04S"}}}, + {33, {2, 6, {"00S", "00E", "04S", "00E", "12S", "00E"}, {"00S", "00E", "00E", "12S", "00E", "12E"}}}, + {34, {2, 6, {"00S", "FLX", "04S", "FLX", "12S", "00E"}, {"00S", "FLX", "00E", "12S", "00E", "12E"}}}, + {35, {2, 6, {"00S", "FLX", "04S", "FLX", "12S", "FLX"}, {"00S", "FLX", "FLX", "12S", "FLX", "FLX"}}}, + {36, {2, 5, {"00S", "FLX", "04S", "FLX", "12S"}, {"00S", "FLX", "00E", "12S", "12E"}}}, + {37, {2, 5, {"00S", "00E", "04S", "00E", "12S"}, {"00S", "00E", "00E", "12S", "12E"}}}, + {38, {2, 5, {"00S", "FLX", "04S", "FLX", "12S"}, {"00S", "FLX", "FLX", "12S", "FLX"}}}, + {39, {2, 4, {"00S", "FLX", "04S", "FLX"}, {"00S", "FLX", "00E", "12S"}}}, + {40, {2, 4, {"00S", "00E", "04S", "12S"}, {"00S", "00E", "00E", "12E"}}}, + {41, {2, 4, {"00S", "FLX", "04S", "FLX"}, {"00S", "FLX", "FLX", "12S"}}}}; + +/** \} */ +/** \} */ +#endif // GNSS_SDR_GALILEO_OSNMA_H \ No newline at end of file diff --git a/src/core/system_parameters/galileo_inav_message.cc b/src/core/system_parameters/galileo_inav_message.cc index d97ca6a90..cb23ddac3 100644 --- a/src/core/system_parameters/galileo_inav_message.cc +++ b/src/core/system_parameters/galileo_inav_message.cc @@ -143,8 +143,6 @@ bool Galileo_Inav_Message::read_navigation_bool(const std::bitset page_type_bits(page_number_bits); // from string to bitset - Page_type = static_cast(read_page_type_unsigned(page_type_bits, TYPE)); - Page_type_time_stamp = Page_type; const std::string Data_jk_ephemeris = Data_k + Data_j; page_jk_decoder(Data_jk_ephemeris.c_str()); + + // Fill OSNMA data + if (page_position_in_inav_subframe != 255) + { + if (page_position_in_inav_subframe == 0) + { // TODO - is it redundant? receiving Word 2 already resets this + nma_position_filled = std::array{}; + nma_msg.mack = std::array{}; + nma_msg.hkroot = std::array{}; + } + 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) + { + hkroot_sis = static_cast(hkroot_bs.to_ulong()); + mack_sis = static_cast(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; + } + } } else { @@ -201,6 +216,7 @@ void Galileo_Inav_Message::split_page(std::string page_string, int32_t flag_even } +// C: tells if W1-->W4 available from same blcok bool Galileo_Inav_Message::have_new_ephemeris() // Check if we have a new ephemeris stored in the galileo navigation class { if ((flag_ephemeris_1 == true) and (flag_ephemeris_2 == true) and (flag_ephemeris_3 == true) and (flag_ephemeris_4 == true) and (flag_iono_and_GST == true)) @@ -335,6 +351,7 @@ bool Galileo_Inav_Message::have_new_ephemeris() // Check if we have a new ephem } +// C: tells if W5 is available bool Galileo_Inav_Message::have_new_iono_and_GST() // Check if we have a new iono data set stored in the galileo navigation class { if ((flag_iono_and_GST == true) and (flag_utc_model == true)) // the condition on flag_utc_model is added to have a time stamp for iono @@ -347,6 +364,7 @@ bool Galileo_Inav_Message::have_new_iono_and_GST() // Check if we have a new io } +// C: tells if W6 is available bool Galileo_Inav_Message::have_new_utc_model() // Check if we have a new utc data set stored in the galileo navigation class { if (flag_utc_model == true) @@ -359,6 +377,7 @@ bool Galileo_Inav_Message::have_new_utc_model() // Check if we have a new utc d } +// flag_almanac_4 tells if W10 available. bool Galileo_Inav_Message::have_new_almanac() // Check if we have a new almanac data set stored in the galileo navigation class { if ((flag_almanac_1 == true) and (flag_almanac_2 == true) and (flag_almanac_3 == true) and (flag_almanac_4 == true)) @@ -599,6 +618,7 @@ void Galileo_Inav_Message::read_page_1(const std::bitset& DLOG(INFO) << "A_1= " << A_1; flag_ephemeris_1 = true; DLOG(INFO) << "flag_tow_set" << flag_TOW_set; + nav_bits_word_1 = data_bits.to_string().substr(6, 120); } @@ -620,6 +640,7 @@ void Galileo_Inav_Message::read_page_2(const std::bitset& DLOG(INFO) << "iDot_2= " << iDot_2; flag_ephemeris_2 = true; DLOG(INFO) << "flag_tow_set" << flag_TOW_set; + nav_bits_word_2 = data_bits.to_string().substr(6, 120); } @@ -649,6 +670,7 @@ void Galileo_Inav_Message::read_page_3(const std::bitset& DLOG(INFO) << "SISA_3= " << SISA_3; flag_ephemeris_3 = true; DLOG(INFO) << "flag_tow_set" << flag_TOW_set; + nav_bits_word_3 = data_bits.to_string().substr(6, 122); } @@ -657,6 +679,7 @@ void Galileo_Inav_Message::read_page_4(const std::bitset& IOD_nav_4 = static_cast(read_navigation_unsigned(data_bits, IOD_NAV_4_BIT)); DLOG(INFO) << "IOD_nav_4= " << IOD_nav_4; SV_ID_PRN_4 = static_cast(read_navigation_unsigned(data_bits, SV_ID_PRN_4_BIT)); + nma_msg.PRN = static_cast(SV_ID_PRN_4); DLOG(INFO) << "SV_ID_PRN_4= " << SV_ID_PRN_4; C_ic_4 = static_cast(read_navigation_signed(data_bits, C_IC_4_BIT)); C_ic_4 = C_ic_4 * C_IC_4_LSB; @@ -681,6 +704,7 @@ void Galileo_Inav_Message::read_page_4(const std::bitset& DLOG(INFO) << "spare_4 = " << spare_4; flag_ephemeris_4 = true; DLOG(INFO) << "flag_tow_set" << flag_TOW_set; + nav_bits_word_4 = data_bits.to_string().substr(6, 120); } @@ -815,6 +839,7 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) { case 1: // Word type 1: Ephemeris (1/4) { + page_position_in_inav_subframe = 10; read_page_1(data_jk_bits); if (enable_rs) { @@ -851,6 +876,14 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) case 2: // Word type 2: Ephemeris (2/4) { + // start of subframe, reset osnma parameters TODO - refactor + page_position_in_inav_subframe = 0; + nma_msg.mack = std::array{}; + nma_msg.hkroot = std::array{}; + nma_position_filled = std::array{}; + reset_osnma_nav_bits_adkd4(); + reset_osnma_nav_bits_adkd0_12(); + read_page_2(data_jk_bits); if (enable_rs) { @@ -882,6 +915,7 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) } case 3: // Word type 3: Ephemeris (3/4) and SISA { + page_position_in_inav_subframe = 11; read_page_3(data_jk_bits); if (enable_rs) { @@ -914,6 +948,7 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) case 4: // Word type 4: Ephemeris (4/4) and Clock correction parameters { + page_position_in_inav_subframe = 1; read_page_4(data_jk_bits); if (enable_rs) { @@ -945,6 +980,7 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) } case 5: // Word type 5: Ionospheric correction, BGD, signal health and data validity status and GST + page_position_in_inav_subframe = 12; // Ionospheric correction ai0_5 = static_cast(read_navigation_unsigned(data_jk_bits, AI0_5_BIT)); ai0_5 = ai0_5 * AI0_5_LSB; @@ -992,9 +1028,11 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) flag_iono_and_GST = true; // set to false externally flag_TOW_set = true; // set to false externally DLOG(INFO) << "flag_tow_set" << flag_TOW_set; + nav_bits_word_5 = data_jk_bits.to_string().substr(6, 67); break; case 6: // Word type 6: GST-UTC conversion parameters + page_position_in_inav_subframe = 2; A0_6 = static_cast(read_navigation_signed(data_jk_bits, A0_6_BIT)); A0_6 = A0_6 * A0_6_LSB; DLOG(INFO) << "A0_6= " << A0_6; @@ -1020,9 +1058,11 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) flag_utc_model = true; // set to false externally flag_TOW_set = true; // set to false externally DLOG(INFO) << "flag_tow_set" << flag_TOW_set; + nav_bits_word_6 = data_jk_bits.to_string().substr(6, 99); break; case 7: // Word type 7: Almanac for SVID1 (1/2), almanac reference time and almanac reference week number + page_position_in_inav_subframe = 3; IOD_a_7 = static_cast(read_navigation_unsigned(data_jk_bits, IOD_A_7_BIT)); DLOG(INFO) << "IOD_a_7= " << IOD_a_7; WN_a_7 = static_cast(read_navigation_unsigned(data_jk_bits, WN_A_7_BIT)); @@ -1057,7 +1097,8 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) DLOG(INFO) << "flag_tow_set" << flag_TOW_set; break; - case 8: // Word type 8: Almanac for SVID1 (2/2) and SVID2 (1/2)*/ + case 8: // Word type 8: Almanac for SVID1 (2/2) and SVID2 (1/2) + page_position_in_inav_subframe = 4; IOD_a_8 = static_cast(read_navigation_unsigned(data_jk_bits, IOD_A_8_BIT)); DLOG(INFO) << "IOD_a_8= " << IOD_a_8; af0_8 = static_cast(read_navigation_signed(data_jk_bits, AF0_8_BIT)); @@ -1095,6 +1136,7 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) break; case 9: // Word type 9: Almanac for SVID2 (2/2) and SVID3 (1/2) + page_position_in_inav_subframe = 3; IOD_a_9 = static_cast(read_navigation_unsigned(data_jk_bits, IOD_A_9_BIT)); DLOG(INFO) << "IOD_a_9= " << IOD_a_9; WN_a_9 = static_cast(read_navigation_unsigned(data_jk_bits, WN_A_9_BIT)); @@ -1134,6 +1176,7 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) break; case 10: // Word type 10: Almanac for SVID3 (2/2) and GST-GPS conversion parameters + page_position_in_inav_subframe = 4; IOD_a_10 = static_cast(read_navigation_unsigned(data_jk_bits, IOD_A_10_BIT)); DLOG(INFO) << "IOD_a_10= " << IOD_a_10; Omega0_10 = static_cast(read_navigation_signed(data_jk_bits, OMEGA0_10_BIT)); @@ -1172,6 +1215,7 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) DLOG(INFO) << "WN_0G_10= " << WN_0G_10; flag_almanac_4 = true; DLOG(INFO) << "flag_tow_set" << flag_TOW_set; + nav_bits_word_10 = data_jk_bits.to_string().substr(86, 42); break; case 16: // Word type 16: Reduced Clock and Ephemeris Data (CED) parameters @@ -1205,6 +1249,7 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) case 17: // Word type 17: FEC2 Reed-Solomon for CED { + page_position_in_inav_subframe = 5; if (enable_rs) { IODnav_LSB17 = read_octet_unsigned(data_jk_bits, RS_IODNAV_LSBS); @@ -1234,6 +1279,7 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) case 18: // Word type 18: FEC2 Reed-Solomon for CED { + page_position_in_inav_subframe = 5; if (enable_rs) { IODnav_LSB18 = read_octet_unsigned(data_jk_bits, RS_IODNAV_LSBS); @@ -1263,6 +1309,7 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) case 19: // Word type 19: FEC2 Reed-Solomon for CED { + page_position_in_inav_subframe = 6; if (enable_rs) { IODnav_LSB19 = read_octet_unsigned(data_jk_bits, RS_IODNAV_LSBS); @@ -1292,6 +1339,7 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) case 20: // Word type 20: FEC2 Reed-Solomon for CED { + page_position_in_inav_subframe = 6; if (enable_rs) { IODnav_LSB20 = read_octet_unsigned(data_jk_bits, RS_IODNAV_LSBS); @@ -1338,5 +1386,85 @@ int32_t Galileo_Inav_Message::page_jk_decoder(const char* data_jk) default: break; } + + if (page_position_in_inav_subframe > 14 && + page_position_in_inav_subframe != 255) + { + // something weird happened, reset + page_position_in_inav_subframe = 255; + nma_position_filled = std::array{}; + nma_msg.mack = std::array{}; + nma_msg.hkroot = std::array{}; + reset_osnma_nav_bits_adkd4(); + reset_osnma_nav_bits_adkd0_12(); + } + return page_number; } + + +/** + * @brief Get data relevant for Galileo OSNMA + * + * \details This function retrieves various parameters and data to compose the OSNMA_msg. + * It fills the TOW and WN fields of the message and retrieves ephemeris, iono, and + * + * @return The OSNMA message + */ +OSNMA_msg Galileo_Inav_Message::get_osnma_msg() +{ + nma_position_filled = std::array{}; + // Fill TOW and WN + nma_msg.WN_sf0 = WN_0; + int32_t TOW_sf0 = TOW_5 - 25; + if (TOW_sf0 < 0) + { + TOW_sf0 += 604800; + } + nma_msg.TOW_sf0 = static_cast(TOW_sf0); + return nma_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; + } +} + + +std::string Galileo_Inav_Message::get_osnma_adkd_4_nav_bits() +{ + nav_bits_adkd_4 = nav_bits_word_6 + nav_bits_word_10; + return nav_bits_adkd_4; +} + + +std::string Galileo_Inav_Message::get_osnma_adkd_0_12_nav_bits() +{ + nav_bits_adkd_0_12 = nav_bits_word_1 + nav_bits_word_2 + nav_bits_word_3 + nav_bits_word_4 + nav_bits_word_5; + return nav_bits_adkd_0_12; +} + + +void Galileo_Inav_Message::reset_osnma_nav_bits_adkd0_12() +{ + nav_bits_word_1 = ""; + nav_bits_word_2 = ""; + nav_bits_word_3 = ""; + nav_bits_word_4 = ""; + nav_bits_word_5 = ""; +} + + +void Galileo_Inav_Message::reset_osnma_nav_bits_adkd4() +{ + nav_bits_word_6 = ""; + nav_bits_word_10 = ""; +} diff --git a/src/core/system_parameters/galileo_inav_message.h b/src/core/system_parameters/galileo_inav_message.h index 1b37e9083..92f3963a1 100644 --- a/src/core/system_parameters/galileo_inav_message.h +++ b/src/core/system_parameters/galileo_inav_message.h @@ -25,6 +25,7 @@ #include "galileo_iono.h" #include "galileo_utc_model.h" #include "gnss_sdr_make_unique.h" // for std::unique_ptr in C++11 +#include #include #include #include @@ -38,7 +39,19 @@ class ReedSolomon; // Forward declaration of the ReedSolomon class * \{ */ /** \addtogroup System_Parameters * \{ */ - +/*! + * \brief This class fills the OSNMA_msg structure with the data received from the telemetry blocks. + */ +class OSNMA_msg +{ +public: + OSNMA_msg() = default; + std::array mack{}; + std::array hkroot{}; + 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 +}; /*! * \brief This class handles the Galileo I/NAV Data message, as described in the @@ -57,13 +70,6 @@ public: */ void split_page(std::string page_string, int32_t flag_even_word); - /* - * \brief Takes in input Data_jk (128 bit) and split it in ephemeris parameters according ICD 4.3.5 - * - * Takes in input Data_jk (128 bit) and split it in ephemeris parameters according ICD 4.3.5 - */ - int32_t page_jk_decoder(const char* data_jk); - /* * \brief Returns true if new Ephemeris has arrived. The flag is set to false when the function is executed */ @@ -89,6 +95,11 @@ public: */ bool have_new_reduced_ced(); + /* + * \brief Returns true if new NMA data have arrived. The flag is set to false when the function is executed + */ + bool have_new_nma(); + /* * \brief Returns a Galileo_Ephemeris object filled with the latest navigation data received */ @@ -114,6 +125,31 @@ public: */ Galileo_Ephemeris get_reduced_ced() const; + /* + * \brief Returns an OSNMA_msg object filled with the latest NMA message received. Resets msg buffer. + */ + OSNMA_msg get_osnma_msg(); + + /* + * @brief Retrieves the OSNMA ADKD 4 NAV bits. Resets the string. + */ + std::string get_osnma_adkd_4_nav_bits(); + + /* + * @brief Resets the OSNMA ADKD 4 NAV bits. + */ + void reset_osnma_nav_bits_adkd4(); + + /* + * @brief Retrieves the OSNMA ADKD 0/12 NAV bits. Resets the string. + */ + std::string get_osnma_adkd_0_12_nav_bits(); + + /* + * @brief Resets the OSNMA ADKD 0/12 NAV bits. + */ + void reset_osnma_nav_bits_adkd0_12(); + inline bool get_flag_CRC_test() const { return flag_CRC_test; @@ -210,6 +246,11 @@ public: inline void init_PRN(uint32_t prn) { SV_ID_PRN_4 = prn; + nma_msg.PRN = prn; + nma_msg.mack = std::array{}; + nma_msg.hkroot = std::array{}; + page_position_in_inav_subframe = 255; + nma_position_filled = std::array{}; } /* @@ -242,7 +283,7 @@ private: std::unique_ptr rs; // The Reed-Solomon decoder std::vector inav_rs_pages; // Pages 1,2,3,4,17,18,19,20. Holds 1 if the page has arrived, 0 otherwise. - int32_t Page_type_time_stamp{}; + int32_t page_jk_decoder(const char* data_jk); int32_t IOD_ephemeris{}; // Word type 1: Ephemeris (1/4) @@ -394,6 +435,22 @@ private: int32_t current_IODnav{}; + // OSNMA + uint32_t mack_sis{}; + uint8_t hkroot_sis{}; + uint8_t page_position_in_inav_subframe{255}; + std::array nma_position_filled{}; + OSNMA_msg nma_msg{}; + std::string nav_bits_adkd_4{}; + std::string nav_bits_word_6{}; + std::string nav_bits_word_10{}; + std::string nav_bits_adkd_0_12{}; + std::string nav_bits_word_1{}; + std::string nav_bits_word_2{}; + std::string nav_bits_word_3{}; + std::string nav_bits_word_4{}; + std::string nav_bits_word_5{}; + uint8_t IODnav_LSB17{}; uint8_t IODnav_LSB18{}; uint8_t IODnav_LSB19{}; diff --git a/src/core/system_parameters/osnma_data.cc b/src/core/system_parameters/osnma_data.cc new file mode 100644 index 000000000..b56d3b940 --- /dev/null +++ b/src/core/system_parameters/osnma_data.cc @@ -0,0 +1,45 @@ +/*! + * \file osnma_data.cc + * \brief Class for Galileo OSNMA data storage + * \author Carles Fernandez-Prades, 2020-2023 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-2023 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "osnma_data.h" + +uint32_t Tag::id_counter = 0; +uint32_t OSNMA_NavData::id_counter = 0; + +bool OSNMA_NavData::add_nav_data(const std::string& nav_data) +{ + if (nav_data.size() == 549) + { + d_ephemeris_iono = nav_data; + std::bitset<10> bits(nav_data.substr(0, 10)); + IOD_nav = static_cast(bits.to_ulong()); + return true; + } + else if (nav_data.size() == 141) + { + d_utc = nav_data; + return true; + } + 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; +} diff --git a/src/core/system_parameters/osnma_data.h b/src/core/system_parameters/osnma_data.h new file mode 100644 index 000000000..7e6d03b42 --- /dev/null +++ b/src/core/system_parameters/osnma_data.h @@ -0,0 +1,250 @@ +/*! + * \file osnma_data.h + * \brief Class for Galileo OSNMA data storage + * \author Carles Fernandez-Prades, 2020-2023 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-2023 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#ifndef GNSS_SDR_OSNMA_DATA_H +#define GNSS_SDR_OSNMA_DATA_H + +#include "galileo_inav_message.h" // for OSNMA_msg +#include +#include +#include +#include +#include + +/** \addtogroup Core + * \{ */ +/** \addtogroup System_Parameters + * \{ */ + +class DSM_nma_header +{ +public: + DSM_nma_header() = default; + uint8_t nmas{}; + uint8_t cid{}; + uint8_t cpks{}; + bool reserved{}; +}; + + +class DSM_dsm_header +{ +public: + DSM_dsm_header() = default; + uint8_t dsm_id{}; + uint8_t dsm_block_id{}; +}; + + +class MACK_header +{ +public: + MACK_header() = default; + uint64_t tag0{}; + uint16_t macseq{}; + uint8_t cop{}; +}; + + +class MACK_tag_info +{ +public: + MACK_tag_info() = default; + uint8_t PRN_d{}; + uint8_t ADKD{}; + uint8_t cop{}; +}; + + +class MACK_tag_and_info +{ +public: + MACK_tag_and_info() = default; + uint64_t tag; // C: 20-40 bits + MACK_tag_info tag_info; + uint32_t counter; // CTR +}; + + +class DSM_PKR_message +{ +public: + DSM_PKR_message() = default; + + std::array itn{}; // bitset<1024> + std::vector npk; + std::vector p_dp; + uint8_t nb_dp{}; + uint8_t mid{}; + uint8_t npkt{}; + uint8_t npktid{}; +}; + + +class DSM_KROOT_message +{ +public: + DSM_KROOT_message() = default; + + std::vector kroot; + std::vector ds; + std::vector p_dk; + uint64_t alpha{}; + uint16_t wn_k{}; + uint8_t nb_dk{}; + uint8_t pkid{}; + uint8_t cidkr{}; + uint8_t reserved1{}; + uint8_t hf{}; + uint8_t mf{}; + uint8_t ks{}; // key size, in bits + uint8_t ts{}; + uint8_t maclt{}; + uint8_t reserved{}; + uint8_t towh_k{}; + bool verified{false}; +}; + + +class MACK_message +{ +public: + MACK_message() = default; + MACK_header header; + std::vector tag_and_info; + std::vector key; + uint32_t TOW; // TODO duplicated variable, also in OSNMA_NavData + uint32_t WN; + uint32_t PRNa; +}; + + +class OSNMA_NavData +{ +public: + OSNMA_NavData() : nav_data_id(id_counter++) {} + const uint32_t nav_data_id; + std::string get_utc_data() const; + std::string get_ephemeris_data() const; + 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_last_received_TOW() const { return d_last_received_TOW; } + uint32_t get_tow_sf0() const { return d_TOW_sf0; } + bool have_this_bits(std::string nav_data); + bool get_verified_status() const { return verified; } + bool add_nav_data(const std::string& nav_data); + void set_tow_sf0(int value) { d_TOW_sf0 = value; } + 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_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; } + +private: + static uint32_t id_counter; + std::string d_ephemeris_iono{""}; + std::string d_utc{""}; + uint32_t d_TOW_sf0{0}; + uint32_t d_last_received_TOW{0}; + uint32_t PRNd{0}; + uint32_t verified_bits{0}; + uint32_t IOD_nav{0}; + bool verified{false}; +}; + + +/*! + * \brief This class handles ONSMA data + * See https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_User_ICD_for_Test_Phase_v1.0.pdf + */ +class OSNMA_data +{ +public: + OSNMA_data() = default; + DSM_nma_header d_nma_header; + DSM_dsm_header d_dsm_header; + DSM_PKR_message d_dsm_pkr_message; + DSM_KROOT_message d_dsm_kroot_message; + DSM_KROOT_message d_dsm_kroot_new_message; + MACK_message d_mack_message; + OSNMA_NavData d_nav_data; +}; + + +class Tag +{ +public: + enum e_verification_status + { + SUCCESS, + FAIL, + UNVERIFIED + }; + 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_id(id_counter++), + TOW(TOW), // TODO missing for build_message WN for GST computation, CTR, NMAS, OSNMA_NavData missing + WN(WN), + PRNa(PRNa), + CTR(CTR), + 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) + { + } + 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 + WN(mack.WN), + PRNa(mack.PRNa), + CTR(1), + 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) + { + } + const uint32_t tag_id; + uint32_t static id_counter; + uint32_t TOW; + uint32_t WN; + uint32_t PRNa; + uint8_t CTR; + e_verification_status status; + uint64_t received_tag; + uint64_t computed_tag; + uint8_t PRN_d; + uint8_t ADKD; + uint8_t cop; + uint32_t skipped; + std::string nav_data; +}; + +/** \} */ +/** \} */ + +#endif // GNSS_SDR_OSNMA_DATA_H diff --git a/src/core/system_parameters/osnma_dsm_reader.cc b/src/core/system_parameters/osnma_dsm_reader.cc new file mode 100644 index 000000000..16bd12114 --- /dev/null +++ b/src/core/system_parameters/osnma_dsm_reader.cc @@ -0,0 +1,228 @@ +/*! + * \file osnma_dsm_reader.cc + * \brief Class for reading OSNMA DSM messages + * \author Carles Fernandez-Prades, 2023 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-2023 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "osnma_dsm_reader.h" +#include "Galileo_OSNMA.h" + + +uint8_t OSNMA_DSM_Reader::get_nmas(uint8_t nma_header) const +{ + return (nma_header & mask_nmas) >> 6; +} + + +uint8_t OSNMA_DSM_Reader::get_cid(uint8_t nma_header) const +{ + return (nma_header & mask_cid) >> 4; +} + + +uint8_t OSNMA_DSM_Reader::get_cpks(uint8_t nma_header) const +{ + return (nma_header & mask_cpks) >> 1; +} + + +bool OSNMA_DSM_Reader::get_nma_header_reserved(uint8_t nma_header) const +{ + return (nma_header & mask_nma_header_reserved) != 0; +} + + +uint8_t OSNMA_DSM_Reader::get_dsm_id(uint8_t dsm_header) const +{ + return (dsm_header & mask_dsm_id) >> 4; +} + + +uint8_t OSNMA_DSM_Reader::get_dsm_block_id(uint8_t dsm_header) const +{ + return dsm_header & mask_dsm_block_id; +} + + +uint8_t OSNMA_DSM_Reader::get_number_blocks_index(uint8_t dsm_msg_0) const +{ + return (dsm_msg_0 & mask_dsm_number_blocks) >> 4; +} + + +uint8_t OSNMA_DSM_Reader::get_pkid(const std::vector& dsm_msg) const +{ + return (dsm_msg[0] & mask_dsm_pkid); +} + + +uint8_t OSNMA_DSM_Reader::get_cidkr(const std::vector& dsm_msg) const +{ + return (dsm_msg[1] & mask_dsm_cidkr) >> 6; +} + + +uint8_t OSNMA_DSM_Reader::get_dsm_reserved1(const std::vector& dsm_msg) const +{ + return (dsm_msg[1] & mask_dsm_reserved1) >> 4; +} + + +uint8_t OSNMA_DSM_Reader::get_hf(const std::vector& dsm_msg) const +{ + return (dsm_msg[1] & mask_dsm_hf) >> 2; +} + + +uint8_t OSNMA_DSM_Reader::get_mf(const std::vector& dsm_msg) const +{ + return (dsm_msg[1] & mask_dsm_mf); +} + + +uint8_t OSNMA_DSM_Reader::get_ks(const std::vector& dsm_msg) const +{ + return (dsm_msg[2] & mask_dsm_ks) >> 4; +} + + +uint8_t OSNMA_DSM_Reader::get_ts(const std::vector& dsm_msg) const +{ + return (dsm_msg[2] & mask_dsm_ts); +} + + +uint8_t OSNMA_DSM_Reader::get_maclt(const std::vector& dsm_msg) const +{ + return dsm_msg[3]; +} + + +uint8_t OSNMA_DSM_Reader::get_dsm_reserved(const std::vector& dsm_msg) const +{ + return (dsm_msg[4] & mask_dsm_reserved) >> 4; +} + + +uint16_t OSNMA_DSM_Reader::get_wn_k(const std::vector& dsm_msg) const +{ + return (static_cast((dsm_msg[4] & mask_dsm_wk_k_msbyte) << 8) + static_cast(dsm_msg[5])); +} + + +uint8_t OSNMA_DSM_Reader::get_towh_k(const std::vector& dsm_msg) const +{ + return dsm_msg[6]; +} + + +uint64_t OSNMA_DSM_Reader::get_alpha(const std::vector& dsm_msg) const +{ + uint64_t alpha = (static_cast(dsm_msg[7]) << 40) + + (static_cast(dsm_msg[8]) << 32) + + (static_cast(dsm_msg[9]) << 24) + + (static_cast(dsm_msg[10]) << 16) + + (static_cast(dsm_msg[11]) << 8) + + (static_cast(dsm_msg[12])); + return alpha; +} + + +uint16_t OSNMA_DSM_Reader::get_l_dk_bits(uint8_t nb_dk) const +{ + const auto it = OSNMA_TABLE_7.find(nb_dk); + if (it != OSNMA_TABLE_7.cend()) + { + return it->second.second; + } + return 0; +} + + +uint16_t OSNMA_DSM_Reader::get_lk_bits(uint8_t ks) const +{ + const auto it = OSNMA_TABLE_10.find(ks); + if (it != OSNMA_TABLE_10.cend()) + { + return it->second; + } + return 0; +} + + +std::vector OSNMA_DSM_Reader::get_kroot(const std::vector& dsm_msg, uint16_t bytes_lk) const +{ + std::vector kroot = std::vector(bytes_lk, 0); + if (dsm_msg.size() > static_cast(13 + bytes_lk)) + { + for (uint16_t k = 0; k < bytes_lk; k++) + { + kroot[k] = dsm_msg[13 + k]; + } + } + return kroot; +} + + +std::string OSNMA_DSM_Reader::get_hash_function(uint8_t hf) const +{ + std::string hash_; + const auto it = OSNMA_TABLE_8.find(hf); + if (it != OSNMA_TABLE_8.cend()) + { + hash_ = it->second; + } + return hash_; +} + + +uint8_t OSNMA_DSM_Reader::get_mid(const std::vector& dsm_msg) const +{ + return (dsm_msg[0] & mask_dsm_mid); +} + + +uint8_t OSNMA_DSM_Reader::get_npkt(const std::vector& dsm_msg) const +{ + return ((dsm_msg[129] & mask_dsm_npkt) >> 4); +} + + +uint8_t OSNMA_DSM_Reader::get_npktid(const std::vector& dsm_msg) const +{ + return (dsm_msg[129] & mask_dsm_npktid); +} + + +std::string OSNMA_DSM_Reader::get_nmas_status(uint8_t nmas) const +{ + std::string status_; + const auto it = OSNMA_TABLE_1.find(nmas); + if (it != OSNMA_TABLE_1.cend()) + { + status_ = it->second; + } + return status_; +} + + +std::string OSNMA_DSM_Reader::get_cpks_status(uint8_t cpks) const +{ + std::string status_; + const auto it = OSNMA_TABLE_2.find(cpks); + if (it != OSNMA_TABLE_2.cend()) + { + status_ = it->second; + } + return status_; +} \ No newline at end of file diff --git a/src/core/system_parameters/osnma_dsm_reader.h b/src/core/system_parameters/osnma_dsm_reader.h new file mode 100644 index 000000000..121746052 --- /dev/null +++ b/src/core/system_parameters/osnma_dsm_reader.h @@ -0,0 +1,89 @@ +/*! + * \file osnma_dsm_reader.h + * \brief Class for reading OSNMA DSM messages + * \author Carles Fernandez-Prades, 2023 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-2023 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_OSNMA_DSM_READER_H +#define GNSS_SDR_OSNMA_DSM_READER_H + +#include +#include +#include + +/** \addtogroup Core + * \{ */ +/** \addtogroup System_Parameters + * \{ */ + +class OSNMA_DSM_Reader +{ +public: + OSNMA_DSM_Reader() = default; + uint8_t get_nmas(uint8_t nma_header) const; + uint8_t get_cid(uint8_t nma_header) const; + uint8_t get_cpks(uint8_t nma_header) const; + bool get_nma_header_reserved(uint8_t nma_header) const; + + uint8_t get_dsm_id(uint8_t dsm_header) const; + uint8_t get_dsm_block_id(uint8_t dsm_header) const; + + uint8_t get_number_blocks_index(uint8_t dsm_msg_0) const; + uint8_t get_pkid(const std::vector& dsm_msg) const; + uint8_t get_cidkr(const std::vector& dsm_msg) const; + uint8_t get_dsm_reserved1(const std::vector& dsm_msg) const; + uint8_t get_hf(const std::vector& dsm_msg) const; + uint8_t get_mf(const std::vector& dsm_msg) const; + uint8_t get_ks(const std::vector& dsm_msg) const; + uint8_t get_ts(const std::vector& dsm_msg) const; + uint8_t get_maclt(const std::vector& dsm_msg) const; + uint8_t get_dsm_reserved(const std::vector& dsm_msg) const; + uint16_t get_wn_k(const std::vector& dsm_msg) const; + uint8_t get_towh_k(const std::vector& dsm_msg) const; + uint64_t get_alpha(const std::vector& dsm_msg) const; + uint16_t get_l_dk_bits(uint8_t nb_dk) const; + uint16_t get_lk_bits(uint8_t ks) const; + std::vector get_kroot(const std::vector& dsm_msg, uint16_t bytes_lk) const; + std::string get_hash_function(uint8_t hf) const; + std::string get_nmas_status(uint8_t nmas) const; + std::string get_cpks_status(uint8_t cpks) const; + + uint8_t get_mid(const std::vector& dsm_msg) const; + uint8_t get_npkt(const std::vector& dsm_msg) const; + uint8_t get_npktid(const std::vector& dsm_msg) const; + +private: + static constexpr std::uint8_t mask_nmas{0xC0}; + static constexpr std::uint8_t mask_cid{0x30}; + static constexpr std::uint8_t mask_cpks{0x0E}; + static constexpr std::uint8_t mask_nma_header_reserved{0x01}; + static constexpr std::uint8_t mask_dsm_id{0xF0}; + static constexpr std::uint8_t mask_dsm_block_id{0x0F}; + static constexpr std::uint8_t mask_dsm_number_blocks{0xF0}; + static constexpr std::uint8_t mask_dsm_pkid{0x0F}; + static constexpr std::uint8_t mask_dsm_cidkr{0xC0}; + static constexpr std::uint8_t mask_dsm_reserved1{0x30}; + static constexpr std::uint8_t mask_dsm_hf{0x0C}; + static constexpr std::uint8_t mask_dsm_mf{0x03}; + static constexpr std::uint8_t mask_dsm_ks{0xF0}; + static constexpr std::uint8_t mask_dsm_ts{0x0F}; + static constexpr std::uint8_t mask_dsm_reserved{0xF0}; + static constexpr std::uint8_t mask_dsm_wk_k_msbyte{0x0F}; + static constexpr std::uint8_t mask_dsm_mid{0x0F}; + static constexpr std::uint8_t mask_dsm_npkt{0xF0}; + static constexpr std::uint8_t mask_dsm_npktid{0x0F}; +}; + +/** \} */ +/** \} */ +#endif // GNSS_SDR_OSNMA_DSM_READER_H \ No newline at end of file diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 160a3c32d..deca361e3 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(unit-tests/signal-processing-blocks/libs) add_subdirectory(system-tests/libs) +include_directories("${GNSSSDR_SOURCE_DIR}/src/core/receiver") include(XcodeRemoveWarningDuplicates) @@ -549,6 +550,18 @@ if(ENABLE_UNIT_TESTING_EXTRA) EXPECTED_HASH MD5=066d0d8434a8bc81e161778b7c34cc07 ) endif() + if(NOT EXISTS ${GNSSSDR_BINARY_DIR}/thirdparty/osnma_tests/Test_vectors.zip) + message(STATUS "Downloading file: Test_vectors.zip") + file(DOWNLOAD https://www.gsc-europa.eu/sites/default/files/sites/all/files/Test_vectors.zip + ${GNSSSDR_BINARY_DIR}/thirdparty/osnma_tests/Test_vectors.zip + SHOW_PROGRESS + EXPECTED_HASH MD5=8158aebee735652c9398e5bb6d944364 + ) + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xzf ${GNSSSDR_BINARY_DIR}/thirdparty/osnma_tests/Test_vectors.zip + WORKING_DIRECTORY ${GNSSSDR_BINARY_DIR}/thirdparty/osnma_tests/ + ) + endif() message(STATUS "Done.") if(ENABLE_INSTALL_TESTS) install(FILES ${GNSSSDR_BINARY_DIR}/thirdparty/signal_samples/gps_l2c_m_prn7_5msps.dat DESTINATION share/gnss-sdr/signal_samples) @@ -651,6 +664,7 @@ if(ENABLE_UNIT_TESTING) if(GNSSTK_OLDER_THAN_9) target_compile_definitions(run_tests PRIVATE -DGNSSTK_OLDER_THAN_9=1) endif() + target_compile_definitions(run_tests PRIVATE -DBASE_OSNMA_TEST_VECTORS="${GNSSSDR_BINARY_DIR}/thirdparty/osnma_tests/Test_vectors/") endif() xcode_remove_warning_duplicates(run_tests) if(ENABLE_STRIP) diff --git a/src/tests/benchmarks/CMakeLists.txt b/src/tests/benchmarks/CMakeLists.txt index 133174871..31ebf9f82 100644 --- a/src/tests/benchmarks/CMakeLists.txt +++ b/src/tests/benchmarks/CMakeLists.txt @@ -104,11 +104,18 @@ macro(add_benchmark) ) endmacro() -add_benchmark(benchmark_copy) -add_benchmark(benchmark_preamble core_system_parameters) -add_benchmark(benchmark_detector core_system_parameters) -add_benchmark(benchmark_reed_solomon core_system_parameters) +set(EXTRA_BENCHMARK_DEPENDENCIES "") +if(ENABLE_GLOG_AND_GFLAGS) + set(EXTRA_BENCHMARK_DEPENDENCIES "Gflags::gflags;Glog::glog") +endif() + add_benchmark(benchmark_atan2 Gnuradio::runtime) +add_benchmark(benchmark_copy) +add_benchmark(benchmark_crypto core_libs Boost::headers ${EXTRA_BENCHMARK_DEPENDENCIES}) +add_benchmark(benchmark_osnma core_libs Boost::headers ${EXTRA_BENCHMARK_DEPENDENCIES}) +add_benchmark(benchmark_detector core_system_parameters ${EXTRA_BENCHMARK_DEPENDENCIES}) +add_benchmark(benchmark_preamble core_system_parameters ${EXTRA_BENCHMARK_DEPENDENCIES}) +add_benchmark(benchmark_reed_solomon core_system_parameters ${EXTRA_BENCHMARK_DEPENDENCIES}) if(has_std_plus_void) target_compile_definitions(benchmark_detector PRIVATE -DCOMPILER_HAS_STD_PLUS_VOID=1) diff --git a/src/tests/benchmarks/benchmark_crypto.cc b/src/tests/benchmarks/benchmark_crypto.cc new file mode 100644 index 000000000..ec1945ab9 --- /dev/null +++ b/src/tests/benchmarks/benchmark_crypto.cc @@ -0,0 +1,184 @@ +/*! + * \file benchmark_crypto.cc + * \brief Benchmarks for cryptographic functions + * \author Carles Fernandez-Prades, 2024. cfernandez(at)cttc.es + * + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2024 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "gnss_crypto.h" +#include +#include + +void bm_SHA_256(benchmark::State& state) +{ + auto d_crypto = std::make_unique(); + + std::vector message{ + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x0A}; + + while (state.KeepRunning()) + { + std::vector output = d_crypto->compute_SHA_256(message); + } +} + + +void bm_SHA3_256(benchmark::State& state) +{ + auto d_crypto = std::make_unique(); + + std::vector message{ + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x0A}; + + while (state.KeepRunning()) + { + std::vector output = d_crypto->compute_SHA3_256(message); + } +} + + +void bm_HMAC_SHA_256(benchmark::State& state) +{ + auto d_crypto = std::make_unique(); + + std::vector key = { + 0x24, 0x24, 0x3B, 0x76, 0xF9, 0x14, 0xB1, 0xA7, + 0x7D, 0x48, 0xE7, 0xF1, 0x48, 0x0C, 0xC2, 0x98, + 0xEB, 0x62, 0x3E, 0x95, 0x6B, 0x2B, 0xCE, 0xA3, + 0xB4, 0xD4, 0xDB, 0x31, 0xEE, 0x96, 0xAB, 0xFA}; + + std::vector message{ + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x0A}; + + while (state.KeepRunning()) + { + std::vector output = d_crypto->compute_HMAC_SHA_256(key, message); + } +} + + +void bm_CMAC_AES(benchmark::State& state) +{ + auto d_crypto = std::make_unique(); + + std::vector key = { + 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, + 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}; + + std::vector message{ + 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, + 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A}; + + while (state.KeepRunning()) + { + std::vector output = d_crypto->compute_CMAC_AES(key, message); + } +} + + +void bm_verify_ecdsa_p256(benchmark::State& state) +{ + auto d_crypto = std::make_unique(); + + // RG example - import crt certificate + std::vector message = { + 0x82, 0x10, 0x49, 0x22, 0x04, 0xE0, 0x60, 0x61, 0x0B, 0xDF, + 0x26, 0xD7, 0x7B, 0x5B, 0xF8, 0xC9, 0xCB, 0xFC, 0xF7, 0x04, + 0x22, 0x08, 0x14, 0x75, 0xFD, 0x44, 0x5D, 0xF0, 0xFF}; + + // ECDSA P-256 signature, raw format + std::vector signature = { + 0xF8, 0xCD, 0x88, 0x29, 0x9F, 0xA4, 0x60, 0x58, 0x00, 0x20, + 0x7B, 0xFE, 0xBE, 0xAC, 0x55, 0x02, 0x40, 0x53, 0xF3, 0x0F, + 0x7C, 0x69, 0xB3, 0x5C, 0x15, 0xE6, 0x08, 0x00, 0xAC, 0x3B, + 0x6F, 0xE3, 0xED, 0x06, 0x39, 0x95, 0x2F, 0x7B, 0x02, 0x8D, + 0x86, 0x86, 0x74, 0x45, 0x96, 0x1F, 0xFE, 0x94, 0xFB, 0x22, + 0x6B, 0xFF, 0x70, 0x06, 0xE0, 0xC4, 0x51, 0xEE, 0x3F, 0x87, + 0x28, 0xC1, 0x77, 0xFB}; + + // compressed ECDSA P-256 format + std::vector publicKey = { + 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); + + while (state.KeepRunning()) + { + bool output = d_crypto->verify_signature_ecdsa_p256(message, signature); + if (output) + { + // Avoid unused-but-set-variable warning + } + } +} + + +void bm_verify_ecdsa_p521(benchmark::State& state) +{ + std::unique_ptr d_crypto = std::make_unique(); + + // Message to be verified + std::vector message = { + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x0A}; // "Hello world\n" + + // Public key in compressed X format + std::vector publicKey = { + 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}; + + // ECDSA P-521 signature, raw format + std::vector signature = { + 0x01, 0x5C, 0x23, 0xC0, 0xBE, 0xAD, 0x1E, 0x44, 0x60, 0xD4, + 0xE0, 0x81, 0x38, 0xF2, 0xBA, 0xF5, 0xB5, 0x37, 0x5A, 0x34, + 0xB5, 0xCA, 0x6B, 0xC8, 0x0F, 0xCD, 0x75, 0x1D, 0x5E, 0xC0, + 0x8A, 0xD3, 0xD7, 0x79, 0xA7, 0xC1, 0xB8, 0xA2, 0xC6, 0xEA, + 0x5A, 0x7D, 0x60, 0x66, 0x50, 0x97, 0x37, 0x6C, 0xF9, 0x0A, + 0xF6, 0x3D, 0x77, 0x9A, 0xE2, 0x19, 0xF7, 0xF9, 0xDD, 0x52, + 0xC4, 0x0F, 0x98, 0xAA, 0xA2, 0xA4, 0x01, 0xC9, 0x41, 0x0B, + 0xD0, 0x25, 0xDD, 0xC9, 0x7C, 0x3F, 0x70, 0x32, 0x23, 0xCF, + 0xFE, 0x37, 0x67, 0x3A, 0xBC, 0x0B, 0x76, 0x16, 0x82, 0x83, + 0x27, 0x3D, 0x1D, 0x19, 0x15, 0x78, 0x08, 0x2B, 0xD4, 0xA7, + 0xC2, 0x0F, 0x11, 0xF4, 0xDD, 0xE5, 0x5A, 0x5D, 0x04, 0x8D, + 0x6D, 0x5E, 0xC4, 0x1F, 0x54, 0x44, 0xA9, 0x13, 0x34, 0x71, + 0x0F, 0xF7, 0x57, 0x9A, 0x9F, 0x2E, 0xF4, 0x97, 0x7D, 0xAE, + 0x28, 0xEF}; + + d_crypto->set_public_key(publicKey); + + while (state.KeepRunning()) + { + bool output = d_crypto->verify_signature_ecdsa_p521(message, signature); + if (output) + { + // Avoid unused-but-set-variable warning + } + } +} + + +BENCHMARK(bm_SHA_256); +BENCHMARK(bm_SHA3_256); +BENCHMARK(bm_HMAC_SHA_256); +BENCHMARK(bm_CMAC_AES); +BENCHMARK(bm_verify_ecdsa_p256); +BENCHMARK(bm_verify_ecdsa_p521); + +BENCHMARK_MAIN(); diff --git a/src/tests/benchmarks/benchmark_osnma.cc b/src/tests/benchmarks/benchmark_osnma.cc new file mode 100644 index 000000000..b99c3efd0 --- /dev/null +++ b/src/tests/benchmarks/benchmark_osnma.cc @@ -0,0 +1,127 @@ +/*! + * \file benchmark_osnma.cc + * \brief Benchmarks for osnma functions + * \author Carles Fernandez-Prades, 2024. cfernandez(at)cttc.es + * + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2024 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "Galileo_OSNMA.h" +#include "gnss_crypto.h" +#include "osnma_helper.h" +#include "osnma_msg_receiver.h" +#include +#include + +void bm_verify_public_key(benchmark::State& state) +{ + osnma_msg_receiver_sptr osnma = osnma_msg_receiver_make(CRTFILE_DEFAULT, MERKLEFILE_DEFAULT); + Osnma_Helper helper; + osnma->set_merkle_root(helper.convert_from_hex_string("A10C440F3AA62453526DB4AF76DF8D9410D35D8277397D7053C700D192702B0D")); + DSM_PKR_message dsm_pkr_message; + dsm_pkr_message.npkt = 0x01; + dsm_pkr_message.npktid = 0x2; + dsm_pkr_message.mid = 0x01; + std::vector vec = helper.convert_from_hex_string( + "7CBE05D9970CFC9E22D0A43A340EF557624453A2E821AADEAC989C405D78BA06" + "956380BAB0D2C939EC6208151040CCFFCF1FB7156178FD1255BA0AECAAA253F7" + "407B6C5DD4DF059FF8789474061301E1C34881DB7A367A913A3674300E21EAB1" + "24EF508389B7D446C3E2ECE8D459FBBD3239A794906F5B1F92469C640164FD87"); + std::copy(vec.begin(), vec.end(), dsm_pkr_message.itn.begin()); + dsm_pkr_message.npk = helper.convert_from_hex_string("0303B2CE64BC207BDD8BC4DF859187FCB686320D63FFA091410FC158FBB77980EA"); + + while (state.KeepRunning()) + { + osnma->verify_dsm_pkr(dsm_pkr_message); + } +} + +void bm_verify_tesla_key(benchmark::State& state) +{ + // osnma_msg_receiver_sptr osnma = osnma_msg_receiver_make(CRTFILE_DEFAULT, MERKLEFILE_DEFAULT); + // Osnma_Helper helper; + // osnma->d_tesla_key_verified = false; + // osnma->d_osnma_data.d_dsm_kroot_message.kroot = {0x5B, 0xF8, 0xC9, 0xCB, 0xFC, 0xF7, 0x04, 0x22, 0x08, 0x14, 0x75, 0xFD, 0x44, 0x5D, 0xF0, 0xFF}; // Kroot, TOW 345570 GST_0 - 30 + // osnma->d_osnma_data.d_dsm_kroot_message.ks = 4; // TABLE 10 --> 128 bits + // osnma->d_osnma_data.d_dsm_kroot_message.alpha = 0x610BDF26D77B; + // osnma->d_GST_SIS = (1248 & 0x00000FFF) << 20 | (345630 & 0x000FFFFF); + // osnma->d_GST_0 = ((1248 & 0x00000FFF) << 20 | (345600 & 0x000FFFFF)); // applicable time (GST_Kroot + 30) + // osnma->d_GST_Sf = osnma->d_GST_0 + 30 * std::floor((osnma->d_GST_SIS - osnma->d_GST_0) / 30); // Eq. 3 R.G. + // + // osnma->d_tesla_keys.insert((std::pair>(345600, {0xEF, 0xF9, 0x99, 0x04, 0x0E, 0x19, 0xB5, 0x70, 0x83, 0x50, 0x60, 0xBE, 0xBD, 0x23, 0xED, 0x92}))); // K1, not needed, just for reference. + // std::vector key = {0x2D, 0xC3, 0xA3, 0xCD, 0xB1, 0x17, 0xFA, 0xAD, 0xB8, 0x3B, 0x5F, 0x0B, 0x6F, 0xEA, 0x88, 0xEB}; // K2 + // uint32_t TOW = 345630; + // + // while (state.KeepRunning()) + // { + // osnma->verify_tesla_key(key, TOW); + // } +} + +void bm_verify_tesla_key_24h(benchmark::State& state) +{ + // TODO - copy of normal tesla verification but with 2800 steps instead of only two (max Kroot time is 1 day as per spec.) +} + +void bm_tag_verification(benchmark::State& state) +{ + // osnma_msg_receiver_sptr osnma = osnma_msg_receiver_make(CRTFILE_DEFAULT, MERKLEFILE_DEFAULT); + // Osnma_Helper helper; + // uint32_t TOW_Tag0 = 345660; + // uint32_t TOW_NavData = TOW_Tag0 - 30; + // uint32_t TOW_Key_Tag0 = TOW_Tag0 + 30; + // uint32_t WN = 1248; + // uint32_t PRNa = 2; + // uint8_t CTR = 1; + // + // osnma->d_osnma_data.d_dsm_kroot_message.ts = 9; // 40 bit + // osnma->d_tesla_keys[TOW_Key_Tag0] = {0x69, 0xC0, 0x0A, 0xA7, 0x36, 0x42, 0x37, 0xA6, 0x5E, 0xBF, 0x00, 0x6A, 0xD8, 0xDD, 0xBC, 0x73}; // K4 + // osnma->d_osnma_data.d_dsm_kroot_message.mf = 0; + // osnma->d_nav_data_manager->add_navigation_data( + // "000011101001011001000100000101000111010110100100100101100000000000" + // "011101101011001111101110101010000001010000011011111100000011101011" + // "011100101101011010101011011011001001110111101011110110111111001111" + // "001000011111101110011000111111110111111010000011101011111111110000" + // "110111000000100000001110110000110110001110000100001110101100010100" + // "110100010001000110001110011010110000111010000010000000000001101000" + // "000000000011100101100100010000000000000110110100110001111100000000" + // "000000100110100000000101010010100000001011000010001001100000011111" + // "110111111111000000000", + // PRNa, TOW_NavData); + // osnma->d_osnma_data.d_nma_header.nmas = 0b10; + // + // MACK_tag_and_info MTI; + // MTI.tag = static_cast(0xE37BC4F858); + // MTI.tag_info.PRN_d = 0x02; + // MTI.tag_info.ADKD = 0x00; + // MTI.tag_info.cop = 0x0F; + // Tag t0(MTI, TOW_Tag0, WN, PRNa, CTR); + // + // while (state.KeepRunning()) + // { + // osnma->verify_tag(t0); + // } +} + +void bm_kroot_verification(benchmark::State& state) +{ + // TODO - this is essentially the signature verification, maybe could implement it for comparison purposes +} + +BENCHMARK(bm_verify_public_key); +BENCHMARK(bm_verify_tesla_key); +BENCHMARK(bm_verify_tesla_key_24h); +BENCHMARK(bm_tag_verification); +BENCHMARK(bm_kroot_verification); + + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/src/tests/single_test_main.cc b/src/tests/single_test_main.cc index 5156ee547..27dc60c17 100644 --- a/src/tests/single_test_main.cc +++ b/src/tests/single_test_main.cc @@ -18,6 +18,7 @@ #include "concurrent_map.h" #include "concurrent_queue.h" +#include "gnss_sdr_flags.h" #include "gps_acq_assist.h" #include #include @@ -36,7 +37,6 @@ using namespace google; DECLARE_string(log_dir); #endif #else -#include "gnss_sdr_flags.h" #include #include #include @@ -44,7 +44,6 @@ DECLARE_string(log_dir); #include #include #include - class TestLogSink : public absl::LogSink { public: @@ -78,15 +77,14 @@ Concurrent_Map global_gps_acq_assist_map; int main(int argc, char **argv) { #if USE_GLOG_AND_GFLAGS - gflags::ParseCommandLineFlags(&argc, &argv, true); try { testing::InitGoogleTest(&argc, argv); + gflags::ParseCommandLineFlags(&argc, &argv, true); } catch (...) { } // catch the "testing::internal::::ClassUniqueToAlwaysTrue" from gtest - google::InitGoogleLogging(argv[0]); #else absl::ParseCommandLine(argc, argv); try diff --git a/src/tests/test_main.cc b/src/tests/test_main.cc index bdc565e0d..cf6dfbccc 100644 --- a/src/tests/test_main.cc +++ b/src/tests/test_main.cc @@ -112,6 +112,8 @@ private: #include "unit-tests/signal-processing-blocks/adapter/adapter_test.cc" #include "unit-tests/signal-processing-blocks/adapter/pass_through_test.cc" #include "unit-tests/signal-processing-blocks/libs/item_type_helpers_test.cc" +#include "unit-tests/signal-processing-blocks/osnma/gnss_crypto_test.cc" +#include "unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc" #include "unit-tests/signal-processing-blocks/pvt/geohash_test.cc" #include "unit-tests/signal-processing-blocks/pvt/nmea_printer_test.cc" #include "unit-tests/signal-processing-blocks/pvt/rinex_printer_test.cc" @@ -181,6 +183,7 @@ private: #ifndef EXCLUDE_TESTS_REQUIRING_BINARIES #include "unit-tests/signal-processing-blocks/acquisition/glonass_l1_ca_pcps_acquisition_test.cc" #include "unit-tests/signal-processing-blocks/acquisition/gps_l2_m_pcps_acquisition_test.cc" +#include "unit-tests/signal-processing-blocks/osnma/osnma_test_vectors.cc" #include "unit-tests/signal-processing-blocks/tracking/gps_l2_m_dll_pll_tracking_test.cc" #endif // #include "unit-tests/signal-processing-blocks/pvt/rtklib_solver_test.cc" diff --git a/src/tests/unit-tests/signal-processing-blocks/osnma/gnss_crypto_test.cc b/src/tests/unit-tests/signal-processing-blocks/osnma/gnss_crypto_test.cc new file mode 100644 index 000000000..58dcc3f2f --- /dev/null +++ b/src/tests/unit-tests/signal-processing-blocks/osnma/gnss_crypto_test.cc @@ -0,0 +1,347 @@ +/*! + * \file gnss_crypto_test.cc + * \brief Tests for the Gnss_Crypto class. + * \author Carles Fernandez, 2023-2024. cfernandez(at)cttc.es + * Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de + * + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2024 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "gnss_crypto.h" +#include "gnss_sdr_filesystem.h" +#include "gnss_sdr_make_unique.h" +#include +#include +#include +#include + +class GnssCryptoTest : public ::testing::Test +{ +}; + + +TEST(GnssCryptoTest, VerifyPubKeyImport) +{ + auto d_crypto = std::make_unique(); + // Input taken from RG 1.3 A7.1 + // compressed ECDSA P-256 format + std::vector publicKey = { + 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}; + + ASSERT_FALSE(d_crypto->have_public_key()); + + d_crypto->set_public_key(publicKey); + + ASSERT_TRUE(d_crypto->have_public_key()); +} + + +TEST(GnssCryptoTest, VerifyPublicKeyStorage) +{ + const std::string f1("./osnma_test_file1.pem"); + const std::string f2("./osnma_test_file2.pem"); + const std::string f3("./osnma_test_file3.pem"); + + // Input taken from RG 1.3 A7.1 + // compressed ECDSA P-256 format + std::vector publicKey = { + 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}; + + auto d_crypto = std::make_unique(); + ASSERT_FALSE(d_crypto->have_public_key()); + ASSERT_TRUE(d_crypto->get_public_key_type() == "Unknown"); + d_crypto->set_public_key(publicKey); + ASSERT_TRUE(d_crypto->have_public_key()); + ASSERT_TRUE(d_crypto->store_public_key(f1)); + + auto d_crypto2 = std::make_unique(f1, ""); + ASSERT_TRUE(d_crypto2->have_public_key()); + ASSERT_TRUE(d_crypto2->get_public_key_type() == "ECDSA P-256"); + ASSERT_TRUE(d_crypto2->store_public_key(f2)); + + std::ifstream t(f1); + std::string content_file((std::istreambuf_iterator(t)), std::istreambuf_iterator()); + + std::ifstream t2(f2); + std::string content_file2((std::istreambuf_iterator(t2)), std::istreambuf_iterator()); + + ASSERT_EQ(content_file, content_file2); + + // P-521 Public key in compressed X format + std::vector 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_TRUE(d_crypto->store_public_key(f3)); + + auto d_crypto3 = std::make_unique(f3, ""); + ASSERT_TRUE(d_crypto3->have_public_key()); + ASSERT_TRUE(d_crypto3->get_public_key_type() == "ECDSA P-521"); + + std::vector wrong_publicKey = { + 0x03, 0x03, 0xB2, 0xCE, 0x64, 0xBC, 0x20, 0x7B, 0xDD, 0x8B, + 0xC4, 0xDF, 0x85, 0x91, 0x87, 0xFC, 0xB6, 0x86, 0x32, 0x0D, + 0x63, 0xFF, 0xA0}; + + auto d_crypto4 = std::make_unique(); + d_crypto4->set_public_key(wrong_publicKey); + ASSERT_FALSE(d_crypto4->have_public_key()); + ASSERT_TRUE(d_crypto4->get_public_key_type() == "Unknown"); + + errorlib::error_code ec; + ASSERT_TRUE(fs::remove(fs::path(f1), ec)); + ASSERT_TRUE(fs::remove(fs::path(f2), ec)); + ASSERT_TRUE(fs::remove(fs::path(f3), ec)); +} + + +TEST(GnssCryptoTest, TestComputeSHA_256) +{ + auto d_crypto = std::make_unique(); + std::vector message{ + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x0A}; // Hello world + + std::vector expected_output = { + 0x18, 0x94, 0xA1, 0x9C, 0x85, 0xBA, 0x15, 0x3A, 0xCB, 0xF7, + 0x43, 0xAC, 0x4E, 0x43, 0xFC, 0x00, 0x4C, 0x89, 0x16, 0x04, + 0xB2, 0x6F, 0x8C, 0x69, 0xE1, 0xE8, 0x3E, 0xA2, 0xAF, 0xC7, + 0xC4, 0x8F}; + + std::vector output = d_crypto->compute_SHA_256(message); + + ASSERT_EQ(expected_output, output); +} + + +TEST(GnssCryptoTest, TestComputeSHA3_256) +{ + auto d_crypto = std::make_unique(); + std::vector message{ + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x0A}; // Hello world + + std::vector expected_output = { + 0xCC, 0xB8, 0xF9, 0x23, 0x5F, 0x4A, 0x93, 0x2C, 0xA0, 0xAB, + 0xBB, 0x2C, 0x24, 0x36, 0x72, 0x5E, 0x2E, 0x8D, 0xC7, 0x5B, + 0x99, 0xE7, 0xF6, 0xC4, 0x50, 0x5B, 0x2A, 0x93, 0x6E, 0xB6, + 0x3B, 0x3F}; + + std::vector output = d_crypto->compute_SHA3_256(message); + + ASSERT_EQ(expected_output, output); +} + + +// Unit test for computeHMAC_SHA_256 function. +TEST(GnssCryptoTest, TestComputeHMACSHA256) +{ + auto d_crypto = std::make_unique(); + + std::vector key = { + 0x24, 0x24, 0x3B, 0x76, 0xF9, 0x14, 0xB1, 0xA7, + 0x7D, 0x48, 0xE7, 0xF1, 0x48, 0x0C, 0xC2, 0x98, + 0xEB, 0x62, 0x3E, 0x95, 0x6B, 0x2B, 0xCE, 0xA3, + 0xB4, 0xD4, 0xDB, 0x31, 0xEE, 0x96, 0xAB, 0xFA}; + + std::vector message{ + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x0A}; // Hello world + + std::vector expected_output = { + 0xC3, 0x51, 0xF6, 0xFD, 0xDD, 0xC9, 0x8B, 0x41, + 0xD6, 0xF4, 0x77, 0x6D, 0xAC, 0xE8, 0xE0, 0x14, + 0xB2, 0x7A, 0xCC, 0x22, 0x00, 0xAA, 0xD2, 0x37, + 0xD0, 0x79, 0x06, 0x12, 0x83, 0x40, 0xB7, 0xA6}; + + std::vector output = d_crypto->compute_HMAC_SHA_256(key, message); + + ASSERT_EQ(expected_output, output); +} + + +TEST(GnssCryptoTest, TestComputeHMACSHA256_m0) +{ + // key and message generated from RG A.6.5.1 + auto d_crypto = std::make_unique(); + + // RG K4 @ 345690 + std::vector key = { + 0x69, 0xC0, 0x0A, 0xA7, 0x36, 0x42, 0x37, 0xA6, + 0x5E, 0xBF, 0x00, 0x6A, 0xD8, 0xDD, 0xBC, 0x73}; + + // m0 + std::vector message = { + 0x02, 0x4E, 0x05, 0x46, 0x3C, 0x01, 0x83, 0xA5, + 0x91, 0x05, 0x1D, 0x69, 0x25, 0x80, 0x07, 0x6B, + 0x3E, 0xEA, 0x81, 0x41, 0xBF, 0x03, 0xAD, 0xCB, + 0x5A, 0xAD, 0xB2, 0x77, 0xAF, 0x6F, 0xCF, 0x21, + 0xFB, 0x98, 0xFF, 0x7E, 0x83, 0xAF, 0xFC, 0x37, + 0x02, 0x03, 0xB0, 0xD8, 0xE1, 0x0E, 0xB1, 0x4D, + 0x11, 0x18, 0xE6, 0xB0, 0xE8, 0x20, 0x01, 0xA0, + 0x00, 0xE5, 0x91, 0x00, 0x06, 0xD3, 0x1F, 0x00, + 0x02, 0x68, 0x05, 0x4A, 0x02, 0xC2, 0x26, 0x07, + 0xF7, 0xFC, 0x00}; + + std::vector expected_output = { + 0xE3, 0x7B, 0xC4, 0xF8, 0x58, 0xAE, 0x1E, 0x5C, + 0xFD, 0xC4, 0x6F, 0x05, 0x4B, 0x1F, 0x47, 0xB9, + 0xD2, 0xEA, 0x61, 0xE1, 0xEF, 0x09, 0x11, 0x5C, + 0xFE, 0x70, 0x68, 0x52, 0xBF, 0xF2, 0x3A, 0x83}; + + std::vector output = d_crypto->compute_HMAC_SHA_256(key, message); + + ASSERT_EQ(expected_output, output); +} + + +TEST(GnssCryptoTest, TestComputeHMACSHA256_adkd4) +{ + // key and message generated from RG A.6.5.2 + auto d_crypto = std::make_unique(); + + // RG K4 @ 345690 + std::vector key = { + 0x69, 0xC0, 0x0A, 0xA7, 0x36, 0x42, 0x37, 0xA6, + 0x5E, 0xBF, 0x00, 0x6A, 0xD8, 0xDD, 0xBC, 0x73}; + + std::vector message = { + 0x02, 0x02, 0x4E, 0x05, 0x46, 0x3C, 0x03, 0xBF, + 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x44, 0x92, + 0x38, 0x22, 0x78, 0x97, 0xFD, 0xEF, 0xF9, 0x30, + 0x40}; + + std::vector expected_output = { + 0x7B, 0xB2, 0x38, 0xC8, 0x83, 0xC0, 0x6A, 0x2B, + 0x50, 0x8F, 0xE6, 0x3F, 0xB7, 0xF4, 0xF5, 0x4D, + 0x44, 0xAB, 0xEE, 0x4D, 0xCE, 0xB9, 0x3D, 0xCF, + 0x65, 0xCB, 0x3A, 0x5B, 0x81, 0x4A, 0x34, 0xE9}; + + std::vector output = d_crypto->compute_HMAC_SHA_256(key, message); + + ASSERT_EQ(expected_output, output); +} + + +TEST(GnssCryptoTest, TestComputeCMAC_AES) +{ + // Tests vectors from https://datatracker.ietf.org/doc/html/rfc4493#appendix-A + auto d_crypto = std::make_unique(); + + std::vector key = { + 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, + 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}; + + std::vector message{ + 0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, + 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93, 0x17, 0x2A}; + + std::vector expected_output = { + 0x07, 0x0A, 0x16, 0xB4, 0x6B, 0x4D, 0x41, 0x44, + 0xF7, 0x9B, 0xDD, 0x9D, 0xD0, 0x4A, 0x28, 0x7C}; + + std::vector output = d_crypto->compute_CMAC_AES(key, message); + + ASSERT_EQ(expected_output, output); +} + + +TEST(GnssCryptoTest, VerifySignatureP256) +{ + auto d_crypto = std::make_unique(); + + // RG example - import crt certificate + std::vector message = { + 0x82, 0x10, 0x49, 0x22, 0x04, 0xE0, 0x60, 0x61, 0x0B, 0xDF, + 0x26, 0xD7, 0x7B, 0x5B, 0xF8, 0xC9, 0xCB, 0xFC, 0xF7, 0x04, + 0x22, 0x08, 0x14, 0x75, 0xFD, 0x44, 0x5D, 0xF0, 0xFF}; + + // ECDSA P-256 signature, raw format + std::vector signature = { + 0xF8, 0xCD, 0x88, 0x29, 0x9F, 0xA4, 0x60, 0x58, 0x00, 0x20, + 0x7B, 0xFE, 0xBE, 0xAC, 0x55, 0x02, 0x40, 0x53, 0xF3, 0x0F, + 0x7C, 0x69, 0xB3, 0x5C, 0x15, 0xE6, 0x08, 0x00, 0xAC, 0x3B, + 0x6F, 0xE3, 0xED, 0x06, 0x39, 0x95, 0x2F, 0x7B, 0x02, 0x8D, + 0x86, 0x86, 0x74, 0x45, 0x96, 0x1F, 0xFE, 0x94, 0xFB, 0x22, + 0x6B, 0xFF, 0x70, 0x06, 0xE0, 0xC4, 0x51, 0xEE, 0x3F, 0x87, + 0x28, 0xC1, 0x77, 0xFB}; + + // Input taken from RG 1.3 A7.1 + // compressed ECDSA P-256 format + std::vector publicKey = { + 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); + ASSERT_TRUE(d_crypto->verify_signature_ecdsa_p256(message, signature)); + + std::vector wrong_signature = std::move(signature); + wrong_signature[1] = 1; + ASSERT_FALSE(d_crypto->verify_signature_ecdsa_p256(message, wrong_signature)); +} + + +TEST(GnssCryptoTest, VerifySignatureP521) +{ + std::unique_ptr d_crypto = std::make_unique(); + + // Message to be verified + std::vector message = { + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x0A}; // "Hello world\n" + + // Public key in compressed X format + std::vector publicKey = { + 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}; + + // ECDSA P-521 signature, raw format + std::vector signature = { + 0x01, 0x5C, 0x23, 0xC0, 0xBE, 0xAD, 0x1E, 0x44, 0x60, 0xD4, + 0xE0, 0x81, 0x38, 0xF2, 0xBA, 0xF5, 0xB5, 0x37, 0x5A, 0x34, + 0xB5, 0xCA, 0x6B, 0xC8, 0x0F, 0xCD, 0x75, 0x1D, 0x5E, 0xC0, + 0x8A, 0xD3, 0xD7, 0x79, 0xA7, 0xC1, 0xB8, 0xA2, 0xC6, 0xEA, + 0x5A, 0x7D, 0x60, 0x66, 0x50, 0x97, 0x37, 0x6C, 0xF9, 0x0A, + 0xF6, 0x3D, 0x77, 0x9A, 0xE2, 0x19, 0xF7, 0xF9, 0xDD, 0x52, + 0xC4, 0x0F, 0x98, 0xAA, 0xA2, 0xA4, 0x01, 0xC9, 0x41, 0x0B, + 0xD0, 0x25, 0xDD, 0xC9, 0x7C, 0x3F, 0x70, 0x32, 0x23, 0xCF, + 0xFE, 0x37, 0x67, 0x3A, 0xBC, 0x0B, 0x76, 0x16, 0x82, 0x83, + 0x27, 0x3D, 0x1D, 0x19, 0x15, 0x78, 0x08, 0x2B, 0xD4, 0xA7, + 0xC2, 0x0F, 0x11, 0xF4, 0xDD, 0xE5, 0x5A, 0x5D, 0x04, 0x8D, + 0x6D, 0x5E, 0xC4, 0x1F, 0x54, 0x44, 0xA9, 0x13, 0x34, 0x71, + 0x0F, 0xF7, 0x57, 0x9A, 0x9F, 0x2E, 0xF4, 0x97, 0x7D, 0xAE, + 0x28, 0xEF}; + + d_crypto->set_public_key(publicKey); + ASSERT_TRUE(d_crypto->verify_signature_ecdsa_p521(message, signature)); + + std::vector wrong_signature = std::move(signature); + wrong_signature[1] = 1; + ASSERT_FALSE(d_crypto->verify_signature_ecdsa_p521(message, wrong_signature)); +} diff --git a/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc b/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc new file mode 100644 index 000000000..7bbdd4e16 --- /dev/null +++ b/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_msg_receiver_test.cc @@ -0,0 +1,301 @@ +/*! + * \file osmna_msg_receiver_test.cc + * \brief Tests for the osnma_msg_receiver class. + * \author Carles Fernandez, 2023-2024. cfernandez(at)cttc.es + * Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de + * + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2024 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "Galileo_OSNMA.h" +#include "gnss_crypto.h" +#include "osnma_helper.h" +#include "osnma_msg_receiver.h" +#include +#include +#include +#include +#include + +#if USE_GLOG_AND_GFLAGS +#include // for LOG +#else +#include +#endif + +class OsnmaMsgReceiverTest : public ::testing::Test +{ +protected: + Osnma_Helper helper; + osnma_msg_receiver_sptr osnma; + OSNMA_msg osnma_msg{}; + std::array nma_position_filled; + uint32_t d_GST_SIS{}; + uint32_t TOW{}; + uint32_t WN{}; + std::tm GST_START_EPOCH = {0, 0, 0, 22, 8 - 1, 1999 - 1900, 0, 0, 0, 0, 0}; // months start with 0 and years since 1900 in std::tm + const uint32_t LEAP_SECONDS = 0; // tried with 13 + 5, which is the official count, but won't parse correctly + void set_time(std::tm& input); + + void SetUp() override + { + // std::tm input_time = {0, 0, 5, 16, 8 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; // conf. 1 + std::tm input_time = {0, 0, 0, 27, 7 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; // conf. 2 + set_time(input_time); + osnma = osnma_msg_receiver_make(CRTFILE_DEFAULT, MERKLEFILE_DEFAULT); + } +}; + + +TEST_F(OsnmaMsgReceiverTest, ComputeMerkleRoot) +{ + // input data taken from Receiver Guidelines v1.3, A.7 + // Arrange + std::vector computed_merkle_root; + std::vector expected_merkle_root = helper.convert_from_hex_string("A10C440F3AA62453526DB4AF76DF8D9410D35D8277397D7053C700D192702B0D"); + DSM_PKR_message dsm_pkr_message; + dsm_pkr_message.npkt = 0x01; + dsm_pkr_message.npktid = 0x2; + dsm_pkr_message.mid = 0x01; + std::vector base_leaf = helper.convert_from_hex_string("120303B2CE64BC207BDD8BC4DF859187FCB686320D63FFA091410FC158FBB77980EA"); + + // ITN + std::vector vec = helper.convert_from_hex_string( + "7CBE05D9970CFC9E22D0A43A340EF557624453A2E821AADEAC989C405D78BA06" + "956380BAB0D2C939EC6208151040CCFFCF1FB7156178FD1255BA0AECAAA253F7" + "407B6C5DD4DF059FF8789474061301E1C34881DB7A367A913A3674300E21EAB1" + "24EF508389B7D446C3E2ECE8D459FBBD3239A794906F5B1F92469C640164FD87"); + std::copy(vec.begin(), vec.end(), dsm_pkr_message.itn.begin()); + dsm_pkr_message.npk = helper.convert_from_hex_string("0303B2CE64BC207BDD8BC4DF859187FCB686320D63FFA091410FC158FBB77980EA"); + + // Act + computed_merkle_root = osnma->compute_merkle_root(dsm_pkr_message, base_leaf); + + // Assert + ASSERT_EQ(computed_merkle_root, expected_merkle_root); +} + + +TEST_F(OsnmaMsgReceiverTest, ComputeBaseLeaf) +{ + // input data taken from Receiver Guidelines v1.3, A.7 + // Arrange + std::vector expected_base_leaf = helper.convert_from_hex_string("120303B2CE64BC207BDD8BC4DF859187FCB686320D63FFA091410FC158FBB77980EA"); + DSM_PKR_message dsm_pkr_message; + dsm_pkr_message.npkt = 0x01; + dsm_pkr_message.npktid = 0x2; + dsm_pkr_message.npk = helper.convert_from_hex_string("0303B2CE64BC207BDD8BC4DF859187FCB686320D63FFA091410FC158FBB77980EA"); + + // Act + std::vector computed_base_leaf = osnma->get_merkle_tree_leaves(dsm_pkr_message); + + // Assert + ASSERT_EQ(computed_base_leaf, expected_base_leaf); +} + + +TEST_F(OsnmaMsgReceiverTest, VerifyPublicKey) +{ + // Input data taken from Receiver Guidelines v1.3, A.7 + // Arrange + osnma->d_crypto->set_merkle_root(helper.convert_from_hex_string("A10C440F3AA62453526DB4AF76DF8D9410D35D8277397D7053C700D192702B0D")); + DSM_PKR_message dsm_pkr_message; + dsm_pkr_message.npkt = 0x01; + dsm_pkr_message.npktid = 0x2; + dsm_pkr_message.mid = 0x01; + std::vector vec = helper.convert_from_hex_string( + "7CBE05D9970CFC9E22D0A43A340EF557624453A2E821AADEAC989C405D78BA06" + "956380BAB0D2C939EC6208151040CCFFCF1FB7156178FD1255BA0AECAAA253F7" + "407B6C5DD4DF059FF8789474061301E1C34881DB7A367A913A3674300E21EAB1" + "24EF508389B7D446C3E2ECE8D459FBBD3239A794906F5B1F92469C640164FD87"); + std::copy(vec.begin(), vec.end(), dsm_pkr_message.itn.begin()); + dsm_pkr_message.npk = helper.convert_from_hex_string("0303B2CE64BC207BDD8BC4DF859187FCB686320D63FFA091410FC158FBB77980EA"); + + // Act + bool result = osnma->verify_dsm_pkr(dsm_pkr_message); // TODO - refactor method so that output is more than a boolean. + + // Assert + ASSERT_TRUE(result); +} + + +TEST_F(OsnmaMsgReceiverTest, BuildTagMessageM0) +{ + // input data taken from Receiver Guidelines v1.3, A.6.5.1 + // Arrange + std::vector expected_message = { + 0x02, 0x4E, 0x05, 0x46, 0x3C, 0x01, 0x83, 0xA5, 0x91, 0x05, 0x1D, 0x69, 0x25, 0x80, 0x07, 0x6B, + 0x3E, 0xEA, 0x81, 0x41, 0xBF, 0x03, 0xAD, 0xCB, 0x5A, 0xAD, 0xB2, 0x77, 0xAF, 0x6F, 0xCF, 0x21, + 0xFB, 0x98, 0xFF, 0x7E, 0x83, 0xAF, 0xFC, 0x37, 0x02, 0x03, 0xB0, 0xD8, 0xE1, 0x0E, 0xB1, 0x4D, + 0x11, 0x18, 0xE6, 0xB0, 0xE8, 0x20, 0x01, 0xA0, 0x00, 0xE5, 0x91, 0x00, 0x06, 0xD3, 0x1F, 0x00, + 0x02, 0x68, 0x05, 0x4A, 0x02, 0xC2, 0x26, 0x07, 0xF7, 0xFC, 0x00}; + + uint32_t TOW_Tag0 = 345660; + uint32_t TOW_NavData = TOW_Tag0 - 30; + uint32_t TOW_Key_Tag0 = TOW_Tag0 + 30; + uint32_t WN = 1248; + uint32_t PRNa = 2; + uint8_t CTR = 1; + + osnma->d_osnma_data.d_dsm_kroot_message.ts = 9; // 40 bit + osnma->d_tesla_keys[TOW_Key_Tag0] = {0x69, 0xC0, 0x0A, 0xA7, 0x36, 0x42, 0x37, 0xA6, 0x5E, 0xBF, 0x00, 0x6A, 0xD8, 0xDB, 0xBC, 0x73}; // K4 + osnma->d_osnma_data.d_dsm_kroot_message.mf = 0; + osnma->d_nav_data_manager->add_navigation_data( + "000011101001011001000100000101000111010110100100100101100000000000" + "011101101011001111101110101010000001010000011011111100000011101011" + "011100101101011010101011011011001001110111101011110110111111001111" + "001000011111101110011000111111110111111010000011101011111111110000" + "110111000000100000001110110000110110001110000100001110101100010100" + "110100010001000110001110011010110000111010000010000000000001101000" + "000000000011100101100100010000000000000110110100110001111100000000" + "000000100110100000000101010010100000001011000010001001100000011111" + "110111111111000000000", + PRNa, TOW_NavData); + osnma->d_osnma_data.d_nma_header.nmas = 0b10; + + MACK_tag_and_info MTI; + MTI.tag = static_cast(0xE37BC4F858); + MTI.tag_info.PRN_d = 0x02; + MTI.tag_info.ADKD = 0x00; + MTI.tag_info.cop = 0x0F; + Tag t0(MTI, TOW_Tag0, WN, PRNa, CTR); + + // Act + auto computed_message = osnma->build_message(t0); + + // Assert + ASSERT_TRUE(computed_message == expected_message); +} + + +TEST_F(OsnmaMsgReceiverTest, TagVerification) +{ + // input data taken from Receiver Guidelines v1.3, A.6.5.1 + // Arrange + // Tag0 + uint32_t TOW_Tag0 = 345660; + uint32_t TOW_NavData = TOW_Tag0 - 30; + uint32_t TOW_Key_Tag0 = TOW_Tag0 + 30; + uint32_t WN = 1248; + uint32_t PRNa = 2; + uint8_t CTR = 1; + + osnma->d_osnma_data.d_dsm_kroot_message.ts = 9; // 40 bit + osnma->d_tesla_keys[TOW_Key_Tag0] = {0x69, 0xC0, 0x0A, 0xA7, 0x36, 0x42, 0x37, 0xA6, 0x5E, 0xBF, 0x00, 0x6A, 0xD8, 0xDD, 0xBC, 0x73}; // K4 + osnma->d_osnma_data.d_dsm_kroot_message.mf = 0; + osnma->d_nav_data_manager->add_navigation_data( + "000011101001011001000100000101000111010110100100100101100000000000" + "011101101011001111101110101010000001010000011011111100000011101011" + "011100101101011010101011011011001001110111101011110110111111001111" + "001000011111101110011000111111110111111010000011101011111111110000" + "110111000000100000001110110000110110001110000100001110101100010100" + "110100010001000110001110011010110000111010000010000000000001101000" + "000000000011100101100100010000000000000110110100110001111100000000" + "000000100110100000000101010010100000001011000010001001100000011111" + "110111111111000000000", + PRNa, TOW_NavData); + osnma->d_osnma_data.d_nma_header.nmas = 0b10; + + MACK_tag_and_info MTI; + MTI.tag = static_cast(0xE37BC4F858); + MTI.tag_info.PRN_d = 0x02; + MTI.tag_info.ADKD = 0x00; + MTI.tag_info.cop = 0x0F; + Tag t0(MTI, TOW_Tag0, WN, PRNa, CTR); + + // Act + bool result_tag0 = osnma->verify_tag(t0); + + // Assert + + // Tag3 + uint32_t TOW_Key_Tag3 = TOW_Tag0 + 30; + WN = 1248; + PRNa = 2; + CTR = 3; + + osnma->d_osnma_data.d_dsm_kroot_message.ts = 9; // 40 bit + osnma->d_tesla_keys[TOW_Key_Tag3] = {0x69, 0xC0, 0x0A, 0xA7, 0x36, 0x42, 0x37, 0xA6, 0x5E, 0xBF, 0x00, 0x6A, 0xD8, 0xDD, 0xBC, 0x73}; // K4 + osnma->d_osnma_data.d_dsm_kroot_message.mf = 0; + osnma->d_nav_data_manager->add_navigation_data( + "111111111111111111111111111111110000000000000000000000010001001001001000" + "111000001000100111100010010111111111011110111111111001001100000100000", + PRNa, TOW_NavData); + osnma->d_osnma_data.d_nma_header.nmas = 0b10; + + MTI.tag = static_cast(0x7BB238C883); + MTI.tag_info.PRN_d = 0x02; + MTI.tag_info.ADKD = 0x04; + MTI.tag_info.cop = 0x0F; + Tag t3(MTI, TOW_Tag0, WN, PRNa, CTR); + + bool result_tag3 = osnma->verify_tag(t3); + + ASSERT_TRUE(result_tag0 && result_tag3); +} + + +TEST_F(OsnmaMsgReceiverTest, TeslaKeyVerification) +{ + // input data taken from Receiver Guidelines v1.3, A.5.2 + // Arrange + osnma->d_tesla_key_verified = false; + osnma->d_osnma_data.d_dsm_kroot_message.kroot = {0x5B, 0xF8, 0xC9, 0xCB, 0xFC, 0xF7, 0x04, 0x22, 0x08, 0x14, 0x75, 0xFD, 0x44, 0x5D, 0xF0, 0xFF}; // Kroot, TOW 345570 GST_0 - 30 + osnma->d_osnma_data.d_dsm_kroot_message.ks = 4; // TABLE 10 --> 128 bits + osnma->d_osnma_data.d_dsm_kroot_message.alpha = 0x610BDF26D77B; + osnma->d_GST_SIS = (1248 & 0x00000FFF) << 20 | (345630 & 0x000FFFFF); + osnma->d_GST_0 = ((1248 & 0x00000FFF) << 20 | (345600 & 0x000FFFFF)); // applicable time (GST_Kroot + 30) + osnma->d_GST_Sf = osnma->d_GST_0 + 30 * std::floor((osnma->d_GST_SIS - osnma->d_GST_0) / 30); // Eq. 3 R.G. + + osnma->d_tesla_keys.insert((std::pair>(345600, {0xEF, 0xF9, 0x99, 0x04, 0x0E, 0x19, 0xB5, 0x70, 0x83, 0x50, 0x60, 0xBE, 0xBD, 0x23, 0xED, 0x92}))); // K1, not needed, just for reference. + std::vector key = {0x2D, 0xC3, 0xA3, 0xCD, 0xB1, 0x17, 0xFA, 0xAD, 0xB8, 0x3B, 0x5F, 0x0B, 0x6F, 0xEA, 0x88, 0xEB}; // K2 + uint32_t TOW = 345630; + + // Act + bool result = osnma->verify_tesla_key(key, TOW); // TODO - refactor so that output is not a boolean. Or use last_verified_tesla_key? + + // Assert + ASSERT_TRUE(result); +} + + +/** + * @brief Sets the time based on the given input. + * + * This function calculates the week number (WN) and time of week (TOW) + * based on the input time and the GST_START_EPOCH. It then stores the + * calculated values in the WN and TOW member variables. Finally, it + * combines the WN and TOW into a single 32-bit value and stores it in + * the d_GST_SIS member variable. + * + * @param input The input time as a tm struct. + */ +void OsnmaMsgReceiverTest::set_time(std::tm& input) +{ + auto epoch_time_point = std::chrono::system_clock::from_time_t(mktime(&GST_START_EPOCH)); + auto input_time_point = std::chrono::system_clock::from_time_t(mktime(&input)); + + // Get the duration from epoch in seconds + auto duration_sec = std::chrono::duration_cast(input_time_point - epoch_time_point); + + // Calculate the week number (WN) and time of week (TOW) + uint32_t sec_in_week = 7 * 24 * 60 * 60; + uint32_t week_number = duration_sec.count() / sec_in_week; + uint32_t time_of_week = duration_sec.count() % sec_in_week; + this->WN = week_number; + this->TOW = time_of_week + LEAP_SECONDS; + // Return the week number and time of week as a pair + + // TODO: d_GST_SIS or d_receiver_time? doubt + // I am assuming that local realisation of receiver is identical to SIS GST time coming from W5 or W0 + this->d_GST_SIS = (this->WN & 0x00000FFF) << 20 | (this->TOW & 0x000FFFFF); +} diff --git a/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_test_vectors.cc b/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_test_vectors.cc new file mode 100644 index 000000000..7ac04d3c7 --- /dev/null +++ b/src/tests/unit-tests/signal-processing-blocks/osnma/osnma_test_vectors.cc @@ -0,0 +1,671 @@ +/*! + * \file osmna_test_vectors.cc + * \brief Tests for the osnma_msg_receiver class. + * \author Carles Fernandez, 2023-2024. cfernandez(at)cttc.es + * Cesare Ghionoiu Martinez, 2023-2024. c.ghionoiu-martinez@tu-braunschweig.de + * + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2024 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "gnss_crypto.h" +#include "osnma_msg_receiver.h" +#include +#include +#include +#include +#include + +#if USE_GLOG_AND_GFLAGS +#include // for LOG +#else +#include +#endif + +struct TestVector +{ + int svId; + int numNavBits; + std::vector navBits; +}; + + +class OsnmaTestVectors : public ::testing::Test +{ +protected: + std::vector parseNavBits(const std::string& hex); + std::vector readTestVectorsFromFile(const std::string& filename); + std::string bytes_to_str(const std::vector& bytes); + std::vector extract_page_bytes(const TestVector& tv, int byte_index, int num_bytes); + bool feedOsnmaWithTestVectors(osnma_msg_receiver_sptr osnma_object, std::vector> testVectors, std::vector startTimesFiles); + void set_time(std::tm& input); + void SetUp() override + { + } + + uint32_t d_GST_SIS{}; + uint32_t TOW{}; + uint32_t WN{}; + std::tm GST_START_EPOCH = {0, 0, 0, 22, 8 - 1, 1999 - 1900, 0, 0, 0, 0, 0}; // months start with 0 and years since 1900 in std::tm + const uint32_t LEAP_SECONDS = 0; + const int SIZE_PAGE_BYTES{240 / 8}; // total bytes of a page + const int SIZE_SUBFRAME_PAGES{15}; // number of pages of a subframe + const int DURATION_SUBFRAME{30}; // duration of a subframe, in seconds// 13 + 5; + + bool d_flag_NPK{false}; // flag for NPK, new MT will be set when the new Kroot is received. +}; + +TEST_F(OsnmaTestVectors, NominalTestConf1) +{ + // Arrange + std::string crtFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_1/PublicKey/OSNMA_PublicKey_20230803105952_newPKID_1.crt"; + std::string merkleFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_1/MerkleTree/OSNMA_MerkleTree_20230803105953_newPKID_1.xml"; + osnma_msg_receiver_sptr osnma = osnma_msg_receiver_make(crtFilePath, merkleFilePath); + + std::tm input_time = {0, 0, 5, 16, 8 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::vector input_times = {input_time}; + + std::vector testVector = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/configuration_1/16_AUG_2023_GST_05_00_01.csv"); + if (testVector.empty()) + { + ASSERT_TRUE(false); + } + std::vector> testVectors = {testVector}; + + // Act + bool result = feedOsnmaWithTestVectors(osnma, testVectors, input_times); + ASSERT_TRUE(result); + + // Assert + LOG(INFO) << "Successful tags count= " << osnma->d_count_successful_tags; + LOG(INFO) << "Failed tags count= " << osnma->d_count_failed_tags; + LOG(INFO) << "Unverified tags count= " << osnma->d_tags_awaiting_verify.size(); + LOG(INFO) << "Failed Kroot count= " << osnma->d_count_failed_Kroot; + LOG(INFO) << "Failed PK count= " << osnma->d_count_failed_pubKey; + LOG(INFO) << "Failed MACSEQ count= " << osnma->d_count_failed_macseq; + ASSERT_EQ(osnma->d_count_failed_tags, 0); + ASSERT_EQ(osnma->d_count_failed_Kroot, 0); + ASSERT_EQ(osnma->d_count_failed_pubKey, 0); + ASSERT_EQ(osnma->d_count_failed_macseq, 0); +} + +TEST_F(OsnmaTestVectors, NominalTestConf2) +{ + // Arrange + std::string crtFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/PublicKey/OSNMA_PublicKey_20230720113300_newPKID_2.crt"; // conf. 2 + std::string merkleFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/MerkleTree/OSNMA_MerkleTree_20230720113300_newPKID_2.xml"; + osnma_msg_receiver_sptr osnma = osnma_msg_receiver_make(crtFilePath, merkleFilePath); + + std::tm input_time = {0, 0, 0, 27, 7 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; // conf. 2 + std::vector input_times = {input_time}; + + std::vector testVector = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/configuration_2/27_JUL_2023_GST_00_00_01.csv"); + if (testVector.empty()) + { + ASSERT_TRUE(false); + } + std::vector> testVectors = {testVector}; + + // Act + bool result = feedOsnmaWithTestVectors(osnma, testVectors, input_times); + ASSERT_TRUE(result); + + // Assert + LOG(INFO) << "Successful tags count= " << osnma->d_count_successful_tags; + LOG(INFO) << "Failed tags count= " << osnma->d_count_failed_tags; + LOG(INFO) << "Unverified tags count= " << osnma->d_tags_awaiting_verify.size(); + LOG(INFO) << "Failed Kroot count= " << osnma->d_count_failed_Kroot; + LOG(INFO) << "Failed PK count= " << osnma->d_count_failed_pubKey; + LOG(INFO) << "Failed MACSEQ count= " << osnma->d_count_failed_macseq; + ASSERT_EQ(osnma->d_count_failed_tags, 0); + ASSERT_EQ(osnma->d_count_failed_Kroot, 0); + ASSERT_EQ(osnma->d_count_failed_pubKey, 0); + ASSERT_EQ(osnma->d_count_failed_macseq, 0); +} + +TEST_F(OsnmaTestVectors, PublicKeyRenewal) +{ + // Arrange + std::string crtFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/PublicKey/OSNMA_PublicKey_20231007041500_PKID_7.crt"; + std::string merkleFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/MerkleTree/OSNMA_MerkleTree_20231007041500_PKID_7.xml"; + osnma_msg_receiver_sptr osnma = osnma_msg_receiver_make(crtFilePath, merkleFilePath); + + std::tm input_time_step1 = {0, 45, 2, 7, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::tm input_time_step2 = {0, 45, 3, 7, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::tm input_time_step3 = {0, 45, 4, 7, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::vector input_times = {input_time_step1, input_time_step2, input_time_step3}; + + std::vector testVectors_step1 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/npk_step1/07_OCT_2023_GST_02_45_01.csv"); + std::vector testVectors_step2 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/npk_step2/07_OCT_2023_GST_03_45_01.csv"); + std::vector testVectors_step3 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/npk_step3/07_OCT_2023_GST_04_45_01.csv"); + if (testVectors_step1.empty() || testVectors_step2.empty() || testVectors_step3.empty()) + { + ASSERT_TRUE(false); + } + std::vector> testVectors = {testVectors_step1, testVectors_step2, testVectors_step3}; + + // Act + d_flag_NPK = true; + bool result = feedOsnmaWithTestVectors(osnma, testVectors, input_times); + ASSERT_TRUE(result); + + // Assert + LOG(INFO) << "Successful tags count= " << osnma->d_count_successful_tags; + LOG(INFO) << "Failed tags count= " << osnma->d_count_failed_tags; + LOG(INFO) << "Unverified tags count= " << osnma->d_tags_awaiting_verify.size(); + LOG(INFO) << "Failed Kroot count= " << osnma->d_count_failed_Kroot; + LOG(INFO) << "Failed PK count= " << osnma->d_count_failed_pubKey; + LOG(INFO) << "Failed MACSEQ count= " << osnma->d_count_failed_macseq; + ASSERT_EQ(osnma->d_count_failed_tags, 0); + ASSERT_EQ(osnma->d_count_failed_Kroot, 0); + ASSERT_EQ(osnma->d_count_failed_pubKey, 0); + ASSERT_EQ(osnma->d_count_failed_macseq, 0); +} + +TEST_F(OsnmaTestVectors, PublicKeyRevocation) +{ + // Arrange + std::string crtFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/PublicKey/OSNMA_PublicKey_20231007081500_PKID_8.crt"; + std::string merkleFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/MerkleTree/OSNMA_MerkleTree_20231007081500_PKID_8.xml"; + osnma_msg_receiver_sptr osnma = osnma_msg_receiver_make(crtFilePath, merkleFilePath); + + std::tm input_time_step1 = {0, 45, 7, 7, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::tm input_time_step2 = {0, 30, 9, 7, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::tm input_time_step3 = {0, 30, 10, 7, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::vector input_times = {input_time_step1, input_time_step2, input_time_step3}; + + std::vector testVectors_step1 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/pkrev_step1/07_OCT_2023_GST_07_45_01.csv"); + std::vector testVectors_step2 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/pkrev_step2/07_OCT_2023_GST_09_30_01.csv"); + std::vector testVectors_step3 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/pkrev_step3/07_OCT_2023_GST_10_30_01.csv"); + if (testVectors_step1.empty() || testVectors_step2.empty() || testVectors_step3.empty()) + { + ASSERT_TRUE(false); + } + std::vector> testVectors = {testVectors_step1, testVectors_step2, testVectors_step3}; + + // Act + bool result = feedOsnmaWithTestVectors(osnma, testVectors, input_times); + ASSERT_TRUE(result); + + // Assert + LOG(INFO) << "Successful tags count= " << osnma->d_count_successful_tags; + LOG(INFO) << "Failed tags count= " << osnma->d_count_failed_tags; + LOG(INFO) << "Unverified tags count= " << osnma->d_tags_awaiting_verify.size(); + LOG(INFO) << "Failed Kroot count= " << osnma->d_count_failed_Kroot; + LOG(INFO) << "Failed PK count= " << osnma->d_count_failed_pubKey; + LOG(INFO) << "Failed MACSEQ count= " << osnma->d_count_failed_macseq; + ASSERT_EQ(osnma->d_count_failed_tags, 0); + ASSERT_EQ(osnma->d_count_failed_Kroot, 0); + ASSERT_EQ(osnma->d_count_failed_pubKey, 0); + ASSERT_EQ(osnma->d_count_failed_macseq, 0); +} + +TEST_F(OsnmaTestVectors, ChainRenewal) +{ + // Arrange + std::string crtFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/PublicKey/OSNMA_PublicKey_20231007041500_PKID_7.crt"; + std::string merkleFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/MerkleTree/OSNMA_MerkleTree_20231007041500_PKID_7.xml"; + osnma_msg_receiver_sptr osnma = osnma_msg_receiver_make(crtFilePath, merkleFilePath); + + std::tm input_time_step1 = {0, 45, 16, 6, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::tm input_time_step2 = {0, 30, 18, 6, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::vector input_times = {input_time_step1, input_time_step2}; + + std::vector testVectors_step1 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/eoc_step1/06_OCT_2023_GST_16_45_01.csv"); + std::vector testVectors_step2 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/eoc_step2/06_OCT_2023_GST_18_30_01.csv"); + if (testVectors_step1.empty() || testVectors_step2.empty()) + { + ASSERT_TRUE(false); + } + std::vector> testVectors = {testVectors_step1, testVectors_step2}; + + // Act + bool result = feedOsnmaWithTestVectors(osnma, testVectors, input_times); + ASSERT_TRUE(result); + + // Assert + LOG(INFO) << "Successful tags count= " << osnma->d_count_successful_tags; + LOG(INFO) << "Failed tags count= " << osnma->d_count_failed_tags; + LOG(INFO) << "Unverified tags count= " << osnma->d_tags_awaiting_verify.size(); + LOG(INFO) << "Failed Kroot count= " << osnma->d_count_failed_Kroot; + LOG(INFO) << "Failed PK count= " << osnma->d_count_failed_pubKey; + LOG(INFO) << "Failed MACSEQ count= " << osnma->d_count_failed_macseq; + ASSERT_EQ(osnma->d_count_failed_tags, 0); + ASSERT_EQ(osnma->d_count_failed_Kroot, 0); + ASSERT_EQ(osnma->d_count_failed_pubKey, 0); + ASSERT_EQ(osnma->d_count_failed_macseq, 0); +} + +TEST_F(OsnmaTestVectors, ChainRevocation) +{ + // Arrange + std::string crtFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/PublicKey/OSNMA_PublicKey_20231007041500_PKID_7.crt"; + std::string merkleFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/MerkleTree/OSNMA_MerkleTree_20231007041500_PKID_7.xml"; + osnma_msg_receiver_sptr osnma = osnma_msg_receiver_make(crtFilePath, merkleFilePath); + + std::tm input_time_step1 = {0, 45, 21, 6, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::tm input_time_step2 = {0, 30, 23, 6, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::tm input_time_step3 = {0, 30, 00, 7, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + + std::vector input_times = {input_time_step1, input_time_step2, input_time_step3}; + + std::vector testVectors_step1 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/crev_step1/06_OCT_2023_GST_21_45_01.csv"); + std::vector testVectors_step2 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/crev_step2/06_OCT_2023_GST_23_30_01.csv"); + std::vector testVectors_step3 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/crev_step3/07_OCT_2023_GST_00_30_01.csv"); + if (testVectors_step1.empty() || testVectors_step2.empty() || testVectors_step3.empty()) + { + ASSERT_TRUE(false); + } + std::vector> testVectors = {testVectors_step1, testVectors_step2, testVectors_step3}; + + // Act + bool result = feedOsnmaWithTestVectors(osnma, testVectors, input_times); + ASSERT_TRUE(result); + + // Assert + LOG(INFO) << "Successful tags count= " << osnma->d_count_successful_tags; + LOG(INFO) << "Failed tags count= " << osnma->d_count_failed_tags; + LOG(INFO) << "Unverified tags count= " << osnma->d_tags_awaiting_verify.size(); + LOG(INFO) << "Failed Kroot count= " << osnma->d_count_failed_Kroot; + LOG(INFO) << "Failed PK count= " << osnma->d_count_failed_pubKey; + LOG(INFO) << "Failed MACSEQ count= " << osnma->d_count_failed_macseq; + ASSERT_EQ(osnma->d_count_failed_tags, 0); + ASSERT_EQ(osnma->d_count_failed_Kroot, 0); + ASSERT_EQ(osnma->d_count_failed_pubKey, 0); + ASSERT_EQ(osnma->d_count_failed_macseq, 0); +} + +TEST_F(OsnmaTestVectors, AlertMessage) +{ + // Arrange + std::string crtFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_3/PublicKey/OSNMA_PublicKey_20231007201500_PKID_1.crt"; + std::string merkleFilePath = std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_3/MerkleTree/OSNMA_MerkleTree_20231007201500_PKID_1.xml"; + osnma_msg_receiver_sptr osnma = osnma_msg_receiver_make(crtFilePath, merkleFilePath); + + std::tm input_time_step1 = {0, 45, 18, 7, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::tm input_time_step2 = {0, 45, 19, 7, 10 - 1, 2023 - 1900, 0, 0, 0, 0, 0}; + std::vector input_times = {input_time_step1, input_time_step2}; + + std::vector testVectors_step1 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/oam_step1/07_OCT_2023_GST_18_45_01.csv"); + std::vector testVectors_step2 = readTestVectorsFromFile(std::string(BASE_OSNMA_TEST_VECTORS) + "osnma_test_vectors/oam_step2/07_OCT_2023_GST_19_45_01.csv"); + if (testVectors_step1.empty() || testVectors_step2.empty()) + { + ASSERT_TRUE(false); + } + std::vector> testVectors = {testVectors_step1, testVectors_step2}; + + // Act + bool result = feedOsnmaWithTestVectors(osnma, testVectors, input_times); + ASSERT_TRUE(result); + + // Assert + LOG(INFO) << "Successful tags count= " << osnma->d_count_successful_tags; + LOG(INFO) << "Failed tags count= " << osnma->d_count_failed_tags; + LOG(INFO) << "Unverified tags count= " << osnma->d_tags_awaiting_verify.size(); + LOG(INFO) << "Failed Kroot count= " << osnma->d_count_failed_Kroot; + LOG(INFO) << "Failed PK count= " << osnma->d_count_failed_pubKey; + LOG(INFO) << "Failed MACSEQ count= " << osnma->d_count_failed_macseq; + ASSERT_EQ(osnma->d_count_failed_tags, 0); + ASSERT_EQ(osnma->d_count_failed_Kroot, 0); + ASSERT_EQ(osnma->d_count_failed_pubKey, 0); + ASSERT_EQ(osnma->d_count_failed_macseq, 0); +} + +// Auxiliary functions for the OsnmaTestVectorsSimulation test fixture. +// Essentially, they perform same work as the telemetry decoder block, but adapted to the osnma-test-vector files. +bool OsnmaTestVectors::feedOsnmaWithTestVectors(osnma_msg_receiver_sptr osnma_object, std::vector> testVectors, std::vector startTimesFiles) +{ + bool end_of_hex_stream; + int offset_byte{0}; + int byte_index{0}; // index containing the last byte position of the hex stream that was retrieved. Takes advantage that all TVs have same size + + const int DUMMY_PAGE{63}; + bool flag_dummy_page{false}; + // Act + // loop over all bytes of data. Note: all TestVectors have same amount of data. + // if needed, add global flags so that particular logic may be done at certain points in between files + for (size_t test_step = 0; test_step < testVectors.size(); test_step++) + { + // set variables for each file + end_of_hex_stream = false; + offset_byte = 0; + byte_index = 0; + set_time(startTimesFiles[test_step]); + std::cout << "OsnmaTestVectorsSimulation:" + << " d_GST_SIS= " << d_GST_SIS + << ", TOW=" << TOW + << ", WN=" << WN << std::endl; + + if (test_step == 1 && d_flag_NPK == true) + { + // step 2: this simulates the osnma connecting to the GSC server and downloading the Merkle tree of the next public key + osnma_object->read_merkle_xml( + std::string(BASE_OSNMA_TEST_VECTORS) + "cryptographic_material/Merkle_tree_2/MerkleTree/OSNMA_MerkleTree_20231007081500_PKID_8.xml"); + } + + while (!end_of_hex_stream) + { + // loop over all SVs, extract a subframe + for (const TestVector& tv : testVectors[test_step]) + { // loop over all SVs, extract a subframe + std::cout << "OsnmaTestVectorsSimulation: SVID (PRN_a) " << tv.svId << std::endl; + auto osnmaMsg_sptr = std::make_shared(); + std::array hkroot{}; + std::array mack{}; + byte_index = offset_byte; // reset byte_index to the offset position for the next test vector. Offset is updated at the end of each Subframe (every 30 s or 450 Bytes) + std::map> words_for_OSNMA; // structure containing and + + for (int idx = 0; idx < SIZE_SUBFRAME_PAGES; ++idx) // extract all pages of a subframe + { + // extract bytes of complete page (odd+even) -- extract SIZE_PAGE from tv.navBits, starting from byte_index + std::vector page_bytes = extract_page_bytes(tv, byte_index, SIZE_PAGE_BYTES); + if (page_bytes.empty()) + { + std::cout << "OsnmaTestVectorsSimulation: end of TestVectors \n" + << "byte_index=" << byte_index << " expected= " << 432000 / 8 << std::endl; + end_of_hex_stream = true; + break; + } + // convert them to bitset representation using bytes_to_string + std::string page_bits = bytes_to_str(page_bytes); + // Extract the 40 OSNMA bits starting from the 18th bit + std::string even_page = page_bits.substr(0, page_bits.size() / 2); + std::string odd_page = page_bits.substr(page_bits.size() / 2); + if (even_page.size() < 120 || odd_page.size() < 120) + { + std::cout << "OsnmaTestVectorsSimulation: error parsing pages" << std::endl; + } + bool even_odd_OK = even_page[0] == '0' && odd_page[0] == '1'; + bool page_type_OK = even_page[1] == '0' && odd_page[1] == '0'; + bool tail_bits_OK = even_page.substr(even_page.size() - 6) == "000000" && odd_page.substr(odd_page.size() - 6) == "000000"; + if (!even_odd_OK || !page_type_OK || !tail_bits_OK) + std::cerr << "OsnmaTestVectorsSimulation: error parsing pages." << std::endl; + + std::bitset<112> data_k(even_page.substr(2, 112)); + std::bitset<16> data_j(odd_page.substr(2, 16)); + std::bitset<112> shifted_data_k = data_k; + uint8_t word_type = static_cast((shifted_data_k >>= 106).to_ulong()); // word type is the first 6 bits of the word + // std::cout << "OsnmaTestVectorsSimulation: received Word " << static_cast(word_type) << std::endl; + if ((word_type >= 1 && word_type <= 5) || word_type == 6 || word_type == 10) + { + // store raw word + std::bitset<128> data_combined(data_k.to_string() + data_j.to_string()); + words_for_OSNMA[word_type] = data_combined; + } + if (word_type == DUMMY_PAGE) + flag_dummy_page = true; + + // place it into osnma object. + std::bitset<40> osnmaBits(odd_page.substr(18, 40)); + + // Extract bits for hkroot and mack + std::bitset<8> hkrootBits(osnmaBits.to_string().substr(0, 8)); + std::bitset<32> mackBits(osnmaBits.to_string().substr(8, 32)); + hkroot[idx] = static_cast(hkrootBits.to_ulong()); + mack[idx] = static_cast(mackBits.to_ulong()); + + byte_index += SIZE_PAGE_BYTES; + } + + // std::cout << "----------" << std::endl; + if (end_of_hex_stream) + break; + if (flag_dummy_page) + { + flag_dummy_page = false; + continue; // skip this SV + } + + // Fill osnma object + osnmaMsg_sptr->hkroot = hkroot; + osnmaMsg_sptr->mack = mack; + + osnmaMsg_sptr->TOW_sf0 = d_GST_SIS & 0x000FFFFF; + osnmaMsg_sptr->WN_sf0 = (d_GST_SIS & 0xFFF00000) >> 20; + osnmaMsg_sptr->PRN = tv.svId; // PRNa + + // TODO - refactor this logic, currently it is split + // check if words_for_OSNMA 1--> 5 words_for_OSNMA are received => fill EphClockStatus data vector + bool ephClockStatusWordsReceived = true; + for (int i = 1; i <= 5; ++i) + { + if (words_for_OSNMA.find(i) == words_for_OSNMA.end()) + { + ephClockStatusWordsReceived = false; + std::cerr << "OsnmaTestVectorsSimulation: error parsing words_for_OSNMA 1->5. " + "Word " + << i << " should be received for each subframe but was not." << std::endl; + } + } + // extract bits as needed by osnma block + if (ephClockStatusWordsReceived) + { + // Define the starting position and length of bits to extract for each word + std::map> extractionParams = { + {1, {6, 120}}, + {2, {6, 120}}, + {3, {6, 122}}, + {4, {6, 120}}, + {5, {6, 67}}, + }; + + // Fill NavData bits -- Iterate over the extraction parameters + std::string nav_data_ADKD_0_12 = ""; + for (const auto& param : extractionParams) + { + uint8_t wordKey = param.first; + uint8_t start = param.second.first; + uint8_t length = param.second.second; + + // Extract the required bits and fill osnma block + nav_data_ADKD_0_12 += words_for_OSNMA[wordKey].to_string().substr(start, length); + } + // send to osnma block + bool check_size_is_ok = nav_data_ADKD_0_12.size() == 549; + if (check_size_is_ok) + { + std::cout << "Galileo OSNMA: sending ADKD=0/12 navData, PRN_d (" << tv.svId << ") " + << "TOW_sf=" << osnmaMsg_sptr->TOW_sf0 << std::endl; + const auto tmp_obj_osnma = std::make_shared>( // < PRNd , navDataBits, TOW_Sosf> + tv.svId, + nav_data_ADKD_0_12, + osnmaMsg_sptr->TOW_sf0); + // LOG(INFO) << "|---> Galileo OSNMA :: Telemetry Decoder NavData (PRN_d=" << static_cast(tv.svId) << ", TOW=" << static_cast(osnmaMsg_sptr->TOW_sf0) << "): 0b" << nav_data_ADKD_0_12; + osnma_object->msg_handler_osnma(pmt::make_any(tmp_obj_osnma)); + } + } + + // check w6 && w10 is received => fill TimingData data vector + bool timingWordsReceived = words_for_OSNMA.find(6) != words_for_OSNMA.end() && + words_for_OSNMA.find(10) != words_for_OSNMA.end(); + // extract bits as needed by osnma block + if (timingWordsReceived) + { + // Define the starting position and length of bits to extract for each word + std::map> extractionParams = { + {6, {6, 99}}, + {10, {86, 42}}}; + + std::string nav_data_ADKD_4 = ""; + // Fill NavData bits -- Iterate over the extraction parameters + for (const auto& param : extractionParams) + { + uint8_t wordKey = param.first; + uint8_t start = param.second.first; + uint8_t length = param.second.second; + + // Extract the required bits and fill osnma block + nav_data_ADKD_4 += words_for_OSNMA[wordKey].to_string().substr(start, length); + } + // send to osnma block + bool check_size_is_ok = nav_data_ADKD_4.size() == 141; + if (check_size_is_ok) + { + std::cout << "Galileo OSNMA: sending ADKD=04 navData, PRN_d (" << tv.svId << ") " + << "TOW_sf=" << osnmaMsg_sptr->TOW_sf0 << std::endl; + const auto tmp_obj_osnma = std::make_shared>( // < PRNd , navDataBits, TOW_Sosf> + tv.svId, + nav_data_ADKD_4, + osnmaMsg_sptr->TOW_sf0); + // LOG(INFO) << "|---> Galileo OSNMA :: Telemetry Decoder NavData (PRN_d=" << static_cast(tv.svId) << ", TOW=" << static_cast(osnmaMsg_sptr->TOW_sf0) << "): 0b" << nav_data_ADKD_4; + osnma_object->msg_handler_osnma(pmt::make_any(tmp_obj_osnma)); + } + } + + // Call the handler, as if it came from telemetry decoder block + auto temp_obj = pmt::make_any(osnmaMsg_sptr); + + osnma_object->msg_handler_osnma(temp_obj); // osnma entry point + } + if (!end_of_hex_stream) + { + offset_byte = byte_index; // update offset for the next subframe + d_GST_SIS += DURATION_SUBFRAME; + TOW = d_GST_SIS & 0x000FFFFF; + WN = (d_GST_SIS & 0xFFF00000) >> 20; + std::cout << "OsnmaTestVectorsSimulation:" + << " d_GST_SIS= " << d_GST_SIS + << ", TOW=" << TOW + << ", WN=" << WN << std::endl; + } + } + if (end_of_hex_stream) + continue; + } + return true; +} + +std::vector OsnmaTestVectors::readTestVectorsFromFile(const std::string& filename) +{ + std::ifstream file(filename); + std::vector testVectors; + if (!file.is_open()) + { + std::cerr << "Error reading the file \"" << filename << "\" \n"; + return testVectors; + } + + std::string line; + std::getline(file, line); + if (line != "SVID,NumNavBits,NavBitsHEX\r") + { + std::cerr << "Error parsing first line" + << "\n"; + } + + while (std::getline(file, line)) + { + std::stringstream ss(line); + TestVector tv; + + std::string val; + + std::getline(ss, val, ','); + tv.svId = std::stoi(val); + + std::getline(ss, val, ','); + tv.numNavBits = std::stoi(val); + + std::getline(ss, val, ','); + tv.navBits = OsnmaTestVectors::parseNavBits(val); + + testVectors.push_back(tv); + } + + return testVectors; +} + +std::vector OsnmaTestVectors::parseNavBits(const std::string& hexadecimal) +{ + std::vector bytes; + + for (unsigned int i = 0; i < hexadecimal.length() - 1; i += 2) + { + std::string byteString = hexadecimal.substr(i, 2); + uint8_t byte = static_cast(strtol(byteString.c_str(), nullptr, 16)); + bytes.push_back(byte); + } + return bytes; +} + +std::string OsnmaTestVectors::bytes_to_str(const std::vector& bytes) +{ + std::string bit_string; + bit_string.reserve(bytes.size() * 8); + for (const auto& byte : bytes) + { + std::bitset<8> bits(byte); + bit_string += bits.to_string(); + } + return bit_string; +} + +/** + * @brief Extracts a range of bytes from a TestVector's navBits vector. + * + * This function extracts a extracts the bytes of complete page (odd+even) + * from the navBits vector of a TestVector object. + * + * + * @param tv The TestVector object from which to extract bytes. + * @param byte_index The index of the first byte to extract. + * @param num_bytes The number of bytes to extract. + * @return A vector containing the extracted bytes, or an empty vector if extraction is not possible. + */ +std::vector OsnmaTestVectors::extract_page_bytes(const TestVector& tv, int byte_index, int num_bytes) +{ + // Ensure we don't go beyond the end of tv.navBits + int num_bytes_to_extract = std::min(num_bytes, static_cast(tv.navBits.size() - byte_index)); + + // If byte_index is beyond the end of tv.navBits, return an empty vector + if (num_bytes_to_extract <= 0) + { + return std::vector(); + } + + // Use std::next to get an iterator to the range to extract + std::vector extracted_bytes(tv.navBits.begin() + byte_index, tv.navBits.begin() + byte_index + num_bytes_to_extract); + + return extracted_bytes; +} + +/** + * @brief Sets the time based on the given input. + * + * This function calculates the week number (WN) and time of week (TOW) + * based on the input time and the GST_START_EPOCH. It then stores the + * calculated values in the WN and TOW member variables. Finally, it + * combines the WN and TOW into a single 32-bit value and stores it in + * the d_GST_SIS member variable. + * \post WN, TOW and GST_SIS are set up based on the input time. + * + * @param input The input time as a tm struct. + */ +void OsnmaTestVectors::set_time(std::tm& input) +{ + auto epoch_time_point = std::chrono::system_clock::from_time_t(mktime(&GST_START_EPOCH)); + auto input_time_point = std::chrono::system_clock::from_time_t(mktime(&input)); + + // Get the duration from epoch in seconds + auto duration_sec = std::chrono::duration_cast(input_time_point - epoch_time_point); + + // Calculate the week number (WN) and time of week (TOW) + uint32_t sec_in_week = 7 * 24 * 60 * 60; + uint32_t week_number = duration_sec.count() / sec_in_week; + uint32_t time_of_week = duration_sec.count() % sec_in_week; + this->WN = week_number; + this->TOW = time_of_week + LEAP_SECONDS; + // Return the week number and time of week as a pair + + // TODO: d_GST_SIS or d_receiver_time? doubt + // I am assuming that local realisation of receiver is identical to SIS GST time coming from W5 or W0 + this->d_GST_SIS = (this->WN & 0x00000FFF) << 20 | (this->TOW & 0x000FFFFF); +}