From 3c2ec92ff08851a6a4c3c0eaed332ae580749895 Mon Sep 17 00:00:00 2001 From: sascha Date: Wed, 31 Dec 2025 13:29:12 +0100 Subject: [PATCH 01/41] Interpret vdv envelope and create cert provider 4 ldif certs from export file --- .vscode/launch.json | 12 ++ .../interpreter/api/source/Interpreter.cpp | 10 +- .../common/include/InterpreterUtility.h | 23 +++- .../common/source/InterpreterUtility.cpp | 28 ++-- .../uic918/source/Uic918Interpreter.cpp | 2 +- .../lib/interpreter/detail/vdv/CMakeLists.txt | 6 +- .../detail/vdv/include/BotanMessageDecoder.h | 23 ++++ .../detail/vdv/include/CertificateProvider.h | 28 ++++ .../interpreter/detail/vdv/include/Envelop.h | 20 +++ .../vdv/include/LDIFFileCertificateProvider.h | 39 ++++++ .../detail/vdv/include/MessageDecoder.h | 21 +++ .../detail/vdv/include/VDVInterpreter.h | 12 +- .../detail/vdv/include/VDVUtility.h | 19 +++ .../detail/vdv/source/BotanMessageDecoder.cpp | 31 +++++ .../source/LDIFFileCertificateProvider.cpp | 118 ++++++++++++++++ .../detail/vdv/source/VDVInterpreter.cpp | 129 +++++++++++++++--- .../detail/vdv/source/VDVUtility.cpp | 35 +++++ source/lib/utility/include/Base64.h | 3 + source/lib/utility/source/Base64.cpp | 5 + .../source/InterpreterUtilityTest.cpp | 37 ++++- 20 files changed, 555 insertions(+), 46 deletions(-) create mode 100644 source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h create mode 100644 source/lib/interpreter/detail/vdv/include/CertificateProvider.h create mode 100644 source/lib/interpreter/detail/vdv/include/Envelop.h create mode 100644 source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h create mode 100644 source/lib/interpreter/detail/vdv/include/MessageDecoder.h create mode 100644 source/lib/interpreter/detail/vdv/include/VDVUtility.h create mode 100644 source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp create mode 100644 source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp create mode 100644 source/lib/interpreter/detail/vdv/source/VDVUtility.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json index e85c3fca..9cc3a57d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -145,6 +145,18 @@ "cwd": "${workspaceFolder}", "preLaunchTask": "build debug" }, + { + "type": "lldb", + "request": "launch", + "name": "decoder - debug - image (VDV Einzelfahrausweis Ermäßigt)", + "program": "${workspaceFolder}/build/Debug/bin/ticket-decoder", + "args": [ + "-i", + "images/VDV Einzelfahrausweis Ermäßigt Berlin Rheinsberg.png" + ], + "cwd": "${workspaceFolder}", + "preLaunchTask": "build debug" + }, { "type": "lldb", "request": "launch", diff --git a/source/lib/interpreter/api/source/Interpreter.cpp b/source/lib/interpreter/api/source/Interpreter.cpp index ef05475f..0a0c8e10 100644 --- a/source/lib/interpreter/api/source/Interpreter.cpp +++ b/source/lib/interpreter/api/source/Interpreter.cpp @@ -27,6 +27,12 @@ namespace interpreter::api return std::make_pair(T::getTypeId(), decltype(interpreterMap)::mapped_type{new T(loggerFactory, signatureChecker)}); } + template + static decltype(interpreterMap)::value_type create(auto &loggerFactory) + { + return std::make_pair(T::getTypeId(), decltype(interpreterMap)::mapped_type{new T(loggerFactory)}); + } + Internal(infrastructure::Context &c, SignatureVerifier const &signatureChecker) : logger(CREATE_LOGGER(c.getLoggerFactory())), interpreterMap() @@ -35,7 +41,7 @@ namespace interpreter::api interpreterMap.emplace(create(c.getLoggerFactory(), signatureChecker)); #endif #ifdef WITH_VDV_INTERPRETER - interpreterMap.emplace(create(c.getLoggerFactory(), signatureChecker)); + interpreterMap.emplace(create(c.getLoggerFactory())); #endif #ifdef WITH_SBB_INTERPRETER interpreterMap.emplace(create(c.getLoggerFactory(), signatureChecker)); @@ -54,7 +60,7 @@ namespace interpreter::api auto const interpreter = interpreterMap.find(detail::common::Interpreter::TypeIdType(typeId.begin(), typeId.end())); if (interpreter == interpreterMap.end()) { - LOG_WARN(logger) << "Unknown message type: " << detail::common::bytesToString(typeId); + LOG_WARN(logger) << "Unknown message type: 0x" << detail::common::bytesToHexString(typeId); return std::move(context); } diff --git a/source/lib/interpreter/detail/common/include/InterpreterUtility.h b/source/lib/interpreter/detail/common/include/InterpreterUtility.h index 1015222d..32a4dc23 100644 --- a/source/lib/interpreter/detail/common/include/InterpreterUtility.h +++ b/source/lib/interpreter/detail/common/include/InterpreterUtility.h @@ -26,7 +26,24 @@ namespace interpreter::detail::common std::string getDate8(Context &context); - std::string bytesToString(std::span bytes); - - std::string bytesToString(std::vector const &bytes); + std::string bytesToAlphanumeric(std::span bytes); + + std::string bytesToHexString(std::span bytes); + + std::string bytesToHexString(std::vector const &bytes); + + template + std::string bytesToHexString(std::array const &bytes) + { + return bytesToHexString(std::span(bytes.data(), S)); + } + + template + std::string bytesToHexString(T const &bytes) + { + auto const raw = std::span((std::uint8_t const *const)&bytes, sizeof(T)); + return std::endian::native == std::endian::big + ? bytesToHexString(raw) + : bytesToHexString(std::vector(raw.rbegin(), raw.rend())); + } } diff --git a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp index c29fcb31..b6e2dd3d 100644 --- a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp +++ b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp @@ -16,12 +16,7 @@ namespace interpreter::detail::common std::string getAlphanumeric(Context &context, std::size_t size) { auto const data = context.consumeMaximalBytes(size); - auto result = std::string{std::begin(data), std::find(std::begin(data), std::end(data), '\0')}; - result.erase(std::find_if(std::rbegin(result), std::rend(result), [](unsigned char ch) - { return !std::isspace(ch); }) - .base(), - std::end(result)); - return result; + return bytesToAlphanumeric(data); } template @@ -111,22 +106,31 @@ namespace interpreter::detail::common return os.str(); } - std::string bytesToString(std::span typeId) + std::string bytesToAlphanumeric(std::span bytes) + { + auto result = std::string{std::begin(bytes), std::find(std::begin(bytes), std::end(bytes), '\0')}; + result.erase(std::find_if(std::rbegin(result), std::rend(result), [](unsigned char ch) + { return !std::isspace(ch); }) + .base(), + std::end(result)); + return result; + } + + std::string bytesToHexString(std::span bytes) { - if (typeId.empty()) + if (bytes.empty()) { return ""; } std::stringstream os; - os << "0x"; - std::for_each(std::begin(typeId), std::end(typeId), [&](auto const &byte) + std::for_each(std::begin(bytes), std::end(bytes), [&](auto const &byte) { os << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (int)byte; }); return os.str(); } - std::string bytesToString(std::vector const &typeId) + std::string bytesToHexString(std::vector const &bytes) { - return bytesToString(std::span(typeId.data(), typeId.size())); + return bytesToHexString(std::span(bytes.data(), bytes.size())); } } \ No newline at end of file diff --git a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp index 36aab70c..88983222 100644 --- a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp +++ b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp @@ -57,7 +57,7 @@ namespace interpreter::detail::uic auto const tid = context.consumeBytes(typeId.size()); if (Uic918Interpreter::TypeIdType(tid.begin(), tid.end()) != typeId) { - throw std::runtime_error("Unexpected UIC918 type ID, expecting " + common::bytesToString(typeId) + ", got: " + common::bytesToString(tid)); + throw std::runtime_error("Unexpected UIC918 type ID, expecting 0x" + common::bytesToHexString(typeId) + ", got: 0x" + common::bytesToHexString(tid)); } if (context.getRemainingSize() < 2) diff --git a/source/lib/interpreter/detail/vdv/CMakeLists.txt b/source/lib/interpreter/detail/vdv/CMakeLists.txt index 1e9da26b..02812c7b 100644 --- a/source/lib/interpreter/detail/vdv/CMakeLists.txt +++ b/source/lib/interpreter/detail/vdv/CMakeLists.txt @@ -3,6 +3,8 @@ PROJECT(ticket-decoder-interpreter-detail-vdv) +find_package(botan REQUIRED) + AUX_SOURCE_DIRECTORY("source" PROJECT_SOURCE) file(GLOB PROJECT_INCLUDES "include/*.h") @@ -11,4 +13,6 @@ target_include_directories(${PROJECT_NAME} PRIVATE) target_link_libraries(${PROJECT_NAME} PRIVATE easyloggingpp::easyloggingpp nlohmann_json::nlohmann_json - ticket-decoder-interpreter-detail-common) + botan::botan + ticket-decoder-interpreter-detail-common + ticket-decoder-utility) diff --git a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h new file mode 100644 index 00000000..b54a5fa1 --- /dev/null +++ b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "MessageDecoder.h" +#include "CertificateProvider.h" + +#include "lib/infrastructure/include/Logger.h" + +namespace interpreter::detail::vdv +{ + class BotanMessageDecoder : public MessageDecoder + { + infrastructure::Logger logger; + CertificateProvider &certificateProvider; + + public: + BotanMessageDecoder(infrastructure::LoggerFactory &loggerFactory, CertificateProvider &certificateProvider); + + virtual std::optional> decode(Envelop const &envelop) override; + }; +} diff --git a/source/lib/interpreter/detail/vdv/include/CertificateProvider.h b/source/lib/interpreter/detail/vdv/include/CertificateProvider.h new file mode 100644 index 00000000..bb0e454d --- /dev/null +++ b/source/lib/interpreter/detail/vdv/include/CertificateProvider.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +namespace interpreter::detail::vdv +{ + struct Certificate + { + std::string commonName; + std::string distinguishedName; + std::string description; + std::vector certificate; + }; + + class CertificateProvider + { + public: + virtual ~CertificateProvider() = default; + + virtual std::optional get(std::string commonName) = 0; + }; +} diff --git a/source/lib/interpreter/detail/vdv/include/Envelop.h b/source/lib/interpreter/detail/vdv/include/Envelop.h new file mode 100644 index 00000000..b3bf0e91 --- /dev/null +++ b/source/lib/interpreter/detail/vdv/include/Envelop.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +namespace interpreter::detail::vdv +{ + struct Envelop + { + std::string kennung; + std::string version; + std::string authority; + std::span signature; + std::span certificate; + }; +} diff --git a/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h new file mode 100644 index 00000000..46e0e043 --- /dev/null +++ b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "CertificateProvider.h" + +#include "lib/infrastructure/include/Logger.h" + +#include +#include + +namespace interpreter::detail::vdv +{ + + /* Use 'Apache Directory Studio' and connect to public ldap 'ldaps://ldap-vdv-ion.telesec.de:636' + to get company and root certificates. + - Install 'Apache Directory Studio' or another useful tool + - on macos via 'brew install apache-directory-studio' + - on linux see: https://directory.apache.org/studio/download/download-linux.html + - Create a new connection by using the host:port from above + - Navigate to 'c=de,o=VDV Kernapplikations GmbH,ou=VDV KA' + - Right click to 'ou=VDV KA' and select 'Export' and 'LDIF Export' + - Click continue and select a file name and continue until the export starts, + use '${workspaceFolder}/cert/VDV_Certificates.ldif' to make it working with default used below + */ + class LDIFFileCertificateProvider : public CertificateProvider + { + infrastructure::Logger logger; + std::optional> const entries; + + static std::optional> import(std::filesystem::path file); + + public: + LDIFFileCertificateProvider(infrastructure::LoggerFactory &loggerFactory, std::filesystem::path file = "cert/VDV_Certificates.ldif"); + + virtual std::optional get(std::string commonName) override; + }; +} diff --git a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h new file mode 100644 index 00000000..89c8345f --- /dev/null +++ b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "Envelop.h" + +#include +#include +#include + +namespace interpreter::detail::vdv +{ + class MessageDecoder + { + public: + virtual ~MessageDecoder() = default; + + virtual std::optional> decode(Envelop const &envelop) = 0; + }; +} diff --git a/source/lib/interpreter/detail/vdv/include/VDVInterpreter.h b/source/lib/interpreter/detail/vdv/include/VDVInterpreter.h index 11d7f4bb..e207724a 100644 --- a/source/lib/interpreter/detail/vdv/include/VDVInterpreter.h +++ b/source/lib/interpreter/detail/vdv/include/VDVInterpreter.h @@ -7,21 +7,23 @@ #include "lib/infrastructure/include/Logger.h" -namespace interpreter::api -{ - class SignatureVerifier; -} +#include "CertificateProvider.h" +#include "MessageDecoder.h" + +#include namespace interpreter::detail::vdv { class VDVInterpreter : public common::Interpreter { infrastructure::Logger logger; + std::unique_ptr certificateProvider; + std::unique_ptr messageDecoder; public: static TypeIdType getTypeId(); - VDVInterpreter(infrastructure::LoggerFactory &loggerFactory, api::SignatureVerifier const &signatureChecker); + VDVInterpreter(infrastructure::LoggerFactory &loggerFactory); virtual common::Context interpret(common::Context &&context) override; }; diff --git a/source/lib/interpreter/detail/vdv/include/VDVUtility.h b/source/lib/interpreter/detail/vdv/include/VDVUtility.h new file mode 100644 index 00000000..4fe84c8d --- /dev/null +++ b/source/lib/interpreter/detail/vdv/include/VDVUtility.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "lib/interpreter/detail/common/include/Context.h" + +#include +#include + +namespace interpreter::detail::vdv +{ + + using TagType = std::array; + + std::uint32_t getLength(common::Context &context); + + TagType getTag(common::Context &context); +} diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp new file mode 100644 index 00000000..049277da --- /dev/null +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../include/BotanMessageDecoder.h" + +#include "lib/infrastructure/include/Logging.h" + +#include + +namespace interpreter::detail::vdv +{ + BotanMessageDecoder::BotanMessageDecoder(infrastructure::LoggerFactory &lf, CertificateProvider &cp) + : logger(CREATE_LOGGER(lf)), + certificateProvider(cp) + { + } + + std::optional> BotanMessageDecoder::decode(Envelop const &envelop) + { + /* The value in 'authority' should match exactly one very specific entry in + the list of exported certificates from public LDAP server identified by + 'cn=,ou=VDV KA,o=VDV Kernapplikations GmbH,c=de' + 4555564456xxxxxx -> EUVDVxxxxxx + 4445564456xxxxxx -> DEVDVxxxxxx + */ + auto const companyCertificate = certificateProvider.get(envelop.authority); + auto const rootCertificate = certificateProvider.get("4555564456100106"); + + return std::nullopt; + } +} diff --git a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp new file mode 100644 index 00000000..04aa08dc --- /dev/null +++ b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../include/LDIFFileCertificateProvider.h" + +#include "lib/infrastructure/include/Logging.h" + +#include "lib/utility/include/Base64.h" + +#include +#include +#include +#include + +namespace interpreter::detail::vdv +{ + static std::string getValue(std::vector const &entryLines, std::string key) + { + auto const result = std::find_if(std::begin(entryLines), std::end(entryLines), [&](auto const &line) + { return line.starts_with(key); }); + return result == entryLines.end() + ? std::string{} + : result->substr(key.size(), result->size() - key.size()); + } + + std::optional> LDIFFileCertificateProvider::import(std::filesystem::path file) + { + if (!std::filesystem::exists(file) || !std::filesystem::is_regular_file(file)) + { + return std::nullopt; + } + + auto entryLines = std::vector>(); + auto entry = std::vector(); + auto stream = std::ifstream(file); + auto count = 1; + for (auto line = std::string(); std::getline(stream, line); count++) + { + if (line.starts_with('#')) // Ignore comments + { + continue; + } + + if (line.empty()) // An empty line (2 directly following newlines) indicate a new entry + { + entryLines.emplace_back(std::move(entry)); + entry = std::vector(); + continue; + } + + if (line.starts_with(' ')) // Line starting with space indicates line-folding, that means the following line belongs the the line before + { + if (entry.empty()) + { + throw std::runtime_error(std::string("Expect line-folding after regular line only but found it as a starting line: ") + std::to_string(count)); + } + (*entry.rbegin()) += line.erase(0, 1); + continue; + } + + entry.push_back(line); + } + + /* Remove all entries not having a distinguished name matching the following pattern + */ + auto const entryStart = std::regex("dn[:] cn[=]\\w+,ou[=]VDV KA,o[=]VDV Kernapplikations GmbH,c[=]de", std::regex::icase); + std::erase_if(entryLines, [&](auto const &lines) + { return lines.end() == std::find_if(std::begin(lines), std::end(lines), + [&](auto const &line) + { return std::regex_match(line, entryStart); }); }); + + /* Given file might be in LDIF format but might not contain desired entries matching the distinguished name above + */ + if (entryLines.empty()) + { + return std::nullopt; + } + + std::map entries; + std::transform(std::begin(entryLines), std::end(entryLines), std::inserter(entries, entries.begin()), [](auto const &lines) + { + auto commonName = getValue(lines, "cn: "); + return std::make_pair(commonName, Certificate{ + commonName, + getValue(lines, "dn: "), + getValue(lines, "description: "), + utility::base64::decode(getValue(lines, "cACertificate:: "))}); }); + + return entries; + } + + LDIFFileCertificateProvider::LDIFFileCertificateProvider(infrastructure::LoggerFactory &loggerFactory, std::filesystem::path file) + : logger(CREATE_LOGGER(loggerFactory)), + entries(import(file)) + { + if (!entries) + { + LOG_INFO(logger) << "Failed to import certificates from given LDIF file: " << file.string(); + } + else + { + LOG_DEBUG(logger) << "Imported no of certificates: " << entries->size(); + } + } + + std::optional LDIFFileCertificateProvider::get(std::string commonName) + { + if (!entries) + { + return std::nullopt; + } + + auto const entry = entries->find(commonName); + return entry == entries->end() + ? std::nullopt + : std::make_optional(entry->second); + } +} diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index f69a1321..b82f2766 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -2,11 +2,19 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "../include/VDVInterpreter.h" +#include "../include/LDIFFileCertificateProvider.h" +#include "../include/BotanMessageDecoder.h" +#include "../include/VDVUtility.h" +#include "../include/Envelop.h" #include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/utility/include/Base64.h" + #include "lib/infrastructure/include/Logging.h" +#include + namespace interpreter::detail::vdv { @@ -17,29 +25,114 @@ namespace interpreter::detail::vdv return typeId; } - VDVInterpreter::VDVInterpreter(infrastructure::LoggerFactory &lf, api::SignatureVerifier const &sc) - : logger(CREATE_LOGGER(lf)) + VDVInterpreter::VDVInterpreter(infrastructure::LoggerFactory &lf) + : logger(CREATE_LOGGER(lf)), + certificateProvider(std::make_unique(lf)), + messageDecoder(std::make_unique(lf, *certificateProvider)) { } + struct RecordDescriptor + { + std::string const name; + std::optional const expectedSize; + std::function const &data, Envelop &)> const collector; + std::function const consumer; + }; + + class RecordBuilder + { + static std::map const tagDescriptorMap; + + MessageDecoder &messageDecoder; + Envelop envelop; + + public: + RecordBuilder(MessageDecoder &md) : messageDecoder(md) {} + + bool add(TagType const &tag, std::span const &data) + { + auto const entry = tagDescriptorMap.find(tag); + if (entry == tagDescriptorMap.end()) + { + return false; + } + + auto const &descriptor = entry->second; + if (descriptor.expectedSize) + { + if (data.size() != *descriptor.expectedSize) + { + throw std::runtime_error(std::string("Expecting exactly ") + std::to_string(*descriptor.expectedSize) + " payload bytes for tag 0x" + common::bytesToHexString(tag) + " (" + descriptor.name + "), got: " + std::to_string(data.size())); + } + } + descriptor.collector(data, envelop); + return true; + } + + common::Record build() const + { + auto const message = messageDecoder.decode(envelop); + + auto jsonBuilder = utility::JsonBuilder::object(); + + std::for_each(std::begin(tagDescriptorMap), std::end(tagDescriptorMap), [&](auto const &entry) + { + auto const &descriptor = entry.second; + descriptor.consumer(descriptor.name, envelop, jsonBuilder); }); + + return common::Record(envelop.kennung, envelop.version, std::move(jsonBuilder)); + } + }; + + std::map const RecordBuilder::tagDescriptorMap = { + {TagType{0x9e, 0x00}, RecordDescriptor{"signature", 128, + [](auto const &data, auto &envelop) + { envelop.signature = data; }, + [](auto const &name, auto const &envelop, auto &jsonBuilder) + { jsonBuilder.add(name, utility::base64::encode(envelop.signature)); }}}, + {TagType{0x9a, 0x00}, RecordDescriptor{"identifier", 5, + [](auto const &data, auto &envelop) + { + envelop.kennung = common::bytesToAlphanumeric(data.subspan(0, 3)); + envelop.version = common::bytesToHexString(data.subspan(3, 2)); }, + [](auto const &name, auto const &envelop, auto &jsonBuilder) + { jsonBuilder + .add("kennung", envelop.kennung) + .add("version", envelop.version); }}}, + {TagType{0x7f, 0x21}, RecordDescriptor{"certificate", 200, [](auto const &data, auto &envelop) + { envelop.certificate = data; }, + [](auto const &name, auto const &envelop, auto &jsonBuilder) + { jsonBuilder.add(name, utility::base64::encode(envelop.certificate)); }}}, + {TagType{0x42, 0x00}, RecordDescriptor{"authority", 8, [](auto const &data, auto &envelop) + { envelop.authority = common::bytesToHexString(data); }, + [](auto const &name, auto const &envelop, auto &jsonBuilder) + { jsonBuilder.add(name, envelop.authority); }}}}; + common::Context VDVInterpreter::interpret(common::Context &&context) { - // Documentation: https://www.kcd-nrw.de/fileadmin/03_KC_Seiten/KCD/Downloads/Technische_Dokumente/Archiv/2010_02_12_kompendiumvrrfa2dvdv_1_4.pdf - // Reference-Impl: https://sourceforge.net/projects/dbuic2vdvbc/ - auto const tag = common::getNumeric8(context); - auto const signatureLength = common::getNumeric16(context); - auto const signature = context.consumeBytes(128); - - auto const ticketDataTag = common::getNumeric8(context); - auto const ticketDataLength = common::getNumeric8(context); - auto const ticketData = context.consumeBytes(128); - - auto const keyTag = common::getNumeric16(context); - auto const keyLength = common::getNumeric8(context); - auto const key = context.consumeBytes(12); - - auto const ignored = context.ignoreRemainingBytes(); - LOG_WARN(logger) << "Unsupported VDV barcode detected of size: " << ignored; + // Documentation (not fully matching anymore): + // - https://www.kcd-nrw.de/fileadmin/03_KC_Seiten/KCD/Downloads/Technische_Dokumente/Archiv/2010_02_12_kompendiumvrrfa2dvdv_1_4.pdf + // Quite old reference impl for encoding: + // - https://sourceforge.net/projects/dbuic2vdvbc/ + // Some more up-to-date hints: + // - https://magicalcodewit.ch/38c3-slides/#/32 + // - https://github.com/akorb/deutschlandticket_parser/blob/main/main.py + // - https://github.com/RWTH-i5-IDSG/ticketserver/blob/master/barti-check/src/main/java/de/rwth/idsg/barti/check/Decode.java + + auto recordBuilder = RecordBuilder(*messageDecoder); + while (context.getRemainingSize() > 0) + { + auto const tag = getTag(context); + auto const length = getLength(context); + + if (!recordBuilder.add(tag, context.consumeBytes(length))) + { + LOG_WARN(logger) << "Unknown record found: tag: 0x" << common::bytesToHexString(tag) << ", length: " << length; + } + } + + context.addRecord(recordBuilder.build()); return std::move(context); } } diff --git a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp new file mode 100644 index 00000000..f70d580e --- /dev/null +++ b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../include/VDVUtility.h" + +#include "lib/interpreter/detail/common/include/InterpreterUtility.h" + +namespace interpreter::detail::vdv +{ + /* Multi-byte integers are big-endian encoded, see DER ASN.1 + */ + std::uint32_t getLength(common::Context &context) + { + auto const first = common::getNumeric8(context); + // clang-format off + if (first < 0x80) { return first; } + else if (first == 0x81) { return common::getNumeric8(context); } + else if (first == 0x82) { return common::getNumeric16(context); } + else if (first == 0x83) { return common::getNumeric24(context); } + else if (first == 0x84) { return common::getNumeric32(context); } + // clang-format on + throw std::runtime_error(std::string("Found unexpected length indicator tag (expecting x<0x80 or x=0x8y with y=): ") + std::to_string(first)); + } + + TagType getTag(common::Context &context) + { + auto const first = common::getNumeric8(context); + if (first == 0x7f || first == 0x5f) + { + return {first, common::getNumeric8(context)}; + } + + return {first, 0}; + } +} diff --git a/source/lib/utility/include/Base64.h b/source/lib/utility/include/Base64.h index d7a1d703..ec877b03 100644 --- a/source/lib/utility/include/Base64.h +++ b/source/lib/utility/include/Base64.h @@ -6,6 +6,7 @@ #include #include #include +#include namespace utility::base64 { @@ -14,6 +15,8 @@ namespace utility::base64 std::string encode(std::vector const &in); + std::string encode(std::span in); + std::string encode(std::uint8_t const *const data, size_t size); } diff --git a/source/lib/utility/source/Base64.cpp b/source/lib/utility/source/Base64.cpp index a9b8dfa3..ff4c2d59 100644 --- a/source/lib/utility/source/Base64.cpp +++ b/source/lib/utility/source/Base64.cpp @@ -27,6 +27,11 @@ namespace utility::base64 return encode(in.data(), in.size()); } + std::string encode(std::span in) + { + return encode(in.data(), in.size()); + } + std::string encode(std::uint8_t const *const data, size_t size) { if (size == 0) diff --git a/source/test/interpreter/source/InterpreterUtilityTest.cpp b/source/test/interpreter/source/InterpreterUtilityTest.cpp index 4a6e9a93..70b40bb4 100644 --- a/source/test/interpreter/source/InterpreterUtilityTest.cpp +++ b/source/test/interpreter/source/InterpreterUtilityTest.cpp @@ -120,15 +120,44 @@ namespace interpreter::detail::common EXPECT_EQ(getDate8(context), "2021-01-13"); } - TEST(bytesToString, filled) + TEST(bytesToHexString, filled) { auto const source = std::vector{0x12, 0x0A, 0xAB, 0x00, 0xFF}; - EXPECT_EQ(bytesToString(source), "0x120AAB00FF"); + EXPECT_EQ(bytesToHexString(source), "120AAB00FF"); } - TEST(bytesToString, empty) + TEST(bytesToHexString, empty) { auto const source = std::vector{}; - EXPECT_EQ(bytesToString(source), ""); + EXPECT_EQ(bytesToHexString(source), ""); + } + + TEST(bytesToHexString, array) + { + EXPECT_EQ(bytesToHexString(std::array{0x12, 0x0A, 0xAB}), "120AAB"); + EXPECT_EQ(bytesToHexString(std::array{0x12, 0x0A}), "120A00"); + EXPECT_EQ(bytesToHexString(std::array{}), "000000"); + EXPECT_EQ(bytesToHexString(std::array{0x11, 0x22, 0x33, 0x44, 0x55}), "1122334455"); + } + + TEST(bytesToHexString, integer4) + { + EXPECT_EQ(bytesToHexString(std::uint32_t(0)), "00000000"); + EXPECT_EQ(bytesToHexString(std::uint32_t(1)), "00000001"); + EXPECT_EQ(bytesToHexString(std::uint32_t(0xffffffff)), "FFFFFFFF"); + } + + TEST(bytesToHexString, integer2) + { + EXPECT_EQ(bytesToHexString(std::uint16_t()), "0000"); + EXPECT_EQ(bytesToHexString(std::uint16_t(1)), "0001"); + EXPECT_EQ(bytesToHexString(std::uint16_t(0xffff)), "FFFF"); + } + + TEST(bytesToHexString, integer1) + { + EXPECT_EQ(bytesToHexString(std::uint8_t()), "00"); + EXPECT_EQ(bytesToHexString(std::uint8_t(1)), "01"); + EXPECT_EQ(bytesToHexString(std::uint8_t(0xff)), "FF"); } } From 51b1b6271e5c3e1df84d0f026e36945439bdb2fd Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 4 Jan 2026 20:59:31 +0100 Subject: [PATCH 02/41] Change interpreter context internal iterators from vector 2 span --- .../detail/common/include/Context.h | 11 ++++++---- .../detail/common/source/Context.cpp | 14 +++++++++++-- .../detail/uic918/include/RecordHeader.h | 2 +- .../detail/vdv/source/BotanMessageDecoder.cpp | 21 ++++++++++++++++++- 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/source/lib/interpreter/detail/common/include/Context.h b/source/lib/interpreter/detail/common/include/Context.h index cbc4f5ae..5c96a5f3 100644 --- a/source/lib/interpreter/detail/common/include/Context.h +++ b/source/lib/interpreter/detail/common/include/Context.h @@ -18,15 +18,18 @@ namespace interpreter::detail::common { struct Context { + using IteratorType = std::span::iterator; + std::size_t inputSize; - std::vector::const_iterator begin; - std::vector::const_iterator position; - std::vector::const_iterator end; + IteratorType begin; + IteratorType position; + IteratorType end; std::map output; std::map records; Context(std::vector const &input, std::string origin); Context(std::vector const &input, std::map &&f); + Context(std::span input); Context(Context const &) = delete; Context &operator=(Context const &) = delete; @@ -37,7 +40,7 @@ namespace interpreter::detail::common /* Returns a copy of iterator to the current position. Attention! The copy gets not updated when the internal position moves on. */ - std::vector::const_iterator getPosition() const; + IteratorType getPosition() const; /* Returns size bytes in a vector from current position to current position + size without consumtion. diff --git a/source/lib/interpreter/detail/common/source/Context.cpp b/source/lib/interpreter/detail/common/source/Context.cpp index 8404cfde..a28a047a 100644 --- a/source/lib/interpreter/detail/common/source/Context.cpp +++ b/source/lib/interpreter/detail/common/source/Context.cpp @@ -15,7 +15,8 @@ namespace interpreter::detail::common : inputSize(input.size()), begin(input.cbegin()), position(begin), - end(input.cend()) + end(input.cend()), + output() { addField("origin", origin); } @@ -29,7 +30,16 @@ namespace interpreter::detail::common { } - std::vector::const_iterator Context::getPosition() const + Context::Context(std::span input) + : inputSize(input.size()), + begin(input.begin()), + position(begin), + end(input.end()), + output() + { + } + + Context::IteratorType Context::getPosition() const { return position; } diff --git a/source/lib/interpreter/detail/uic918/include/RecordHeader.h b/source/lib/interpreter/detail/uic918/include/RecordHeader.h index f07c4a91..ded6624a 100644 --- a/source/lib/interpreter/detail/uic918/include/RecordHeader.h +++ b/source/lib/interpreter/detail/uic918/include/RecordHeader.h @@ -13,7 +13,7 @@ namespace interpreter::detail::uic { struct RecordHeader { - std::vector::const_iterator const start; + common::Context::IteratorType const start; std::string const recordId; std::string const recordVersion; unsigned int const recordLength; diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 049277da..520910cb 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -2,10 +2,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "../include/BotanMessageDecoder.h" +#include "../include/VDVUtility.h" + +#include "lib/interpreter/detail/common/include/Context.h" #include "lib/infrastructure/include/Logging.h" -#include +#include +#include namespace interpreter::detail::vdv { @@ -17,6 +21,16 @@ namespace interpreter::detail::vdv std::optional> BotanMessageDecoder::decode(Envelop const &envelop) { + auto context = common::Context(envelop.certificate); + auto const certificateTag = getTag(context); + auto const certificateLength = getLength(context); + auto const certificate = context.consumeBytes(certificateLength); + auto const exponentTag = getTag(context); + auto const exponentLength = getLength(context); + auto const exponent = context.consumeBytes(exponentLength); + + // auto scheme = Botan::ISO_9796_DS2(); + /* The value in 'authority' should match exactly one very specific entry in the list of exported certificates from public LDAP server identified by 'cn=,ou=VDV KA,o=VDV Kernapplikations GmbH,c=de' @@ -26,6 +40,11 @@ namespace interpreter::detail::vdv auto const companyCertificate = certificateProvider.get(envelop.authority); auto const rootCertificate = certificateProvider.get("4555564456100106"); + if (companyCertificate && rootCertificate) + { + // auto const cert = Botan::X509_Certificate(bla.data(), bla.size()); + } + return std::nullopt; } } From fbb735d139e00037dd221ab6cf47f02ae71f3b63 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 4 Jan 2026 21:20:45 +0100 Subject: [PATCH 03/41] Add todo and some makeup --- .../lib/interpreter/detail/vdv/source/VDVInterpreter.cpp | 6 ++++-- .../lib/interpreter/detail/verifier/include/Certificate.h | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index b82f2766..f2ec983a 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -100,11 +100,13 @@ namespace interpreter::detail::vdv { jsonBuilder .add("kennung", envelop.kennung) .add("version", envelop.version); }}}, - {TagType{0x7f, 0x21}, RecordDescriptor{"certificate", 200, [](auto const &data, auto &envelop) + {TagType{0x7f, 0x21}, RecordDescriptor{"certificate", 200, + [](auto const &data, auto &envelop) { envelop.certificate = data; }, [](auto const &name, auto const &envelop, auto &jsonBuilder) { jsonBuilder.add(name, utility::base64::encode(envelop.certificate)); }}}, - {TagType{0x42, 0x00}, RecordDescriptor{"authority", 8, [](auto const &data, auto &envelop) + {TagType{0x42, 0x00}, RecordDescriptor{"authority", 8, + [](auto const &data, auto &envelop) { envelop.authority = common::bytesToHexString(data); }, [](auto const &name, auto const &envelop, auto &jsonBuilder) { jsonBuilder.add(name, envelop.authority); }}}}; diff --git a/source/lib/interpreter/detail/verifier/include/Certificate.h b/source/lib/interpreter/detail/verifier/include/Certificate.h index f5f7e8a4..4d4487dc 100644 --- a/source/lib/interpreter/detail/verifier/include/Certificate.h +++ b/source/lib/interpreter/detail/verifier/include/Certificate.h @@ -11,12 +11,17 @@ namespace interpreter::detail::verifier { - struct Certificate + /* TODO Separate key retrieval (from xml) from verification and hide behind interface, see vdv/include/CertificateProvider.h + */ + class Certificate { + public: struct Internal; + private: std::shared_ptr internal; + public: static std::string getNormalizedCode(std::string const &ricsCode); static std::string getNormalizedId(std::string const &keyId); From 460cb01e09dc01232eb37564f59778f35ae44851 Mon Sep 17 00:00:00 2001 From: sascha Date: Mon, 5 Jan 2026 21:04:50 +0100 Subject: [PATCH 04/41] Fix iterator assignment issue in context with gcc from switch over 2 span --- .../detail/common/include/Context.h | 2 +- .../detail/common/source/Context.cpp | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/source/lib/interpreter/detail/common/include/Context.h b/source/lib/interpreter/detail/common/include/Context.h index 5c96a5f3..fac183dd 100644 --- a/source/lib/interpreter/detail/common/include/Context.h +++ b/source/lib/interpreter/detail/common/include/Context.h @@ -20,7 +20,7 @@ namespace interpreter::detail::common { using IteratorType = std::span::iterator; - std::size_t inputSize; + std::span data; IteratorType begin; IteratorType position; IteratorType end; diff --git a/source/lib/interpreter/detail/common/source/Context.cpp b/source/lib/interpreter/detail/common/source/Context.cpp index a28a047a..c08262cb 100644 --- a/source/lib/interpreter/detail/common/source/Context.cpp +++ b/source/lib/interpreter/detail/common/source/Context.cpp @@ -12,29 +12,29 @@ namespace interpreter::detail::common { Context::Context(std::vector const &input, std::string origin) - : inputSize(input.size()), - begin(input.cbegin()), + : data(input.data(), input.size()), + begin(std::begin(data)), position(begin), - end(input.cend()), + end(std::end(data)), output() { addField("origin", origin); } Context::Context(std::vector const &input, std::map &&fields) - : inputSize(input.size()), - begin(input.cbegin()), + : data(input.data(), input.size()), + begin(std::begin(data)), position(begin), - end(input.cend()), + end(std::end(data)), output(std::move(fields)) { } Context::Context(std::span input) - : inputSize(input.size()), - begin(input.begin()), + : data(std::move(input)), + begin(std::begin(data)), position(begin), - end(input.end()), + end(std::end(data)), output() { } @@ -109,7 +109,7 @@ namespace interpreter::detail::common bool Context::hasInput() const { - return inputSize > 0; + return data.size() > 0; } bool Context::hasOutput() const From 328ef8d72666a30816b71e7c88e127cd332b32b2 Mon Sep 17 00:00:00 2001 From: sascha Date: Mon, 5 Jan 2026 21:05:14 +0100 Subject: [PATCH 05/41] Some experiments and trial and error stuff --- .../vdv/include/LDIFFileCertificateProvider.h | 2 ++ .../detail/vdv/source/BotanMessageDecoder.cpp | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h index 46e0e043..161bdff3 100644 --- a/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h +++ b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h @@ -23,6 +23,8 @@ namespace interpreter::detail::vdv - Right click to 'ou=VDV KA' and select 'Export' and 'LDIF Export' - Click continue and select a file name and continue until the export starts, use '${workspaceFolder}/cert/VDV_Certificates.ldif' to make it working with default used below + + See: https://www.telesec.de/assets/downloads/Public-Key-Service/PKS-LDAP_Schnittstelle-V2.1-DE.pdf */ class LDIFFileCertificateProvider : public CertificateProvider { diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 520910cb..a07114a0 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -8,8 +8,11 @@ #include "lib/infrastructure/include/Logging.h" -#include +#include #include +#include +#include +#include namespace interpreter::detail::vdv { @@ -29,7 +32,8 @@ namespace interpreter::detail::vdv auto const exponentLength = getLength(context); auto const exponent = context.consumeBytes(exponentLength); - // auto scheme = Botan::ISO_9796_DS2(); + auto const rootCertificate = certificateProvider.get("4555564456100106"); + auto const companyCertificate = certificateProvider.get(envelop.authority); /* The value in 'authority' should match exactly one very specific entry in the list of exported certificates from public LDAP server identified by @@ -37,12 +41,17 @@ namespace interpreter::detail::vdv 4555564456xxxxxx -> EUVDVxxxxxx 4445564456xxxxxx -> DEVDVxxxxxx */ - auto const companyCertificate = certificateProvider.get(envelop.authority); - auto const rootCertificate = certificateProvider.get("4555564456100106"); if (companyCertificate && rootCertificate) { - // auto const cert = Botan::X509_Certificate(bla.data(), bla.size()); + /* + auto rng = std::make_unique(); + auto dataSource = Botan::DataSource_Memory(companyCertificate->certificate.data(), companyCertificate->certificate.size()); + auto const key = Botan::PKCS8::load_key(dataSource, *rng); + // auto const key = Botan::X509_Certificate(dataSource); + auto decoder = Botan::PK_Decryptor_EME(*key, *rng, "RSA-1984(SHA-1)"); + auto const message = decoder.decrypt(certificate.data(), certificate.size()); + */ } return std::nullopt; From 60c7bb954296f3b64555ab649b26e79c4e3a853b Mon Sep 17 00:00:00 2001 From: sascha Date: Mon, 5 Jan 2026 21:36:25 +0100 Subject: [PATCH 06/41] Move uic pub key install script into cert folder 2 have at native location --- .github/workflows/arm64-macos.yml | 2 +- .github/workflows/ubuntu22-gcc11.yml | 2 +- .github/workflows/ubuntu24-clang16.yml | 2 +- .github/workflows/ubuntu24-gcc13.yml | 2 +- .github/workflows/x64-macos.yml | 2 +- README.md | 4 ++-- {etc => cert}/install-uic-keys.sh | 1 - etc/docker/ubuntu22.gcc.Dockerfile | 5 +++-- etc/docker/ubuntu22.gcc.Python.Dockerfile | 3 ++- etc/docker/ubuntu24.clang.Dockerfile | 5 +++-- etc/docker/ubuntu24.gcc.Dockerfile | 5 +++-- 11 files changed, 18 insertions(+), 15 deletions(-) rename {etc => cert}/install-uic-keys.sh (94%) diff --git a/.github/workflows/arm64-macos.yml b/.github/workflows/arm64-macos.yml index 79e8569b..c3d0857f 100644 --- a/.github/workflows/arm64-macos.yml +++ b/.github/workflows/arm64-macos.yml @@ -56,7 +56,7 @@ jobs: - name: Unit-Test C++ run: | - etc/install-uic-keys.sh + cert/install-uic-keys.sh build/Release/bin/ticket-decoder-test - name: Unit-Test Python3 diff --git a/.github/workflows/ubuntu22-gcc11.yml b/.github/workflows/ubuntu22-gcc11.yml index 98bb6d3b..54efebae 100644 --- a/.github/workflows/ubuntu22-gcc11.yml +++ b/.github/workflows/ubuntu22-gcc11.yml @@ -60,7 +60,7 @@ jobs: - name: Unit-Test C++ run: | - etc/install-uic-keys.sh + cert/install-uic-keys.sh build/Release/bin/ticket-decoder-test - name: Unit-Test Python3 diff --git a/.github/workflows/ubuntu24-clang16.yml b/.github/workflows/ubuntu24-clang16.yml index 0df7b6ab..43e764ba 100644 --- a/.github/workflows/ubuntu24-clang16.yml +++ b/.github/workflows/ubuntu24-clang16.yml @@ -64,7 +64,7 @@ jobs: - name: Unit-Test C++ run: | - etc/install-uic-keys.sh + cert/install-uic-keys.sh build/Release/bin/ticket-decoder-test - name: Unit-Test Python3 diff --git a/.github/workflows/ubuntu24-gcc13.yml b/.github/workflows/ubuntu24-gcc13.yml index 707f9fab..438c053e 100644 --- a/.github/workflows/ubuntu24-gcc13.yml +++ b/.github/workflows/ubuntu24-gcc13.yml @@ -60,7 +60,7 @@ jobs: - name: Unit-Test C++ run: | - etc/install-uic-keys.sh + cert/install-uic-keys.sh build/Release/bin/ticket-decoder-test - name: Unit-Test Python3 diff --git a/.github/workflows/x64-macos.yml b/.github/workflows/x64-macos.yml index 71237969..f4f6ed5f 100644 --- a/.github/workflows/x64-macos.yml +++ b/.github/workflows/x64-macos.yml @@ -49,7 +49,7 @@ jobs: - name: Unit-Test C++ run: | - etc/install-uic-keys.sh + cert/install-uic-keys.sh build/Release/bin/ticket-decoder-test - name: Unit-Test Python3 diff --git a/README.md b/README.md index a1cc24f8..349a58fe 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,7 @@ pip install -r requirements.txt git clone https://github.com/user4223/ticket-decoder.git && cd ticket-decoder ./setup.All.sh -- -j -etc/install-uic-keys.sh +cert/install-uic-keys.sh build/Release/bin/ticket-decoder-test etc/python-test.sh @@ -375,7 +375,7 @@ pip install -r requirements.txt git clone https://github.com/user4223/ticket-decoder.git && cd ticket-decoder ./setup.All.sh -- -j -etc/install-uic-keys.sh +cert/install-uic-keys.sh build/Release/bin/ticket-decoder-test etc/python-test.sh diff --git a/etc/install-uic-keys.sh b/cert/install-uic-keys.sh similarity index 94% rename from etc/install-uic-keys.sh rename to cert/install-uic-keys.sh index 884a77ba..a4c0556c 100755 --- a/etc/install-uic-keys.sh +++ b/cert/install-uic-keys.sh @@ -9,6 +9,5 @@ readonly WORKSPACE_ROOT="$(readlink -f $(dirname "$0"))"/.. # curl --output ${WORKSPACE_ROOT}/cert/UIC_PublicKeys.xml \ # --show-error --fail 'https://railpublickey.uic.org/download.php' -mkdir -p ${WORKSPACE_ROOT}/cert wget -nv -O ${WORKSPACE_ROOT}/cert/UIC_PublicKeys.xml \ 'https://railpublickey.uic.org/download.php' diff --git a/etc/docker/ubuntu22.gcc.Dockerfile b/etc/docker/ubuntu22.gcc.Dockerfile index 5852fce1..d5f160cb 100644 --- a/etc/docker/ubuntu22.gcc.Dockerfile +++ b/etc/docker/ubuntu22.gcc.Dockerfile @@ -20,9 +20,10 @@ RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-$GCC_VERSION 800 RUN update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-$GCC_VERSION 800 WORKDIR /ticket-decoder -COPY etc/conan-config.sh etc/conan-install.sh etc/cmake-config.sh etc/cmake-build.sh etc/python-test.sh etc/install-uic-keys.sh etc/ +COPY etc/conan-config.sh etc/conan-install.sh etc/cmake-config.sh etc/cmake-build.sh etc/python-test.sh etc/ COPY etc/poppler/ etc/poppler COPY etc/conan/profiles etc/conan/profiles +COPY cert/install-uic-keys.sh cert/ COPY requirements.txt . RUN pip install -r requirements.txt @@ -42,6 +43,6 @@ COPY < Date: Wed, 7 Jan 2026 22:19:10 +0100 Subject: [PATCH 07/41] Some trial and error decoding of payload of key and certificates --- .../detail/vdv/include/VDVUtility.h | 4 ++ .../detail/vdv/source/BotanMessageDecoder.cpp | 72 +++++++++++++++---- .../detail/vdv/source/VDVInterpreter.cpp | 2 +- .../detail/vdv/source/VDVUtility.cpp | 18 +++++ 4 files changed, 80 insertions(+), 16 deletions(-) diff --git a/source/lib/interpreter/detail/vdv/include/VDVUtility.h b/source/lib/interpreter/detail/vdv/include/VDVUtility.h index 4fe84c8d..ba08c6d7 100644 --- a/source/lib/interpreter/detail/vdv/include/VDVUtility.h +++ b/source/lib/interpreter/detail/vdv/include/VDVUtility.h @@ -16,4 +16,8 @@ namespace interpreter::detail::vdv std::uint32_t getLength(common::Context &context); TagType getTag(common::Context &context); + + common::Context &ensureExpectedTag(common::Context &context, TagType expectedTag); + + void ensureEmpty(common::Context const &context); } diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index a07114a0..85cdd9c3 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -5,6 +5,7 @@ #include "../include/VDVUtility.h" #include "lib/interpreter/detail/common/include/Context.h" +#include "lib/interpreter/detail/common/include/InterpreterUtility.h" #include "lib/infrastructure/include/Logging.h" @@ -13,6 +14,7 @@ #include #include #include +#include namespace interpreter::detail::vdv { @@ -22,15 +24,54 @@ namespace interpreter::detail::vdv { } + Botan::RSA_PublicKey getPublicKey(std::span const &certificate) + { + auto context = common::Context(certificate); + auto const modulus = context.consumeBytes(getLength(ensureExpectedTag(context, {0x5f, 0x37}))); + auto const exponent = context.consumeBytes(getLength(ensureExpectedTag(context, {0x5f, 0x38}))); + ensureEmpty(context); + + // TODO This does not throw, but it might not be correct anyway + return Botan::RSA_PublicKey( + Botan::BigInt(modulus.data(), modulus.size()), + Botan::BigInt(exponent.data(), exponent.size())); + } + + Botan::RSA_PublicKey getRootKey(std::vector const &element) + { + auto context = common::Context(element); + auto payload = context.consumeBytes(getLength(ensureExpectedTag(context, {0x7f, 0x21}))); + ensureEmpty(context); + + auto payloadContext = common::Context(payload); + auto a = payloadContext.consumeBytes(getLength(ensureExpectedTag(payloadContext, {0x5f, 0x4e}))); + auto b = payloadContext.consumeBytes(getLength(ensureExpectedTag(payloadContext, {0x5f, 0x37}))); + ensureEmpty(payloadContext); + + // TODO This does not throw, but it might not be correct anyway + return Botan::RSA_PublicKey( + Botan::BigInt(a.data(), a.size()), + Botan::BigInt(b.data(), b.size())); + } + + std::span getCompanyKey(std::vector const &element) + { + auto context = common::Context(element); + auto payload = context.consumeBytes(getLength(ensureExpectedTag(context, {0x7f, 0x21}))); + ensureEmpty(context); + + auto payloadContext = common::Context(payload); + auto a = payloadContext.consumeBytes(getLength(ensureExpectedTag(payloadContext, {0x5f, 0x37}))); + auto b = payloadContext.consumeBytes(getLength(ensureExpectedTag(payloadContext, {0x5f, 0x38}))); + ensureEmpty(payloadContext); + + // TODO Find out what a and b is + return {}; + } + std::optional> BotanMessageDecoder::decode(Envelop const &envelop) { - auto context = common::Context(envelop.certificate); - auto const certificateTag = getTag(context); - auto const certificateLength = getLength(context); - auto const certificate = context.consumeBytes(certificateLength); - auto const exponentTag = getTag(context); - auto const exponentLength = getLength(context); - auto const exponent = context.consumeBytes(exponentLength); + auto const publicKey = getPublicKey(envelop.certificate); auto const rootCertificate = certificateProvider.get("4555564456100106"); auto const companyCertificate = certificateProvider.get(envelop.authority); @@ -44,14 +85,15 @@ namespace interpreter::detail::vdv if (companyCertificate && rootCertificate) { - /* - auto rng = std::make_unique(); - auto dataSource = Botan::DataSource_Memory(companyCertificate->certificate.data(), companyCertificate->certificate.size()); - auto const key = Botan::PKCS8::load_key(dataSource, *rng); - // auto const key = Botan::X509_Certificate(dataSource); - auto decoder = Botan::PK_Decryptor_EME(*key, *rng, "RSA-1984(SHA-1)"); - auto const message = decoder.decrypt(certificate.data(), certificate.size()); - */ + auto const rootKey = getRootKey(rootCertificate->certificate); + auto const companyKey = getCompanyKey(companyCertificate->certificate); + + // auto rng = std::make_unique(); + // auto dataSource = Botan::DataSource_Memory(rootCertificate->certificate.data(), rootCertificate->certificate.size()); + // auto const publicKey = Botan::X509_Certificate(dataSource); + + // auto decoder = Botan::PK_Decryptor_EME(*key, *rng, "RSA-1984(SHA-1)"); + // auto const message = decoder.decrypt(certificate.data(), certificate.size()); } return std::nullopt; diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index f2ec983a..691830fb 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -123,7 +123,7 @@ namespace interpreter::detail::vdv // - https://github.com/RWTH-i5-IDSG/ticketserver/blob/master/barti-check/src/main/java/de/rwth/idsg/barti/check/Decode.java auto recordBuilder = RecordBuilder(*messageDecoder); - while (context.getRemainingSize() > 0) + while (!context.isEmpty()) { auto const tag = getTag(context); auto const length = getLength(context); diff --git a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp index f70d580e..9f851b3e 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp @@ -32,4 +32,22 @@ namespace interpreter::detail::vdv return {first, 0}; } + + common::Context &ensureExpectedTag(common::Context &context, TagType expectedTag) + { + auto tag = getTag(context); + if (tag != expectedTag) + { + throw std::runtime_error(std::string("Unexpected tag found: ") + common::bytesToHexString(tag)); + } + return context; + } + + void ensureEmpty(common::Context const &context) + { + if (!context.isEmpty()) + { + throw std::runtime_error(std::string("Expecting fully consumed context, but found remaining bytes: ") + std::to_string(context.getRemainingSize())); + } + } } From 877805aa9e323dab0131ffc8e2d1b08566b1e391 Mon Sep 17 00:00:00 2001 From: sascha Date: Thu, 8 Jan 2026 20:26:01 +0100 Subject: [PATCH 08/41] Simplify decoding --- .../detail/vdv/include/BotanMessageDecoder.h | 2 +- .../interpreter/detail/vdv/include/Envelop.h | 20 --- .../detail/vdv/include/MessageDecoder.h | 6 +- .../detail/vdv/include/VDVUtility.h | 2 + .../detail/vdv/source/BotanMessageDecoder.cpp | 35 +++--- .../detail/vdv/source/VDVInterpreter.cpp | 114 +++--------------- .../detail/vdv/source/VDVUtility.cpp | 5 + 7 files changed, 48 insertions(+), 136 deletions(-) delete mode 100644 source/lib/interpreter/detail/vdv/include/Envelop.h diff --git a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h index b54a5fa1..7f6c5b85 100644 --- a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h @@ -18,6 +18,6 @@ namespace interpreter::detail::vdv public: BotanMessageDecoder(infrastructure::LoggerFactory &loggerFactory, CertificateProvider &certificateProvider); - virtual std::optional> decode(Envelop const &envelop) override; + virtual std::optional> decode(std::span const &ticketCertificate, std::string authority) override; }; } diff --git a/source/lib/interpreter/detail/vdv/include/Envelop.h b/source/lib/interpreter/detail/vdv/include/Envelop.h deleted file mode 100644 index b3bf0e91..00000000 --- a/source/lib/interpreter/detail/vdv/include/Envelop.h +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include - -namespace interpreter::detail::vdv -{ - struct Envelop - { - std::string kennung; - std::string version; - std::string authority; - std::span signature; - std::span certificate; - }; -} diff --git a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h index 89c8345f..631bdfb0 100644 --- a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h @@ -3,11 +3,11 @@ #pragma once -#include "Envelop.h" - #include #include #include +#include +#include namespace interpreter::detail::vdv { @@ -16,6 +16,6 @@ namespace interpreter::detail::vdv public: virtual ~MessageDecoder() = default; - virtual std::optional> decode(Envelop const &envelop) = 0; + virtual std::optional> decode(std::span const &ticketCertificate, std::string authority) = 0; }; } diff --git a/source/lib/interpreter/detail/vdv/include/VDVUtility.h b/source/lib/interpreter/detail/vdv/include/VDVUtility.h index ba08c6d7..b1af65b3 100644 --- a/source/lib/interpreter/detail/vdv/include/VDVUtility.h +++ b/source/lib/interpreter/detail/vdv/include/VDVUtility.h @@ -19,5 +19,7 @@ namespace interpreter::detail::vdv common::Context &ensureExpectedTag(common::Context &context, TagType expectedTag); + std::span consumeExpectedTag(common::Context &context, TagType expectedTag); + void ensureEmpty(common::Context const &context); } diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 85cdd9c3..8565d16b 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -24,11 +24,11 @@ namespace interpreter::detail::vdv { } - Botan::RSA_PublicKey getPublicKey(std::span const &certificate) + Botan::RSA_PublicKey extractPublicKey(std::span const &certificate) { auto context = common::Context(certificate); - auto const modulus = context.consumeBytes(getLength(ensureExpectedTag(context, {0x5f, 0x37}))); - auto const exponent = context.consumeBytes(getLength(ensureExpectedTag(context, {0x5f, 0x38}))); + auto const modulus = consumeExpectedTag(context, {0x5f, 0x37}); + auto const exponent = consumeExpectedTag(context, {0x5f, 0x38}); ensureEmpty(context); // TODO This does not throw, but it might not be correct anyway @@ -37,15 +37,15 @@ namespace interpreter::detail::vdv Botan::BigInt(exponent.data(), exponent.size())); } - Botan::RSA_PublicKey getRootKey(std::vector const &element) + Botan::RSA_PublicKey extractRootPublicKey(std::vector const &element) { auto context = common::Context(element); - auto payload = context.consumeBytes(getLength(ensureExpectedTag(context, {0x7f, 0x21}))); + auto payload = consumeExpectedTag(context, {0x7f, 0x21}); ensureEmpty(context); auto payloadContext = common::Context(payload); - auto a = payloadContext.consumeBytes(getLength(ensureExpectedTag(payloadContext, {0x5f, 0x4e}))); - auto b = payloadContext.consumeBytes(getLength(ensureExpectedTag(payloadContext, {0x5f, 0x37}))); + auto a = consumeExpectedTag(payloadContext, {0x5f, 0x4e}); + auto b = consumeExpectedTag(payloadContext, {0x5f, 0x37}); ensureEmpty(payloadContext); // TODO This does not throw, but it might not be correct anyway @@ -54,27 +54,24 @@ namespace interpreter::detail::vdv Botan::BigInt(b.data(), b.size())); } - std::span getCompanyKey(std::vector const &element) + std::span extractUnknown(std::vector const &element) { auto context = common::Context(element); - auto payload = context.consumeBytes(getLength(ensureExpectedTag(context, {0x7f, 0x21}))); + auto payload = consumeExpectedTag(context, {0x7f, 0x21}); ensureEmpty(context); auto payloadContext = common::Context(payload); - auto a = payloadContext.consumeBytes(getLength(ensureExpectedTag(payloadContext, {0x5f, 0x37}))); - auto b = payloadContext.consumeBytes(getLength(ensureExpectedTag(payloadContext, {0x5f, 0x38}))); + auto a = consumeExpectedTag(payloadContext, {0x5f, 0x37}); + auto b = consumeExpectedTag(payloadContext, {0x5f, 0x38}); ensureEmpty(payloadContext); // TODO Find out what a and b is return {}; } - std::optional> BotanMessageDecoder::decode(Envelop const &envelop) + std::optional> BotanMessageDecoder::decode(std::span const &ticketCertificate, std::string authority) { - auto const publicKey = getPublicKey(envelop.certificate); - - auto const rootCertificate = certificateProvider.get("4555564456100106"); - auto const companyCertificate = certificateProvider.get(envelop.authority); + auto const publicKey = extractPublicKey(ticketCertificate); /* The value in 'authority' should match exactly one very specific entry in the list of exported certificates from public LDAP server identified by @@ -83,10 +80,12 @@ namespace interpreter::detail::vdv 4445564456xxxxxx -> DEVDVxxxxxx */ + auto const rootCertificate = certificateProvider.get("4555564456100106"); + auto const companyCertificate = certificateProvider.get(authority); if (companyCertificate && rootCertificate) { - auto const rootKey = getRootKey(rootCertificate->certificate); - auto const companyKey = getCompanyKey(companyCertificate->certificate); + auto const rootKey = extractRootPublicKey(rootCertificate->certificate); + auto const unknown = extractUnknown(companyCertificate->certificate); // auto rng = std::make_unique(); // auto dataSource = Botan::DataSource_Memory(rootCertificate->certificate.data(), rootCertificate->certificate.size()); diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 691830fb..58733e38 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -5,7 +5,6 @@ #include "../include/LDIFFileCertificateProvider.h" #include "../include/BotanMessageDecoder.h" #include "../include/VDVUtility.h" -#include "../include/Envelop.h" #include "lib/interpreter/detail/common/include/InterpreterUtility.h" @@ -18,7 +17,7 @@ namespace interpreter::detail::vdv { - static std::vector const typeId = {0x9E, 0x81, 0x80}; + static std::vector const typeId = {0x9E, 0x81, 0x80}; // This is actually not a fixed ident. 0x9e is a tag and 0x81+0x80 a length. VDVInterpreter::TypeIdType VDVInterpreter::getTypeId() { @@ -32,85 +31,6 @@ namespace interpreter::detail::vdv { } - struct RecordDescriptor - { - std::string const name; - std::optional const expectedSize; - std::function const &data, Envelop &)> const collector; - std::function const consumer; - }; - - class RecordBuilder - { - static std::map const tagDescriptorMap; - - MessageDecoder &messageDecoder; - Envelop envelop; - - public: - RecordBuilder(MessageDecoder &md) : messageDecoder(md) {} - - bool add(TagType const &tag, std::span const &data) - { - auto const entry = tagDescriptorMap.find(tag); - if (entry == tagDescriptorMap.end()) - { - return false; - } - - auto const &descriptor = entry->second; - if (descriptor.expectedSize) - { - if (data.size() != *descriptor.expectedSize) - { - throw std::runtime_error(std::string("Expecting exactly ") + std::to_string(*descriptor.expectedSize) + " payload bytes for tag 0x" + common::bytesToHexString(tag) + " (" + descriptor.name + "), got: " + std::to_string(data.size())); - } - } - descriptor.collector(data, envelop); - return true; - } - - common::Record build() const - { - auto const message = messageDecoder.decode(envelop); - - auto jsonBuilder = utility::JsonBuilder::object(); - - std::for_each(std::begin(tagDescriptorMap), std::end(tagDescriptorMap), [&](auto const &entry) - { - auto const &descriptor = entry.second; - descriptor.consumer(descriptor.name, envelop, jsonBuilder); }); - - return common::Record(envelop.kennung, envelop.version, std::move(jsonBuilder)); - } - }; - - std::map const RecordBuilder::tagDescriptorMap = { - {TagType{0x9e, 0x00}, RecordDescriptor{"signature", 128, - [](auto const &data, auto &envelop) - { envelop.signature = data; }, - [](auto const &name, auto const &envelop, auto &jsonBuilder) - { jsonBuilder.add(name, utility::base64::encode(envelop.signature)); }}}, - {TagType{0x9a, 0x00}, RecordDescriptor{"identifier", 5, - [](auto const &data, auto &envelop) - { - envelop.kennung = common::bytesToAlphanumeric(data.subspan(0, 3)); - envelop.version = common::bytesToHexString(data.subspan(3, 2)); }, - [](auto const &name, auto const &envelop, auto &jsonBuilder) - { jsonBuilder - .add("kennung", envelop.kennung) - .add("version", envelop.version); }}}, - {TagType{0x7f, 0x21}, RecordDescriptor{"certificate", 200, - [](auto const &data, auto &envelop) - { envelop.certificate = data; }, - [](auto const &name, auto const &envelop, auto &jsonBuilder) - { jsonBuilder.add(name, utility::base64::encode(envelop.certificate)); }}}, - {TagType{0x42, 0x00}, RecordDescriptor{"authority", 8, - [](auto const &data, auto &envelop) - { envelop.authority = common::bytesToHexString(data); }, - [](auto const &name, auto const &envelop, auto &jsonBuilder) - { jsonBuilder.add(name, envelop.authority); }}}}; - common::Context VDVInterpreter::interpret(common::Context &&context) { // Documentation (not fully matching anymore): @@ -122,19 +42,25 @@ namespace interpreter::detail::vdv // - https://github.com/akorb/deutschlandticket_parser/blob/main/main.py // - https://github.com/RWTH-i5-IDSG/ticketserver/blob/master/barti-check/src/main/java/de/rwth/idsg/barti/check/Decode.java - auto recordBuilder = RecordBuilder(*messageDecoder); - while (!context.isEmpty()) - { - auto const tag = getTag(context); - auto const length = getLength(context); - - if (!recordBuilder.add(tag, context.consumeBytes(length))) - { - LOG_WARN(logger) << "Unknown record found: tag: 0x" << common::bytesToHexString(tag) << ", length: " << length; - } - } - - context.addRecord(recordBuilder.build()); + auto const signature = consumeExpectedTag(context, {0x9e, 0x00}); + auto const ident = consumeExpectedTag(context, {0x9a, 0x00}); + auto const kennung = common::bytesToAlphanumeric(ident.subspan(0, 3)); + auto const version = common::bytesToHexString(ident.subspan(3, 2)); + auto const certificate = consumeExpectedTag(context, {0x7f, 0x21}); + auto const authority = common::bytesToHexString(consumeExpectedTag(context, {0x42, 0x00})); + ensureEmpty(context); + + auto const message = messageDecoder->decode(certificate, authority); + + auto jsonBuilder = utility::JsonBuilder::object(); + jsonBuilder + .add("kennung", kennung) + .add("version", version) + .add("signature", utility::base64::encode(signature)) + .add("certificate", utility::base64::encode(certificate)) + .add("authority", authority); + + context.addRecord(common::Record(kennung, version, std::move(jsonBuilder))); return std::move(context); } } diff --git a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp index 9f851b3e..bcd3ffd1 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp @@ -43,6 +43,11 @@ namespace interpreter::detail::vdv return context; } + std::span consumeExpectedTag(common::Context &context, TagType expectedTag) + { + return context.consumeBytes(getLength(ensureExpectedTag(context, expectedTag))); + } + void ensureEmpty(common::Context const &context) { if (!context.isEmpty()) From 7f7fc70ef2542c67b850bc73791aac0775f5a515 Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 10 Jan 2026 16:25:51 +0100 Subject: [PATCH 09/41] Copy existing uic pub keys before downloading new --- cert/install-uic-keys.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cert/install-uic-keys.sh b/cert/install-uic-keys.sh index a4c0556c..db2f0cca 100755 --- a/cert/install-uic-keys.sh +++ b/cert/install-uic-keys.sh @@ -9,5 +9,11 @@ readonly WORKSPACE_ROOT="$(readlink -f $(dirname "$0"))"/.. # curl --output ${WORKSPACE_ROOT}/cert/UIC_PublicKeys.xml \ # --show-error --fail 'https://railpublickey.uic.org/download.php' -wget -nv -O ${WORKSPACE_ROOT}/cert/UIC_PublicKeys.xml \ - 'https://railpublickey.uic.org/download.php' +readonly DESTINATION_FILE="${WORKSPACE_ROOT}/cert/UIC_PublicKeys.xml" + +if [ -f ${DESTINATION_FILE} ]; then + readonly DATE=$(date +%F) + cp ${DESTINATION_FILE} "${WORKSPACE_ROOT}/cert/UIC_PublicKeys_before_${DATE}.xml" +fi + +wget -nv -O ${DESTINATION_FILE} 'https://railpublickey.uic.org/download.php' From 0ba30024ed139355ac824082c366ec97e039afb4 Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 17 Jan 2026 17:37:41 +0100 Subject: [PATCH 10/41] Prepare MessageDecoder 2 support decoding flow properly --- .../detail/vdv/include/BotanMessageDecoder.h | 14 +++++- .../detail/vdv/include/CertificateProvider.h | 2 +- .../vdv/include/LDIFFileCertificateProvider.h | 8 +++- .../detail/vdv/include/MessageDecoder.h | 14 +++++- .../detail/vdv/source/BotanMessageDecoder.cpp | 43 ++++++++++--------- .../source/LDIFFileCertificateProvider.cpp | 4 +- .../detail/vdv/source/VDVInterpreter.cpp | 19 ++++---- 7 files changed, 68 insertions(+), 36 deletions(-) diff --git a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h index 7f6c5b85..db4ebcfd 100644 --- a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h @@ -18,6 +18,18 @@ namespace interpreter::detail::vdv public: BotanMessageDecoder(infrastructure::LoggerFactory &loggerFactory, CertificateProvider &certificateProvider); - virtual std::optional> decode(std::span const &ticketCertificate, std::string authority) override; + /* Takes certificate from envelop and decodes the ticket certificate + by using root + company (identified by authority) certificate. + */ + virtual std::optional> decodeCertificate( + std::span const &certificate, + std::string const &authority) override; + + /* Decodes message from signature and ticket certificate. + */ + virtual std::optional> decodeMessage( + std::span const &signature, + std::span const &residual, + std::span const &certificate) override; }; } diff --git a/source/lib/interpreter/detail/vdv/include/CertificateProvider.h b/source/lib/interpreter/detail/vdv/include/CertificateProvider.h index bb0e454d..180df9fe 100644 --- a/source/lib/interpreter/detail/vdv/include/CertificateProvider.h +++ b/source/lib/interpreter/detail/vdv/include/CertificateProvider.h @@ -23,6 +23,6 @@ namespace interpreter::detail::vdv public: virtual ~CertificateProvider() = default; - virtual std::optional get(std::string commonName) = 0; + virtual std::optional get(std::string authority) = 0; }; } diff --git a/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h index 161bdff3..1cd9718a 100644 --- a/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h +++ b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h @@ -36,6 +36,12 @@ namespace interpreter::detail::vdv public: LDIFFileCertificateProvider(infrastructure::LoggerFactory &loggerFactory, std::filesystem::path file = "cert/VDV_Certificates.ldif"); - virtual std::optional get(std::string commonName) override; + /* The value in 'authority' should match exactly one very specific entry in + the list of exported certificates from public LDAP server identified by + 'cn=,ou=VDV KA,o=VDV Kernapplikations GmbH,c=de' + 4555564456xxxxxx -> EUVDVxxxxxx + 4445564456xxxxxx -> DEVDVxxxxxx + */ + virtual std::optional get(std::string authority) override; }; } diff --git a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h index 631bdfb0..d0e5d04a 100644 --- a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h @@ -16,6 +16,18 @@ namespace interpreter::detail::vdv public: virtual ~MessageDecoder() = default; - virtual std::optional> decode(std::span const &ticketCertificate, std::string authority) = 0; + /* Takes certificate from envelop and decodes the ticket certificate + by using root + company (identified by authority) certificate. + */ + virtual std::optional> decodeCertificate( + std::span const &certificate, + std::string const &authority) = 0; + + /* Decodes message from signature and ticket certificate. + */ + virtual std::optional> decodeMessage( + std::span const &signature, + std::span const &residual, + std::span const &certificate) = 0; }; } diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 8565d16b..13d6b16d 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -69,31 +69,32 @@ namespace interpreter::detail::vdv return {}; } - std::optional> BotanMessageDecoder::decode(std::span const &ticketCertificate, std::string authority) + std::optional> BotanMessageDecoder::decodeCertificate( + std::span const &certificate, + std::string const &authority) { - auto const publicKey = extractPublicKey(ticketCertificate); - - /* The value in 'authority' should match exactly one very specific entry in - the list of exported certificates from public LDAP server identified by - 'cn=,ou=VDV KA,o=VDV Kernapplikations GmbH,c=de' - 4555564456xxxxxx -> EUVDVxxxxxx - 4445564456xxxxxx -> DEVDVxxxxxx - */ + // TODO + // - Identify and get authority certificate by using authority + // - Get root certificate + // - Decode authority certificate by using root certificate + // - Decode ticket certificate by using given certificate + authority certificate + // - Return ticket certificate auto const rootCertificate = certificateProvider.get("4555564456100106"); auto const companyCertificate = certificateProvider.get(authority); - if (companyCertificate && rootCertificate) - { - auto const rootKey = extractRootPublicKey(rootCertificate->certificate); - auto const unknown = extractUnknown(companyCertificate->certificate); - - // auto rng = std::make_unique(); - // auto dataSource = Botan::DataSource_Memory(rootCertificate->certificate.data(), rootCertificate->certificate.size()); - // auto const publicKey = Botan::X509_Certificate(dataSource); - - // auto decoder = Botan::PK_Decryptor_EME(*key, *rng, "RSA-1984(SHA-1)"); - // auto const message = decoder.decrypt(certificate.data(), certificate.size()); - } + + return std::nullopt; + } + + std::optional> BotanMessageDecoder::decodeMessage( + std::span const &signature, + std::span const &residual, + std::span const &certificate) + { + auto const publicKey = extractPublicKey(certificate); + + // auto decoder = Botan::PK_Decryptor_EME(*key, *rng, "RSA-1984(SHA-1)"); + // auto const message = decoder.decrypt(certificate.data(), certificate.size()); return std::nullopt; } diff --git a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp index 04aa08dc..185c51c0 100644 --- a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp +++ b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp @@ -103,14 +103,14 @@ namespace interpreter::detail::vdv } } - std::optional LDIFFileCertificateProvider::get(std::string commonName) + std::optional LDIFFileCertificateProvider::get(std::string authority) { if (!entries) { return std::nullopt; } - auto const entry = entries->find(commonName); + auto const entry = entries->find(authority); return entry == entries->end() ? std::nullopt : std::make_optional(entry->second); diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 58733e38..46a83c61 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -43,24 +43,25 @@ namespace interpreter::detail::vdv // - https://github.com/RWTH-i5-IDSG/ticketserver/blob/master/barti-check/src/main/java/de/rwth/idsg/barti/check/Decode.java auto const signature = consumeExpectedTag(context, {0x9e, 0x00}); - auto const ident = consumeExpectedTag(context, {0x9a, 0x00}); - auto const kennung = common::bytesToAlphanumeric(ident.subspan(0, 3)); - auto const version = common::bytesToHexString(ident.subspan(3, 2)); + auto const signatureResidual = consumeExpectedTag(context, {0x9a, 0x00}); + auto const signatureIdent = common::bytesToAlphanumeric(signatureResidual.subspan(0, 3)); + auto const signatureVersion = common::bytesToHexString(signatureResidual.subspan(3, 2)); auto const certificate = consumeExpectedTag(context, {0x7f, 0x21}); - auto const authority = common::bytesToHexString(consumeExpectedTag(context, {0x42, 0x00})); + auto const certificateAuthority = common::bytesToHexString(consumeExpectedTag(context, {0x42, 0x00})); ensureEmpty(context); - auto const message = messageDecoder->decode(certificate, authority); + auto const ticketCertificate = messageDecoder->decodeCertificate(certificate, certificateAuthority); + auto const message = messageDecoder->decodeMessage(signature, signatureResidual, *ticketCertificate); auto jsonBuilder = utility::JsonBuilder::object(); jsonBuilder - .add("kennung", kennung) - .add("version", version) .add("signature", utility::base64::encode(signature)) + .add("signatureIdent", signatureIdent) + .add("signatureVersion", signatureVersion) .add("certificate", utility::base64::encode(certificate)) - .add("authority", authority); + .add("certificateAuthority", certificateAuthority); - context.addRecord(common::Record(kennung, version, std::move(jsonBuilder))); + context.addRecord(common::Record(signatureIdent, signatureVersion, std::move(jsonBuilder))); return std::move(context); } } From 0b835d0b498b94ad747b4f28bb7c179fc09b94a6 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 18 Jan 2026 17:23:49 +0100 Subject: [PATCH 11/41] Refactor certificate extraction 2 improve simplicity of message recovery chain --- .../detail/vdv/include/Certificate.h | 22 ++ .../detail/vdv/include/CertificateProvider.h | 17 +- .../vdv/include/LDIFFileCertificateProvider.h | 12 +- .../detail/vdv/source/BotanMessageDecoder.cpp | 62 ++---- .../source/LDIFFileCertificateProvider.cpp | 197 ++++++++++++------ .../detail/vdv/source/VDVInterpreter.cpp | 10 +- 6 files changed, 196 insertions(+), 124 deletions(-) create mode 100644 source/lib/interpreter/detail/vdv/include/Certificate.h diff --git a/source/lib/interpreter/detail/vdv/include/Certificate.h b/source/lib/interpreter/detail/vdv/include/Certificate.h new file mode 100644 index 00000000..0fee9f5a --- /dev/null +++ b/source/lib/interpreter/detail/vdv/include/Certificate.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +namespace interpreter::detail::vdv +{ + /* This object does not own the data, it's just a view. + */ + struct Certificate + { + std::string authority; + std::string description; + std::span signature; + std::span remainder; + std::span content; + }; +} diff --git a/source/lib/interpreter/detail/vdv/include/CertificateProvider.h b/source/lib/interpreter/detail/vdv/include/CertificateProvider.h index 180df9fe..a9eaf9b2 100644 --- a/source/lib/interpreter/detail/vdv/include/CertificateProvider.h +++ b/source/lib/interpreter/detail/vdv/include/CertificateProvider.h @@ -3,26 +3,23 @@ #pragma once +#include "Certificate.h" + #include -#include -#include #include +#include namespace interpreter::detail::vdv { - struct Certificate - { - std::string commonName; - std::string distinguishedName; - std::string description; - std::vector certificate; - }; - class CertificateProvider { public: virtual ~CertificateProvider() = default; + virtual std::vector getAuthorities() = 0; + + virtual std::optional getRoot() = 0; + virtual std::optional get(std::string authority) = 0; }; } diff --git a/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h index 1cd9718a..f6d60dee 100644 --- a/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h +++ b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h @@ -8,11 +8,10 @@ #include "lib/infrastructure/include/Logger.h" #include -#include +#include namespace interpreter::detail::vdv { - /* Use 'Apache Directory Studio' and connect to public ldap 'ldaps://ldap-vdv-ion.telesec.de:636' to get company and root certificates. - Install 'Apache Directory Studio' or another useful tool @@ -29,13 +28,16 @@ namespace interpreter::detail::vdv class LDIFFileCertificateProvider : public CertificateProvider { infrastructure::Logger logger; - std::optional> const entries; - - static std::optional> import(std::filesystem::path file); + struct Internal; + std::shared_ptr internal; public: LDIFFileCertificateProvider(infrastructure::LoggerFactory &loggerFactory, std::filesystem::path file = "cert/VDV_Certificates.ldif"); + virtual std::vector getAuthorities() override; + + virtual std::optional getRoot() override; + /* The value in 'authority' should match exactly one very specific entry in the list of exported certificates from public LDAP server identified by 'cn=,ou=VDV KA,o=VDV Kernapplikations GmbH,c=de' diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 13d6b16d..049310ef 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -24,51 +24,6 @@ namespace interpreter::detail::vdv { } - Botan::RSA_PublicKey extractPublicKey(std::span const &certificate) - { - auto context = common::Context(certificate); - auto const modulus = consumeExpectedTag(context, {0x5f, 0x37}); - auto const exponent = consumeExpectedTag(context, {0x5f, 0x38}); - ensureEmpty(context); - - // TODO This does not throw, but it might not be correct anyway - return Botan::RSA_PublicKey( - Botan::BigInt(modulus.data(), modulus.size()), - Botan::BigInt(exponent.data(), exponent.size())); - } - - Botan::RSA_PublicKey extractRootPublicKey(std::vector const &element) - { - auto context = common::Context(element); - auto payload = consumeExpectedTag(context, {0x7f, 0x21}); - ensureEmpty(context); - - auto payloadContext = common::Context(payload); - auto a = consumeExpectedTag(payloadContext, {0x5f, 0x4e}); - auto b = consumeExpectedTag(payloadContext, {0x5f, 0x37}); - ensureEmpty(payloadContext); - - // TODO This does not throw, but it might not be correct anyway - return Botan::RSA_PublicKey( - Botan::BigInt(a.data(), a.size()), - Botan::BigInt(b.data(), b.size())); - } - - std::span extractUnknown(std::vector const &element) - { - auto context = common::Context(element); - auto payload = consumeExpectedTag(context, {0x7f, 0x21}); - ensureEmpty(context); - - auto payloadContext = common::Context(payload); - auto a = consumeExpectedTag(payloadContext, {0x5f, 0x37}); - auto b = consumeExpectedTag(payloadContext, {0x5f, 0x38}); - ensureEmpty(payloadContext); - - // TODO Find out what a and b is - return {}; - } - std::optional> BotanMessageDecoder::decodeCertificate( std::span const &certificate, std::string const &authority) @@ -80,8 +35,21 @@ namespace interpreter::detail::vdv // - Decode ticket certificate by using given certificate + authority certificate // - Return ticket certificate - auto const rootCertificate = certificateProvider.get("4555564456100106"); + auto const rootCertificate = certificateProvider.getRoot(); + if (!rootCertificate) + { + return std::nullopt; + } auto const companyCertificate = certificateProvider.get(authority); + if (!companyCertificate) + { + return std::nullopt; + } + + auto context = common::Context(certificate); + auto const ticketSignature = consumeExpectedTag(context, {0x5f, 0x37}); + auto const ticketRemainder = consumeExpectedTag(context, {0x5f, 0x38}); + ensureEmpty(context); return std::nullopt; } @@ -91,8 +59,6 @@ namespace interpreter::detail::vdv std::span const &residual, std::span const &certificate) { - auto const publicKey = extractPublicKey(certificate); - // auto decoder = Botan::PK_Decryptor_EME(*key, *rng, "RSA-1984(SHA-1)"); // auto const message = decoder.decrypt(certificate.data(), certificate.size()); diff --git a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp index 185c51c0..2d8beb44 100644 --- a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp +++ b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp @@ -2,117 +2,196 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "../include/LDIFFileCertificateProvider.h" +#include "../include/VDVUtility.h" -#include "lib/infrastructure/include/Logging.h" +#include "lib/interpreter/detail/common/include/Context.h" +#include "lib/interpreter/detail/common/include/InterpreterUtility.h" #include "lib/utility/include/Base64.h" +#include "lib/infrastructure/include/Logging.h" + #include #include #include #include +#include namespace interpreter::detail::vdv { - static std::string getValue(std::vector const &entryLines, std::string key) + struct RawCertificate { - auto const result = std::find_if(std::begin(entryLines), std::end(entryLines), [&](auto const &line) - { return line.starts_with(key); }); - return result == entryLines.end() - ? std::string{} - : result->substr(key.size(), result->size() - key.size()); - } + std::string commonName; + std::string distinguishedName; + std::string description; + std::vector data; + std::optional mutable certificate; - std::optional> LDIFFileCertificateProvider::import(std::filesystem::path file) - { - if (!std::filesystem::exists(file) || !std::filesystem::is_regular_file(file)) + std::optional get() const { - return std::nullopt; - } - - auto entryLines = std::vector>(); - auto entry = std::vector(); - auto stream = std::ifstream(file); - auto count = 1; - for (auto line = std::string(); std::getline(stream, line); count++) - { - if (line.starts_with('#')) // Ignore comments + if (certificate) { - continue; + return certificate; } - if (line.empty()) // An empty line (2 directly following newlines) indicate a new entry - { - entryLines.emplace_back(std::move(entry)); - entry = std::vector(); - continue; - } + auto context = common::Context(data); + auto payload = consumeExpectedTag(context, {0x7f, 0x21}); + ensureEmpty(context); + + auto content = std::span{}; + auto signature = std::span{}; + auto remainder = std::span{}; - if (line.starts_with(' ')) // Line starting with space indicates line-folding, that means the following line belongs the the line before + auto payloadContext = common::Context(payload); + while (!payloadContext.isEmpty()) { - if (entry.empty()) + auto const tag = getTag(payloadContext); + auto const value = payloadContext.consumeBytes(getLength(payloadContext)); + if (tag == TagType{0x5f, 0x4e}) + { + content = value; + } + else if (tag == TagType{0x5f, 0x37}) + { + signature = value; + } + else if (tag == TagType{0x5f, 0x38}) { - throw std::runtime_error(std::string("Expect line-folding after regular line only but found it as a starting line: ") + std::to_string(count)); + remainder = value; + } + else + { + throw std::runtime_error(std::string("Unexpected tag found: ") + common::bytesToHexString(tag)); } - (*entry.rbegin()) += line.erase(0, 1); - continue; } - entry.push_back(line); + certificate = std::make_optional(Certificate{commonName, description, signature, remainder, content}); + return certificate; } + }; + + struct LDIFFileCertificateProvider::Internal + { + std::optional> const entries; - /* Remove all entries not having a distinguished name matching the following pattern - */ - auto const entryStart = std::regex("dn[:] cn[=]\\w+,ou[=]VDV KA,o[=]VDV Kernapplikations GmbH,c[=]de", std::regex::icase); - std::erase_if(entryLines, [&](auto const &lines) - { return lines.end() == std::find_if(std::begin(lines), std::end(lines), - [&](auto const &line) - { return std::regex_match(line, entryStart); }); }); - - /* Given file might be in LDIF format but might not contain desired entries matching the distinguished name above - */ - if (entryLines.empty()) + static std::string getValue(std::vector const &entryLines, std::string key) { - return std::nullopt; + auto const result = std::find_if(std::begin(entryLines), std::end(entryLines), [&](auto const &line) + { return line.starts_with(key); }); + return result == entryLines.end() + ? std::string{} + : result->substr(key.size(), result->size() - key.size()); } - std::map entries; - std::transform(std::begin(entryLines), std::end(entryLines), std::inserter(entries, entries.begin()), [](auto const &lines) - { + static std::optional> import(std::filesystem::path file) + { + if (!std::filesystem::exists(file) || !std::filesystem::is_regular_file(file)) + { + return std::nullopt; + } + + auto entryLines = std::vector>(); + auto entry = std::vector(); + auto stream = std::ifstream(file); + auto count = 1; + for (auto line = std::string(); std::getline(stream, line); count++) + { + if (line.starts_with('#')) // Ignore comments + { + continue; + } + + if (line.empty()) // An empty line (2 directly following newlines) indicate a new entry + { + entryLines.emplace_back(std::move(entry)); + entry = std::vector(); + continue; + } + + if (line.starts_with(' ')) // Line starting with space indicates line-folding, that means the following line belongs the the line before + { + if (entry.empty()) + { + throw std::runtime_error(std::string("Expect line-folding after regular line only but found it as a starting line: ") + std::to_string(count)); + } + (*entry.rbegin()) += line.erase(0, 1); + continue; + } + + entry.push_back(line); + } + + /* Remove all entries not having a distinguished name matching the following pattern + */ + auto const entryStart = std::regex("dn[:] cn[=]\\w+,ou[=]VDV KA,o[=]VDV Kernapplikations GmbH,c[=]de", std::regex::icase); + std::erase_if(entryLines, [&](auto const &lines) + { return lines.end() == std::find_if(std::begin(lines), std::end(lines), + [&](auto const &line) + { return std::regex_match(line, entryStart); }); }); + + /* Given file might be in LDIF format but might not contain desired entries matching the distinguished name above + */ + if (entryLines.empty()) + { + return std::nullopt; + } + + std::map entries; + std::transform(std::begin(entryLines), std::end(entryLines), std::inserter(entries, entries.begin()), [](auto const &lines) + { auto commonName = getValue(lines, "cn: "); - return std::make_pair(commonName, Certificate{ + return std::make_pair(commonName, RawCertificate{ commonName, getValue(lines, "dn: "), getValue(lines, "description: "), utility::base64::decode(getValue(lines, "cACertificate:: "))}); }); - return entries; - } + return entries; + } + }; LDIFFileCertificateProvider::LDIFFileCertificateProvider(infrastructure::LoggerFactory &loggerFactory, std::filesystem::path file) : logger(CREATE_LOGGER(loggerFactory)), - entries(import(file)) + internal(std::make_shared(Internal::import(file))) { - if (!entries) + if (!internal->entries) { LOG_INFO(logger) << "Failed to import certificates from given LDIF file: " << file.string(); } else { - LOG_DEBUG(logger) << "Imported no of certificates: " << entries->size(); + LOG_DEBUG(logger) << "Imported no of certificates: " << internal->entries->size(); + } + } + + std::vector LDIFFileCertificateProvider::getAuthorities() + { + if (!internal->entries) + { + return {}; } + + auto keys = std::vector{}; + std::transform(std::begin(*(internal->entries)), std::end(*(internal->entries)), std::back_inserter(keys), [](auto const &entry) + { return entry.first; }); + return keys; + } + + std::optional LDIFFileCertificateProvider::getRoot() + { + return get("4555564456100106"); } std::optional LDIFFileCertificateProvider::get(std::string authority) { - if (!entries) + if (!internal->entries) { return std::nullopt; } - auto const entry = entries->find(authority); - return entry == entries->end() + auto const entry = internal->entries->find(authority); + return entry == internal->entries->end() ? std::nullopt - : std::make_optional(entry->second); + : entry->second.get(); } } diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 46a83c61..6c80b118 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -17,7 +17,10 @@ namespace interpreter::detail::vdv { - static std::vector const typeId = {0x9E, 0x81, 0x80}; // This is actually not a fixed ident. 0x9e is a tag and 0x81+0x80 a length. + /* This is actually not a fixed ident. 0x9e is a BER-TLV tag (signature) and 0x81+0x80 a length (128 bytes). + But it should be sufficient for now to identify VDV tickets. + */ + static std::vector const typeId = {0x9E, 0x81, 0x80}; VDVInterpreter::TypeIdType VDVInterpreter::getTypeId() { @@ -51,7 +54,10 @@ namespace interpreter::detail::vdv ensureEmpty(context); auto const ticketCertificate = messageDecoder->decodeCertificate(certificate, certificateAuthority); - auto const message = messageDecoder->decodeMessage(signature, signatureResidual, *ticketCertificate); + if (ticketCertificate) + { + auto const message = messageDecoder->decodeMessage(signature, signatureResidual, *ticketCertificate); + } auto jsonBuilder = utility::JsonBuilder::object(); jsonBuilder From 923c1dfa808da076e9a67d3fb8be9792c09e7676 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 18 Jan 2026 20:36:58 +0100 Subject: [PATCH 12/41] Migrate 2 botan 3 --- conanfile.py | 3 +-- .../detail/verifier/source/Certificate.cpp | 19 +++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/conanfile.py b/conanfile.py index 4377147c..b3633afa 100644 --- a/conanfile.py +++ b/conanfile.py @@ -62,8 +62,7 @@ def requirements(self): # https://conan.io/center/recipes/pugixml self.requires("pugixml/1.15") # https://conan.io/center/recipes/botan - # - version 3.x is available but has breaking changes - self.requires("botan/2.19.5") + self.requires("botan/3.10.0") if self.options.with_pdf_input: # https://conan.io/center/recipes/poppler diff --git a/source/lib/interpreter/detail/verifier/source/Certificate.cpp b/source/lib/interpreter/detail/verifier/source/Certificate.cpp index e72818c0..aeb488c0 100644 --- a/source/lib/interpreter/detail/verifier/source/Certificate.cpp +++ b/source/lib/interpreter/detail/verifier/source/Certificate.cpp @@ -24,9 +24,9 @@ namespace interpreter::detail::verifier static auto const sha224Pattern = std::regex("sha[^\\d]?224[^\\d]?", std::regex::icase); static auto const sha256Pattern = std::regex("sha[^\\d]?(2048|256)[^\\d]?", std::regex::icase); - static auto const sha1 = "EMSA1(SHA-160)"; - static auto const sha224 = "EMSA1(SHA-224)"; - static auto const sha256 = "EMSA1(SHA-256)"; + static auto const sha1 = "SHA-1"; + static auto const sha224 = "SHA-224"; + static auto const sha256 = "SHA-256"; static auto const sha1Der46 = std::make_tuple(sha1, 46, Botan::Signature_Format::DER_SEQUENCE); static auto const sha224Der62 = std::make_tuple(sha224, 62, Botan::Signature_Format::DER_SEQUENCE); @@ -86,9 +86,9 @@ namespace interpreter::detail::verifier auto keyBits = std::vector{}; Botan::BER_Decoder(dataSource) - .start_cons(Botan::ASN1_Tag::SEQUENCE) + .start_sequence() .decode(algorithmIdent) - .decode(keyBits, Botan::ASN1_Tag::BIT_STRING) + .decode(keyBits, Botan::ASN1_Type::BitString) .end_cons(); internal.publicKey = std::make_unique(algorithmIdent, keyBits); @@ -216,13 +216,12 @@ namespace interpreter::detail::verifier bool Certificate::verify(std::span message, std::span signature) const { - auto const config = getConfig(internal->algorithm); - auto const signatureLength = std::get<1>(config); - if (signatureLength > signature.size()) + auto const [ident, length, format] = getConfig(internal->algorithm); + if (length > signature.size()) { - throw std::runtime_error("Signature with length " + std::to_string(signature.size()) + " is shorter than expected, minimal expected: " + std::to_string(signatureLength)); + throw std::runtime_error("Signature with length " + std::to_string(signature.size()) + " is shorter than expected, minimal expected: " + std::to_string(length)); } - auto verifier = Botan::PK_Verifier(getPublicKey(*internal), std::get<0>(config), std::get<2>(config)); + auto verifier = Botan::PK_Verifier(getPublicKey(*internal), ident, format); auto const sig = Certificate::trimTrailingNulls(signature); return verifier.verify_message(message.data(), message.size(), sig.data(), sig.size()); } From d2740811ba651328cacef9efa8de8a6283bef1c1 Mon Sep 17 00:00:00 2001 From: sascha Date: Wed, 21 Jan 2026 21:58:26 +0100 Subject: [PATCH 13/41] Disable botan 4 ubuntu22; gcc11.04 is officially supporting c++20 but conan build fails for whatever reason --- .github/workflows/ubuntu22-gcc11.yml | 2 +- source/lib/interpreter/detail/vdv/CMakeLists.txt | 5 ++++- .../detail/vdv/source/BotanMessageDecoder.cpp | 12 ++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ubuntu22-gcc11.yml b/.github/workflows/ubuntu22-gcc11.yml index 54efebae..734cc046 100644 --- a/.github/workflows/ubuntu22-gcc11.yml +++ b/.github/workflows/ubuntu22-gcc11.yml @@ -51,7 +51,7 @@ jobs: - name: Install dependencies run: | . venv/bin/activate - etc/conan-install.sh Release -pr:a ./etc/conan/profiles/ubuntu22 + etc/conan-install.sh Release -pr:a ./etc/conan/profiles/ubuntu22 -o "&:with_signature_verifier=False" - name: Build run: | diff --git a/source/lib/interpreter/detail/vdv/CMakeLists.txt b/source/lib/interpreter/detail/vdv/CMakeLists.txt index 02812c7b..75225aaa 100644 --- a/source/lib/interpreter/detail/vdv/CMakeLists.txt +++ b/source/lib/interpreter/detail/vdv/CMakeLists.txt @@ -13,6 +13,9 @@ target_include_directories(${PROJECT_NAME} PRIVATE) target_link_libraries(${PROJECT_NAME} PRIVATE easyloggingpp::easyloggingpp nlohmann_json::nlohmann_json - botan::botan ticket-decoder-interpreter-detail-common ticket-decoder-utility) + +IF (WITH_SIGNATURE_VERIFIER) + target_link_libraries(${PROJECT_NAME} PRIVATE botan::botan) +ENDIF() diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 049310ef..42988140 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -9,12 +9,15 @@ #include "lib/infrastructure/include/Logging.h" +#ifdef WITH_SIGNATURE_VERIFIER #include #include #include +#include #include #include #include +#endif namespace interpreter::detail::vdv { @@ -51,6 +54,13 @@ namespace interpreter::detail::vdv auto const ticketRemainder = consumeExpectedTag(context, {0x5f, 0x38}); ensureEmpty(context); + // auto dataSource = Botan::DataSource_Memory(rootCertificate->content); + // auto cert = Botan::X509_Certificate(dataSource); + // Botan::X509_Certificate(rootCertificate->) + + // auto decoder = Botan::PK_Decryptor_EME(*key, *rng, "RSA-1984(SHA-1)"); + // auto const message = decoder.decrypt(certificate.data(), certificate.size()); + return std::nullopt; } @@ -59,8 +69,6 @@ namespace interpreter::detail::vdv std::span const &residual, std::span const &certificate) { - // auto decoder = Botan::PK_Decryptor_EME(*key, *rng, "RSA-1984(SHA-1)"); - // auto const message = decoder.decrypt(certificate.data(), certificate.size()); return std::nullopt; } From b931152fb11a45c213b0c99f331a2232728fb5f8 Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 24 Jan 2026 11:03:17 +0100 Subject: [PATCH 14/41] Fix build on ubuntu22 using gcc11.4 for botan3 by skipping use of verification and decryption temporarily --- etc/docker/ubuntu22.gcc.Dockerfile | 3 ++- source/lib/api/include/DecoderFacade.h | 2 ++ source/lib/api/source/DecoderFacade.cpp | 2 ++ source/lib/input/detail/CMakeLists.txt | 1 + source/lib/interpreter/detail/vdv/CMakeLists.txt | 11 +++++++++-- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/etc/docker/ubuntu22.gcc.Dockerfile b/etc/docker/ubuntu22.gcc.Dockerfile index d5f160cb..ade58f3e 100644 --- a/etc/docker/ubuntu22.gcc.Dockerfile +++ b/etc/docker/ubuntu22.gcc.Dockerfile @@ -32,7 +32,8 @@ RUN etc/conan-config.sh gcc $GCC_VERSION COPY conanfile.py . RUN etc/conan-install.sh Release \ -pr:a ./etc/conan/profiles/ubuntu22 \ - -o libxml2/*:zlib=False + -o libxml2/*:zlib=False \ + -o with_signature_verifier=False COPY <publicKeyFilePath = std::make_optional(publicKeyFilePath); diff --git a/source/lib/input/detail/CMakeLists.txt b/source/lib/input/detail/CMakeLists.txt index a4e81283..78a1b0dc 100644 --- a/source/lib/input/detail/CMakeLists.txt +++ b/source/lib/input/detail/CMakeLists.txt @@ -5,6 +5,7 @@ PROJECT(ticket-decoder-input-detail) add_subdirectory(api) add_subdirectory(image) + IF (WITH_PDF_INPUT) add_subdirectory(pdf) ENDIF() diff --git a/source/lib/interpreter/detail/vdv/CMakeLists.txt b/source/lib/interpreter/detail/vdv/CMakeLists.txt index 75225aaa..ec8a024b 100644 --- a/source/lib/interpreter/detail/vdv/CMakeLists.txt +++ b/source/lib/interpreter/detail/vdv/CMakeLists.txt @@ -3,8 +3,6 @@ PROJECT(ticket-decoder-interpreter-detail-vdv) -find_package(botan REQUIRED) - AUX_SOURCE_DIRECTORY("source" PROJECT_SOURCE) file(GLOB PROJECT_INCLUDES "include/*.h") @@ -16,6 +14,15 @@ target_link_libraries(${PROJECT_NAME} PRIVATE ticket-decoder-interpreter-detail-common ticket-decoder-utility) +# TODO This is just a temporary solution to solve compiling issues with botan3 and gcc11. +# gcc11.4 is the default compiler coming with ubuntu22, but conan build of botan3 +# is complaining about missing c++20 support and states a minimum required gcc11.2. +# Maybe this is just an issue in conan recipe of botan3. But it could be a failing +# test during build about a specific c++20 feature as well, just failing with gcc11.4 +# and the message about minimum required version is just outdated. IF (WITH_SIGNATURE_VERIFIER) + + find_package(botan REQUIRED) target_link_libraries(${PROJECT_NAME} PRIVATE botan::botan) + ENDIF() From 0752463a0f4a834d1b96662d789c0be931533071 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 25 Jan 2026 18:33:11 +0100 Subject: [PATCH 15/41] Fix conan argument syntax and avoid source removal in debug build 2 improve debugability --- .github/workflows/arm64-macos.yml | 2 +- .github/workflows/ubuntu22-gcc11.yml | 2 +- .github/workflows/ubuntu24-clang16.yml | 2 +- .github/workflows/ubuntu24-gcc13.yml | 2 +- .github/workflows/x64-macos.yml | 2 +- README.md | 4 ++-- etc/conan-install.sh | 9 ++++++--- etc/docker/ubuntu22.gcc.Dockerfile | 6 +++--- etc/docker/ubuntu22.gcc.Python.Dockerfile | 6 +++--- etc/docker/ubuntu24.clang.Dockerfile | 8 ++++---- etc/docker/ubuntu24.gcc.Dockerfile | 4 ++-- etc/poppler/conan-create.sh | 7 +++++-- setup.All.sh | 22 +++++++++++----------- setup.Decoder.sh | 22 +++++++++++----------- setup.Python.sh | 22 +++++++++++----------- 15 files changed, 63 insertions(+), 57 deletions(-) diff --git a/.github/workflows/arm64-macos.yml b/.github/workflows/arm64-macos.yml index c3d0857f..19e64d64 100644 --- a/.github/workflows/arm64-macos.yml +++ b/.github/workflows/arm64-macos.yml @@ -47,7 +47,7 @@ jobs: - name: Install dependencies run: | . venv/bin/activate - etc/conan-install.sh Release -pr:a ./etc/conan/profiles/macos${{ steps.retrieve-version.outputs.MACOS_MAJOR_VERSION }} + etc/conan-install.sh Release -pr:a="./etc/conan/profiles/macos${{ steps.retrieve-version.outputs.MACOS_MAJOR_VERSION }}" - name: Build run: | diff --git a/.github/workflows/ubuntu22-gcc11.yml b/.github/workflows/ubuntu22-gcc11.yml index 734cc046..71d9606b 100644 --- a/.github/workflows/ubuntu22-gcc11.yml +++ b/.github/workflows/ubuntu22-gcc11.yml @@ -51,7 +51,7 @@ jobs: - name: Install dependencies run: | . venv/bin/activate - etc/conan-install.sh Release -pr:a ./etc/conan/profiles/ubuntu22 -o "&:with_signature_verifier=False" + etc/conan-install.sh Release -pr:a="./etc/conan/profiles/ubuntu22" -o:a="&:with_signature_verifier=False" - name: Build run: | diff --git a/.github/workflows/ubuntu24-clang16.yml b/.github/workflows/ubuntu24-clang16.yml index 43e764ba..6f9d29c0 100644 --- a/.github/workflows/ubuntu24-clang16.yml +++ b/.github/workflows/ubuntu24-clang16.yml @@ -55,7 +55,7 @@ jobs: - name: Install dependencies run: | . venv/bin/activate - etc/conan-install.sh Release -pr:a ./etc/conan/profiles/ubuntu24 -pr:a ./etc/conan/profiles/clang$CLANG_VERSION + etc/conan-install.sh Release -pr:a="./etc/conan/profiles/ubuntu24" -pr:a="./etc/conan/profiles/clang$CLANG_VERSION" - name: Build run: | diff --git a/.github/workflows/ubuntu24-gcc13.yml b/.github/workflows/ubuntu24-gcc13.yml index 438c053e..4b5cb0e2 100644 --- a/.github/workflows/ubuntu24-gcc13.yml +++ b/.github/workflows/ubuntu24-gcc13.yml @@ -51,7 +51,7 @@ jobs: - name: Install dependencies run: | . venv/bin/activate - etc/conan-install.sh Release -pr:a ./etc/conan/profiles/ubuntu24 + etc/conan-install.sh Release -pr:a="./etc/conan/profiles/ubuntu24" - name: Build run: | diff --git a/.github/workflows/x64-macos.yml b/.github/workflows/x64-macos.yml index f4f6ed5f..1c236e54 100644 --- a/.github/workflows/x64-macos.yml +++ b/.github/workflows/x64-macos.yml @@ -40,7 +40,7 @@ jobs: - name: Install dependencies run: | . venv/bin/activate - etc/conan-install.sh Release -pr:a ./etc/conan/profiles/macos${{ steps.retrieve-version.outputs.MACOS_MAJOR_VERSION }} + etc/conan-install.sh Release -pr:a="./etc/conan/profiles/macos${{ steps.retrieve-version.outputs.MACOS_MAJOR_VERSION }}" - name: Build run: | diff --git a/README.md b/README.md index 349a58fe..a82b9e43 100644 --- a/README.md +++ b/README.md @@ -277,7 +277,7 @@ It is possible to enable/disable parts of the application or the Python module * * **with_sbb_interpreter=False** skips creation of SBB interpreter module and avoids dependency to protobuf -To enable/disable, please use prepared scripts like [setup.Python.sh](setup.Python.sh) or [setup.Decoder.sh](setup.Decoder.sh) and change desired feature toggles there. Or pass options like `-o "&:with_ticket_analyzer=False"` to conan install script. Check the script mentioned above as a guideline. +To enable/disable, please use prepared scripts like [setup.Python.sh](setup.Python.sh) or [setup.Decoder.sh](setup.Decoder.sh) and change desired feature toggles there. Or pass options like `-o:a="&:with_ticket_analyzer=False"` to conan install script. Check the script mentioned above as a guideline. Following libraries are used by the project. Usually you should not care about it since conan will do that for you. @@ -332,7 +332,7 @@ Take a look into `./build/` folder to discover artifacts. You should be able to When opencv has to be built from source because of missing pre-built package for your arch/os/compiler/config mix, it might be necessary to install some further xorg/system libraries to make highgui stuff building inside conan install process. To get this handled automatically, use the conan config flags shown below in `~/conan2/profiles/default` or pass additional -argument `-pr:a ./etc/conan/profiles/package-manager-config` to conan-install call in `setup.All.sh`. +argument `-pr:a="./etc/conan/profiles/package-manager-config"` to conan-install call in `setup.All.sh`. ``` [conf] tools.system.package_manager:mode=install diff --git a/etc/conan-install.sh b/etc/conan-install.sh index 137ad39c..3af66fbf 100755 --- a/etc/conan-install.sh +++ b/etc/conan-install.sh @@ -24,10 +24,13 @@ export CMAKE_BUILD_TYPE=${BUILD_TYPE} conan install ${WORKSPACE_ROOT} \ --build missing \ -of ${WORKSPACE_ROOT}/build/${BUILD_TYPE} \ - -s:a build_type=${BUILD_TYPE} \ - -s:a compiler.cppstd=20 \ + -s:a="build_type=${BUILD_TYPE}" \ + -s:a="compiler.cppstd=20" \ ${@:2} # Remove temporary stuff like source and build folders 2 keep cache folder as small as possible. # This does NOT remove the created binaries. -conan cache clean '*' +# In Debug config, we do need source folders for debugging +if [[ "${BUILD_TYPE}" == "Release" ]]; then + conan cache clean -p="build_type=Release" +fi diff --git a/etc/docker/ubuntu22.gcc.Dockerfile b/etc/docker/ubuntu22.gcc.Dockerfile index ade58f3e..1cd0a634 100644 --- a/etc/docker/ubuntu22.gcc.Dockerfile +++ b/etc/docker/ubuntu22.gcc.Dockerfile @@ -31,9 +31,9 @@ RUN etc/conan-config.sh gcc $GCC_VERSION COPY conanfile.py . RUN etc/conan-install.sh Release \ - -pr:a ./etc/conan/profiles/ubuntu22 \ - -o libxml2/*:zlib=False \ - -o with_signature_verifier=False + -pr:a="./etc/conan/profiles/ubuntu22" \ + -o:a="libxml2/*:zlib=False" \ + -o:a="with_signature_verifier=False" COPY < Date: Sun, 25 Jan 2026 20:31:09 +0100 Subject: [PATCH 16/41] Some trial and error on vdv public key stuff --- etc/conan-install.sh | 2 +- etc/poppler/conan-create.sh | 2 +- .../detail/common/include/Context.h | 15 ++ .../common/include/InterpreterUtility.h | 4 + .../detail/common/source/Context.cpp | 24 ++ .../common/source/InterpreterUtility.cpp | 15 ++ .../lib/interpreter/detail/vdv/CMakeLists.txt | 19 +- .../detail/vdv/include/BotanMessageDecoder.h | 20 +- .../detail/vdv/include/Certificate.h | 124 +++++++++- .../detail/vdv/include/MessageDecoder.h | 17 +- .../detail/vdv/include/VDVUtility.h | 20 +- .../detail/vdv/source/BotanMessageDecoder.cpp | 97 +++++--- .../detail/vdv/source/Certificate.cpp | 230 ++++++++++++++++++ .../source/LDIFFileCertificateProvider.cpp | 32 +-- .../detail/vdv/source/VDVInterpreter.cpp | 29 +-- .../detail/vdv/source/VDVUtility.cpp | 56 ++++- 16 files changed, 588 insertions(+), 118 deletions(-) create mode 100644 source/lib/interpreter/detail/vdv/source/Certificate.cpp diff --git a/etc/conan-install.sh b/etc/conan-install.sh index 3af66fbf..9d02c7b8 100755 --- a/etc/conan-install.sh +++ b/etc/conan-install.sh @@ -31,6 +31,6 @@ conan install ${WORKSPACE_ROOT} \ # Remove temporary stuff like source and build folders 2 keep cache folder as small as possible. # This does NOT remove the created binaries. # In Debug config, we do need source folders for debugging -if [[ "${BUILD_TYPE}" == "Release" ]]; then +if [ "$BUILD_TYPE" = "Release" ]; then conan cache clean -p="build_type=Release" fi diff --git a/etc/poppler/conan-create.sh b/etc/poppler/conan-create.sh index f3b3b40e..cc6e83cf 100755 --- a/etc/poppler/conan-create.sh +++ b/etc/poppler/conan-create.sh @@ -22,6 +22,6 @@ conan create ${WORKSPACE_ROOT}/etc/poppler \ # Remove temporary stuff like source and build folders 2 keep cache folder as small as possible. # This does NOT remove the created binaries. # In Debug config, we do need source folders for debugging -if [[ "${BUILD_TYPE}" == "Release" ]]; then +if [ "$BUILD_TYPE" = "Release" ]; then conan cache clean -p="build_type=Release" fi diff --git a/source/lib/interpreter/detail/common/include/Context.h b/source/lib/interpreter/detail/common/include/Context.h index fac183dd..b96c0c94 100644 --- a/source/lib/interpreter/detail/common/include/Context.h +++ b/source/lib/interpreter/detail/common/include/Context.h @@ -60,6 +60,11 @@ namespace interpreter::detail::common */ std::span consumeBytes(std::size_t size); + /* Returns and consumes size bytes from end of all bytes. + Throws runtime_error if size exceeds remaining bytes. + */ + std::span consumeBytesEnd(std::size_t size); + /* Returns and consumes as a maximum size bytes from current position to current position + 0...size. */ @@ -70,6 +75,16 @@ namespace interpreter::detail::common */ std::span consumeRemainingBytes(); + /* Consumes and copies all remaining bytes from current + postion to end. + */ + std::vector consumeRemainingBytesCopy(); + + /* Consumes all remaining bytes from current postion to end, + appends given postfix string to the end and returns a copy. + */ + std::vector consumeRemainingBytesAppend(std::span postfix); + /* Ignores and skips size bytes from current position to current position + size. */ diff --git a/source/lib/interpreter/detail/common/include/InterpreterUtility.h b/source/lib/interpreter/detail/common/include/InterpreterUtility.h index 32a4dc23..71b3f5e0 100644 --- a/source/lib/interpreter/detail/common/include/InterpreterUtility.h +++ b/source/lib/interpreter/detail/common/include/InterpreterUtility.h @@ -20,6 +20,10 @@ namespace interpreter::detail::common std::uint8_t getNumeric8(Context &context); + std::uint16_t getDecimal16(Context &context); + + std::uint8_t getDecimal8(Context &context); + std::string getDateTimeCompact(Context &context); std::string getDateTime12(Context &context); diff --git a/source/lib/interpreter/detail/common/source/Context.cpp b/source/lib/interpreter/detail/common/source/Context.cpp index c08262cb..9aa063a2 100644 --- a/source/lib/interpreter/detail/common/source/Context.cpp +++ b/source/lib/interpreter/detail/common/source/Context.cpp @@ -76,6 +76,17 @@ namespace interpreter::detail::common return result; } + std::span Context::consumeBytesEnd(std::size_t size) + { + if (getRemainingSize() < size) + { + throw std::runtime_error("Not enough bytes available to consume from end"); + } + + end -= size; + return std::span(end, size); + } + std::span Context::consumeMaximalBytes(std::size_t size) { return consumeBytes(std::min(getRemainingSize(), size)); @@ -86,6 +97,19 @@ namespace interpreter::detail::common return consumeBytes(getRemainingSize()); } + std::vector Context::consumeRemainingBytesCopy() + { + auto const data = consumeRemainingBytes(); + return {data.begin(), data.end()}; + } + + std::vector Context::consumeRemainingBytesAppend(std::span postfix) + { + auto data = consumeRemainingBytesCopy(); + data.insert(data.end(), postfix.begin(), postfix.end()); + return data; + } + std::size_t Context::ignoreBytes(std::size_t size) { if (getRemainingSize() < size) diff --git a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp index b6e2dd3d..ec65ffe0 100644 --- a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp +++ b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp @@ -64,6 +64,21 @@ namespace interpreter::detail::common return getNumeric(context); } + std::uint16_t getDecimal16(Context &context) + { + std::uint16_t const high = getDecimal8(context); + std::uint16_t const low = getDecimal8(context); + return high * 100 + low; + } + + std::uint8_t getDecimal8(Context &context) + { + auto byte = context.consumeBytes(1)[0]; + std::uint8_t const high = byte >> 4 & 0x0F; + std::uint8_t const low = byte & 0x0F; + return high * 10 + low; + } + std::string getDateTimeCompact(Context &context) { auto const date = common::getNumeric16(context); diff --git a/source/lib/interpreter/detail/vdv/CMakeLists.txt b/source/lib/interpreter/detail/vdv/CMakeLists.txt index ec8a024b..aee1f8a8 100644 --- a/source/lib/interpreter/detail/vdv/CMakeLists.txt +++ b/source/lib/interpreter/detail/vdv/CMakeLists.txt @@ -3,6 +3,9 @@ PROJECT(ticket-decoder-interpreter-detail-vdv) +find_package(Boost REQUIRED COMPONENTS headers) +find_package(botan REQUIRED) + AUX_SOURCE_DIRECTORY("source" PROJECT_SOURCE) file(GLOB PROJECT_INCLUDES "include/*.h") @@ -12,7 +15,9 @@ target_link_libraries(${PROJECT_NAME} PRIVATE easyloggingpp::easyloggingpp nlohmann_json::nlohmann_json ticket-decoder-interpreter-detail-common - ticket-decoder-utility) + ticket-decoder-utility + Boost::headers + botan::botan) # TODO This is just a temporary solution to solve compiling issues with botan3 and gcc11. # gcc11.4 is the default compiler coming with ubuntu22, but conan build of botan3 @@ -20,9 +25,9 @@ target_link_libraries(${PROJECT_NAME} PRIVATE # Maybe this is just an issue in conan recipe of botan3. But it could be a failing # test during build about a specific c++20 feature as well, just failing with gcc11.4 # and the message about minimum required version is just outdated. -IF (WITH_SIGNATURE_VERIFIER) - - find_package(botan REQUIRED) - target_link_libraries(${PROJECT_NAME} PRIVATE botan::botan) - -ENDIF() +# IF (WITH_SIGNATURE_VERIFIER) +# +# find_package(botan REQUIRED) +# target_link_libraries(${PROJECT_NAME} PRIVATE botan::botan) +# +# ENDIF() diff --git a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h index db4ebcfd..d8c554b8 100644 --- a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h @@ -6,7 +6,9 @@ #include "MessageDecoder.h" #include "CertificateProvider.h" -#include "lib/infrastructure/include/Logger.h" +#include "lib/infrastructure/include/LoggingFwd.h" + +#include namespace interpreter::detail::vdv { @@ -14,22 +16,14 @@ namespace interpreter::detail::vdv { infrastructure::Logger logger; CertificateProvider &certificateProvider; + class Internal; + std::shared_ptr internal; public: BotanMessageDecoder(infrastructure::LoggerFactory &loggerFactory, CertificateProvider &certificateProvider); - /* Takes certificate from envelop and decodes the ticket certificate - by using root + company (identified by authority) certificate. - */ - virtual std::optional> decodeCertificate( - std::span const &certificate, - std::string const &authority) override; - - /* Decodes message from signature and ticket certificate. - */ virtual std::optional> decodeMessage( - std::span const &signature, - std::span const &residual, - std::span const &certificate) override; + Certificate const &envelopeCertificate, + Signature const &envelopeSignature) override; }; } diff --git a/source/lib/interpreter/detail/vdv/include/Certificate.h b/source/lib/interpreter/detail/vdv/include/Certificate.h index 0fee9f5a..75d951cb 100644 --- a/source/lib/interpreter/detail/vdv/include/Certificate.h +++ b/source/lib/interpreter/detail/vdv/include/Certificate.h @@ -3,20 +3,140 @@ #pragma once +#include "lib/interpreter/detail/common/include/Context.h" + #include #include #include +#include namespace interpreter::detail::vdv { + + struct Signature + { + std::span value; + std::span remainder; + + static Signature consumeFrom(common::Context &context); + + static Signature consumeFromEnvelope(common::Context &context); + }; + + struct PublicKey + { + std::span exponent; + std::span modulus; + + static PublicKey consumeFrom(common::Context &context); + }; + /* This object does not own the data, it's just a view. */ struct Certificate { std::string authority; std::string description; - std::span signature; - std::span remainder; + Signature signature; std::span content; + + static Certificate consumeFromEnvelope(common::Context &context); + }; + + /* Has no fixed lengh, right now we are passing a length argument but this may break for any other ticket + TODO Find a way to determine length automatically or to discover termination 2 avoid length argument! + */ + struct CertificateOID + { + std::vector parts; + + std::string toString() const; + + static CertificateOID consumeFrom(common::Context &context, std::size_t length); + }; + + /* 8 bytes long on wire + */ + struct CertificateParticipant + { + std::string region; + std::string name; + int serviceIdenticator = 0; + int algorithmReference = 0; + std::string year; + + std::string toString() const; + + static CertificateParticipant consumeFrom(common::Context &context); + }; + + /* 4 or 3 or 2 bytes long on wire + */ + struct CertificateDate + { + std::uint16_t year = 0; + std::uint8_t month = 0; + std::uint8_t day = 0; + + std::string toString() const; + + static CertificateDate consumeFrom4(common::Context &context); + + static CertificateDate consumeFrom3(common::Context &context); + + static CertificateDate consumeFrom2(common::Context &context); + }; + + /* 12 bytes long on wire + */ + struct CertificateReference + { + std::string organisationId; + CertificateDate samValidUntil; + CertificateDate samValidFrom; + std::string ownerOrganisationId; + std::string samId; + + std::string toString() const; + + static CertificateReference consumeFrom(common::Context &context); + }; + + /* 7 bytes long on wire + */ + struct CertificateAuthorization + { + std::string name; + int serviceIndicator = 0; + + std::string toString() const; + + static CertificateAuthorization consumeFrom(common::Context &context); + }; + + /* 1 byte long on wire + */ + struct CertificateProfile + { + std::string identifier; + + std::string toString() const; + + static CertificateProfile consumeFrom(common::Context &context); + }; + + struct CertificateIdentity + { + CertificateProfile profile; + CertificateParticipant authority; + std::optional holder; + std::optional reference; + CertificateAuthorization authorization; + CertificateDate expiryDate; + CertificateOID algorithm; + + std::string toString() const; + + static CertificateIdentity consumeFrom(common::Context &context, std::size_t oidLength); }; } diff --git a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h index d0e5d04a..aa75f433 100644 --- a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h @@ -3,6 +3,8 @@ #pragma once +#include "Certificate.h" + #include #include #include @@ -16,18 +18,11 @@ namespace interpreter::detail::vdv public: virtual ~MessageDecoder() = default; - /* Takes certificate from envelop and decodes the ticket certificate - by using root + company (identified by authority) certificate. - */ - virtual std::optional> decodeCertificate( - std::span const &certificate, - std::string const &authority) = 0; - - /* Decodes message from signature and ticket certificate. + /* Takes certificate from envelope and signature and decodes the + message by using root + issuing certificate. */ virtual std::optional> decodeMessage( - std::span const &signature, - std::span const &residual, - std::span const &certificate) = 0; + Certificate const &envelopeCertificate, + Signature const &envelopeSignature) = 0; }; } diff --git a/source/lib/interpreter/detail/vdv/include/VDVUtility.h b/source/lib/interpreter/detail/vdv/include/VDVUtility.h index b1af65b3..a1d41f0a 100644 --- a/source/lib/interpreter/detail/vdv/include/VDVUtility.h +++ b/source/lib/interpreter/detail/vdv/include/VDVUtility.h @@ -3,6 +3,8 @@ #pragma once +#include "Certificate.h" + #include "lib/interpreter/detail/common/include/Context.h" #include @@ -13,13 +15,23 @@ namespace interpreter::detail::vdv using TagType = std::array; - std::uint32_t getLength(common::Context &context); + std::uint32_t consumeLength(common::Context &context); + + TagType consumeTag(common::Context &context); + + common::Context &consumeExpectedTag(common::Context &context, TagType const &expectedTag); + + common::Context &consumeExpectedEndTag(common::Context &context, TagType const &expectedTag); + + common::Context &consumeExpectedFrameTags(common::Context &context, TagType const &expectedBeginTag, TagType const &expectedEndTag); + + std::span consumeExpectedTagValue(common::Context &context, TagType const &expectedTag); - TagType getTag(common::Context &context); + std::span consumeExpected(common::Context &context, std::vector expectedValue); - common::Context &ensureExpectedTag(common::Context &context, TagType expectedTag); + bool peekExpected(common::Context &context, std::vector expectedValue); - std::span consumeExpectedTag(common::Context &context, TagType expectedTag); + void ensureTag(TagType const &tag, TagType const &expectedTag); void ensureEmpty(common::Context const &context); } diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 42988140..843b3059 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -9,67 +9,86 @@ #include "lib/infrastructure/include/Logging.h" -#ifdef WITH_SIGNATURE_VERIFIER -#include -#include -#include -#include -#include -#include -#include -#endif +#include +#include +#include namespace interpreter::detail::vdv { + + class BotanMessageDecoder::Internal + { + std::unique_ptr const sha1HashFunction = Botan::HashFunction::create_or_throw("SHA-1"); + + public: + std::vector decryptVerify(Signature const &signature, PublicKey const &publicKey) + { + auto const decryptedContent = Botan::power_mod(Botan::BigInt(signature.value), + Botan::BigInt(publicKey.exponent), + Botan::BigInt(publicKey.modulus)) + .serialize(); + + auto contentContext = common::Context(decryptedContent); + consumeExpectedFrameTags(contentContext, {0x6A}, {0xBC}); + auto const expectedHash = contentContext.consumeBytesEnd(20); + auto content = contentContext.consumeRemainingBytesAppend(signature.remainder); + ensureEmpty(contentContext); + + auto const actualHash = sha1HashFunction->process(content); + if (!std::equal(actualHash.begin(), actualHash.end(), expectedHash.begin(), expectedHash.end())) + { + throw std::runtime_error("SHA1 hash value for content does not match expected hash value"); + } + return content; // Copy of vector owning the data and moving out here + } + }; + BotanMessageDecoder::BotanMessageDecoder(infrastructure::LoggerFactory &lf, CertificateProvider &cp) : logger(CREATE_LOGGER(lf)), - certificateProvider(cp) + certificateProvider(cp), + internal(std::make_shared()) { } - std::optional> BotanMessageDecoder::decodeCertificate( - std::span const &certificate, - std::string const &authority) + std::optional> BotanMessageDecoder::decodeMessage( + Certificate const &envelopeCertificate, + Signature const &envelopeSignature) { - // TODO - // - Identify and get authority certificate by using authority - // - Get root certificate - // - Decode authority certificate by using root certificate - // - Decode ticket certificate by using given certificate + authority certificate - // - Return ticket certificate - auto const rootCertificate = certificateProvider.getRoot(); if (!rootCertificate) { return std::nullopt; } - auto const companyCertificate = certificateProvider.get(authority); - if (!companyCertificate) + auto const issuingCertificate = certificateProvider.get(envelopeCertificate.authority); + if (!issuingCertificate) { return std::nullopt; } - auto context = common::Context(certificate); - auto const ticketSignature = consumeExpectedTag(context, {0x5f, 0x37}); - auto const ticketRemainder = consumeExpectedTag(context, {0x5f, 0x38}); - ensureEmpty(context); + auto rootContext = common::Context(rootCertificate->content); + auto const rootCertIdentity = CertificateIdentity::consumeFrom(rootContext, 9); + auto const rootCertPublicKey = PublicKey::consumeFrom(rootContext); + ensureEmpty(rootContext); + auto const issuingContent = internal->decryptVerify(issuingCertificate->signature, rootCertPublicKey); - // auto dataSource = Botan::DataSource_Memory(rootCertificate->content); - // auto cert = Botan::X509_Certificate(dataSource); - // Botan::X509_Certificate(rootCertificate->) + LOG_INFO(logger) << "Using root certificate " << rootCertIdentity.toString(); - // auto decoder = Botan::PK_Decryptor_EME(*key, *rng, "RSA-1984(SHA-1)"); - // auto const message = decoder.decrypt(certificate.data(), certificate.size()); + auto issuingContext = common::Context(issuingContent); + auto const issuingCertIdentity = CertificateIdentity::consumeFrom(issuingContext, 7); + auto const issuingCertPublicKey = PublicKey::consumeFrom(issuingContext); + ensureEmpty(issuingContext); + auto const envelopeContent = internal->decryptVerify(envelopeCertificate.signature, issuingCertPublicKey); - return std::nullopt; - } + LOG_INFO(logger) << "Using issuing certificate " << issuingCertIdentity.toString(); - std::optional> BotanMessageDecoder::decodeMessage( - std::span const &signature, - std::span const &residual, - std::span const &certificate) - { + auto envelopeContext = common::Context(envelopeContent); + auto const envelopeCertIdentity = CertificateIdentity::consumeFrom(envelopeContext, 7); + auto const envelopeCertPublicKey = PublicKey::consumeFrom(envelopeContext); + ensureEmpty(envelopeContext); + auto messageContent = internal->decryptVerify(envelopeSignature, envelopeCertPublicKey); + + LOG_INFO(logger) << "Using envelope certificate " << envelopeCertIdentity.toString(); - return std::nullopt; + return std::make_optional(std::move(messageContent)); } } diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp new file mode 100644 index 00000000..8c44572b --- /dev/null +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -0,0 +1,230 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../include/Certificate.h" +#include "../include/VDVUtility.h" + +#include "lib/interpreter/detail/common/include/InterpreterUtility.h" + +#include +#include + +#include + +namespace interpreter::detail::vdv +{ + + Signature Signature::consumeFrom(common::Context &context) + { + auto const value = consumeExpectedTagValue(context, {0x5f, 0x37}); + auto const remainder = consumeExpectedTagValue(context, {0x5f, 0x38}); + return Signature{value, remainder}; + } + + Signature Signature::consumeFromEnvelope(common::Context &context) + { + auto const value = consumeExpectedTagValue(context, {0x9e, 0x00}); + auto const remainder = consumeExpectedTagValue(context, {0x9a, 0x00}); + return Signature{value, remainder}; + } + + PublicKey PublicKey::consumeFrom(common::Context &context) + { // 65537 = 2^16+1 is the fifth Fermat, used commonly as exponent in RSA algorithms + auto const exponent = context.consumeBytesEnd(4); // TODO Length unsure, always 4 bytes for exponent? + auto const modulus = context.consumeRemainingBytes(); + return PublicKey{exponent, modulus}; + } + + Certificate Certificate::consumeFromEnvelope(common::Context &context) + { + auto const signatureData = consumeExpectedTagValue(context, {0x7f, 0x21}); + auto const authority = common::bytesToHexString(consumeExpectedTagValue(context, {0x42, 0x00})); + + auto signatureContext = common::Context(signatureData); + auto signature = Signature::consumeFrom(signatureContext); + ensureEmpty(signatureContext); + + return Certificate{authority, "envelope", signature, {}}; + } + + CertificateOID CertificateOID::consumeFrom(common::Context &inputContext, std::size_t length) + { + /* There is no 0x06 tag in the beginning and there is also no byte-length encoded. + So it should be a fixed length or there is a termination indicator, but + the specification does not mention a terminator. + */ + auto context = common::Context(inputContext.consumeBytes(length)); + auto parts = std::vector{}; + + auto const header = common::getNumeric8(context); + if (header < 40) { // ITU-T + parts.insert(parts.begin(), {0, header}); + } else if (header < 80) { // ISO + parts.insert(parts.begin(), {1, header - 40u}); + } else { // joint-iso-itu-t + parts.insert(parts.begin(), {2, header - 80u}); + } + + for (;length > 1 && !context.isEmpty(); length -= 1u) + { + auto part = std::uint32_t{0}; + auto chunk = std::uint32_t{0}; // MSB = 1 means more bytes + for (chunk = common::getNumeric8(context); chunk & 0x80; chunk = common::getNumeric8(context)) + { + part = (part | (chunk & 0x7f)) << 7; // Drop MSB, OR it to what we have already and shift left to ensure space 4 next chunk + } + + parts.push_back(part | chunk); + } + + return CertificateOID{std::accumulate(std::begin(parts), std::end(parts), std::vector{}, [](auto &&result, auto value) + { + result.emplace_back(std::to_string(value)); + return std::move(result); })}; + } + + std::string CertificateOID::toString() const + { + return boost::algorithm::join(parts, "."); + } + + CertificateParticipant CertificateParticipant::consumeFrom(common::Context &context) + { + auto const region = common::bytesToAlphanumeric(context.consumeBytes(2)); + auto const name = common::bytesToAlphanumeric(context.consumeBytes(3)); + auto const serviceIdenticator = common::getNumeric8(context); + auto const algorithmReference = common::getNumeric8(context); + auto const year = std::to_string(1990 + common::getNumeric8(context)); + return CertificateParticipant{region, name, serviceIdenticator, algorithmReference, year}; + } + + std::string CertificateParticipant::toString() const + { + auto out = std::ostringstream(); + out << region << "-" + << name << "-" + << year << "-" + << "SVC(" << serviceIdenticator << ")-" + << "ALG(" << algorithmReference << ")"; + return out.str(); + } + + std::string CertificateReference::toString() const + { + auto out = std::ostringstream(); + out << organisationId << "/" + << ownerOrganisationId << "-" + << "SAM(" << samId << ")-" + << "FROM(" << samValidFrom.toString() << ")-" + << "UNTIL(" << samValidUntil.toString() << ")"; + return out.str(); + } + + CertificateReference CertificateReference::consumeFrom(common::Context &context) + { + auto const orgId = std::to_string(common::getNumeric16(context)); + auto const samValidUntil = CertificateDate::consumeFrom2(context); + auto const samValidFrom = CertificateDate::consumeFrom3(context); + auto const ownerOrgId = std::to_string(common::getNumeric16(context)); + auto const samId = std::to_string(common::getNumeric24(context)); + return CertificateReference{orgId, samValidUntil, samValidFrom, ownerOrgId, samId}; + } + + std::string CertificateDate::toString() const + { + auto out = std::ostringstream(); + out << std::setw(2) << std::setfill('0') << (int)day << "-" + << std::setw(2) << std::setfill('0') << (int)month << "-" + << std::setw(4) << std::setfill('0') << (int)year; + return out.str(); + } + + CertificateDate CertificateDate::consumeFrom4(common::Context &context) + { + auto const year = common::getDecimal16(context); + auto const month = common::getDecimal8(context); + auto const day = common::getDecimal8(context); + return CertificateDate{year, month, day}; + } + + CertificateDate CertificateDate::consumeFrom3(common::Context &context) + { + auto const year = static_cast(2000 + common::getDecimal8(context)); + auto const month = common::getDecimal8(context); + auto const day = common::getDecimal8(context); + return CertificateDate{year, month, day}; + } + + CertificateDate CertificateDate::consumeFrom2(common::Context &context) + { + auto const year = static_cast(2000 + common::getDecimal8(context)); + auto const month = common::getDecimal8(context); + return CertificateDate{year, month, 1}; + } + + std::string CertificateAuthorization::toString() const + { + auto out = std::ostringstream(); + out << name << "-" + << "SVC(" << serviceIndicator << ")"; + return out.str(); + } + + CertificateAuthorization CertificateAuthorization::consumeFrom(common::Context &context) + { + auto const name = common::getAlphanumeric(context, 6); + auto const serviceIndicator = common::getNumeric8(context); + return CertificateAuthorization{name, serviceIndicator}; + } + + std::string CertificateProfile::toString() const + { + return identifier; + } + + CertificateProfile CertificateProfile::consumeFrom(common::Context &context) + { + auto const identifier = std::to_string(common::getNumeric8(context)); + return CertificateProfile{identifier}; + } + + std::string CertificateIdentity::toString() const + { + auto out = std::ostringstream(); + out << "PROFILE(" << profile.toString() << ")-" + << "AUTHORITY(" << authority.toString() << ")-"; + if (holder) + { + out << "HOLDER(" << holder->toString() << ")-"; + } + if (reference) + { + out << "REFERENCE(" << reference->toString() << ")-"; + } + out << "AUTHORIZATION(" << authorization.toString() << ")-" + << "EXPIRY(" << expiryDate.toString() << ")-" + << "ALG(" << algorithm.toString() << ")"; + return out.str(); + } + + CertificateIdentity CertificateIdentity::consumeFrom(common::Context &context, std::size_t oidLength) + { + auto const profile = CertificateProfile::consumeFrom(context); + auto const authority = CertificateParticipant::consumeFrom(context); + auto holder = std::optional{}; + auto reference = std::optional{}; + if (peekExpected(context, {0, 0, 0, 0})) + { + auto const fillBytes = context.consumeBytes(4); + holder = std::make_optional(CertificateParticipant::consumeFrom(context)); + } + else + { + reference = std::make_optional(CertificateReference::consumeFrom(context)); + } + auto const authorization = CertificateAuthorization::consumeFrom(context); + auto const expiryDate = CertificateDate::consumeFrom4(context); + auto const algorithm = CertificateOID::consumeFrom(context, oidLength); + return CertificateIdentity{profile, authority, holder, reference, authorization, expiryDate, algorithm}; + } +} diff --git a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp index 2d8beb44..fddc9a5a 100644 --- a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp +++ b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp @@ -19,13 +19,13 @@ namespace interpreter::detail::vdv { - struct RawCertificate + struct LDIFCertificate { std::string commonName; std::string distinguishedName; std::string description; std::vector data; - std::optional mutable certificate; + std::optional mutable certificate; // Cache a certificate extracted once std::optional get() const { @@ -35,7 +35,7 @@ namespace interpreter::detail::vdv } auto context = common::Context(data); - auto payload = consumeExpectedTag(context, {0x7f, 0x21}); + auto payload = consumeExpectedTagValue(context, {0x7f, 0x21}); ensureEmpty(context); auto content = std::span{}; @@ -45,8 +45,8 @@ namespace interpreter::detail::vdv auto payloadContext = common::Context(payload); while (!payloadContext.isEmpty()) { - auto const tag = getTag(payloadContext); - auto const value = payloadContext.consumeBytes(getLength(payloadContext)); + auto const tag = consumeTag(payloadContext); + auto const value = payloadContext.consumeBytes(consumeLength(payloadContext)); if (tag == TagType{0x5f, 0x4e}) { content = value; @@ -65,14 +65,14 @@ namespace interpreter::detail::vdv } } - certificate = std::make_optional(Certificate{commonName, description, signature, remainder, content}); + certificate = std::make_optional(Certificate{commonName, description, Signature{signature, remainder}, content}); return certificate; } }; struct LDIFFileCertificateProvider::Internal { - std::optional> const entries; + std::optional> const entries; static std::string getValue(std::vector const &entryLines, std::string key) { @@ -83,7 +83,7 @@ namespace interpreter::detail::vdv : result->substr(key.size(), result->size() - key.size()); } - static std::optional> import(std::filesystem::path file) + static std::optional> import(std::filesystem::path file) { if (!std::filesystem::exists(file) || !std::filesystem::is_regular_file(file)) { @@ -136,15 +136,15 @@ namespace interpreter::detail::vdv return std::nullopt; } - std::map entries; + std::map entries; std::transform(std::begin(entryLines), std::end(entryLines), std::inserter(entries, entries.begin()), [](auto const &lines) { - auto commonName = getValue(lines, "cn: "); - return std::make_pair(commonName, RawCertificate{ - commonName, - getValue(lines, "dn: "), - getValue(lines, "description: "), - utility::base64::decode(getValue(lines, "cACertificate:: "))}); }); + auto commonName = getValue(lines, "cn: "); + return std::make_pair(commonName, LDIFCertificate{ + commonName, + getValue(lines, "dn: "), + getValue(lines, "description: "), + utility::base64::decode(getValue(lines, "cACertificate:: "))}); }); return entries; } @@ -179,7 +179,7 @@ namespace interpreter::detail::vdv std::optional LDIFFileCertificateProvider::getRoot() { - return get("4555564456100106"); + return get("4555564456100106"); // EUVDV, 16, 01, 1996 } std::optional LDIFFileCertificateProvider::get(std::string authority) diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 6c80b118..06956718 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -12,8 +12,6 @@ #include "lib/infrastructure/include/Logging.h" -#include - namespace interpreter::detail::vdv { @@ -45,27 +43,26 @@ namespace interpreter::detail::vdv // - https://github.com/akorb/deutschlandticket_parser/blob/main/main.py // - https://github.com/RWTH-i5-IDSG/ticketserver/blob/master/barti-check/src/main/java/de/rwth/idsg/barti/check/Decode.java - auto const signature = consumeExpectedTag(context, {0x9e, 0x00}); - auto const signatureResidual = consumeExpectedTag(context, {0x9a, 0x00}); - auto const signatureIdent = common::bytesToAlphanumeric(signatureResidual.subspan(0, 3)); - auto const signatureVersion = common::bytesToHexString(signatureResidual.subspan(3, 2)); - auto const certificate = consumeExpectedTag(context, {0x7f, 0x21}); - auto const certificateAuthority = common::bytesToHexString(consumeExpectedTag(context, {0x42, 0x00})); + auto const signature = Signature::consumeFromEnvelope(context); + auto const certificate = Certificate::consumeFromEnvelope(context); ensureEmpty(context); - auto const ticketCertificate = messageDecoder->decodeCertificate(certificate, certificateAuthority); - if (ticketCertificate) - { - auto const message = messageDecoder->decodeMessage(signature, signatureResidual, *ticketCertificate); - } + auto const signatureIdent = common::bytesToAlphanumeric(signature.remainder.subspan(0, 3)); + auto const signatureVersion = common::bytesToHexString(signature.remainder.subspan(3, 2)); auto jsonBuilder = utility::JsonBuilder::object(); jsonBuilder - .add("signature", utility::base64::encode(signature)) .add("signatureIdent", signatureIdent) .add("signatureVersion", signatureVersion) - .add("certificate", utility::base64::encode(certificate)) - .add("certificateAuthority", certificateAuthority); + .add("certificateAuthority", certificate.authority); + + auto const message = messageDecoder->decodeMessage(certificate, signature); + if (message) + { + auto messageContext = common::Context(*message); + auto const ticketId = common::getNumeric32(messageContext); + jsonBuilder.add("ticketId", ticketId); + } context.addRecord(common::Record(signatureIdent, signatureVersion, std::move(jsonBuilder))); return std::move(context); diff --git a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp index bcd3ffd1..fb6a05eb 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp @@ -9,7 +9,7 @@ namespace interpreter::detail::vdv { /* Multi-byte integers are big-endian encoded, see DER ASN.1 */ - std::uint32_t getLength(common::Context &context) + std::uint32_t consumeLength(common::Context &context) { auto const first = common::getNumeric8(context); // clang-format off @@ -22,7 +22,7 @@ namespace interpreter::detail::vdv throw std::runtime_error(std::string("Found unexpected length indicator tag (expecting x<0x80 or x=0x8y with y=): ") + std::to_string(first)); } - TagType getTag(common::Context &context) + TagType consumeTag(common::Context &context) { auto const first = common::getNumeric8(context); if (first == 0x7f || first == 0x5f) @@ -33,19 +33,59 @@ namespace interpreter::detail::vdv return {first, 0}; } - common::Context &ensureExpectedTag(common::Context &context, TagType expectedTag) + common::Context &consumeExpectedTag(common::Context &context, TagType const &expectedTag) { - auto tag = getTag(context); - if (tag != expectedTag) + auto tag = consumeTag(context); + ensureTag(tag, expectedTag); + return context; + } + + common::Context &consumeExpectedEndTag(common::Context &context, TagType const &expectedTag) + { + auto const tag = TagType{context.consumeBytesEnd(1)[0], 0}; + if (tag[0] == 0x7f || tag[0] == 0x5f) { - throw std::runtime_error(std::string("Unexpected tag found: ") + common::bytesToHexString(tag)); + throw std::runtime_error(std::string("Unexpected end tag found: ") + common::bytesToHexString(tag)); } + ensureTag(tag, expectedTag); return context; } - std::span consumeExpectedTag(common::Context &context, TagType expectedTag) + common::Context &consumeExpectedFrameTags(common::Context &context, TagType const &expectedBeginTag, TagType const &expectedEndTag) + { + consumeExpectedTag(context, expectedBeginTag); + consumeExpectedEndTag(context, expectedEndTag); + return context; + } + + std::span consumeExpectedTagValue(common::Context &context, TagType const &expectedTag) + { + return context.consumeBytes(consumeLength(consumeExpectedTag(context, expectedTag))); + } + + std::span consumeExpected(common::Context &context, std::vector expectedValue) { - return context.consumeBytes(getLength(ensureExpectedTag(context, expectedTag))); + auto const value = context.consumeBytes(expectedValue.size()); + if (!std::equal(value.begin(), value.end(), expectedValue.begin(), expectedValue.end())) + { + throw std::runtime_error(std::string("Unexpected value found: ") + common::bytesToHexString(value)); + } + return value; + } + + bool peekExpected(common::Context &context, std::vector expectedValue) + { + + auto const value = context.peekBytes(expectedValue.size()); + return std::equal(value.begin(), value.end(), expectedValue.begin(), expectedValue.end()); + } + + void ensureTag(TagType const &tag, TagType const &expectedTag) + { + if (tag != expectedTag) + { + throw std::runtime_error(std::string("Unexpected tag found: ") + common::bytesToHexString(tag)); + } } void ensureEmpty(common::Context const &context) From 697b3dda6a4a85a62ddcb8ea81e26039f05889c4 Mon Sep 17 00:00:00 2001 From: sascha Date: Thu, 5 Feb 2026 20:54:32 +0100 Subject: [PATCH 17/41] Fix/improve some method names --- .../common/include/InterpreterUtility.h | 47 ++++++++++++++----- .../common/source/InterpreterUtility.cpp | 46 +++++++++--------- .../detail/uic918/source/Record0080BL.cpp | 24 +++++----- .../detail/uic918/source/Record0080VU.cpp | 38 +++++++-------- .../detail/uic918/source/Record118199.cpp | 2 +- .../detail/uic918/source/RecordHeader.cpp | 6 +-- .../detail/uic918/source/RecordU_HEAD.cpp | 12 ++--- .../detail/uic918/source/RecordU_TLAY.cpp | 18 +++---- .../uic918/source/Uic918Interpreter.cpp | 8 ++-- .../detail/vdv/source/Certificate.cpp | 42 ++++++++--------- .../detail/vdv/source/VDVInterpreter.cpp | 4 +- .../detail/vdv/source/VDVUtility.cpp | 14 +++--- .../source/InterpreterUtilityTest.cpp | 44 ++++++++--------- 13 files changed, 165 insertions(+), 140 deletions(-) diff --git a/source/lib/interpreter/detail/common/include/InterpreterUtility.h b/source/lib/interpreter/detail/common/include/InterpreterUtility.h index 71b3f5e0..0a847ed1 100644 --- a/source/lib/interpreter/detail/common/include/InterpreterUtility.h +++ b/source/lib/interpreter/detail/common/include/InterpreterUtility.h @@ -10,27 +10,52 @@ namespace interpreter::detail::common { - std::string getAlphanumeric(Context &context, std::size_t size); + /* Consumes maximumSize or less bytes and returns a 0 terminated string. + When the buffer is filled by multiple 0 at the end, the returned string might be shorter. + */ + std::string consumeString(Context &context, std::size_t maximumSize); - std::uint32_t getNumeric32(Context &context); + /* Consumes 4 bytes and converts from big-endian to system byte order + */ + std::uint32_t consumeInteger4(Context &context); - std::uint32_t getNumeric24(Context &context); + /* Consumes 3 bytes and converts from big-endian to system byte order + */ + std::uint32_t consumeInteger3(Context &context); - std::uint16_t getNumeric16(Context &context); + /* Consumes 2 bytes and converts from big-endian to system byte order + */ + std::uint16_t consumeInteger2(Context &context); - std::uint8_t getNumeric8(Context &context); + /* Consumes 1 byte + */ + std::uint8_t consumeInteger1(Context &context); - std::uint16_t getDecimal16(Context &context); + /* Consumes 2 bytes and decodes binary decimal encoded value + */ + std::uint16_t consumeDecimalInteger2(Context &context); - std::uint8_t getDecimal8(Context &context); + /* Consumes 1 bytes and decodes binary decimal encoded value + */ + std::uint8_t consumeDecimalInteger1(Context &context); - std::string getDateTimeCompact(Context &context); + /* Consumes 4 bytes and decodes date-time to ISO-8601 format + */ + std::string consumeDateTimeCompact4(Context &context); - std::string getDateTime12(Context &context); + /* Consumes 3 bytes and decodes date-time to ISO-8601 format + */ + std::string consumeDateTime3(Context &context); - std::string getDate8(Context &context); + /* Consumes 8 bytes and decodes date to ISO-8601 format + */ + std::string consumeDate8(Context &context); - std::string bytesToAlphanumeric(std::span bytes); + /*** + Converters + ***/ + + std::string bytesToString(std::span bytes); std::string bytesToHexString(std::span bytes); diff --git a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp index ec65ffe0..2fef84c8 100644 --- a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp +++ b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp @@ -13,14 +13,14 @@ namespace interpreter::detail::common { - std::string getAlphanumeric(Context &context, std::size_t size) + std::string consumeString(Context &context, std::size_t size) { auto const data = context.consumeMaximalBytes(size); - return bytesToAlphanumeric(data); + return bytesToString(data); } template - T getNumeric(Context &context, std::size_t sourceLength = sizeof(T)) + T getInteger(Context &context, std::size_t sourceLength = sizeof(T)) { if (sizeof(T) < sourceLength) { @@ -44,34 +44,34 @@ namespace interpreter::detail::common return result; } - std::uint32_t getNumeric32(Context &context) + std::uint32_t consumeInteger4(Context &context) { - return getNumeric(context); + return getInteger(context); } - std::uint32_t getNumeric24(Context &context) + std::uint32_t consumeInteger3(Context &context) { - return getNumeric(context, 3); + return getInteger(context, 3); } - std::uint16_t getNumeric16(Context &context) + std::uint16_t consumeInteger2(Context &context) { - return getNumeric(context); + return getInteger(context); } - std::uint8_t getNumeric8(Context &context) + std::uint8_t consumeInteger1(Context &context) { - return getNumeric(context); + return getInteger(context); } - std::uint16_t getDecimal16(Context &context) + std::uint16_t consumeDecimalInteger2(Context &context) { - std::uint16_t const high = getDecimal8(context); - std::uint16_t const low = getDecimal8(context); + std::uint16_t const high = consumeDecimalInteger1(context); + std::uint16_t const low = consumeDecimalInteger1(context); return high * 100 + low; } - std::uint8_t getDecimal8(Context &context) + std::uint8_t consumeDecimalInteger1(Context &context) { auto byte = context.consumeBytes(1)[0]; std::uint8_t const high = byte >> 4 & 0x0F; @@ -79,10 +79,10 @@ namespace interpreter::detail::common return high * 10 + low; } - std::string getDateTimeCompact(Context &context) + std::string consumeDateTimeCompact4(Context &context) { - auto const date = common::getNumeric16(context); - auto const time = common::getNumeric16(context); + auto const date = common::consumeInteger2(context); + auto const time = common::consumeInteger2(context); // TODO Use chrono parse or apply validation for all values std::ostringstream os; os << std::setw(4) << std::setfill('0') << std::to_string(((date & 0xFE00) >> 9) + 1990) << "-" @@ -94,9 +94,9 @@ namespace interpreter::detail::common return os.str(); } - std::string getDateTime12(Context &context) + std::string consumeDateTime3(Context &context) { - auto const input = getAlphanumeric(context, 12); + auto const input = consumeString(context, 12); auto const p = input.begin(); // TODO Use chrono parse or apply validation for all values std::ostringstream os; // DDMMYYYYHHMM @@ -109,9 +109,9 @@ namespace interpreter::detail::common return os.str(); } - std::string getDate8(Context &context) + std::string consumeDate8(Context &context) { - auto const input = getAlphanumeric(context, 8); + auto const input = consumeString(context, 8); auto const p = input.begin(); // TODO Use chrono parse or apply validation for all values std::ostringstream os; // DDMMYYYY @@ -121,7 +121,7 @@ namespace interpreter::detail::common return os.str(); } - std::string bytesToAlphanumeric(std::span bytes) + std::string bytesToString(std::span bytes) { auto result = std::string{std::begin(bytes), std::find(std::begin(bytes), std::end(bytes), '\0')}; result.erase(std::find_if(std::rbegin(result), std::rend(result), [](unsigned char ch) diff --git a/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp b/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp index 8b380edc..51841001 100644 --- a/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp +++ b/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp @@ -74,16 +74,16 @@ namespace interpreter::detail::uic auto const certificate2 = context.consumeBytes(11); builder - .add("validFrom", common::getDate8(context)) - .add("validTo", common::getDate8(context)) - .add("serial", common::getAlphanumeric(context, 8)); + .add("validFrom", common::consumeDate8(context)) + .add("validTo", common::consumeDate8(context)) + .add("serial", common::consumeString(context, 8)); }}, {std::string("03"), [](auto &context, auto &builder) { builder - .add("validFrom", common::getDate8(context)) - .add("validTo", common::getDate8(context)) - .add("serial", common::getAlphanumeric(context, 10)); + .add("validFrom", common::consumeDate8(context)) + .add("validTo", common::consumeDate8(context)) + .add("serial", common::consumeString(context, 10)); }}}; Record0080BL::Record0080BL(infrastructure::LoggerFactory &loggerFactory, RecordHeader &&h) @@ -98,14 +98,14 @@ namespace interpreter::detail::uic auto recordJson = ::utility::JsonBuilder::object(); // clang-format off recordJson - .add("ticketType", common::getAlphanumeric(context, 2)) - .add("trips", ::utility::toArray(std::stoi(common::getAlphanumeric(context, 1)), [&](auto &builder) + .add("ticketType", common::consumeString(context, 2)) + .add("trips", ::utility::toArray(std::stoi(common::consumeString(context, 1)), [&](auto &builder) { tripInterpreter(context, builder); })) - .add("fields", ::utility::toObject(std::stoi(common::getAlphanumeric(context, 2)), [&](auto & builder) + .add("fields", ::utility::toObject(std::stoi(common::consumeString(context, 2)), [&](auto & builder) { - auto const type = common::getAlphanumeric(context, 4); - auto const length = std::stoi(common::getAlphanumeric(context, 4)); - auto const content = common::getAlphanumeric(context, length); + auto const type = common::consumeString(context, 4); + auto const length = std::stoi(common::consumeString(context, 4)); + auto const content = common::consumeString(context, length); auto const annotation = annotationMap.find(type); builder diff --git a/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp b/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp index 40c73dd7..d420eec2 100644 --- a/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp +++ b/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp @@ -23,16 +23,16 @@ namespace interpreter::detail::uic { auto recordJson = ::utility::JsonBuilder::object(); // clang-format off recordJson - .add("terminalNummer", std::to_string(common::getNumeric16(context))) - .add("samNummer", std::to_string(common::getNumeric24(context))) - .add("anzahlPersonen", std::to_string(common::getNumeric8(context))) - .add("efs", ::utility::toArray(common::getNumeric8(context), [&context](auto &builder) + .add("terminalNummer", std::to_string(common::consumeInteger2(context))) + .add("samNummer", std::to_string(common::consumeInteger3(context))) + .add("anzahlPersonen", std::to_string(common::consumeInteger1(context))) + .add("efs", ::utility::toArray(common::consumeInteger1(context), [&context](auto &builder) { // TODO Unsure if numeric is the proper interpretation of berechtigungsNummer - auto const berechtigungsNummer = common::getNumeric32(context); - auto const kvpOrganisationsId = common::getNumeric16(context); - auto const pvProduktnummer = common::getNumeric16(context); - auto const pvOrganisationsId = common::getNumeric16(context); + auto const berechtigungsNummer = common::consumeInteger4(context); + auto const kvpOrganisationsId = common::consumeInteger2(context); + auto const pvProduktnummer = common::consumeInteger2(context); + auto const pvOrganisationsId = common::consumeInteger2(context); auto const pvProduktbezeichnung = getProduktbezeichnung(pvOrganisationsId, pvProduktnummer); builder @@ -43,27 +43,27 @@ namespace interpreter::detail::uic .add("pvProduktnummer", std::to_string(pvProduktnummer)) .add("pvProduktbezeichnung", pvProduktbezeichnung) .add("pvOrganisationsId", std::to_string(pvOrganisationsId)) - .add("gueltigAb", common::getDateTimeCompact(context)) - .add("gueltigBis", common::getDateTimeCompact(context)) - .add("preis", common::getNumeric24(context)) - .add("samSequenznummer", std::to_string(common::getNumeric32(context))) - .add("flaechenelemente", ::utility::toDynamicArray(common::getNumeric8(context), [&context](auto &builder) + .add("gueltigAb", common::consumeDateTimeCompact4(context)) + .add("gueltigBis", common::consumeDateTimeCompact4(context)) + .add("preis", common::consumeInteger3(context)) + .add("samSequenznummer", std::to_string(common::consumeInteger4(context))) + .add("flaechenelemente", ::utility::toDynamicArray(common::consumeInteger1(context), [&context](auto &builder) { auto tagStream = std::ostringstream(); - auto const tagValue = int(common::getNumeric8(context)); + auto const tagValue = int(common::consumeInteger1(context)); tagStream << std::hex << std::noshowbase << tagValue; auto const tag = tagStream.str(); - auto const elementLength = common::getNumeric8(context); - auto const typ = std::to_string(common::getNumeric8(context)); - auto const organisationsId = std::to_string(common::getNumeric16(context)); + auto const elementLength = common::consumeInteger1(context); + auto const typ = std::to_string(common::consumeInteger1(context)); + auto const organisationsId = std::to_string(common::consumeInteger2(context)); auto const flaechenIdLength = elementLength - 3; if (flaechenIdLength != 2 && flaechenIdLength != 3) { throw std::runtime_error(std::string("Unexpected FlaechenelementId length: ") + std::to_string(flaechenIdLength)); } auto const flaechenId = std::to_string(flaechenIdLength == 2 - ? common::getNumeric16(context) - : common::getNumeric24(context)); + ? common::consumeInteger2(context) + : common::consumeInteger3(context)); builder .add("tag", tag) diff --git a/source/lib/interpreter/detail/uic918/source/Record118199.cpp b/source/lib/interpreter/detail/uic918/source/Record118199.cpp index f44db1be..e4ef4feb 100644 --- a/source/lib/interpreter/detail/uic918/source/Record118199.cpp +++ b/source/lib/interpreter/detail/uic918/source/Record118199.cpp @@ -20,7 +20,7 @@ namespace interpreter::detail::uic common::Context Record118199::interpret(common::Context &&context) { - auto const jsonString = common::getAlphanumeric(context, context.getRemainingSize()); + auto const jsonString = common::consumeString(context, context.getRemainingSize()); context.addRecord(common::Record(header.recordId, header.recordVersion, ::utility::JsonBuilder(jsonString))); return std::move(context); diff --git a/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp b/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp index 5034429b..a9e4fa74 100644 --- a/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp +++ b/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp @@ -12,9 +12,9 @@ namespace interpreter::detail::uic { RecordHeader::RecordHeader(common::Context &context) : start(context.getPosition()), - recordId(common::getAlphanumeric(context, 6)), - recordVersion(common::getAlphanumeric(context, 2)), - recordLength(std::stoi(common::getAlphanumeric(context, 4))) + recordId(common::consumeString(context, 6)), + recordVersion(common::consumeString(context, 2)), + recordLength(std::stoi(common::consumeString(context, 4))) { context.addField(recordId + ".recordId", recordId); context.addField(recordId + ".recordVersion", recordVersion); diff --git a/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp b/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp index 9fe48bb6..ebb4f5ef 100644 --- a/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp +++ b/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp @@ -21,12 +21,12 @@ namespace interpreter::detail::uic { auto recordJson = ::utility::JsonBuilder::object(); recordJson - .add("companyCode", common::getAlphanumeric(context, 4)) - .add("uniqueTicketKey", common::getAlphanumeric(context, 20)) - .add("editionTime", common::getDateTime12(context)) - .add("flags", common::getAlphanumeric(context, 1)) - .add("editionLanguageOfTicket", common::getAlphanumeric(context, 2)) - .add("secondLanguageOfContract", common::getAlphanumeric(context, 2)); + .add("companyCode", common::consumeString(context, 4)) + .add("uniqueTicketKey", common::consumeString(context, 20)) + .add("editionTime", common::consumeDateTime3(context)) + .add("flags", common::consumeString(context, 1)) + .add("editionLanguageOfTicket", common::consumeString(context, 2)) + .add("secondLanguageOfContract", common::consumeString(context, 2)); context.addRecord(common::Record(header.recordId, header.recordVersion, std::move(recordJson))); return std::move(context); diff --git a/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp b/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp index f61324f0..5bf47bd4 100644 --- a/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp +++ b/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp @@ -24,7 +24,7 @@ namespace interpreter::detail::uic common::Context RecordU_TLAY::interpret(common::Context &&context) { - auto const layoutStandard = common::getAlphanumeric(context, 4); + auto const layoutStandard = common::consumeString(context, 4); context.addField("U_TLAY.layoutStandard", layoutStandard); if (layoutStandard.compare("RCT2") != 0 && layoutStandard.compare("PLAI") != 0) { @@ -35,17 +35,17 @@ namespace interpreter::detail::uic auto recordJson = ::utility::JsonBuilder::object(); // clang-format off recordJson - .add("fields", ::utility::toArray(std::stoi(common::getAlphanumeric(context, 4)), [&](auto &builder) + .add("fields", ::utility::toArray(std::stoi(common::consumeString(context, 4)), [&](auto &builder) { builder - .add("line", std::stoi(common::getAlphanumeric(context, 2))) - .add("column", std::stoi(common::getAlphanumeric(context, 2))) - .add("height", std::stoi(common::getAlphanumeric(context, 2))) - .add("width", std::stoi(common::getAlphanumeric(context, 2))) - .add("formatting", common::getAlphanumeric(context, 1)); + .add("line", std::stoi(common::consumeString(context, 2))) + .add("column", std::stoi(common::consumeString(context, 2))) + .add("height", std::stoi(common::consumeString(context, 2))) + .add("width", std::stoi(common::consumeString(context, 2))) + .add("formatting", common::consumeString(context, 1)); - auto const length = std::stoi(common::getAlphanumeric(context, 4)); + auto const length = std::stoi(common::consumeString(context, 4)); builder - .add("text", common::getAlphanumeric(context, length)); + .add("text", common::consumeString(context, length)); })); // clang-format on context.addRecord(common::Record(header.recordId, header.recordVersion, std::move(recordJson))); diff --git a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp index 88983222..8cb2afa6 100644 --- a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp +++ b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp @@ -66,7 +66,7 @@ namespace interpreter::detail::uic return std::move(context); } - auto const messageTypeVersion = common::getAlphanumeric(context, 2); + auto const messageTypeVersion = common::consumeString(context, 2); auto const version = std::stoi(messageTypeVersion); // Might be "OTI" as well if (version != 1 && version != 2) @@ -78,15 +78,15 @@ namespace interpreter::detail::uic context.addField("raw", context.getAllBase64Encoded()); context.addField("uniqueMessageTypeId", "#UT"); context.addField("messageTypeVersion", messageTypeVersion); - auto const ricsCode = common::getAlphanumeric(context, 4); + auto const ricsCode = common::consumeString(context, 4); context.addField("companyCode", ricsCode); - auto const keyId = common::getAlphanumeric(context, 5); + auto const keyId = common::consumeString(context, 5); context.addField("signatureKeyId", keyId); auto const signatureLength = version == 2 ? 64 : 50; auto const signature = context.consumeBytes(signatureLength); auto const consumed = context.getConsumedSize(); - auto const messageLengthString = common::getAlphanumeric(context, 4); + auto const messageLengthString = common::consumeString(context, 4); auto const messageLength = std::stoi(messageLengthString); context.addField("compressedMessageLength", std::to_string(messageLength)); if (messageLength < 0 || messageLength > context.getRemainingSize()) diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp index 8c44572b..9d2e771b 100644 --- a/source/lib/interpreter/detail/vdv/source/Certificate.cpp +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -56,7 +56,7 @@ namespace interpreter::detail::vdv auto context = common::Context(inputContext.consumeBytes(length)); auto parts = std::vector{}; - auto const header = common::getNumeric8(context); + auto const header = common::consumeInteger1(context); if (header < 40) { // ITU-T parts.insert(parts.begin(), {0, header}); } else if (header < 80) { // ISO @@ -69,7 +69,7 @@ namespace interpreter::detail::vdv { auto part = std::uint32_t{0}; auto chunk = std::uint32_t{0}; // MSB = 1 means more bytes - for (chunk = common::getNumeric8(context); chunk & 0x80; chunk = common::getNumeric8(context)) + for (chunk = common::consumeInteger1(context); chunk & 0x80; chunk = common::consumeInteger1(context)) { part = (part | (chunk & 0x7f)) << 7; // Drop MSB, OR it to what we have already and shift left to ensure space 4 next chunk } @@ -90,11 +90,11 @@ namespace interpreter::detail::vdv CertificateParticipant CertificateParticipant::consumeFrom(common::Context &context) { - auto const region = common::bytesToAlphanumeric(context.consumeBytes(2)); - auto const name = common::bytesToAlphanumeric(context.consumeBytes(3)); - auto const serviceIdenticator = common::getNumeric8(context); - auto const algorithmReference = common::getNumeric8(context); - auto const year = std::to_string(1990 + common::getNumeric8(context)); + auto const region = common::bytesToString(context.consumeBytes(2)); + auto const name = common::bytesToString(context.consumeBytes(3)); + auto const serviceIdenticator = common::consumeInteger1(context); + auto const algorithmReference = common::consumeInteger1(context); + auto const year = std::to_string(1990 + common::consumeInteger1(context)); return CertificateParticipant{region, name, serviceIdenticator, algorithmReference, year}; } @@ -122,11 +122,11 @@ namespace interpreter::detail::vdv CertificateReference CertificateReference::consumeFrom(common::Context &context) { - auto const orgId = std::to_string(common::getNumeric16(context)); + auto const orgId = std::to_string(common::consumeInteger2(context)); auto const samValidUntil = CertificateDate::consumeFrom2(context); auto const samValidFrom = CertificateDate::consumeFrom3(context); - auto const ownerOrgId = std::to_string(common::getNumeric16(context)); - auto const samId = std::to_string(common::getNumeric24(context)); + auto const ownerOrgId = std::to_string(common::consumeInteger2(context)); + auto const samId = std::to_string(common::consumeInteger3(context)); return CertificateReference{orgId, samValidUntil, samValidFrom, ownerOrgId, samId}; } @@ -141,24 +141,24 @@ namespace interpreter::detail::vdv CertificateDate CertificateDate::consumeFrom4(common::Context &context) { - auto const year = common::getDecimal16(context); - auto const month = common::getDecimal8(context); - auto const day = common::getDecimal8(context); + auto const year = common::consumeDecimalInteger2(context); + auto const month = common::consumeDecimalInteger1(context); + auto const day = common::consumeDecimalInteger1(context); return CertificateDate{year, month, day}; } CertificateDate CertificateDate::consumeFrom3(common::Context &context) { - auto const year = static_cast(2000 + common::getDecimal8(context)); - auto const month = common::getDecimal8(context); - auto const day = common::getDecimal8(context); + auto const year = static_cast(2000 + common::consumeDecimalInteger1(context)); + auto const month = common::consumeDecimalInteger1(context); + auto const day = common::consumeDecimalInteger1(context); return CertificateDate{year, month, day}; } CertificateDate CertificateDate::consumeFrom2(common::Context &context) { - auto const year = static_cast(2000 + common::getDecimal8(context)); - auto const month = common::getDecimal8(context); + auto const year = static_cast(2000 + common::consumeDecimalInteger1(context)); + auto const month = common::consumeDecimalInteger1(context); return CertificateDate{year, month, 1}; } @@ -172,8 +172,8 @@ namespace interpreter::detail::vdv CertificateAuthorization CertificateAuthorization::consumeFrom(common::Context &context) { - auto const name = common::getAlphanumeric(context, 6); - auto const serviceIndicator = common::getNumeric8(context); + auto const name = common::consumeString(context, 6); + auto const serviceIndicator = common::consumeInteger1(context); return CertificateAuthorization{name, serviceIndicator}; } @@ -184,7 +184,7 @@ namespace interpreter::detail::vdv CertificateProfile CertificateProfile::consumeFrom(common::Context &context) { - auto const identifier = std::to_string(common::getNumeric8(context)); + auto const identifier = std::to_string(common::consumeInteger1(context)); return CertificateProfile{identifier}; } diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 06956718..88381264 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -47,7 +47,7 @@ namespace interpreter::detail::vdv auto const certificate = Certificate::consumeFromEnvelope(context); ensureEmpty(context); - auto const signatureIdent = common::bytesToAlphanumeric(signature.remainder.subspan(0, 3)); + auto const signatureIdent = common::bytesToString(signature.remainder.subspan(0, 3)); auto const signatureVersion = common::bytesToHexString(signature.remainder.subspan(3, 2)); auto jsonBuilder = utility::JsonBuilder::object(); @@ -60,7 +60,7 @@ namespace interpreter::detail::vdv if (message) { auto messageContext = common::Context(*message); - auto const ticketId = common::getNumeric32(messageContext); + auto const ticketId = common::consumeInteger4(messageContext); jsonBuilder.add("ticketId", ticketId); } diff --git a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp index fb6a05eb..8c1ab159 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp @@ -11,23 +11,23 @@ namespace interpreter::detail::vdv */ std::uint32_t consumeLength(common::Context &context) { - auto const first = common::getNumeric8(context); + auto const first = common::consumeInteger1(context); // clang-format off if (first < 0x80) { return first; } - else if (first == 0x81) { return common::getNumeric8(context); } - else if (first == 0x82) { return common::getNumeric16(context); } - else if (first == 0x83) { return common::getNumeric24(context); } - else if (first == 0x84) { return common::getNumeric32(context); } + else if (first == 0x81) { return common::consumeInteger1(context); } + else if (first == 0x82) { return common::consumeInteger2(context); } + else if (first == 0x83) { return common::consumeInteger3(context); } + else if (first == 0x84) { return common::consumeInteger4(context); } // clang-format on throw std::runtime_error(std::string("Found unexpected length indicator tag (expecting x<0x80 or x=0x8y with y=): ") + std::to_string(first)); } TagType consumeTag(common::Context &context) { - auto const first = common::getNumeric8(context); + auto const first = common::consumeInteger1(context); if (first == 0x7f || first == 0x5f) { - return {first, common::getNumeric8(context)}; + return {first, common::consumeInteger1(context)}; } return {first, 0}; diff --git a/source/test/interpreter/source/InterpreterUtilityTest.cpp b/source/test/interpreter/source/InterpreterUtilityTest.cpp index 70b40bb4..057d22db 100644 --- a/source/test/interpreter/source/InterpreterUtilityTest.cpp +++ b/source/test/interpreter/source/InterpreterUtilityTest.cpp @@ -7,32 +7,32 @@ namespace interpreter::detail::common { - TEST(getAlphanumeric, readAndStopAtNull) + TEST(consumeString, readAndStopAtNull) { auto const source = std::vector{'R', 'P', 'E', 'X', '4', 'F', '-', '4', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; auto context = Context(source, ""); - EXPECT_EQ(getAlphanumeric(context, 20), std::string("RPEX4F-4")); + EXPECT_EQ(consumeString(context, 20), std::string("RPEX4F-4")); } - TEST(getAlphanumeric, readAndTrimTrailingSpaces) + TEST(consumeString, readAndTrimTrailingSpaces) { auto const source = std::vector{'A', 'B', 'C', ' ', '\n', ' ', ' ', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; auto context = Context(source, ""); - EXPECT_EQ(getAlphanumeric(context, 20), std::string("ABC")); + EXPECT_EQ(consumeString(context, 20), std::string("ABC")); } - TEST(getAlphanumeric, readAll) + TEST(consumeString, readAll) { auto const source = std::vector{'R', 'P', 'E', 'X', '4', 'F', '-', '4'}; auto context = Context(source, ""); - EXPECT_EQ(getAlphanumeric(context, 8), std::string("RPEX4F-4")); + EXPECT_EQ(consumeString(context, 8), std::string("RPEX4F-4")); } - TEST(getAlphanumeric, readEmpty) + TEST(consumeString, readEmpty) { auto const source = std::vector{0}; auto context = Context(source, ""); - EXPECT_EQ(getAlphanumeric(context, 8), std::string("")); + EXPECT_EQ(consumeString(context, 8), std::string("")); } TEST(getNumeric, min8) @@ -40,7 +40,7 @@ namespace interpreter::detail::common auto const source = std::vector{0xff, 1, 0xff}; auto context = Context(source, ""); context.ignoreBytes(1); - EXPECT_EQ(getNumeric8(context), 1); + EXPECT_EQ(consumeInteger1(context), 1); } TEST(getNumeric, max8) @@ -48,7 +48,7 @@ namespace interpreter::detail::common auto const source = std::vector{0xfe, 0xff, 0xfe}; auto context = Context(source, ""); context.ignoreBytes(1); - EXPECT_EQ(getNumeric8(context), 255); + EXPECT_EQ(consumeInteger1(context), 255); } TEST(getNumeric, min16) @@ -56,7 +56,7 @@ namespace interpreter::detail::common auto const source = std::vector{0xff, 0, 1, 0xff}; auto context = Context(source, ""); context.ignoreBytes(1); - EXPECT_EQ(getNumeric16(context), 1); + EXPECT_EQ(consumeInteger2(context), 1); } TEST(getNumeric, max16) @@ -64,7 +64,7 @@ namespace interpreter::detail::common auto const source = std::vector{0xfe, 0xff, 0xff, 0xfe}; auto context = Context(source, ""); context.ignoreBytes(1); - EXPECT_EQ(getNumeric16(context), 65535); + EXPECT_EQ(consumeInteger2(context), 65535); } TEST(getNumeric, min24) @@ -72,7 +72,7 @@ namespace interpreter::detail::common auto const source = std::vector{0xff, 0, 0, 1, 0xff}; // big endian 1 auto context = Context(source, ""); context.ignoreBytes(1); - EXPECT_EQ(getNumeric24(context), 1); + EXPECT_EQ(consumeInteger3(context), 1); } TEST(getNumeric, max24) @@ -80,7 +80,7 @@ namespace interpreter::detail::common auto const source = std::vector{0xfe, 0xff, 0xff, 0xff, 0xfe}; auto context = Context(source, ""); context.ignoreBytes(1); - EXPECT_EQ(getNumeric24(context), 16777215); + EXPECT_EQ(consumeInteger3(context), 16777215); } TEST(getNumeric, min32) @@ -88,7 +88,7 @@ namespace interpreter::detail::common auto const source = std::vector{0xff, 0, 0, 0, 1, 0xff}; // big endian 1 auto context = Context(source, ""); context.ignoreBytes(1); - EXPECT_EQ(getNumeric32(context), 1); + EXPECT_EQ(consumeInteger4(context), 1); } TEST(getNumeric, max32) @@ -96,28 +96,28 @@ namespace interpreter::detail::common auto const source = std::vector{0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe}; auto context = Context(source, ""); context.ignoreBytes(1); - EXPECT_EQ(getNumeric32(context), 4294967295); + EXPECT_EQ(consumeInteger4(context), 4294967295); } - TEST(getDateTimeCompact, initial) + TEST(consumeDateTimeCompact4, initial) { auto const source = std::vector{0x28, 0x39, 0x70, 0x62}; auto context = Context(source, ""); - EXPECT_EQ(getDateTimeCompact(context), "2010-01-25T14:03:02"); + EXPECT_EQ(consumeDateTimeCompact4(context), "2010-01-25T14:03:02"); } - TEST(getDateTime12, initial) + TEST(consumeDateTime3, initial) { auto const source = std::vector{'2', '7', '1', '0', '2', '0', '2', '0', '1', '3', '4', '5'}; auto context = Context(source, ""); - EXPECT_EQ(getDateTime12(context), "2020-10-27T13:45:00"); + EXPECT_EQ(consumeDateTime3(context), "2020-10-27T13:45:00"); } - TEST(getDate8, initial) + TEST(consumeDate8, initial) { auto const source = std::vector{'1', '3', '0', '1', '2', '0', '2', '1'}; auto context = Context(source, ""); - EXPECT_EQ(getDate8(context), "2021-01-13"); + EXPECT_EQ(consumeDate8(context), "2021-01-13"); } TEST(bytesToHexString, filled) From 93667f35f0c3a4529d53bf99433e0a08f77407c9 Mon Sep 17 00:00:00 2001 From: sascha Date: Thu, 5 Feb 2026 21:44:26 +0100 Subject: [PATCH 18/41] Improve access 2 interpreter context and add data owning ctor --- .../detail/common/include/Context.h | 192 ++++++++++-------- .../detail/common/source/Context.cpp | 31 ++- .../detail/uic918/include/RecordHeader.h | 2 +- .../uic918/source/Uic918Interpreter.cpp | 2 +- .../detail/vdv/source/BotanMessageDecoder.cpp | 18 +- .../source/InterpreterContextTest.cpp | 26 +-- 6 files changed, 148 insertions(+), 123 deletions(-) diff --git a/source/lib/interpreter/detail/common/include/Context.h b/source/lib/interpreter/detail/common/include/Context.h index b96c0c94..9049bd6e 100644 --- a/source/lib/interpreter/detail/common/include/Context.h +++ b/source/lib/interpreter/detail/common/include/Context.h @@ -16,128 +16,140 @@ namespace interpreter::detail::common { - struct Context - { - using IteratorType = std::span::iterator; + class Context + { + using IteratorType = std::span::iterator; + + std::optional> raw; + std::span data; + IteratorType begin; + IteratorType position; + IteratorType end; + std::map output; + std::map records; + + public: + /* Does NOT take ownership of input data + */ + Context(std::vector const &input, std::string origin); + /* Does NOT take ownership of input data + */ + Context(std::vector const &input, Context &&otherContext); + /* Does NOT take ownership of input data + */ + Context(std::span input); + /* Takes ownership of data + */ + Context(std::vector &&input); - std::span data; - IteratorType begin; - IteratorType position; - IteratorType end; - std::map output; - std::map records; + Context(Context const &) = delete; + Context &operator=(Context const &) = delete; - Context(std::vector const &input, std::string origin); - Context(std::vector const &input, std::map &&f); - Context(std::span input); + Context(Context &&) = default; + Context &operator=(Context &&) = default; - Context(Context const &) = delete; - Context &operator=(Context const &) = delete; + /* Returns a copy of iterator to the current position. + Attention! The copy gets not updated when the internal position moves on. + */ + IteratorType getPosition() const; - Context(Context &&) = default; - Context &operator=(Context &&) = default; + /* Returns size bytes in a vector from current position + to current position + size without consumtion. + Throws runtime_error if size exceeds remaining bytes. + */ + std::span peekBytes(std::size_t size); - /* Returns a copy of iterator to the current position. - Attention! The copy gets not updated when the internal position moves on. - */ - IteratorType getPosition() const; + /* Returns size bytes in a span from current position + offset + to current position + offset + size without consumtion. + Throws runtime_error if offset + size exceeds remaining bytes. + */ + std::span peekBytes(std::size_t offset, std::size_t size); - /* Returns size bytes in a vector from current position - to current position + size without consumtion. - Throws runtime_error if size exceeds remaining bytes. - */ - std::span peekBytes(std::size_t size); + /* Returns and consumes size bytes from current position + to current position + size. + Throws runtime_error if size exceeds remaining bytes. + */ + std::span consumeBytes(std::size_t size); - /* Returns size bytes in a span from current position + offset - to current position + offset + size without consumtion. - Throws runtime_error if offset + size exceeds remaining bytes. - */ - std::span peekBytes(std::size_t offset, std::size_t size); + /* Returns and consumes size bytes from end of all bytes. + Throws runtime_error if size exceeds remaining bytes. + */ + std::span consumeBytesEnd(std::size_t size); - /* Returns and consumes size bytes from current position - to current position + size. - Throws runtime_error if size exceeds remaining bytes. - */ - std::span consumeBytes(std::size_t size); + /* Returns and consumes as a maximum size bytes from current position + to current position + 0...size. + */ + std::span consumeMaximalBytes(std::size_t size); - /* Returns and consumes size bytes from end of all bytes. - Throws runtime_error if size exceeds remaining bytes. - */ - std::span consumeBytesEnd(std::size_t size); + /* Returns and consumes all remaining bytes from current + postion to end. + */ + std::span consumeRemainingBytes(); - /* Returns and consumes as a maximum size bytes from current position - to current position + 0...size. - */ - std::span consumeMaximalBytes(std::size_t size); + /* Consumes and copies all remaining bytes from current + postion to end. + */ + std::vector consumeRemainingBytesCopy(); - /* Returns and consumes all remaining bytes from current - postion to end. - */ - std::span consumeRemainingBytes(); + /* Consumes all remaining bytes from current postion to end, + appends given postfix string to the end and returns a copy. + */ + std::vector consumeRemainingBytesAppend(std::span postfix); - /* Consumes and copies all remaining bytes from current - postion to end. - */ - std::vector consumeRemainingBytesCopy(); + /* Ignores and skips size bytes from current position + to current position + size. + */ + std::size_t ignoreBytes(std::size_t size); - /* Consumes all remaining bytes from current postion to end, - appends given postfix string to the end and returns a copy. - */ - std::vector consumeRemainingBytesAppend(std::span postfix); + /* Ignores and skips all remaining bytes from current + position to end. + */ + std::size_t ignoreRemainingBytes(); - /* Ignores and skips size bytes from current position - to current position + size. - */ - std::size_t ignoreBytes(std::size_t size); + /* Returns all bytes from begin to end as base64 + encoded string. This does NOT consume bytes, it just returns the + entire buffer as base64 encoded string. + */ + std::string getAllBase64Encoded() const; - /* Ignores and skips all remaining bytes from current - position to end. - */ - std::size_t ignoreRemainingBytes(); + bool hasInput() const; - /* Returns all bytes from begin to end as base64 - encoded string. - */ - std::string getAllBase64Encoded() const; + bool hasOutput() const; - bool hasInput() const; + bool isEmpty() const; - bool hasOutput() const; + std::size_t getOverallSize() const; - bool isEmpty() const; + std::size_t getRemainingSize() const; - std::size_t getOverallSize() const; + std::size_t getConsumedSize() const; - std::size_t getRemainingSize() const; + // Fields - std::size_t getConsumedSize() const; + std::map const &getFields() const; - // Fields + std::optional getField(std::string key) const; - std::map const &getFields() const; + Context &ifField(std::string key, std::function consumer); - std::optional getField(std::string key) const; + Context &setField(std::string key, Field &&field); - Context &ifField(std::string key, std::function consumer); + Context &addField(std::string key, std::string value); - Context &setField(std::string key, Field &&field); + Context &addField(std::string key, std::string value, std::string description); - Context &addField(std::string key, std::string value); + Context &addField(std::string key, std::string value, std::optional description); - Context &addField(std::string key, std::string value, std::string description); + // Json - Context &addField(std::string key, std::string value, std::optional description); + std::optional getJson(int indent = -1); - // Json + // Records - std::optional getJson(int indent = -1); + Context &addRecord(Record &&record); - // Records + Record const &getRecord(std::string recordKey) const; - Context &addRecord(Record &&record); - - Record const &getRecord(std::string recordKey) const; - - std::map const &getRecords() const; - }; + std::map const &getRecords() const; + }; } diff --git a/source/lib/interpreter/detail/common/source/Context.cpp b/source/lib/interpreter/detail/common/source/Context.cpp index 9aa063a2..14965def 100644 --- a/source/lib/interpreter/detail/common/source/Context.cpp +++ b/source/lib/interpreter/detail/common/source/Context.cpp @@ -12,30 +12,47 @@ namespace interpreter::detail::common { Context::Context(std::vector const &input, std::string origin) - : data(input.data(), input.size()), + : raw(std::nullopt), + data(input.data(), input.size()), begin(std::begin(data)), position(begin), end(std::end(data)), - output() + output(), + records() { addField("origin", origin); } - Context::Context(std::vector const &input, std::map &&fields) - : data(input.data(), input.size()), + Context::Context(std::vector const &input, Context &&otherContext) + : raw(std::nullopt), + data(input.data(), input.size()), begin(std::begin(data)), position(begin), end(std::end(data)), - output(std::move(fields)) + output(std::move(otherContext.output)), + records(std::move(otherContext.records)) { } Context::Context(std::span input) - : data(std::move(input)), + : raw(std::nullopt), + data(std::move(input)), begin(std::begin(data)), position(begin), end(std::end(data)), - output() + output(), + records() + { + } + + Context::Context(std::vector &&input) + : raw(std::make_optional(std::move(input))), + data(std::begin(*raw), std::end(*raw)), + begin(std::begin(data)), + position(begin), + end(std::end(data)), + output(), + records() { } diff --git a/source/lib/interpreter/detail/uic918/include/RecordHeader.h b/source/lib/interpreter/detail/uic918/include/RecordHeader.h index ded6624a..a9329f82 100644 --- a/source/lib/interpreter/detail/uic918/include/RecordHeader.h +++ b/source/lib/interpreter/detail/uic918/include/RecordHeader.h @@ -13,7 +13,7 @@ namespace interpreter::detail::uic { struct RecordHeader { - common::Context::IteratorType const start; + std::span::iterator const start; std::string const recordId; std::string const recordVersion; unsigned int const recordLength; diff --git a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp index 8cb2afa6..897a269b 100644 --- a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp +++ b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp @@ -112,7 +112,7 @@ namespace interpreter::detail::uic auto const uncompressedMessage = deflate(compressedMessage); context.addField("uncompressedMessageLength", std::to_string(uncompressedMessage.size())); - auto messageContext = common::Context(uncompressedMessage, std::move(context.output)); + auto messageContext = common::Context(uncompressedMessage, std::move(context)); while (!messageContext.isEmpty()) { LOG_DEBUG(logger) << "Overall remaining bytes: " << messageContext.getRemainingSize(); diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 843b3059..1918ed46 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -23,12 +23,11 @@ namespace interpreter::detail::vdv public: std::vector decryptVerify(Signature const &signature, PublicKey const &publicKey) { - auto const decryptedContent = Botan::power_mod(Botan::BigInt(signature.value), - Botan::BigInt(publicKey.exponent), - Botan::BigInt(publicKey.modulus)) - .serialize(); + auto contentContext = common::Context(Botan::power_mod(Botan::BigInt(signature.value), + Botan::BigInt(publicKey.exponent), + Botan::BigInt(publicKey.modulus)) + .serialize()); - auto contentContext = common::Context(decryptedContent); consumeExpectedFrameTags(contentContext, {0x6A}, {0xBC}); auto const expectedHash = contentContext.consumeBytesEnd(20); auto content = contentContext.consumeRemainingBytesAppend(signature.remainder); @@ -69,26 +68,23 @@ namespace interpreter::detail::vdv auto const rootCertIdentity = CertificateIdentity::consumeFrom(rootContext, 9); auto const rootCertPublicKey = PublicKey::consumeFrom(rootContext); ensureEmpty(rootContext); - auto const issuingContent = internal->decryptVerify(issuingCertificate->signature, rootCertPublicKey); LOG_INFO(logger) << "Using root certificate " << rootCertIdentity.toString(); - auto issuingContext = common::Context(issuingContent); + auto issuingContext = common::Context(internal->decryptVerify(issuingCertificate->signature, rootCertPublicKey)); auto const issuingCertIdentity = CertificateIdentity::consumeFrom(issuingContext, 7); auto const issuingCertPublicKey = PublicKey::consumeFrom(issuingContext); ensureEmpty(issuingContext); - auto const envelopeContent = internal->decryptVerify(envelopeCertificate.signature, issuingCertPublicKey); LOG_INFO(logger) << "Using issuing certificate " << issuingCertIdentity.toString(); - auto envelopeContext = common::Context(envelopeContent); + auto envelopeContext = common::Context(internal->decryptVerify(envelopeCertificate.signature, issuingCertPublicKey)); auto const envelopeCertIdentity = CertificateIdentity::consumeFrom(envelopeContext, 7); auto const envelopeCertPublicKey = PublicKey::consumeFrom(envelopeContext); ensureEmpty(envelopeContext); - auto messageContent = internal->decryptVerify(envelopeSignature, envelopeCertPublicKey); LOG_INFO(logger) << "Using envelope certificate " << envelopeCertIdentity.toString(); - return std::make_optional(std::move(messageContent)); + return std::make_optional(internal->decryptVerify(envelopeSignature, envelopeCertPublicKey)); } } diff --git a/source/test/interpreter/source/InterpreterContextTest.cpp b/source/test/interpreter/source/InterpreterContextTest.cpp index aa239693..6bb2f4fa 100644 --- a/source/test/interpreter/source/InterpreterContextTest.cpp +++ b/source/test/interpreter/source/InterpreterContextTest.cpp @@ -20,11 +20,11 @@ namespace interpreter::detail::common auto context = Context(data, "origin"); EXPECT_EQ((std::vector{0x1, 0x2, 0x3}), toVector(context.peekBytes(3))); EXPECT_EQ((std::vector{0x1, 0x2, 0x3}), toVector(context.peekBytes(3))); - EXPECT_EQ(context.position, context.begin); - context.position += 1; + EXPECT_EQ(0, context.getConsumedSize()); + context.consumeBytes(1); EXPECT_EQ((std::vector{0x2, 0x3, 0x4}), toVector(context.peekBytes(3))); EXPECT_EQ((std::vector{0x2, 0x3, 0x4, 0x5}), toVector(context.peekBytes(4))); - EXPECT_EQ(context.position, context.begin + 1); + EXPECT_EQ(1, context.getConsumedSize()); } TEST(InterpreterContext, peekExceedingBytes) @@ -32,7 +32,7 @@ namespace interpreter::detail::common auto context = Context(data, "origin"); EXPECT_EQ(data, toVector(context.peekBytes(5))); EXPECT_THROW(context.peekBytes(6), std::runtime_error); - context.position += 1; + context.consumeBytes(1); EXPECT_EQ((std::vector{0x2, 0x3, 0x4, 0x5}), toVector(context.peekBytes(4))); EXPECT_THROW(context.peekBytes(5), std::runtime_error); } @@ -42,11 +42,11 @@ namespace interpreter::detail::common auto context = Context(data, "origin"); EXPECT_EQ((std::vector{0x1, 0x2, 0x3}), toVector(context.peekBytes(0, 3))); EXPECT_EQ((std::vector{0x2, 0x3, 0x4}), toVector(context.peekBytes(1, 3))); - EXPECT_EQ(context.position, context.begin); - context.position += 1; + EXPECT_EQ(0, context.getConsumedSize()); + context.consumeBytes(1); EXPECT_EQ((std::vector{0x4, 0x5}), toVector(context.peekBytes(2, 2))); EXPECT_THROW(toVector(context.peekBytes(2, 3)), std::runtime_error); - EXPECT_EQ(context.position, context.begin + 1); + EXPECT_EQ(1, context.getConsumedSize()); } TEST(InterpreterContext, consumeBytes) @@ -55,10 +55,10 @@ namespace interpreter::detail::common EXPECT_EQ((std::vector{}), toVector(context.consumeBytes(0))); EXPECT_EQ((std::vector{0x1}), toVector(context.consumeBytes(1))); EXPECT_EQ((std::vector{0x2, 0x3}), toVector(context.consumeBytes(2))); - EXPECT_EQ(context.position, context.begin + 3); - context.position += 1; + EXPECT_EQ(3, context.getConsumedSize()); + context.consumeBytes(1); EXPECT_EQ((std::vector{0x5}), toVector(context.consumeBytes(1))); - EXPECT_EQ(context.position, context.begin + 5); + EXPECT_EQ(5, context.getConsumedSize()); } TEST(InterpreterContext, consumeExceedingBytes) @@ -86,10 +86,10 @@ namespace interpreter::detail::common EXPECT_EQ(0, context.ignoreBytes(0)); EXPECT_EQ(1, context.ignoreBytes(1)); EXPECT_EQ(2, context.ignoreBytes(2)); - EXPECT_EQ(context.position, context.begin + 3); - context.position += 1; + EXPECT_EQ(3, context.getConsumedSize()); + context.consumeBytes(1); EXPECT_EQ(1, context.ignoreBytes(1)); - EXPECT_EQ(context.position, context.begin + 5); + EXPECT_EQ(5, context.getConsumedSize()); } TEST(InterpreterContext, ignoreExceedingBytes) From 24776143cfd218bbe03310c96c896100852143f8 Mon Sep 17 00:00:00 2001 From: sascha Date: Thu, 5 Feb 2026 23:24:06 +0100 Subject: [PATCH 19/41] Make decryption chain even more compact --- .../detail/vdv/include/BotanMessageDecoder.h | 2 +- .../detail/vdv/include/MessageDecoder.h | 4 +- .../detail/vdv/source/BotanMessageDecoder.cpp | 55 +++++++++++----- .../detail/vdv/source/Certificate.cpp | 66 +++++++++---------- .../detail/vdv/source/VDVInterpreter.cpp | 9 ++- 5 files changed, 79 insertions(+), 57 deletions(-) diff --git a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h index d8c554b8..d746a980 100644 --- a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h @@ -22,7 +22,7 @@ namespace interpreter::detail::vdv public: BotanMessageDecoder(infrastructure::LoggerFactory &loggerFactory, CertificateProvider &certificateProvider); - virtual std::optional> decodeMessage( + virtual std::optional decodeMessage( Certificate const &envelopeCertificate, Signature const &envelopeSignature) override; }; diff --git a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h index aa75f433..8a789def 100644 --- a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h @@ -5,6 +5,8 @@ #include "Certificate.h" +#include "lib/interpreter/detail/common/include/Context.h" + #include #include #include @@ -21,7 +23,7 @@ namespace interpreter::detail::vdv /* Takes certificate from envelope and signature and decodes the message by using root + issuing certificate. */ - virtual std::optional> decodeMessage( + virtual std::optional decodeMessage( Certificate const &envelopeCertificate, Signature const &envelopeSignature) = 0; }; diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 1918ed46..186893eb 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -16,6 +16,31 @@ namespace interpreter::detail::vdv { + struct DecodedCertificate + { + std::optional> rawData; + CertificateIdentity identity; + PublicKey publicKey; + + static DecodedCertificate decodeRootFrom(std::span content) + { + auto context = common::Context(content); + auto identity = CertificateIdentity::consumeFrom(context, 9); + auto publicKey = PublicKey::consumeFrom(context); + ensureEmpty(context); + return DecodedCertificate{std::nullopt, std::move(identity), std::move(publicKey)}; + } + + static DecodedCertificate decodeFrom(std::vector &&content) + { + auto context = common::Context(content); + auto identity = CertificateIdentity::consumeFrom(context, 7); // TODO OID length is probably not always 7 for all sub-certificates + auto publicKey = PublicKey::consumeFrom(context); + ensureEmpty(context); + return DecodedCertificate{std::make_optional(std::move(content)), std::move(identity), std::move(publicKey)}; + } + }; + class BotanMessageDecoder::Internal { std::unique_ptr const sha1HashFunction = Botan::HashFunction::create_or_throw("SHA-1"); @@ -49,42 +74,38 @@ namespace interpreter::detail::vdv { } - std::optional> BotanMessageDecoder::decodeMessage( + std::optional BotanMessageDecoder::decodeMessage( Certificate const &envelopeCertificate, Signature const &envelopeSignature) { auto const rootCertificate = certificateProvider.getRoot(); if (!rootCertificate) { + LOG_INFO(logger) << "Root certificate not found"; return std::nullopt; } auto const issuingCertificate = certificateProvider.get(envelopeCertificate.authority); if (!issuingCertificate) { + LOG_INFO(logger) << "Issuing certificate not found: " << envelopeCertificate.authority; return std::nullopt; } - auto rootContext = common::Context(rootCertificate->content); - auto const rootCertIdentity = CertificateIdentity::consumeFrom(rootContext, 9); - auto const rootCertPublicKey = PublicKey::consumeFrom(rootContext); - ensureEmpty(rootContext); + auto const decodedRootCertificate = DecodedCertificate::decodeRootFrom(rootCertificate->content); - LOG_INFO(logger) << "Using root certificate " << rootCertIdentity.toString(); + LOG_INFO(logger) << "Using root certificate " << decodedRootCertificate.identity.toString(); - auto issuingContext = common::Context(internal->decryptVerify(issuingCertificate->signature, rootCertPublicKey)); - auto const issuingCertIdentity = CertificateIdentity::consumeFrom(issuingContext, 7); - auto const issuingCertPublicKey = PublicKey::consumeFrom(issuingContext); - ensureEmpty(issuingContext); + auto const decodedIssuingCertificate = DecodedCertificate::decodeFrom( + internal->decryptVerify(issuingCertificate->signature, decodedRootCertificate.publicKey)); - LOG_INFO(logger) << "Using issuing certificate " << issuingCertIdentity.toString(); + LOG_INFO(logger) << "Using issuing certificate " << decodedIssuingCertificate.identity.toString(); - auto envelopeContext = common::Context(internal->decryptVerify(envelopeCertificate.signature, issuingCertPublicKey)); - auto const envelopeCertIdentity = CertificateIdentity::consumeFrom(envelopeContext, 7); - auto const envelopeCertPublicKey = PublicKey::consumeFrom(envelopeContext); - ensureEmpty(envelopeContext); + auto const decodedEnvelopeCertificate = DecodedCertificate::decodeFrom( + internal->decryptVerify(envelopeCertificate.signature, decodedIssuingCertificate.publicKey)); - LOG_INFO(logger) << "Using envelope certificate " << envelopeCertIdentity.toString(); + LOG_INFO(logger) << "Using envelope certificate " << decodedEnvelopeCertificate.identity.toString(); - return std::make_optional(internal->decryptVerify(envelopeSignature, envelopeCertPublicKey)); + return std::make_optional(common::Context( + internal->decryptVerify(envelopeSignature, decodedEnvelopeCertificate.publicKey))); } } diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp index 9d2e771b..851fb680 100644 --- a/source/lib/interpreter/detail/vdv/source/Certificate.cpp +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -16,35 +16,35 @@ namespace interpreter::detail::vdv Signature Signature::consumeFrom(common::Context &context) { - auto const value = consumeExpectedTagValue(context, {0x5f, 0x37}); - auto const remainder = consumeExpectedTagValue(context, {0x5f, 0x38}); - return Signature{value, remainder}; + auto value = consumeExpectedTagValue(context, {0x5f, 0x37}); + auto remainder = consumeExpectedTagValue(context, {0x5f, 0x38}); + return Signature{std::move(value), std::move(remainder)}; } Signature Signature::consumeFromEnvelope(common::Context &context) { - auto const value = consumeExpectedTagValue(context, {0x9e, 0x00}); - auto const remainder = consumeExpectedTagValue(context, {0x9a, 0x00}); - return Signature{value, remainder}; + auto value = consumeExpectedTagValue(context, {0x9e, 0x00}); + auto remainder = consumeExpectedTagValue(context, {0x9a, 0x00}); + return Signature{std::move(value), std::move(remainder)}; } PublicKey PublicKey::consumeFrom(common::Context &context) { // 65537 = 2^16+1 is the fifth Fermat, used commonly as exponent in RSA algorithms - auto const exponent = context.consumeBytesEnd(4); // TODO Length unsure, always 4 bytes for exponent? - auto const modulus = context.consumeRemainingBytes(); - return PublicKey{exponent, modulus}; + auto exponent = context.consumeBytesEnd(4); // TODO Length unsure, always 4 bytes for exponent? + auto modulus = context.consumeRemainingBytes(); + return PublicKey{std::move(exponent), std::move(modulus)}; } Certificate Certificate::consumeFromEnvelope(common::Context &context) { auto const signatureData = consumeExpectedTagValue(context, {0x7f, 0x21}); - auto const authority = common::bytesToHexString(consumeExpectedTagValue(context, {0x42, 0x00})); + auto authority = common::bytesToHexString(consumeExpectedTagValue(context, {0x42, 0x00})); auto signatureContext = common::Context(signatureData); auto signature = Signature::consumeFrom(signatureContext); ensureEmpty(signatureContext); - return Certificate{authority, "envelope", signature, {}}; + return Certificate{std::move(authority), "envelope", std::move(signature), {}}; } CertificateOID CertificateOID::consumeFrom(common::Context &inputContext, std::size_t length) @@ -90,12 +90,12 @@ namespace interpreter::detail::vdv CertificateParticipant CertificateParticipant::consumeFrom(common::Context &context) { - auto const region = common::bytesToString(context.consumeBytes(2)); - auto const name = common::bytesToString(context.consumeBytes(3)); - auto const serviceIdenticator = common::consumeInteger1(context); - auto const algorithmReference = common::consumeInteger1(context); - auto const year = std::to_string(1990 + common::consumeInteger1(context)); - return CertificateParticipant{region, name, serviceIdenticator, algorithmReference, year}; + auto region = common::bytesToString(context.consumeBytes(2)); + auto name = common::bytesToString(context.consumeBytes(3)); + auto serviceIdenticator = common::consumeInteger1(context); + auto algorithmReference = common::consumeInteger1(context); + auto year = std::to_string(1990 + common::consumeInteger1(context)); + return CertificateParticipant{std::move(region), std::move(name), serviceIdenticator, algorithmReference, std::move(year)}; } std::string CertificateParticipant::toString() const @@ -122,12 +122,12 @@ namespace interpreter::detail::vdv CertificateReference CertificateReference::consumeFrom(common::Context &context) { - auto const orgId = std::to_string(common::consumeInteger2(context)); - auto const samValidUntil = CertificateDate::consumeFrom2(context); - auto const samValidFrom = CertificateDate::consumeFrom3(context); - auto const ownerOrgId = std::to_string(common::consumeInteger2(context)); - auto const samId = std::to_string(common::consumeInteger3(context)); - return CertificateReference{orgId, samValidUntil, samValidFrom, ownerOrgId, samId}; + auto orgId = std::to_string(common::consumeInteger2(context)); + auto samValidUntil = CertificateDate::consumeFrom2(context); + auto samValidFrom = CertificateDate::consumeFrom3(context); + auto ownerOrgId = std::to_string(common::consumeInteger2(context)); + auto samId = std::to_string(common::consumeInteger3(context)); + return CertificateReference{std::move(orgId), std::move(samValidUntil), std::move(samValidFrom), std::move(ownerOrgId), std::move(samId)}; } std::string CertificateDate::toString() const @@ -172,9 +172,9 @@ namespace interpreter::detail::vdv CertificateAuthorization CertificateAuthorization::consumeFrom(common::Context &context) { - auto const name = common::consumeString(context, 6); + auto name = common::consumeString(context, 6); auto const serviceIndicator = common::consumeInteger1(context); - return CertificateAuthorization{name, serviceIndicator}; + return CertificateAuthorization{std::move(name), serviceIndicator}; } std::string CertificateProfile::toString() const @@ -184,8 +184,8 @@ namespace interpreter::detail::vdv CertificateProfile CertificateProfile::consumeFrom(common::Context &context) { - auto const identifier = std::to_string(common::consumeInteger1(context)); - return CertificateProfile{identifier}; + auto identifier = std::to_string(common::consumeInteger1(context)); + return CertificateProfile{std::move(identifier)}; } std::string CertificateIdentity::toString() const @@ -209,8 +209,8 @@ namespace interpreter::detail::vdv CertificateIdentity CertificateIdentity::consumeFrom(common::Context &context, std::size_t oidLength) { - auto const profile = CertificateProfile::consumeFrom(context); - auto const authority = CertificateParticipant::consumeFrom(context); + auto profile = CertificateProfile::consumeFrom(context); + auto authority = CertificateParticipant::consumeFrom(context); auto holder = std::optional{}; auto reference = std::optional{}; if (peekExpected(context, {0, 0, 0, 0})) @@ -222,9 +222,9 @@ namespace interpreter::detail::vdv { reference = std::make_optional(CertificateReference::consumeFrom(context)); } - auto const authorization = CertificateAuthorization::consumeFrom(context); - auto const expiryDate = CertificateDate::consumeFrom4(context); - auto const algorithm = CertificateOID::consumeFrom(context, oidLength); - return CertificateIdentity{profile, authority, holder, reference, authorization, expiryDate, algorithm}; + auto authorization = CertificateAuthorization::consumeFrom(context); + auto expiryDate = CertificateDate::consumeFrom4(context); + auto algorithm = CertificateOID::consumeFrom(context, oidLength); + return CertificateIdentity{std::move(profile), std::move(authority), std::move(holder), std::move(reference), std::move(authorization), std::move(expiryDate), std::move(algorithm)}; } } diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 88381264..995f8ab8 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -56,12 +56,11 @@ namespace interpreter::detail::vdv .add("signatureVersion", signatureVersion) .add("certificateAuthority", certificate.authority); - auto const message = messageDecoder->decodeMessage(certificate, signature); - if (message) + auto messageContext = messageDecoder->decodeMessage(certificate, signature); + if (messageContext) { - auto messageContext = common::Context(*message); - auto const ticketId = common::consumeInteger4(messageContext); - jsonBuilder.add("ticketId", ticketId); + auto const ticketId = common::consumeInteger4(*messageContext); + jsonBuilder.add("ticketId", std::to_string(ticketId)); } context.addRecord(common::Record(signatureIdent, signatureVersion, std::move(jsonBuilder))); From 928a3b8f0196d8f2120f1c49adabd31195be4cd9 Mon Sep 17 00:00:00 2001 From: sascha Date: Fri, 6 Feb 2026 22:44:21 +0100 Subject: [PATCH 20/41] Cache decoded certificates properly --- .../detail/vdv/include/BotanMessageDecoder.h | 10 +- .../detail/vdv/include/Certificate.h | 11 ++ .../detail/vdv/include/CertificateProvider.h | 2 - .../vdv/include/LDIFFileCertificateProvider.h | 2 - .../detail/vdv/include/MessageDecoder.h | 2 +- .../detail/vdv/source/BotanMessageDecoder.cpp | 115 ++++++++++-------- .../detail/vdv/source/Certificate.cpp | 18 +++ .../source/LDIFFileCertificateProvider.cpp | 5 - .../detail/vdv/source/VDVInterpreter.cpp | 1 + 9 files changed, 102 insertions(+), 64 deletions(-) diff --git a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h index d746a980..2354d1fd 100644 --- a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h @@ -4,20 +4,26 @@ #pragma once #include "MessageDecoder.h" +#include "Certificate.h" #include "CertificateProvider.h" #include "lib/infrastructure/include/LoggingFwd.h" #include +#include +#include namespace interpreter::detail::vdv { class BotanMessageDecoder : public MessageDecoder { - infrastructure::Logger logger; - CertificateProvider &certificateProvider; class Internal; + + infrastructure::Logger logger; std::shared_ptr internal; + std::map> issuingCertificates; + + std::optional getIssuingCertificate(std::string authority); public: BotanMessageDecoder(infrastructure::LoggerFactory &loggerFactory, CertificateProvider &certificateProvider); diff --git a/source/lib/interpreter/detail/vdv/include/Certificate.h b/source/lib/interpreter/detail/vdv/include/Certificate.h index 75d951cb..842b7dcd 100644 --- a/source/lib/interpreter/detail/vdv/include/Certificate.h +++ b/source/lib/interpreter/detail/vdv/include/Certificate.h @@ -139,4 +139,15 @@ namespace interpreter::detail::vdv static CertificateIdentity consumeFrom(common::Context &context, std::size_t oidLength); }; + + struct DecodedCertificate + { + std::optional> rawData; + CertificateIdentity identity; + PublicKey publicKey; + + static DecodedCertificate decodeRootFrom(std::span content); + + static DecodedCertificate decodeFrom(std::vector &&content); + }; } diff --git a/source/lib/interpreter/detail/vdv/include/CertificateProvider.h b/source/lib/interpreter/detail/vdv/include/CertificateProvider.h index a9eaf9b2..2d225f59 100644 --- a/source/lib/interpreter/detail/vdv/include/CertificateProvider.h +++ b/source/lib/interpreter/detail/vdv/include/CertificateProvider.h @@ -18,8 +18,6 @@ namespace interpreter::detail::vdv virtual std::vector getAuthorities() = 0; - virtual std::optional getRoot() = 0; - virtual std::optional get(std::string authority) = 0; }; } diff --git a/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h index f6d60dee..fdb927a0 100644 --- a/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h +++ b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h @@ -36,8 +36,6 @@ namespace interpreter::detail::vdv virtual std::vector getAuthorities() override; - virtual std::optional getRoot() override; - /* The value in 'authority' should match exactly one very specific entry in the list of exported certificates from public LDAP server identified by 'cn=,ou=VDV KA,o=VDV Kernapplikations GmbH,c=de' diff --git a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h index 8a789def..7ca9f83f 100644 --- a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h @@ -21,7 +21,7 @@ namespace interpreter::detail::vdv virtual ~MessageDecoder() = default; /* Takes certificate from envelope and signature and decodes the - message by using root + issuing certificate. + message by using root + issuing certificate internally. */ virtual std::optional decodeMessage( Certificate const &envelopeCertificate, diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 186893eb..f22a5e1c 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -16,47 +16,52 @@ namespace interpreter::detail::vdv { - struct DecodedCertificate + class BotanMessageDecoder::Internal { - std::optional> rawData; - CertificateIdentity identity; - PublicKey publicKey; + std::unique_ptr const sha1HashFunction = Botan::HashFunction::create_or_throw("SHA-1"); - static DecodedCertificate decodeRootFrom(std::span content) + CertificateProvider &certificateProvider; + std::optional rootCertificate; + + public: + Internal(CertificateProvider &cp) + : certificateProvider(cp) { - auto context = common::Context(content); - auto identity = CertificateIdentity::consumeFrom(context, 9); - auto publicKey = PublicKey::consumeFrom(context); - ensureEmpty(context); - return DecodedCertificate{std::nullopt, std::move(identity), std::move(publicKey)}; + auto const certificate = certificateProvider.get("4555564456100106"); // EUVDV, 16, 01, 1996 - self signed root certificate + rootCertificate = certificate + ? std::make_optional(DecodedCertificate::decodeRootFrom(certificate->content)) + : std::nullopt; } - static DecodedCertificate decodeFrom(std::vector &&content) + bool isEnabled() const { - auto context = common::Context(content); - auto identity = CertificateIdentity::consumeFrom(context, 7); // TODO OID length is probably not always 7 for all sub-certificates - auto publicKey = PublicKey::consumeFrom(context); - ensureEmpty(context); - return DecodedCertificate{std::make_optional(std::move(content)), std::move(identity), std::move(publicKey)}; + return rootCertificate.has_value(); } - }; - class BotanMessageDecoder::Internal - { - std::unique_ptr const sha1HashFunction = Botan::HashFunction::create_or_throw("SHA-1"); + std::optional decryptIssuingCertificate(std::string authority) + { + if (!rootCertificate) + { + return std::nullopt; + } + + auto const certificate = certificateProvider.get(authority); + return certificate + ? std::make_optional(DecodedCertificate::decodeFrom(decryptVerify(certificate->signature, rootCertificate->publicKey))) + : std::nullopt; + } - public: std::vector decryptVerify(Signature const &signature, PublicKey const &publicKey) { - auto contentContext = common::Context(Botan::power_mod(Botan::BigInt(signature.value), - Botan::BigInt(publicKey.exponent), - Botan::BigInt(publicKey.modulus)) - .serialize()); - - consumeExpectedFrameTags(contentContext, {0x6A}, {0xBC}); - auto const expectedHash = contentContext.consumeBytesEnd(20); - auto content = contentContext.consumeRemainingBytesAppend(signature.remainder); - ensureEmpty(contentContext); + auto context = common::Context(Botan::power_mod(Botan::BigInt(signature.value), + Botan::BigInt(publicKey.exponent), + Botan::BigInt(publicKey.modulus)) + .serialize()); + + consumeExpectedFrameTags(context, {0x6A}, {0xBC}); + auto const expectedHash = context.consumeBytesEnd(20); + auto content = context.consumeRemainingBytesAppend(signature.remainder); + ensureEmpty(context); auto const actualHash = sha1HashFunction->process(content); if (!std::equal(actualHash.begin(), actualHash.end(), expectedHash.begin(), expectedHash.end())) @@ -69,43 +74,49 @@ namespace interpreter::detail::vdv BotanMessageDecoder::BotanMessageDecoder(infrastructure::LoggerFactory &lf, CertificateProvider &cp) : logger(CREATE_LOGGER(lf)), - certificateProvider(cp), - internal(std::make_shared()) + internal(std::make_shared(cp)), + issuingCertificates() { + if (!internal->isEnabled()) + { + LOG_INFO(logger) << "Decryption disabled, certificates not found or unable to read"; + } + } + + std::optional BotanMessageDecoder::getIssuingCertificate(std::string authority) + { + auto cacheEntry = issuingCertificates.find(authority); + if (cacheEntry != issuingCertificates.end()) + { + return cacheEntry->second; + } + + auto certificate = internal->decryptIssuingCertificate(authority); + return issuingCertificates.emplace(std::make_pair(authority, std::move(certificate))).first->second; } std::optional BotanMessageDecoder::decodeMessage( - Certificate const &envelopeCertificate, - Signature const &envelopeSignature) + Certificate const &certificate, + Signature const &signature) { - auto const rootCertificate = certificateProvider.getRoot(); - if (!rootCertificate) + if (!internal->isEnabled()) { - LOG_INFO(logger) << "Root certificate not found"; return std::nullopt; } - auto const issuingCertificate = certificateProvider.get(envelopeCertificate.authority); + + auto const issuingCertificate = getIssuingCertificate(certificate.authority); if (!issuingCertificate) { - LOG_INFO(logger) << "Issuing certificate not found: " << envelopeCertificate.authority; + LOG_INFO(logger) << "Decryption faild, issuing certificate not found: " << certificate.authority; return std::nullopt; } - auto const decodedRootCertificate = DecodedCertificate::decodeRootFrom(rootCertificate->content); - - LOG_INFO(logger) << "Using root certificate " << decodedRootCertificate.identity.toString(); - - auto const decodedIssuingCertificate = DecodedCertificate::decodeFrom( - internal->decryptVerify(issuingCertificate->signature, decodedRootCertificate.publicKey)); - - LOG_INFO(logger) << "Using issuing certificate " << decodedIssuingCertificate.identity.toString(); - - auto const decodedEnvelopeCertificate = DecodedCertificate::decodeFrom( - internal->decryptVerify(envelopeCertificate.signature, decodedIssuingCertificate.publicKey)); + auto const envelopeCertificate = DecodedCertificate::decodeFrom( + internal->decryptVerify(certificate.signature, issuingCertificate->publicKey)); - LOG_INFO(logger) << "Using envelope certificate " << decodedEnvelopeCertificate.identity.toString(); + LOG_DEBUG(logger) << "Using envelope certificate " << envelopeCertificate.identity.toString(); return std::make_optional(common::Context( - internal->decryptVerify(envelopeSignature, decodedEnvelopeCertificate.publicKey))); + internal->decryptVerify(signature, envelopeCertificate.publicKey))); } } diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp index 851fb680..faf55601 100644 --- a/source/lib/interpreter/detail/vdv/source/Certificate.cpp +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -227,4 +227,22 @@ namespace interpreter::detail::vdv auto algorithm = CertificateOID::consumeFrom(context, oidLength); return CertificateIdentity{std::move(profile), std::move(authority), std::move(holder), std::move(reference), std::move(authorization), std::move(expiryDate), std::move(algorithm)}; } + + DecodedCertificate DecodedCertificate::decodeRootFrom(std::span content) + { + auto context = common::Context(content); + auto identity = CertificateIdentity::consumeFrom(context, 9); + auto publicKey = PublicKey::consumeFrom(context); + ensureEmpty(context); + return DecodedCertificate{std::nullopt, std::move(identity), std::move(publicKey)}; + } + + DecodedCertificate DecodedCertificate::decodeFrom(std::vector &&content) + { + auto context = common::Context(content); + auto identity = CertificateIdentity::consumeFrom(context, 7); // TODO OID length is probably not always 7 for all sub-certificates + auto publicKey = PublicKey::consumeFrom(context); + ensureEmpty(context); + return DecodedCertificate{std::make_optional(std::move(content)), std::move(identity), std::move(publicKey)}; + } } diff --git a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp index fddc9a5a..038cf654 100644 --- a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp +++ b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp @@ -177,11 +177,6 @@ namespace interpreter::detail::vdv return keys; } - std::optional LDIFFileCertificateProvider::getRoot() - { - return get("4555564456100106"); // EUVDV, 16, 01, 1996 - } - std::optional LDIFFileCertificateProvider::get(std::string authority) { if (!internal->entries) diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 995f8ab8..650f7a2c 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -40,6 +40,7 @@ namespace interpreter::detail::vdv // - https://sourceforge.net/projects/dbuic2vdvbc/ // Some more up-to-date hints: // - https://magicalcodewit.ch/38c3-slides/#/32 + // - https://github.com/TheEnbyperor/zuegli/tree/root/main/vdv // - https://github.com/akorb/deutschlandticket_parser/blob/main/main.py // - https://github.com/RWTH-i5-IDSG/ticketserver/blob/master/barti-check/src/main/java/de/rwth/idsg/barti/check/Decode.java From 54fb76b4d3019222d5f6e7502d4004d80c239f30 Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 7 Feb 2026 22:23:56 +0100 Subject: [PATCH 21/41] Avoid broken ident in case if signature remainder is longer than 5 bytes --- source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 650f7a2c..3d7380f9 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -48,8 +48,9 @@ namespace interpreter::detail::vdv auto const certificate = Certificate::consumeFromEnvelope(context); ensureEmpty(context); - auto const signatureIdent = common::bytesToString(signature.remainder.subspan(0, 3)); - auto const signatureVersion = common::bytesToHexString(signature.remainder.subspan(3, 2)); + auto const remainderEnd = common::Context(signature.remainder).consumeBytesEnd(5); + auto const signatureIdent = common::bytesToString(remainderEnd.subspan(0, 3)); + auto const signatureVersion = common::bytesToHexString(remainderEnd.subspan(3, 2)); auto jsonBuilder = utility::JsonBuilder::object(); jsonBuilder From 708b833d27e101ca89e122966fcb212640897e33 Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 7 Feb 2026 22:50:12 +0100 Subject: [PATCH 22/41] Add some more details from decrypted message content 2 json output --- .../interpreter/detail/vdv/source/BotanMessageDecoder.cpp | 5 +++-- .../lib/interpreter/detail/vdv/source/VDVInterpreter.cpp | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index f22a5e1c..b4d36723 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -25,7 +25,8 @@ namespace interpreter::detail::vdv public: Internal(CertificateProvider &cp) - : certificateProvider(cp) + : certificateProvider(cp), + rootCertificate() { auto const certificate = certificateProvider.get("4555564456100106"); // EUVDV, 16, 01, 1996 - self signed root certificate rootCertificate = certificate @@ -40,7 +41,7 @@ namespace interpreter::detail::vdv std::optional decryptIssuingCertificate(std::string authority) { - if (!rootCertificate) + if (!isEnabled()) { return std::nullopt; } diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 3d7380f9..139a8f32 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -61,8 +61,11 @@ namespace interpreter::detail::vdv auto messageContext = messageDecoder->decodeMessage(certificate, signature); if (messageContext) { - auto const ticketId = common::consumeInteger4(*messageContext); - jsonBuilder.add("ticketId", std::to_string(ticketId)); + jsonBuilder.add("ticketId", std::to_string(common::consumeInteger4(*messageContext))); + jsonBuilder.add("ticketOrganisationId", std::to_string(common::consumeInteger4(*messageContext))); + jsonBuilder.add("productNumber", std::to_string(common::consumeInteger2(*messageContext))); + jsonBuilder.add("validFrom", common::consumeDateTimeCompact4(*messageContext)); + jsonBuilder.add("validTo", common::consumeDateTimeCompact4(*messageContext)); } context.addRecord(common::Record(signatureIdent, signatureVersion, std::move(jsonBuilder))); From 09b658ba964a04efffe1040e3b5826b69965b26a Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 7 Feb 2026 23:15:57 +0100 Subject: [PATCH 23/41] Set validated 2 true in case of decrypted message 4 VDV ticket --- source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 139a8f32..51bb513b 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -68,6 +68,8 @@ namespace interpreter::detail::vdv jsonBuilder.add("validTo", common::consumeDateTimeCompact4(*messageContext)); } + context.addField("validated", messageContext ? "true" : "false"); + context.addRecord(common::Record(signatureIdent, signatureVersion, std::move(jsonBuilder))); return std::move(context); } From 8bf7c3eda2892f5efffc97e99898dfac9e18c546 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 8 Feb 2026 11:05:37 +0100 Subject: [PATCH 24/41] Some makeup and add todo 4 issue/holder certificate verification --- .../common/source/InterpreterUtility.cpp | 1 - .../detail/vdv/include/BotanMessageDecoder.h | 2 +- .../detail/vdv/include/MessageDecoder.h | 4 +--- .../detail/vdv/source/BotanMessageDecoder.cpp | 9 ++++--- .../detail/vdv/source/VDVInterpreter.cpp | 24 ++++++++++--------- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp index 2fef84c8..c1dd7ec2 100644 --- a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp +++ b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp @@ -31,7 +31,6 @@ namespace interpreter::detail::common auto result = T(); auto destination = std::span(reinterpret_cast(&result), sizeof(T)); - // TODO This depends on endianess, test and verify big-endian style conversion if constexpr (std::endian::native == std::endian::big) { std::copy(source.begin(), source.end(), destination.begin()); diff --git a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h index 2354d1fd..7b84f7e9 100644 --- a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h @@ -28,7 +28,7 @@ namespace interpreter::detail::vdv public: BotanMessageDecoder(infrastructure::LoggerFactory &loggerFactory, CertificateProvider &certificateProvider); - virtual std::optional decodeMessage( + virtual std::optional> decodeMessage( Certificate const &envelopeCertificate, Signature const &envelopeSignature) override; }; diff --git a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h index 7ca9f83f..689f04b1 100644 --- a/source/lib/interpreter/detail/vdv/include/MessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/MessageDecoder.h @@ -5,8 +5,6 @@ #include "Certificate.h" -#include "lib/interpreter/detail/common/include/Context.h" - #include #include #include @@ -23,7 +21,7 @@ namespace interpreter::detail::vdv /* Takes certificate from envelope and signature and decodes the message by using root + issuing certificate internally. */ - virtual std::optional decodeMessage( + virtual std::optional> decodeMessage( Certificate const &envelopeCertificate, Signature const &envelopeSignature) = 0; }; diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index b4d36723..6fbb9224 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -96,7 +96,7 @@ namespace interpreter::detail::vdv return issuingCertificates.emplace(std::make_pair(authority, std::move(certificate))).first->second; } - std::optional BotanMessageDecoder::decodeMessage( + std::optional> BotanMessageDecoder::decodeMessage( Certificate const &certificate, Signature const &signature) { @@ -105,6 +105,9 @@ namespace interpreter::detail::vdv return std::nullopt; } + /* TODO Verify issuer and holder identiy for the entire chain of certificates + */ + auto const issuingCertificate = getIssuingCertificate(certificate.authority); if (!issuingCertificate) { @@ -117,7 +120,7 @@ namespace interpreter::detail::vdv LOG_DEBUG(logger) << "Using envelope certificate " << envelopeCertificate.identity.toString(); - return std::make_optional(common::Context( - internal->decryptVerify(signature, envelopeCertificate.publicKey))); + return std::make_optional( + internal->decryptVerify(signature, envelopeCertificate.publicKey)); } } diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 51bb513b..4725b8f6 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -48,9 +48,9 @@ namespace interpreter::detail::vdv auto const certificate = Certificate::consumeFromEnvelope(context); ensureEmpty(context); - auto const remainderEnd = common::Context(signature.remainder).consumeBytesEnd(5); - auto const signatureIdent = common::bytesToString(remainderEnd.subspan(0, 3)); - auto const signatureVersion = common::bytesToHexString(remainderEnd.subspan(3, 2)); + auto const remainderTail = common::Context(signature.remainder).consumeBytesEnd(5); + auto const signatureIdent = common::bytesToString(remainderTail.subspan(0, 3)); + auto const signatureVersion = common::bytesToHexString(remainderTail.subspan(3, 2)); auto jsonBuilder = utility::JsonBuilder::object(); jsonBuilder @@ -58,17 +58,19 @@ namespace interpreter::detail::vdv .add("signatureVersion", signatureVersion) .add("certificateAuthority", certificate.authority); - auto messageContext = messageDecoder->decodeMessage(certificate, signature); - if (messageContext) + auto message = messageDecoder->decodeMessage(certificate, signature); + if (message) { - jsonBuilder.add("ticketId", std::to_string(common::consumeInteger4(*messageContext))); - jsonBuilder.add("ticketOrganisationId", std::to_string(common::consumeInteger4(*messageContext))); - jsonBuilder.add("productNumber", std::to_string(common::consumeInteger2(*messageContext))); - jsonBuilder.add("validFrom", common::consumeDateTimeCompact4(*messageContext)); - jsonBuilder.add("validTo", common::consumeDateTimeCompact4(*messageContext)); + auto messageContext = common::Context(*message); + jsonBuilder + .add("ticketId", std::to_string(common::consumeInteger4(messageContext))) + .add("ticketOrganisationId", std::to_string(common::consumeInteger4(messageContext))) + .add("productNumber", std::to_string(common::consumeInteger2(messageContext))) + .add("validFrom", common::consumeDateTimeCompact4(messageContext)) + .add("validTo", common::consumeDateTimeCompact4(messageContext)); } - context.addField("validated", messageContext ? "true" : "false"); + context.addField("validated", message ? "true" : "false"); context.addRecord(common::Record(signatureIdent, signatureVersion, std::move(jsonBuilder))); return std::move(context); From c75327e2a59ad12788c704fc44e5f45cb65c57d5 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 8 Feb 2026 11:18:32 +0100 Subject: [PATCH 25/41] Move sample ticket install script into image folder and update download url --- README.md | 2 +- {etc => images}/install-sample-tickets.sh | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) rename {etc => images}/install-sample-tickets.sh (85%) diff --git a/README.md b/README.md index a82b9e43..889a1295 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ Optional and minimal user interaction methods to support fast interactive experi * Interoperability UIC/VDV codes, UIC918-3 and UIC918-9 example tickets and mappings for ids used in VDV codes https://www.bahn.de/angebot/regio/barcode * [UIC918-3 Muster](https://assets.static-bahn.de/dam/jcr:c362849f-210d-4dbe-bb18-34141b5ba274/mdb_320951_muster-tickets_nach_uic_918-3_2.zip) - * [UIC918-9 Muster](https://assets.static-bahn.de/dam/jcr:3c7a020a-7632-4f23-8716-6ebfc9f93ccb/Muster%20918-9.zip) + * [UIC918-9 Muster](https://assets.static-bahn.de/dam/jcr:ec74454d-557b-438f-8ed9-689abcc276f5/Muster%20918-9.zip) ``` # You can use the following command to convert PDF file into images for further processing, but you don't have to because application is able to precess pdf files directly. But decoding quality might differ depending on parameters like DPI. # brew|apt install imagemagick diff --git a/etc/install-sample-tickets.sh b/images/install-sample-tickets.sh similarity index 85% rename from etc/install-sample-tickets.sh rename to images/install-sample-tickets.sh index d6258c53..017ab739 100755 --- a/etc/install-sample-tickets.sh +++ b/images/install-sample-tickets.sh @@ -12,7 +12,7 @@ mkdir -p ${WORKSPACE_ROOT}/images/temp/ wget https://assets.static-bahn.de/dam/jcr:c362849f-210d-4dbe-bb18-34141b5ba274/mdb_320951_muster-tickets_nach_uic_918-3_2.zip \ -O ${WORKSPACE_ROOT}/images/temp/uic918-3.zip -wget https://assets.static-bahn.de/dam/jcr:95540b93-5c38-4554-8f00-676214f4ba76/Muster%20918-9.zip \ +wget https://assets.static-bahn.de/dam/jcr:ec74454d-557b-438f-8ed9-689abcc276f5/Muster%20918-9.zip \ -O ${WORKSPACE_ROOT}/images/temp/uic918-9.zip # on macos, default unzip app is not able to handle german character encoding (-I) 4 filenames properly, so we use python @@ -21,6 +21,7 @@ wget https://assets.static-bahn.de/dam/jcr:95540b93-5c38-4554-8f00-676214f4ba76/ python3 -m zipfile -e ${WORKSPACE_ROOT}/images/temp/uic918-3.zip ${WORKSPACE_ROOT}/images/ mv "${WORKSPACE_ROOT}/images/Muster-Tickets nach UIC 918-3" ${WORKSPACE_ROOT}/images/Muster-UIC918-3/ -python3 -m zipfile -e ${WORKSPACE_ROOT}/images/temp/uic918-9.zip ${WORKSPACE_ROOT}/images/Muster-UIC918-9/ +python3 -m zipfile -e ${WORKSPACE_ROOT}/images/temp/uic918-9.zip ${WORKSPACE_ROOT}/images/ +mv "${WORKSPACE_ROOT}/images/Muster 918-9" ${WORKSPACE_ROOT}/images/Muster-UIC918-9/ rm -rf ${WORKSPACE_ROOT}/images/temp From e9cc0199b30613f0174bd032bc9c882ad0519821 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 8 Feb 2026 21:12:08 +0100 Subject: [PATCH 26/41] Fix interpretation of header --- source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 4725b8f6..63c4d076 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -64,8 +64,9 @@ namespace interpreter::detail::vdv auto messageContext = common::Context(*message); jsonBuilder .add("ticketId", std::to_string(common::consumeInteger4(messageContext))) - .add("ticketOrganisationId", std::to_string(common::consumeInteger4(messageContext))) + .add("ticketOrganisationId", std::to_string(common::consumeInteger2(messageContext))) .add("productNumber", std::to_string(common::consumeInteger2(messageContext))) + .add("productOrganisationId", std::to_string(common::consumeInteger2(messageContext))) .add("validFrom", common::consumeDateTimeCompact4(messageContext)) .add("validTo", common::consumeDateTimeCompact4(messageContext)); } From 475bfdeec25490eb9ce726462cfb97bbba746fca Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 14 Feb 2026 18:01:58 +0100 Subject: [PATCH 27/41] Generalize TagType; Start impl 4 TLVDecoder; Simplify some utility consumers --- .../detail/common/include/Context.h | 7 + .../detail/common/include/TLVDecoder.h | 70 ++++++++++ .../detail/common/source/Context.cpp | 20 +++ .../common/source/InterpreterUtility.cpp | 4 +- .../detail/common/source/TLVDecoder.cpp | 58 ++++++++ .../detail/vdv/include/VDVUtility.h | 23 ++-- .../detail/vdv/source/BotanMessageDecoder.cpp | 2 +- .../detail/vdv/source/Certificate.cpp | 12 +- .../source/LDIFFileCertificateProvider.cpp | 10 +- .../detail/vdv/source/VDVInterpreter.cpp | 2 +- .../detail/vdv/source/VDVUtility.cpp | 39 ++---- .../source/InterpreterContextTest.cpp | 10 ++ .../interpreter/source/TLVDecoderTest.cpp | 125 ++++++++++++++++++ 13 files changed, 325 insertions(+), 57 deletions(-) create mode 100644 source/lib/interpreter/detail/common/include/TLVDecoder.h create mode 100644 source/lib/interpreter/detail/common/source/TLVDecoder.cpp create mode 100644 source/test/interpreter/source/TLVDecoderTest.cpp diff --git a/source/lib/interpreter/detail/common/include/Context.h b/source/lib/interpreter/detail/common/include/Context.h index 9049bd6e..1ca673b3 100644 --- a/source/lib/interpreter/detail/common/include/Context.h +++ b/source/lib/interpreter/detail/common/include/Context.h @@ -65,6 +65,11 @@ namespace interpreter::detail::common */ std::span peekBytes(std::size_t offset, std::size_t size); + /* Returns and consumes just one byte from current position + to current position + 1. + */ + std::uint8_t consumeByte(); + /* Returns and consumes size bytes from current position to current position + size. Throws runtime_error if size exceeds remaining bytes. @@ -118,6 +123,8 @@ namespace interpreter::detail::common bool isEmpty() const; + void ensureEmpty() const; + std::size_t getOverallSize() const; std::size_t getRemainingSize() const; diff --git a/source/lib/interpreter/detail/common/include/TLVDecoder.h b/source/lib/interpreter/detail/common/include/TLVDecoder.h new file mode 100644 index 00000000..3a47fc9d --- /dev/null +++ b/source/lib/interpreter/detail/common/include/TLVDecoder.h @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +namespace interpreter::detail::common +{ + class Context; + + class TLVTag + { + std::uint32_t value; // this is used in big endian byte order, so do not access it directly or be confused on little endian machines + std::size_t currentSize; + + constexpr std::uint8_t *getByte(std::size_t index) const { return ((std::uint8_t *)&value) + index; } + + public: + constexpr TLVTag() : value(0), currentSize(0) {} + constexpr TLVTag(std::initializer_list const initial) : TLVTag::TLVTag() + { + auto const *source = initial.begin(); + auto *destination = getByte(0); + std::size_t index = 0; + for (; index < initial.size() && index < maximumSize(); ++index) + { + destination[index] = source[index]; + } + currentSize = index; + } + + TLVTag(TLVTag const &) = default; + TLVTag(TLVTag &&) = default; + + TLVTag &operator=(TLVTag const &) = default; + TLVTag &operator=(TLVTag &&) = default; + + constexpr void assign(std::size_t index, std::uint8_t value) + { + currentSize = index + 1; + *getByte(index) = value; + } + + constexpr std::uint8_t const &operator[](std::size_t index) const { return *getByte(index); } + + constexpr std::size_t size() const { return currentSize; } + + constexpr std::size_t maximumSize() const { return sizeof(decltype(value)); } + + constexpr bool operator==(TLVTag const &rhs) const { return currentSize == rhs.currentSize && value == rhs.value; } + + constexpr bool operator!=(TLVTag const &rhs) const { return currentSize != rhs.currentSize || value != rhs.value; } + + void ensureEqual(TLVTag const &rhs) const; + + std::string toHexString() const; + }; + + class TLVDecoder + { + public: + constexpr static bool hasSuccessor(std::uint8_t const &value); + + static TLVTag consumeTag(common::Context &context); + }; +} diff --git a/source/lib/interpreter/detail/common/source/Context.cpp b/source/lib/interpreter/detail/common/source/Context.cpp index 14965def..b998586b 100644 --- a/source/lib/interpreter/detail/common/source/Context.cpp +++ b/source/lib/interpreter/detail/common/source/Context.cpp @@ -81,6 +81,18 @@ namespace interpreter::detail::common return std::span(position + offset, size); } + std::uint8_t Context::consumeByte() + { + if (getRemainingSize() < 1) + { + throw std::runtime_error("Not enough bytes available to consume"); + } + + auto value = *position; + position += 1; + return value; + } + std::span Context::consumeBytes(std::size_t size) { if (getRemainingSize() < size) @@ -163,6 +175,14 @@ namespace interpreter::detail::common return position == end; } + void Context::ensureEmpty() const + { + if (!isEmpty()) + { + throw std::runtime_error(std::string("Expecting fully consumed context, but found remaining bytes: ") + std::to_string(getRemainingSize())); + } + } + std::size_t Context::getOverallSize() const { return std::distance(begin, end); diff --git a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp index c1dd7ec2..4ed1196e 100644 --- a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp +++ b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp @@ -60,7 +60,7 @@ namespace interpreter::detail::common std::uint8_t consumeInteger1(Context &context) { - return getInteger(context); + return context.consumeByte(); } std::uint16_t consumeDecimalInteger2(Context &context) @@ -72,7 +72,7 @@ namespace interpreter::detail::common std::uint8_t consumeDecimalInteger1(Context &context) { - auto byte = context.consumeBytes(1)[0]; + auto byte = context.consumeByte(); std::uint8_t const high = byte >> 4 & 0x0F; std::uint8_t const low = byte & 0x0F; return high * 10 + low; diff --git a/source/lib/interpreter/detail/common/source/TLVDecoder.cpp b/source/lib/interpreter/detail/common/source/TLVDecoder.cpp new file mode 100644 index 00000000..05029224 --- /dev/null +++ b/source/lib/interpreter/detail/common/source/TLVDecoder.cpp @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../include/TLVDecoder.h" + +#include "lib/interpreter/detail/common/include/Context.h" + +#include + +namespace interpreter::detail::common +{ + + void TLVTag::ensureEqual(TLVTag const &rhs) const + { + if (*this != rhs) + { + throw std::runtime_error(std::string("Unexpected tag found: ") + toHexString()); + } + } + + std::string TLVTag::toHexString() const + { + std::stringstream os; + auto const bytes = std::span(getByte(0), size()); + std::for_each(std::begin(bytes), std::end(bytes), [&](auto const &byte) + { os << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (int)byte; }); + return os.str(); + } + + constexpr bool TLVDecoder::hasSuccessor(std::uint8_t const &value) + { + return (value & 0x1F) == 0x1F; + } + + TLVTag TLVDecoder::consumeTag(common::Context &context) + { + auto tag = TLVTag{0, 0, 0, 0}; + tag.assign(0, context.consumeByte()); + // auto const usage = (first & 0xC0) >> 6; // 0 universal, 1 application, 2 context-specific, 3 private + // auto const type = (first & 0x20) >> 5; // 0 primitive, 1 constructed + // auto const tag = (first & 0x1F); // 0b11111 (31) see further bytes or else single byte tag value + + if (hasSuccessor(tag[0])) + { + for (int index = 1; index < tag.maximumSize(); index++) + { + auto const byte = context.consumeByte(); + tag.assign(index, byte); + if ((byte & 0x80) == 0) + { + break; + } + } + } + + return tag; + } +} diff --git a/source/lib/interpreter/detail/vdv/include/VDVUtility.h b/source/lib/interpreter/detail/vdv/include/VDVUtility.h index a1d41f0a..17987c17 100644 --- a/source/lib/interpreter/detail/vdv/include/VDVUtility.h +++ b/source/lib/interpreter/detail/vdv/include/VDVUtility.h @@ -5,33 +5,32 @@ #include "Certificate.h" -#include "lib/interpreter/detail/common/include/Context.h" +#include "lib/interpreter/detail/common/include/TLVDecoder.h" #include #include -namespace interpreter::detail::vdv +namespace interpreter::detail::common { + class Context; +} - using TagType = std::array; +namespace interpreter::detail::vdv +{ std::uint32_t consumeLength(common::Context &context); - TagType consumeTag(common::Context &context); - - common::Context &consumeExpectedTag(common::Context &context, TagType const &expectedTag); + common::Context &consumeExpectedTag(common::Context &context, common::TLVTag const &expectedTag); - common::Context &consumeExpectedEndTag(common::Context &context, TagType const &expectedTag); + common::Context &consumeExpectedEndTag(common::Context &context, common::TLVTag const &expectedTag); - common::Context &consumeExpectedFrameTags(common::Context &context, TagType const &expectedBeginTag, TagType const &expectedEndTag); + common::Context &consumeExpectedFrameTags(common::Context &context, common::TLVTag const &expectedBeginTag, common::TLVTag const &expectedEndTag); - std::span consumeExpectedTagValue(common::Context &context, TagType const &expectedTag); + std::span consumeExpectedTagValue(common::Context &context, common::TLVTag const &expectedTag); std::span consumeExpected(common::Context &context, std::vector expectedValue); bool peekExpected(common::Context &context, std::vector expectedValue); - void ensureTag(TagType const &tag, TagType const &expectedTag); - - void ensureEmpty(common::Context const &context); + void ensureTag(common::TLVTag const &tag, common::TLVTag const &expectedTag); } diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 6fbb9224..77c84b02 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -62,7 +62,7 @@ namespace interpreter::detail::vdv consumeExpectedFrameTags(context, {0x6A}, {0xBC}); auto const expectedHash = context.consumeBytesEnd(20); auto content = context.consumeRemainingBytesAppend(signature.remainder); - ensureEmpty(context); + context.ensureEmpty(); auto const actualHash = sha1HashFunction->process(content); if (!std::equal(actualHash.begin(), actualHash.end(), expectedHash.begin(), expectedHash.end())) diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp index faf55601..6ea3ad2e 100644 --- a/source/lib/interpreter/detail/vdv/source/Certificate.cpp +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -23,8 +23,8 @@ namespace interpreter::detail::vdv Signature Signature::consumeFromEnvelope(common::Context &context) { - auto value = consumeExpectedTagValue(context, {0x9e, 0x00}); - auto remainder = consumeExpectedTagValue(context, {0x9a, 0x00}); + auto value = consumeExpectedTagValue(context, {0x9e}); + auto remainder = consumeExpectedTagValue(context, {0x9a}); return Signature{std::move(value), std::move(remainder)}; } @@ -38,11 +38,11 @@ namespace interpreter::detail::vdv Certificate Certificate::consumeFromEnvelope(common::Context &context) { auto const signatureData = consumeExpectedTagValue(context, {0x7f, 0x21}); - auto authority = common::bytesToHexString(consumeExpectedTagValue(context, {0x42, 0x00})); + auto authority = common::bytesToHexString(consumeExpectedTagValue(context, {0x42})); auto signatureContext = common::Context(signatureData); auto signature = Signature::consumeFrom(signatureContext); - ensureEmpty(signatureContext); + context.ensureEmpty(); return Certificate{std::move(authority), "envelope", std::move(signature), {}}; } @@ -233,7 +233,7 @@ namespace interpreter::detail::vdv auto context = common::Context(content); auto identity = CertificateIdentity::consumeFrom(context, 9); auto publicKey = PublicKey::consumeFrom(context); - ensureEmpty(context); + context.ensureEmpty(); return DecodedCertificate{std::nullopt, std::move(identity), std::move(publicKey)}; } @@ -242,7 +242,7 @@ namespace interpreter::detail::vdv auto context = common::Context(content); auto identity = CertificateIdentity::consumeFrom(context, 7); // TODO OID length is probably not always 7 for all sub-certificates auto publicKey = PublicKey::consumeFrom(context); - ensureEmpty(context); + context.ensureEmpty(); return DecodedCertificate{std::make_optional(std::move(content)), std::move(identity), std::move(publicKey)}; } } diff --git a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp index 038cf654..7a72ee70 100644 --- a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp +++ b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp @@ -36,7 +36,7 @@ namespace interpreter::detail::vdv auto context = common::Context(data); auto payload = consumeExpectedTagValue(context, {0x7f, 0x21}); - ensureEmpty(context); + context.ensureEmpty(); auto content = std::span{}; auto signature = std::span{}; @@ -45,17 +45,17 @@ namespace interpreter::detail::vdv auto payloadContext = common::Context(payload); while (!payloadContext.isEmpty()) { - auto const tag = consumeTag(payloadContext); + auto const tag = common::TLVDecoder::consumeTag(payloadContext); auto const value = payloadContext.consumeBytes(consumeLength(payloadContext)); - if (tag == TagType{0x5f, 0x4e}) + if (tag == common::TLVTag{0x5f, 0x4e}) { content = value; } - else if (tag == TagType{0x5f, 0x37}) + else if (tag == common::TLVTag{0x5f, 0x37}) { signature = value; } - else if (tag == TagType{0x5f, 0x38}) + else if (tag == common::TLVTag{0x5f, 0x38}) { remainder = value; } diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 63c4d076..18de5ff5 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -46,7 +46,7 @@ namespace interpreter::detail::vdv auto const signature = Signature::consumeFromEnvelope(context); auto const certificate = Certificate::consumeFromEnvelope(context); - ensureEmpty(context); + context.ensureEmpty(); auto const remainderTail = common::Context(signature.remainder).consumeBytesEnd(5); auto const signatureIdent = common::bytesToString(remainderTail.subspan(0, 3)); diff --git a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp index 8c1ab159..488efacf 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp @@ -3,6 +3,7 @@ #include "../include/VDVUtility.h" +#include "lib/interpreter/detail/common/include/Context.h" #include "lib/interpreter/detail/common/include/InterpreterUtility.h" namespace interpreter::detail::vdv @@ -22,43 +23,29 @@ namespace interpreter::detail::vdv throw std::runtime_error(std::string("Found unexpected length indicator tag (expecting x<0x80 or x=0x8y with y=): ") + std::to_string(first)); } - TagType consumeTag(common::Context &context) + common::Context &consumeExpectedTag(common::Context &context, common::TLVTag const &expectedTag) { - auto const first = common::consumeInteger1(context); - if (first == 0x7f || first == 0x5f) - { - return {first, common::consumeInteger1(context)}; - } - - return {first, 0}; - } - - common::Context &consumeExpectedTag(common::Context &context, TagType const &expectedTag) - { - auto tag = consumeTag(context); + auto tag = common::TLVDecoder::consumeTag(context); ensureTag(tag, expectedTag); return context; } - common::Context &consumeExpectedEndTag(common::Context &context, TagType const &expectedTag) + common::Context &consumeExpectedEndTag(common::Context &context, common::TLVTag const &expectedTag) { - auto const tag = TagType{context.consumeBytesEnd(1)[0], 0}; - if (tag[0] == 0x7f || tag[0] == 0x5f) - { - throw std::runtime_error(std::string("Unexpected end tag found: ") + common::bytesToHexString(tag)); - } + // TODO This is working only when expectedTag is just a single byte, refactor this to make it generic + auto const tag = common::TLVTag{context.consumeBytesEnd(1)[0]}; ensureTag(tag, expectedTag); return context; } - common::Context &consumeExpectedFrameTags(common::Context &context, TagType const &expectedBeginTag, TagType const &expectedEndTag) + common::Context &consumeExpectedFrameTags(common::Context &context, common::TLVTag const &expectedBeginTag, common::TLVTag const &expectedEndTag) { consumeExpectedTag(context, expectedBeginTag); consumeExpectedEndTag(context, expectedEndTag); return context; } - std::span consumeExpectedTagValue(common::Context &context, TagType const &expectedTag) + std::span consumeExpectedTagValue(common::Context &context, common::TLVTag const &expectedTag) { return context.consumeBytes(consumeLength(consumeExpectedTag(context, expectedTag))); } @@ -80,19 +67,11 @@ namespace interpreter::detail::vdv return std::equal(value.begin(), value.end(), expectedValue.begin(), expectedValue.end()); } - void ensureTag(TagType const &tag, TagType const &expectedTag) + void ensureTag(common::TLVTag const &tag, common::TLVTag const &expectedTag) { if (tag != expectedTag) { throw std::runtime_error(std::string("Unexpected tag found: ") + common::bytesToHexString(tag)); } } - - void ensureEmpty(common::Context const &context) - { - if (!context.isEmpty()) - { - throw std::runtime_error(std::string("Expecting fully consumed context, but found remaining bytes: ") + std::to_string(context.getRemainingSize())); - } - } } diff --git a/source/test/interpreter/source/InterpreterContextTest.cpp b/source/test/interpreter/source/InterpreterContextTest.cpp index 6bb2f4fa..fe9fc77b 100644 --- a/source/test/interpreter/source/InterpreterContextTest.cpp +++ b/source/test/interpreter/source/InterpreterContextTest.cpp @@ -100,4 +100,14 @@ namespace interpreter::detail::common EXPECT_EQ(0, context.ignoreBytes(0)); EXPECT_THROW(context.ignoreBytes(1), std::runtime_error); } + + TEST(InterpreterContext, ensureEmpty) + { + auto context = Context(data, "origin"); + context.ignoreBytes(4); + EXPECT_THROW(context.ensureEmpty(), std::runtime_error); + context.ignoreBytes(1); + EXPECT_NO_THROW(context.ensureEmpty()); + EXPECT_NO_THROW(Context(std::vector{}).ensureEmpty()); + } } diff --git a/source/test/interpreter/source/TLVDecoderTest.cpp b/source/test/interpreter/source/TLVDecoderTest.cpp new file mode 100644 index 00000000..8d7c6065 --- /dev/null +++ b/source/test/interpreter/source/TLVDecoderTest.cpp @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "lib/interpreter/detail/common/include/Context.h" +#include "lib/interpreter/detail/common/include/TLVDecoder.h" + +namespace interpreter::detail::common +{ + TEST(TLVTag, empty) + { + auto tag = TLVTag{}; + EXPECT_EQ(0, tag[0]); + EXPECT_EQ(0, tag[1]); + EXPECT_EQ(0, tag[2]); + EXPECT_EQ(0, tag[3]); + } + + TEST(TLVTag, compareEqual) + { + EXPECT_EQ((TLVTag{}), (TLVTag{})); + EXPECT_EQ((TLVTag{0, 0}), (TLVTag{0, 0})); + auto tag = TLVTag{}; + tag.assign(2, 5); + EXPECT_EQ((TLVTag{0, 0, 5}), tag); + } + + TEST(TLVTag, compareNotEqual) + { + EXPECT_NE((TLVTag{}), (TLVTag{1})); + EXPECT_NE((TLVTag{1}), (TLVTag{})); + EXPECT_NE((TLVTag{0}), (TLVTag{0, 0})); + EXPECT_NE((TLVTag{0, 0}), (TLVTag{0, 0, 0})); + EXPECT_NE((TLVTag{1}), (TLVTag{0, 0, 0, 1})); + } + + TEST(TLVTag, constSubscription) + { + auto tag = TLVTag{23, 42, 5, 6}; + EXPECT_EQ(23, tag[0]); + EXPECT_EQ(42, tag[1]); + EXPECT_EQ(5, tag[2]); + EXPECT_EQ(6, tag[3]); + } + + TEST(TLVTag, subscription) + { + auto tag = TLVTag{}; + tag.assign(0, 23); + tag.assign(1, 42); + tag.assign(2, 5); + tag.assign(3, 6); + EXPECT_EQ((TLVTag{23, 42, 5, 6}), tag); + } + + TEST(TLVTag, toHexString) + { + EXPECT_EQ((TLVTag{}).toHexString(), ""); + EXPECT_EQ((TLVTag{0, 8}).toHexString(), "0008"); + EXPECT_EQ((TLVTag{0, 0, 0, 1}).toHexString(), "00000001"); + EXPECT_EQ((TLVTag{1, 0, 0, 0}).toHexString(), "01000000"); + EXPECT_EQ((TLVTag{0xff, 0, 0, 0}).toHexString(), "FF000000"); + EXPECT_EQ((TLVTag{23, 42, 80, 255}).toHexString(), "172A50FF"); + + auto tag = TLVTag{}; + EXPECT_EQ(tag[0], 0); + EXPECT_EQ(tag.toHexString(), ""); + tag.assign(0, 0); + EXPECT_EQ(tag.toHexString(), "00"); + tag.assign(1, 7); + EXPECT_EQ(tag.toHexString(), "0007"); + } + + TEST(TLVTag, ensureEqual) + { + EXPECT_NO_THROW((TLVTag{}.ensureEqual(TLVTag{}))); + EXPECT_NO_THROW((TLVTag{23}.ensureEqual(TLVTag{23}))); + EXPECT_NO_THROW((TLVTag{0, 1}.ensureEqual(TLVTag{0, 1}))); + EXPECT_NO_THROW((TLVTag{1, 2, 3, 4}.ensureEqual(TLVTag{1, 2, 3, 4}))); + } + + TEST(TLVTag, ensureNotEqual) + { + EXPECT_THROW((TLVTag{}.ensureEqual(TLVTag{0})), std::runtime_error); + EXPECT_THROW((TLVTag{23}.ensureEqual(TLVTag{0, 23})), std::runtime_error); + EXPECT_THROW((TLVTag{23}.ensureEqual(TLVTag{23, 0})), std::runtime_error); + EXPECT_THROW((TLVTag{0, 1}.ensureEqual(TLVTag{1, 0})), std::runtime_error); + EXPECT_THROW((TLVTag{1, 2, 3, 4}.ensureEqual(TLVTag{1, 2, 3})), std::runtime_error); + EXPECT_THROW((TLVTag{1, 2, 3, 4}.ensureEqual(TLVTag{})), std::runtime_error); + } + + TEST(TLVDecoder, consume4ByteTagStatic) + { + auto context = Context(std::vector{0b01111111, 0b10000001, 0b11111111, 0b01010101, 0x23}); + auto const tag = TLVDecoder::consumeTag(context); + EXPECT_EQ(tag, (TLVTag{0x7f, 0x81, 0xff, 0x55})); + EXPECT_EQ(1, context.getRemainingSize()); + } + + TEST(TLVDecoder, consume3ByteTagStatic) + { + auto context = Context(std::vector{0b01111111, 0b10000001, 0b01111111, 0x23}); + auto const tag = TLVDecoder::consumeTag(context); + EXPECT_EQ(tag, (TLVTag{0x7f, 0x81, 0x7f})); + EXPECT_EQ(1, context.getRemainingSize()); + } + + TEST(TLVDecoder, consume2ByteTagStatic) + { + auto context = Context(std::vector{0b01111111, 0b00100001, 0x42}); + auto const tag = TLVDecoder::consumeTag(context); + EXPECT_EQ(tag, (TLVTag{0x7f, 0x21})); + EXPECT_EQ(1, context.getRemainingSize()); + } + + TEST(TLVDecoder, consume1ByteTagStatic) + { + auto context = Context(std::vector{0b10011010, 0x42}); + auto const tag = TLVDecoder::consumeTag(context); + EXPECT_EQ(tag, (TLVTag{0x9a})); + EXPECT_EQ(1, context.getRemainingSize()); + } +} From 50bf0952d6f173e7e20f6a5bdf005dd4427cab1a Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 15 Feb 2026 15:39:21 +0100 Subject: [PATCH 28/41] Move some more methods from utility 2 TLVDecoder --- .../detail/common/include/Context.h | 44 ++++++- .../detail/common/include/TLVDecoder.h | 21 ++-- .../detail/common/source/Context.cpp | 92 +++++++------- .../common/source/InterpreterUtility.cpp | 2 +- .../detail/common/source/TLVDecoder.cpp | 50 ++++++-- .../detail/vdv/include/VDVUtility.h | 28 ----- .../detail/vdv/source/BotanMessageDecoder.cpp | 7 +- .../detail/vdv/source/Certificate.cpp | 17 ++- .../source/LDIFFileCertificateProvider.cpp | 6 +- .../detail/vdv/source/VDVUtility.cpp | 69 ----------- .../source/InterpreterContextTest.cpp | 69 +++++++++++ .../interpreter/source/TLVDecoderTest.cpp | 112 ++++++++++++++++++ 12 files changed, 344 insertions(+), 173 deletions(-) diff --git a/source/lib/interpreter/detail/common/include/Context.h b/source/lib/interpreter/detail/common/include/Context.h index 1ca673b3..1ac58faf 100644 --- a/source/lib/interpreter/detail/common/include/Context.h +++ b/source/lib/interpreter/detail/common/include/Context.h @@ -53,17 +53,29 @@ namespace interpreter::detail::common */ IteratorType getPosition() const; + /* Returns just one byte from current position + to current position + 1 without consumtion. + Throws runtime_error if size exceeds remaining bytes. + */ + std::uint8_t peekByte() const; + + /* Returns just one byte from current position + offset + to current position + offset + 1 without consumtion. + Throws runtime_error if size exceeds remaining bytes. + */ + std::uint8_t peekByte(std::size_t offset) const; + /* Returns size bytes in a vector from current position to current position + size without consumtion. Throws runtime_error if size exceeds remaining bytes. */ - std::span peekBytes(std::size_t size); + std::span peekBytes(std::size_t size) const; /* Returns size bytes in a span from current position + offset to current position + offset + size without consumtion. Throws runtime_error if offset + size exceeds remaining bytes. */ - std::span peekBytes(std::size_t offset, std::size_t size); + std::span peekBytes(std::size_t offset, std::size_t size) const; /* Returns and consumes just one byte from current position to current position + 1. @@ -76,6 +88,11 @@ namespace interpreter::detail::common */ std::span consumeBytes(std::size_t size); + /* Returns and consumes just one byte from end of all bytes. + Throws runtime_error if size exceeds remaining bytes. + */ + std::uint8_t consumeByteEnd(); + /* Returns and consumes size bytes from end of all bytes. Throws runtime_error if size exceeds remaining bytes. */ @@ -106,6 +123,12 @@ namespace interpreter::detail::common */ std::size_t ignoreBytes(std::size_t size); + /* Consumes expected bytes from current position when they + match, otherwise nothing is consumed. + Returns true when matching bytes has been consumed, false otherwise. + */ + bool ignoreBytesIf(std::vector expectedValue); + /* Ignores and skips all remaining bytes from current position to end. */ @@ -125,11 +148,22 @@ namespace interpreter::detail::common void ensureEmpty() const; - std::size_t getOverallSize() const; + void ensureRemaining(std::size_t size) const; + + constexpr std::size_t getOverallSize() const + { + return std::distance(begin, end); + } - std::size_t getRemainingSize() const; + constexpr std::size_t getRemainingSize() const + { + return std::distance(position, end); + } - std::size_t getConsumedSize() const; + constexpr std::size_t getConsumedSize() const + { + return std::distance(begin, position); + } // Fields diff --git a/source/lib/interpreter/detail/common/include/TLVDecoder.h b/source/lib/interpreter/detail/common/include/TLVDecoder.h index 3a47fc9d..33c2ccb2 100644 --- a/source/lib/interpreter/detail/common/include/TLVDecoder.h +++ b/source/lib/interpreter/detail/common/include/TLVDecoder.h @@ -3,10 +3,10 @@ #pragma once -#include #include #include #include +#include namespace interpreter::detail::common { @@ -21,12 +21,12 @@ namespace interpreter::detail::common public: constexpr TLVTag() : value(0), currentSize(0) {} - constexpr TLVTag(std::initializer_list const initial) : TLVTag::TLVTag() + constexpr TLVTag(std::initializer_list const bytes) : TLVTag::TLVTag() { - auto const *source = initial.begin(); + auto const *source = bytes.begin(); auto *destination = getByte(0); std::size_t index = 0; - for (; index < initial.size() && index < maximumSize(); ++index) + for (; index < bytes.size() && index < maximumSize(); ++index) { destination[index] = source[index]; } @@ -41,7 +41,10 @@ namespace interpreter::detail::common constexpr void assign(std::size_t index, std::uint8_t value) { - currentSize = index + 1; + if (index + 1 > currentSize) + { + currentSize = index + 1; + } *getByte(index) = value; } @@ -63,8 +66,12 @@ namespace interpreter::detail::common class TLVDecoder { public: - constexpr static bool hasSuccessor(std::uint8_t const &value); - static TLVTag consumeTag(common::Context &context); + + static common::Context &consumeExpectedTag(common::Context &context, common::TLVTag const &expectedTag); + + static std::size_t consumeLength(common::Context &context); + + static std::span consumeExpectedElement(common::Context &context, common::TLVTag const &expectedTag); }; } diff --git a/source/lib/interpreter/detail/common/source/Context.cpp b/source/lib/interpreter/detail/common/source/Context.cpp index b998586b..1fa17cc2 100644 --- a/source/lib/interpreter/detail/common/source/Context.cpp +++ b/source/lib/interpreter/detail/common/source/Context.cpp @@ -61,33 +61,33 @@ namespace interpreter::detail::common return position; } - std::span Context::peekBytes(std::size_t size) + std::uint8_t Context::peekByte() const { - if (getRemainingSize() < size) - { - throw std::runtime_error("Not enough bytes available to peek"); - } + ensureRemaining(1); + return *position; + } - return std::span(position, size); + std::uint8_t Context::peekByte(std::size_t offset) const + { + ensureRemaining(offset + 1); + return *(position + offset); } - std::span Context::peekBytes(std::size_t offset, std::size_t size) + std::span Context::peekBytes(std::size_t size) const { - if (getRemainingSize() < (offset + size)) - { - throw std::runtime_error("Not enough bytes available to peek"); - } + ensureRemaining(size); + return std::span(position, size); + } + std::span Context::peekBytes(std::size_t offset, std::size_t size) const + { + ensureRemaining(offset + size); return std::span(position + offset, size); } std::uint8_t Context::consumeByte() { - if (getRemainingSize() < 1) - { - throw std::runtime_error("Not enough bytes available to consume"); - } - + ensureRemaining(1); auto value = *position; position += 1; return value; @@ -95,23 +95,22 @@ namespace interpreter::detail::common std::span Context::consumeBytes(std::size_t size) { - if (getRemainingSize() < size) - { - throw std::runtime_error("Not enough bytes available to consume"); - } - + ensureRemaining(size); auto result = std::span(position, size); position += size; return result; } - std::span Context::consumeBytesEnd(std::size_t size) + std::uint8_t Context::consumeByteEnd() { - if (getRemainingSize() < size) - { - throw std::runtime_error("Not enough bytes available to consume from end"); - } + ensureRemaining(1); + end -= 1; + return *end; + } + std::span Context::consumeBytesEnd(std::size_t size) + { + ensureRemaining(size); end -= size; return std::span(end, size); } @@ -141,13 +140,29 @@ namespace interpreter::detail::common std::size_t Context::ignoreBytes(std::size_t size) { - if (getRemainingSize() < size) + ensureRemaining(size); + std::advance(position, size); + return size; + } + + bool Context::ignoreBytesIf(std::vector expectedValue) + { + if (getRemainingSize() < expectedValue.size()) { - throw std::runtime_error("Not enough bytes available to ignore"); + return false; } - std::advance(position, size); - return size; + auto index = std::size_t(0); + for (auto const value : expectedValue) + { + if (value != peekByte(index++)) + { + return false; + } + } + + ignoreBytes(expectedValue.size()); + return true; } std::size_t Context::ignoreRemainingBytes() @@ -183,19 +198,12 @@ namespace interpreter::detail::common } } - std::size_t Context::getOverallSize() const - { - return std::distance(begin, end); - } - - std::size_t Context::getRemainingSize() const + void Context::ensureRemaining(std::size_t size) const { - return std::distance(position, end); - } - - std::size_t Context::getConsumedSize() const - { - return std::distance(begin, position); + if (getRemainingSize() < size) + { + throw std::runtime_error("Less than expected bytes available"); + } } std::map const &Context::getFields() const diff --git a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp index 4ed1196e..cb28a669 100644 --- a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp +++ b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp @@ -33,7 +33,7 @@ namespace interpreter::detail::common if constexpr (std::endian::native == std::endian::big) { - std::copy(source.begin(), source.end(), destination.begin()); + std::copy(source.begin(), source.end(), destination.begin() + sizeof(T) - sourceLength); } else { diff --git a/source/lib/interpreter/detail/common/source/TLVDecoder.cpp b/source/lib/interpreter/detail/common/source/TLVDecoder.cpp index 05029224..9589a1bd 100644 --- a/source/lib/interpreter/detail/common/source/TLVDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/TLVDecoder.cpp @@ -9,7 +9,6 @@ namespace interpreter::detail::common { - void TLVTag::ensureEqual(TLVTag const &rhs) const { if (*this != rhs) @@ -27,20 +26,15 @@ namespace interpreter::detail::common return os.str(); } - constexpr bool TLVDecoder::hasSuccessor(std::uint8_t const &value) - { - return (value & 0x1F) == 0x1F; - } - TLVTag TLVDecoder::consumeTag(common::Context &context) { - auto tag = TLVTag{0, 0, 0, 0}; + auto tag = TLVTag{}; tag.assign(0, context.consumeByte()); // auto const usage = (first & 0xC0) >> 6; // 0 universal, 1 application, 2 context-specific, 3 private // auto const type = (first & 0x20) >> 5; // 0 primitive, 1 constructed // auto const tag = (first & 0x1F); // 0b11111 (31) see further bytes or else single byte tag value - if (hasSuccessor(tag[0])) + if ((tag[0] & 0x1F) == 0x1F) { for (int index = 1; index < tag.maximumSize(); index++) { @@ -55,4 +49,44 @@ namespace interpreter::detail::common return tag; } + + common::Context &TLVDecoder::consumeExpectedTag(common::Context &context, common::TLVTag const &expectedTag) + { + consumeTag(context).ensureEqual(expectedTag); + return context; + } + + std::size_t TLVDecoder::consumeLength(common::Context &context) + { + auto const first = context.consumeByte(); + if (first < 0x80) + { + return first; + } + + auto const maxSize = sizeof(std::size_t); + auto const length = first & 0x7f; + if (length > maxSize) + { + throw std::runtime_error(std::string("Expecting ") + std::to_string(maxSize) + " as a max no of successor bytes for length, found unsupported length: " + std::to_string(length)); + } + + auto const source = context.consumeBytes(length); + auto result = std::size_t(0); + auto const destination = std::span(reinterpret_cast(&result), maxSize); + if constexpr (std::endian::native == std::endian::little) + { + std::copy(source.rbegin(), source.rend(), destination.begin()); + } + else + { + std::copy(source.begin(), source.end(), destination.begin() + maxSize - length); + } + return result; + } + + std::span TLVDecoder::consumeExpectedElement(common::Context &context, common::TLVTag const &expectedTag) + { + return context.consumeBytes(consumeLength(consumeExpectedTag(context, expectedTag))); + } } diff --git a/source/lib/interpreter/detail/vdv/include/VDVUtility.h b/source/lib/interpreter/detail/vdv/include/VDVUtility.h index 17987c17..aa3a43df 100644 --- a/source/lib/interpreter/detail/vdv/include/VDVUtility.h +++ b/source/lib/interpreter/detail/vdv/include/VDVUtility.h @@ -3,34 +3,6 @@ #pragma once -#include "Certificate.h" - -#include "lib/interpreter/detail/common/include/TLVDecoder.h" - -#include -#include - -namespace interpreter::detail::common -{ - class Context; -} - namespace interpreter::detail::vdv { - - std::uint32_t consumeLength(common::Context &context); - - common::Context &consumeExpectedTag(common::Context &context, common::TLVTag const &expectedTag); - - common::Context &consumeExpectedEndTag(common::Context &context, common::TLVTag const &expectedTag); - - common::Context &consumeExpectedFrameTags(common::Context &context, common::TLVTag const &expectedBeginTag, common::TLVTag const &expectedEndTag); - - std::span consumeExpectedTagValue(common::Context &context, common::TLVTag const &expectedTag); - - std::span consumeExpected(common::Context &context, std::vector expectedValue); - - bool peekExpected(common::Context &context, std::vector expectedValue); - - void ensureTag(common::TLVTag const &tag, common::TLVTag const &expectedTag); } diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 77c84b02..9f5d7664 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -59,7 +59,12 @@ namespace interpreter::detail::vdv Botan::BigInt(publicKey.modulus)) .serialize()); - consumeExpectedFrameTags(context, {0x6A}, {0xBC}); + auto const head = context.consumeByte(); + auto const tail = context.consumeByteEnd(); + if (head != 0x6A || tail != 0xBC) + { + throw std::runtime_error(std::string("Expected head 0x6A / tail 0xBC bytes not found") + std::to_string(head) + "/" + std::to_string(tail)); + } auto const expectedHash = context.consumeBytesEnd(20); auto content = context.consumeRemainingBytesAppend(signature.remainder); context.ensureEmpty(); diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp index 6ea3ad2e..afb54661 100644 --- a/source/lib/interpreter/detail/vdv/source/Certificate.cpp +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -2,9 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "../include/Certificate.h" -#include "../include/VDVUtility.h" #include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/TLVDecoder.h" #include #include @@ -16,15 +16,15 @@ namespace interpreter::detail::vdv Signature Signature::consumeFrom(common::Context &context) { - auto value = consumeExpectedTagValue(context, {0x5f, 0x37}); - auto remainder = consumeExpectedTagValue(context, {0x5f, 0x38}); + auto value = common::TLVDecoder::consumeExpectedElement(context, {0x5f, 0x37}); + auto remainder = common::TLVDecoder::consumeExpectedElement(context, {0x5f, 0x38}); return Signature{std::move(value), std::move(remainder)}; } Signature Signature::consumeFromEnvelope(common::Context &context) { - auto value = consumeExpectedTagValue(context, {0x9e}); - auto remainder = consumeExpectedTagValue(context, {0x9a}); + auto value = common::TLVDecoder::consumeExpectedElement(context, {0x9e}); + auto remainder = common::TLVDecoder::consumeExpectedElement(context, {0x9a}); return Signature{std::move(value), std::move(remainder)}; } @@ -37,8 +37,8 @@ namespace interpreter::detail::vdv Certificate Certificate::consumeFromEnvelope(common::Context &context) { - auto const signatureData = consumeExpectedTagValue(context, {0x7f, 0x21}); - auto authority = common::bytesToHexString(consumeExpectedTagValue(context, {0x42})); + auto const signatureData = common::TLVDecoder::consumeExpectedElement(context, {0x7f, 0x21}); + auto authority = common::bytesToHexString(common::TLVDecoder::consumeExpectedElement(context, {0x42})); auto signatureContext = common::Context(signatureData); auto signature = Signature::consumeFrom(signatureContext); @@ -213,9 +213,8 @@ namespace interpreter::detail::vdv auto authority = CertificateParticipant::consumeFrom(context); auto holder = std::optional{}; auto reference = std::optional{}; - if (peekExpected(context, {0, 0, 0, 0})) + if (context.ignoreBytesIf({0, 0, 0, 0})) { - auto const fillBytes = context.consumeBytes(4); holder = std::make_optional(CertificateParticipant::consumeFrom(context)); } else diff --git a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp index 7a72ee70..f14b310d 100644 --- a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp +++ b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp @@ -2,10 +2,10 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "../include/LDIFFileCertificateProvider.h" -#include "../include/VDVUtility.h" #include "lib/interpreter/detail/common/include/Context.h" #include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/TLVDecoder.h" #include "lib/utility/include/Base64.h" @@ -35,7 +35,7 @@ namespace interpreter::detail::vdv } auto context = common::Context(data); - auto payload = consumeExpectedTagValue(context, {0x7f, 0x21}); + auto payload = common::TLVDecoder::consumeExpectedElement(context, {0x7f, 0x21}); context.ensureEmpty(); auto content = std::span{}; @@ -46,7 +46,7 @@ namespace interpreter::detail::vdv while (!payloadContext.isEmpty()) { auto const tag = common::TLVDecoder::consumeTag(payloadContext); - auto const value = payloadContext.consumeBytes(consumeLength(payloadContext)); + auto const value = payloadContext.consumeBytes(common::TLVDecoder::consumeLength(payloadContext)); if (tag == common::TLVTag{0x5f, 0x4e}) { content = value; diff --git a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp index 488efacf..811df0d3 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVUtility.cpp @@ -3,75 +3,6 @@ #include "../include/VDVUtility.h" -#include "lib/interpreter/detail/common/include/Context.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" - namespace interpreter::detail::vdv { - /* Multi-byte integers are big-endian encoded, see DER ASN.1 - */ - std::uint32_t consumeLength(common::Context &context) - { - auto const first = common::consumeInteger1(context); - // clang-format off - if (first < 0x80) { return first; } - else if (first == 0x81) { return common::consumeInteger1(context); } - else if (first == 0x82) { return common::consumeInteger2(context); } - else if (first == 0x83) { return common::consumeInteger3(context); } - else if (first == 0x84) { return common::consumeInteger4(context); } - // clang-format on - throw std::runtime_error(std::string("Found unexpected length indicator tag (expecting x<0x80 or x=0x8y with y=): ") + std::to_string(first)); - } - - common::Context &consumeExpectedTag(common::Context &context, common::TLVTag const &expectedTag) - { - auto tag = common::TLVDecoder::consumeTag(context); - ensureTag(tag, expectedTag); - return context; - } - - common::Context &consumeExpectedEndTag(common::Context &context, common::TLVTag const &expectedTag) - { - // TODO This is working only when expectedTag is just a single byte, refactor this to make it generic - auto const tag = common::TLVTag{context.consumeBytesEnd(1)[0]}; - ensureTag(tag, expectedTag); - return context; - } - - common::Context &consumeExpectedFrameTags(common::Context &context, common::TLVTag const &expectedBeginTag, common::TLVTag const &expectedEndTag) - { - consumeExpectedTag(context, expectedBeginTag); - consumeExpectedEndTag(context, expectedEndTag); - return context; - } - - std::span consumeExpectedTagValue(common::Context &context, common::TLVTag const &expectedTag) - { - return context.consumeBytes(consumeLength(consumeExpectedTag(context, expectedTag))); - } - - std::span consumeExpected(common::Context &context, std::vector expectedValue) - { - auto const value = context.consumeBytes(expectedValue.size()); - if (!std::equal(value.begin(), value.end(), expectedValue.begin(), expectedValue.end())) - { - throw std::runtime_error(std::string("Unexpected value found: ") + common::bytesToHexString(value)); - } - return value; - } - - bool peekExpected(common::Context &context, std::vector expectedValue) - { - - auto const value = context.peekBytes(expectedValue.size()); - return std::equal(value.begin(), value.end(), expectedValue.begin(), expectedValue.end()); - } - - void ensureTag(common::TLVTag const &tag, common::TLVTag const &expectedTag) - { - if (tag != expectedTag) - { - throw std::runtime_error(std::string("Unexpected tag found: ") + common::bytesToHexString(tag)); - } - } } diff --git a/source/test/interpreter/source/InterpreterContextTest.cpp b/source/test/interpreter/source/InterpreterContextTest.cpp index fe9fc77b..d079a1e3 100644 --- a/source/test/interpreter/source/InterpreterContextTest.cpp +++ b/source/test/interpreter/source/InterpreterContextTest.cpp @@ -15,6 +15,17 @@ namespace interpreter::detail::common return std::vector(input.begin(), input.end()); } + TEST(InterpreterContext, peekByte) + { + auto context = Context(data, "origin"); + EXPECT_EQ(1, context.peekByte()); + EXPECT_EQ(1, context.peekByte()); + context.ignoreBytes(1); + EXPECT_EQ(2, context.peekByte()); + EXPECT_EQ(2, context.peekByte()); + EXPECT_EQ(1, context.getConsumedSize()); + } + TEST(InterpreterContext, peekBytes) { auto context = Context(data, "origin"); @@ -49,6 +60,28 @@ namespace interpreter::detail::common EXPECT_EQ(1, context.getConsumedSize()); } + TEST(InterpreterContext, consumeByte) + { + auto context = Context(data, "origin"); + EXPECT_EQ(1, context.consumeByte()); + EXPECT_EQ(2, context.consumeByte()); + EXPECT_EQ(3, context.consumeByte()); + EXPECT_EQ(4, context.consumeByte()); + EXPECT_EQ(5, context.consumeByte()); + EXPECT_THROW(context.consumeByte(), std::runtime_error); + } + + TEST(InterpreterContext, consumeByteEnd) + { + auto context = Context(data, "origin"); + EXPECT_EQ(5, context.consumeByteEnd()); + EXPECT_EQ(4, context.consumeByteEnd()); + EXPECT_EQ(3, context.consumeByteEnd()); + EXPECT_EQ(2, context.consumeByteEnd()); + EXPECT_EQ(1, context.consumeByteEnd()); + EXPECT_THROW(context.consumeByteEnd(), std::runtime_error); + } + TEST(InterpreterContext, consumeBytes) { auto context = Context(data, "origin"); @@ -80,6 +113,14 @@ namespace interpreter::detail::common EXPECT_EQ((std::vector{}), toVector(context.consumeMaximalBytes(5))); } + TEST(InterpreterContext, consumeRemainingBytesAppend) + { + auto context = Context({0x1, 0x2, 0x3}); + auto postfix = std::vector{0x4, 0x5}; + context.ignoreBytes(1); + EXPECT_EQ((std::vector{0x2, 0x3, 0x4, 0x5}), context.consumeRemainingBytesAppend(std::span(postfix.begin(), postfix.end()))); + } + TEST(InterpreterContext, ignoreBytes) { auto context = Context(data, "origin"); @@ -101,6 +142,27 @@ namespace interpreter::detail::common EXPECT_THROW(context.ignoreBytes(1), std::runtime_error); } + TEST(InterpreterContext, ignoreBytesIf) + { + auto context = Context({0x1, 0x23, 0x42, 0x2}); + EXPECT_FALSE(context.ignoreBytesIf({0x23, 0x42})); + EXPECT_TRUE(context.ignoreBytesIf({0x1})); + EXPECT_FALSE(context.ignoreBytesIf({0x1})); + EXPECT_TRUE(context.ignoreBytesIf({0x23, 0x42})); + EXPECT_FALSE(context.ignoreBytesIf({0x23, 0x42})); + EXPECT_TRUE(context.ignoreBytesIf({0x2})); + EXPECT_TRUE(context.isEmpty()); + } + + TEST(InterpreterContext, ignoreRemainingBytes) + { + auto context = Context(data, "origin"); + context.consumeBytes(2); + EXPECT_FALSE(context.isEmpty()); + EXPECT_EQ(3, context.ignoreRemainingBytes()); + EXPECT_TRUE(context.isEmpty()); + } + TEST(InterpreterContext, ensureEmpty) { auto context = Context(data, "origin"); @@ -110,4 +172,11 @@ namespace interpreter::detail::common EXPECT_NO_THROW(context.ensureEmpty()); EXPECT_NO_THROW(Context(std::vector{}).ensureEmpty()); } + + TEST(InterpreterContext, getAllBase64Encoded) + { + auto context = Context(data, "origin"); + context.consumeByte(); + EXPECT_EQ("AQIDBAU=", context.getAllBase64Encoded()); + } } diff --git a/source/test/interpreter/source/TLVDecoderTest.cpp b/source/test/interpreter/source/TLVDecoderTest.cpp index 8d7c6065..57ca12d9 100644 --- a/source/test/interpreter/source/TLVDecoderTest.cpp +++ b/source/test/interpreter/source/TLVDecoderTest.cpp @@ -48,13 +48,30 @@ namespace interpreter::detail::common TEST(TLVTag, subscription) { auto tag = TLVTag{}; + EXPECT_EQ(0, tag.size()); tag.assign(0, 23); + EXPECT_EQ(1, tag.size()); tag.assign(1, 42); + EXPECT_EQ(2, tag.size()); tag.assign(2, 5); + EXPECT_EQ(3, tag.size()); tag.assign(3, 6); EXPECT_EQ((TLVTag{23, 42, 5, 6}), tag); } + TEST(TLVTag, size) + { + auto tag = TLVTag{}; + tag.assign(2, 23); + EXPECT_EQ(3, tag.size()); + tag.assign(1, 42); + EXPECT_EQ(3, tag.size()); + tag.assign(0, 5); + EXPECT_EQ(3, tag.size()); + tag.assign(3, 6); + EXPECT_EQ(4, tag.size()); + } + TEST(TLVTag, toHexString) { EXPECT_EQ((TLVTag{}).toHexString(), ""); @@ -122,4 +139,99 @@ namespace interpreter::detail::common EXPECT_EQ(tag, (TLVTag{0x9a})); EXPECT_EQ(1, context.getRemainingSize()); } + + TEST(TLVDecoder, consumeExpectedTagStatic) + { + auto context = Context(std::vector{0b01111111, 0b00100001, 0x42}); + EXPECT_NO_THROW(TLVDecoder::consumeExpectedTag(context, {0x7f, 0x21})); + EXPECT_EQ(1, context.getRemainingSize()); + EXPECT_NO_THROW(TLVDecoder::consumeExpectedTag(context, {0x42})); + EXPECT_EQ(0, context.getRemainingSize()); + } + + TEST(TLVDecoder, consumeNotExpectedTagStatic) + { + auto context = Context(std::vector{0b01111111, 0b00100001, 0x42}); + EXPECT_THROW(TLVDecoder::consumeExpectedTag(context, {0x7e, 0x21}), std::runtime_error); + EXPECT_EQ(1, context.getRemainingSize()); + EXPECT_THROW(TLVDecoder::consumeExpectedTag(context, {0x41}), std::runtime_error); + EXPECT_EQ(0, context.getRemainingSize()); + } + + TEST(TLVDecoder, consume1ByteLength) + { + auto context = Context(std::vector{1, 127, 0x80}); + EXPECT_EQ(1, TLVDecoder::consumeLength(context)); + EXPECT_EQ(127, TLVDecoder::consumeLength(context)); + EXPECT_EQ(0, TLVDecoder::consumeLength(context)); + } + + TEST(TLVDecoder, consume2ByteLength) + { + auto context = Context(std::vector{0x81, 23, 0x81, 0, 0x81, 0xff}); + EXPECT_EQ(23, TLVDecoder::consumeLength(context)); + EXPECT_EQ(0, TLVDecoder::consumeLength(context)); + EXPECT_EQ(255, TLVDecoder::consumeLength(context)); + } + + TEST(TLVDecoder, consume3ByteLength) + { + auto context = Context(std::vector{0x82, 1, 2, 0x82, 0, 0, 0x82, 0xff, 0xff}); + EXPECT_EQ(0x0102, TLVDecoder::consumeLength(context)); + EXPECT_EQ(0, TLVDecoder::consumeLength(context)); + EXPECT_EQ(std::numeric_limits::max(), TLVDecoder::consumeLength(context)); + } + + TEST(TLVDecoder, consume4ByteLength) + { + auto context = Context(std::vector{0x83, 1, 2, 3, 0x83, 0, 0, 0, 0x83, 0xff, 0xff, 0xff}); + EXPECT_EQ(0x010203, TLVDecoder::consumeLength(context)); + EXPECT_EQ(0, TLVDecoder::consumeLength(context)); + EXPECT_EQ(0xffffff, TLVDecoder::consumeLength(context)); + } + + TEST(TLVDecoder, consume5ByteLength) + { + auto context = Context(std::vector{0x84, 1, 2, 3, 4, 0x84, 0, 0, 0, 0, 0x84, 0xff, 0xff, 0xff, 0xff}); + EXPECT_EQ(0x01020304, TLVDecoder::consumeLength(context)); + EXPECT_EQ(0, TLVDecoder::consumeLength(context)); + EXPECT_EQ(0xffffffff, TLVDecoder::consumeLength(context)); + } + + TEST(TLVDecoder, consume6ByteLength) + { + auto context = Context(std::vector{0x85, 1, 2, 3, 4, 5, 0x85, 0, 0, 0, 0, 0, 0x85, 0xff, 0xff, 0xff, 0xff, 0xff}); + EXPECT_EQ(0x0102030405, TLVDecoder::consumeLength(context)); + EXPECT_EQ(0, TLVDecoder::consumeLength(context)); + EXPECT_EQ(0xffffffffff, TLVDecoder::consumeLength(context)); + } + + TEST(TLVDecoder, consume9ByteLength) + { + auto context = Context(std::vector{0x88, 1, 2, 3, 4, 5, 6, 7, 8, 0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0x88, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}); + EXPECT_EQ(0x0102030405060708, TLVDecoder::consumeLength(context)); + EXPECT_EQ(0, TLVDecoder::consumeLength(context)); + EXPECT_EQ(0xffffffffffffffff, TLVDecoder::consumeLength(context)); + } + + TEST(TLVDecoder, consume10ByteLength) + { + auto context = Context(std::vector{0x89, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + EXPECT_THROW(TLVDecoder::consumeLength(context), std::runtime_error); + } + + TEST(TLVDecoder, consumeExpectedElement) + { + auto context = Context(std::vector{0x9a, 0x81, 0x02, 0x23, 0x42}); + auto const value = TLVDecoder::consumeExpectedElement(context, {0x9a}); + EXPECT_EQ(value.size(), 2); + EXPECT_EQ(0x23, value[0]); + EXPECT_EQ(0x42, value[1]); + } + + TEST(TLVDecoder, consumeUnexpectedElement) + { + auto context = Context(std::vector{0x9a, 0x81, 0x02, 0x23, 0x42}); + EXPECT_THROW(TLVDecoder::consumeExpectedElement(context, {0x9b}), std::runtime_error); + } } From 28b14075b6e530bfcbd6307f50ceef4611c047aa Mon Sep 17 00:00:00 2001 From: sascha Date: Fri, 20 Feb 2026 07:23:30 +0100 Subject: [PATCH 29/41] Find passenger details --- .../interpreter/detail/vdv/source/VDVInterpreter.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 18de5ff5..010f155a 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -7,6 +7,7 @@ #include "../include/VDVUtility.h" #include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/TLVDecoder.h" #include "lib/utility/include/Base64.h" @@ -62,6 +63,9 @@ namespace interpreter::detail::vdv if (message) { auto messageContext = common::Context(*message); + auto const messageTail = messageContext.consumeBytesEnd(5); + auto const messageIdent = common::bytesToString(messageTail.subspan(0, 3)); + auto const messageVersion = common::bytesToHexString(messageTail.subspan(3, 2)); jsonBuilder .add("ticketId", std::to_string(common::consumeInteger4(messageContext))) .add("ticketOrganisationId", std::to_string(common::consumeInteger2(messageContext))) @@ -69,6 +73,14 @@ namespace interpreter::detail::vdv .add("productOrganisationId", std::to_string(common::consumeInteger2(messageContext))) .add("validFrom", common::consumeDateTimeCompact4(messageContext)) .add("validTo", common::consumeDateTimeCompact4(messageContext)); + + auto const product = common::TLVDecoder::consumeExpectedElement(messageContext, {0x85}); + { + auto productContext = common::Context(product); + auto const unknown1 = common::TLVDecoder::consumeExpectedElement(productContext, {0xda}); + auto const passenger = common::TLVDecoder::consumeExpectedElement(productContext, {0xdb}); + jsonBuilder; + } } context.addField("validated", message ? "true" : "false"); From 63156335349ca5f08e2fd067bc86ed62cfdce9ea Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 21 Feb 2026 14:23:16 +0100 Subject: [PATCH 30/41] Move packed bcd decoding into separated decoder class --- .../detail/common/include/BCDDecoder.h | 34 +++++++++ .../common/include/InterpreterUtility.h | 8 -- .../detail/common/source/BCDDecoder.cpp | 39 ++++++++++ .../detail/common/source/Context.cpp | 2 +- .../common/source/InterpreterUtility.cpp | 20 +---- .../detail/vdv/source/Certificate.cpp | 17 +++-- .../interpreter/source/BCDDecoderTest.cpp | 73 +++++++++++++++++++ 7 files changed, 160 insertions(+), 33 deletions(-) create mode 100644 source/lib/interpreter/detail/common/include/BCDDecoder.h create mode 100644 source/lib/interpreter/detail/common/source/BCDDecoder.cpp create mode 100644 source/test/interpreter/source/BCDDecoderTest.cpp diff --git a/source/lib/interpreter/detail/common/include/BCDDecoder.h b/source/lib/interpreter/detail/common/include/BCDDecoder.h new file mode 100644 index 00000000..5376e114 --- /dev/null +++ b/source/lib/interpreter/detail/common/include/BCDDecoder.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +namespace interpreter::detail::common +{ + class Context; + + /* Decoder for binary coded decimal numbers (BCD). + */ + class BCDDecoder + { + public: + /* Consumes 2 bytes in big endian order and decodes packed binary coded decimal value + */ + static std::uint16_t consumePackedInteger2(Context &context); + + /* Decodes 2 bytes in big endian order and decodes packed binary coded decimal value + */ + static std::uint16_t decodePackedInteger2(std::span bytes); + + /* Consumes 1 byte and decodes packed binary coded decimal value + */ + static std::uint8_t consumePackedInteger1(Context &context); + + /* Decodes 1 byte and decodes packed binary coded decimal value + */ + static std::uint8_t decodePackedInteger1(std::uint8_t byte); + }; +} diff --git a/source/lib/interpreter/detail/common/include/InterpreterUtility.h b/source/lib/interpreter/detail/common/include/InterpreterUtility.h index 0a847ed1..2a4e85d7 100644 --- a/source/lib/interpreter/detail/common/include/InterpreterUtility.h +++ b/source/lib/interpreter/detail/common/include/InterpreterUtility.h @@ -31,14 +31,6 @@ namespace interpreter::detail::common */ std::uint8_t consumeInteger1(Context &context); - /* Consumes 2 bytes and decodes binary decimal encoded value - */ - std::uint16_t consumeDecimalInteger2(Context &context); - - /* Consumes 1 bytes and decodes binary decimal encoded value - */ - std::uint8_t consumeDecimalInteger1(Context &context); - /* Consumes 4 bytes and decodes date-time to ISO-8601 format */ std::string consumeDateTimeCompact4(Context &context); diff --git a/source/lib/interpreter/detail/common/source/BCDDecoder.cpp b/source/lib/interpreter/detail/common/source/BCDDecoder.cpp new file mode 100644 index 00000000..10dbdf7e --- /dev/null +++ b/source/lib/interpreter/detail/common/source/BCDDecoder.cpp @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../include/BCDDecoder.h" + +#include "lib/interpreter/detail/common/include/Context.h" + +#include + +namespace interpreter::detail::common +{ + + std::uint16_t BCDDecoder::consumePackedInteger2(Context &context) + { + return decodePackedInteger2(context.consumeBytes(2)); + } + + std::uint16_t BCDDecoder::decodePackedInteger2(std::span bytes) + { + if (bytes.size() < 2) { + throw std::runtime_error(std::string("Less than expected bytes available, expecting at least: 2")); + } + std::uint16_t const high = decodePackedInteger1(bytes[0]); + std::uint16_t const low = decodePackedInteger1(bytes[1]); + return high * 100 + low; + } + + std::uint8_t BCDDecoder::consumePackedInteger1(Context &context) + { + return decodePackedInteger1(context.consumeByte()); + } + + std::uint8_t BCDDecoder::decodePackedInteger1(std::uint8_t byte) + { + std::uint8_t const high = byte >> 4 & 0x0F; + std::uint8_t const low = byte & 0x0F; + return high * 10 + low; + } +} diff --git a/source/lib/interpreter/detail/common/source/Context.cpp b/source/lib/interpreter/detail/common/source/Context.cpp index 1fa17cc2..3a4ded62 100644 --- a/source/lib/interpreter/detail/common/source/Context.cpp +++ b/source/lib/interpreter/detail/common/source/Context.cpp @@ -202,7 +202,7 @@ namespace interpreter::detail::common { if (getRemainingSize() < size) { - throw std::runtime_error("Less than expected bytes available"); + throw std::runtime_error(std::string("Less than expected bytes available, expecting at least: ") + std::to_string(size)); } } diff --git a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp index cb28a669..715c5857 100644 --- a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp +++ b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp @@ -63,21 +63,6 @@ namespace interpreter::detail::common return context.consumeByte(); } - std::uint16_t consumeDecimalInteger2(Context &context) - { - std::uint16_t const high = consumeDecimalInteger1(context); - std::uint16_t const low = consumeDecimalInteger1(context); - return high * 100 + low; - } - - std::uint8_t consumeDecimalInteger1(Context &context) - { - auto byte = context.consumeByte(); - std::uint8_t const high = byte >> 4 & 0x0F; - std::uint8_t const low = byte & 0x0F; - return high * 10 + low; - } - std::string consumeDateTimeCompact4(Context &context) { auto const date = common::consumeInteger2(context); @@ -122,7 +107,10 @@ namespace interpreter::detail::common std::string bytesToString(std::span bytes) { - auto result = std::string{std::begin(bytes), std::find(std::begin(bytes), std::end(bytes), '\0')}; + auto ascii = std::vector(); + std::transform(std::begin(bytes), std::end(bytes), std::back_inserter(ascii), [](std::uint8_t const &v) + { return v >= 128 ? ' ' : v; }); + auto result = std::string{std::begin(ascii), std::find(std::begin(ascii), std::end(ascii), '\0')}; result.erase(std::find_if(std::rbegin(result), std::rend(result), [](unsigned char ch) { return !std::isspace(ch); }) .base(), diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp index afb54661..d267b264 100644 --- a/source/lib/interpreter/detail/vdv/source/Certificate.cpp +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -5,6 +5,7 @@ #include "lib/interpreter/detail/common/include/InterpreterUtility.h" #include "lib/interpreter/detail/common/include/TLVDecoder.h" +#include "lib/interpreter/detail/common/include/BCDDecoder.h" #include #include @@ -141,24 +142,24 @@ namespace interpreter::detail::vdv CertificateDate CertificateDate::consumeFrom4(common::Context &context) { - auto const year = common::consumeDecimalInteger2(context); - auto const month = common::consumeDecimalInteger1(context); - auto const day = common::consumeDecimalInteger1(context); + auto const year = common::BCDDecoder::consumePackedInteger2(context); + auto const month = common::BCDDecoder::consumePackedInteger1(context); + auto const day = common::BCDDecoder::consumePackedInteger1(context); return CertificateDate{year, month, day}; } CertificateDate CertificateDate::consumeFrom3(common::Context &context) { - auto const year = static_cast(2000 + common::consumeDecimalInteger1(context)); - auto const month = common::consumeDecimalInteger1(context); - auto const day = common::consumeDecimalInteger1(context); + auto const year = static_cast(2000 + common::BCDDecoder::consumePackedInteger1(context)); + auto const month = common::BCDDecoder::consumePackedInteger1(context); + auto const day = common::BCDDecoder::consumePackedInteger1(context); return CertificateDate{year, month, day}; } CertificateDate CertificateDate::consumeFrom2(common::Context &context) { - auto const year = static_cast(2000 + common::consumeDecimalInteger1(context)); - auto const month = common::consumeDecimalInteger1(context); + auto const year = static_cast(2000 + common::BCDDecoder::consumePackedInteger1(context)); + auto const month = common::BCDDecoder::consumePackedInteger1(context); return CertificateDate{year, month, 1}; } diff --git a/source/test/interpreter/source/BCDDecoderTest.cpp b/source/test/interpreter/source/BCDDecoderTest.cpp new file mode 100644 index 00000000..acab8484 --- /dev/null +++ b/source/test/interpreter/source/BCDDecoderTest.cpp @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "lib/interpreter/detail/common/include/BCDDecoder.h" +#include "lib/interpreter/detail/common/include/Context.h" + +namespace interpreter::detail::common +{ + auto const buffer = std::vector{0x20, 0x26, 0x0, 0x0, 0x99, 0x99}; + + TEST(BCDDecoder, decodePackedInteger1) + { + EXPECT_EQ(20, BCDDecoder::decodePackedInteger1(buffer[0])); + EXPECT_EQ(20, BCDDecoder::decodePackedInteger1(buffer[0])); + EXPECT_EQ(26, BCDDecoder::decodePackedInteger1(buffer[1])); + EXPECT_EQ(26, BCDDecoder::decodePackedInteger1(buffer[1])); + } + + TEST(BCDDecoder, decodePackedInteger1Min) + { + EXPECT_EQ(0, BCDDecoder::decodePackedInteger1(buffer[2])); + EXPECT_EQ(0, BCDDecoder::decodePackedInteger1(buffer[3])); + } + + TEST(BCDDecoder, decodePackedInteger1Max) + { + EXPECT_EQ(99, BCDDecoder::decodePackedInteger1(buffer[4])); + EXPECT_EQ(99, BCDDecoder::decodePackedInteger1(buffer[5])); + } + + TEST(BCDDecoder, consumePackedInteger1) + { + auto context = common::Context({0x20, 0x26}); + EXPECT_EQ(20, BCDDecoder::consumePackedInteger1(context)); + EXPECT_FALSE(context.isEmpty()); + EXPECT_EQ(26, BCDDecoder::consumePackedInteger1(context)); + EXPECT_TRUE(context.isEmpty()); + } + + TEST(BCDDecoder, decodePackedInteger2) + { + EXPECT_EQ(2026, BCDDecoder::decodePackedInteger2({buffer.begin(), 2})); + EXPECT_EQ(2026, BCDDecoder::decodePackedInteger2({buffer.begin(), buffer.end()})); + EXPECT_EQ(2600, BCDDecoder::decodePackedInteger2({buffer.begin() + 1, 2})); + EXPECT_EQ(2600, BCDDecoder::decodePackedInteger2({buffer.begin() + 1, buffer.end()})); + } + + TEST(BCDDecoder, decodePackedInteger2Min) + { + EXPECT_EQ(0, BCDDecoder::decodePackedInteger2({buffer.begin() + 2, 2})); + EXPECT_EQ(0, BCDDecoder::decodePackedInteger2({buffer.begin() + 2, buffer.end()})); + } + + TEST(BCDDecoder, decodePackedInteger2Max) + { + EXPECT_EQ(9999, BCDDecoder::decodePackedInteger2({buffer.begin() + 4, 2})); + EXPECT_EQ(9999, BCDDecoder::decodePackedInteger2({buffer.begin() + 4, buffer.end()})); + } + + TEST(BCDDecoder, decodePackedInteger2Invalid) + { + EXPECT_THROW(BCDDecoder::decodePackedInteger2({buffer.begin(), 1}), std::runtime_error); + } + + TEST(BCDDecoder, consumePackedInteger2) + { + auto context = common::Context({0x20, 0x26}); + EXPECT_EQ(2026, BCDDecoder::consumePackedInteger2(context)); + EXPECT_TRUE(context.isEmpty()); + } +} From 1b3933f887c10e51d0c19b292c1a3dd53bb357d0 Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 21 Feb 2026 16:23:16 +0100 Subject: [PATCH 31/41] Move decoders 4 string and datetime into separated modules --- .../interpreter/api/source/Interpreter.cpp | 4 +- .../detail/common/include/DateTimeDecoder.h | 27 ++++ .../common/include/InterpreterUtility.h | 45 +------ .../detail/common/include/StringDecoder.h | 47 +++++++ .../detail/common/source/DateTimeDecoder.cpp | 56 +++++++++ .../common/source/InterpreterUtility.cpp | 87 +------------ .../detail/common/source/StringDecoder.cpp | 51 ++++++++ .../detail/uic918/source/Record0080BL.cpp | 26 ++-- .../detail/uic918/source/Record0080VU.cpp | 5 +- .../detail/uic918/source/Record118199.cpp | 4 +- .../detail/uic918/source/RecordHeader.cpp | 8 +- .../detail/uic918/source/RecordU_HEAD.cpp | 15 +-- .../detail/uic918/source/RecordU_TLAY.cpp | 20 +-- .../uic918/source/Uic918Interpreter.cpp | 12 +- .../detail/vdv/source/Certificate.cpp | 9 +- .../source/LDIFFileCertificateProvider.cpp | 4 +- .../detail/vdv/source/VDVInterpreter.cpp | 24 ++-- .../source/DateTimeDecoderTest.cpp | 28 +++++ .../source/InterpreterUtilityTest.cpp | 115 ++---------------- .../interpreter/source/StringDecoderTest.cpp | 76 ++++++++++++ 20 files changed, 372 insertions(+), 291 deletions(-) create mode 100644 source/lib/interpreter/detail/common/include/DateTimeDecoder.h create mode 100644 source/lib/interpreter/detail/common/include/StringDecoder.h create mode 100644 source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp create mode 100644 source/lib/interpreter/detail/common/source/StringDecoder.cpp create mode 100644 source/test/interpreter/source/DateTimeDecoderTest.cpp create mode 100644 source/test/interpreter/source/StringDecoderTest.cpp diff --git a/source/lib/interpreter/api/source/Interpreter.cpp b/source/lib/interpreter/api/source/Interpreter.cpp index 0a0c8e10..06fecca5 100644 --- a/source/lib/interpreter/api/source/Interpreter.cpp +++ b/source/lib/interpreter/api/source/Interpreter.cpp @@ -4,7 +4,7 @@ #include "../include/Interpreter.h" #include "lib/interpreter/detail/common/include/Context.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" #include "lib/interpreter/detail/uic918/include/Uic918Interpreter.h" #include "lib/interpreter/detail/vdv/include/VDVInterpreter.h" @@ -60,7 +60,7 @@ namespace interpreter::api auto const interpreter = interpreterMap.find(detail::common::Interpreter::TypeIdType(typeId.begin(), typeId.end())); if (interpreter == interpreterMap.end()) { - LOG_WARN(logger) << "Unknown message type: 0x" << detail::common::bytesToHexString(typeId); + LOG_WARN(logger) << "Unknown message type: 0x" << detail::common::StringDecoder::bytesToHexString(typeId); return std::move(context); } diff --git a/source/lib/interpreter/detail/common/include/DateTimeDecoder.h b/source/lib/interpreter/detail/common/include/DateTimeDecoder.h new file mode 100644 index 00000000..727308f7 --- /dev/null +++ b/source/lib/interpreter/detail/common/include/DateTimeDecoder.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: (C) 2026 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace interpreter::detail::common +{ + class Context; + + class DateTimeDecoder + { + public: + /* Consumes 4 bytes and decodes date-time to ISO-8601 format + */ + static std::string consumeDateTimeCompact4(Context &context); + + /* Consumes 3 bytes and decodes date-time to ISO-8601 format + */ + static std::string consumeDateTime3(Context &context); + + /* Consumes 8 bytes and decodes date to ISO-8601 format + */ + static std::string consumeDate8(Context &context); + }; +} diff --git a/source/lib/interpreter/detail/common/include/InterpreterUtility.h b/source/lib/interpreter/detail/common/include/InterpreterUtility.h index 2a4e85d7..acd82d30 100644 --- a/source/lib/interpreter/detail/common/include/InterpreterUtility.h +++ b/source/lib/interpreter/detail/common/include/InterpreterUtility.h @@ -3,17 +3,11 @@ #pragma once -#include "Context.h" - -#include #include namespace interpreter::detail::common { - /* Consumes maximumSize or less bytes and returns a 0 terminated string. - When the buffer is filled by multiple 0 at the end, the returned string might be shorter. - */ - std::string consumeString(Context &context, std::size_t maximumSize); + class Context; /* Consumes 4 bytes and converts from big-endian to system byte order */ @@ -30,41 +24,4 @@ namespace interpreter::detail::common /* Consumes 1 byte */ std::uint8_t consumeInteger1(Context &context); - - /* Consumes 4 bytes and decodes date-time to ISO-8601 format - */ - std::string consumeDateTimeCompact4(Context &context); - - /* Consumes 3 bytes and decodes date-time to ISO-8601 format - */ - std::string consumeDateTime3(Context &context); - - /* Consumes 8 bytes and decodes date to ISO-8601 format - */ - std::string consumeDate8(Context &context); - - /*** - Converters - ***/ - - std::string bytesToString(std::span bytes); - - std::string bytesToHexString(std::span bytes); - - std::string bytesToHexString(std::vector const &bytes); - - template - std::string bytesToHexString(std::array const &bytes) - { - return bytesToHexString(std::span(bytes.data(), S)); - } - - template - std::string bytesToHexString(T const &bytes) - { - auto const raw = std::span((std::uint8_t const *const)&bytes, sizeof(T)); - return std::endian::native == std::endian::big - ? bytesToHexString(raw) - : bytesToHexString(std::vector(raw.rbegin(), raw.rend())); - } } diff --git a/source/lib/interpreter/detail/common/include/StringDecoder.h b/source/lib/interpreter/detail/common/include/StringDecoder.h new file mode 100644 index 00000000..455bf89a --- /dev/null +++ b/source/lib/interpreter/detail/common/include/StringDecoder.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: (C) 2026 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace interpreter::detail::common +{ + + class Context; + + class StringDecoder + { + public: + /* Consumes maximumSize or less bytes and returns a 0 terminated string. + When the buffer is filled by multiple 0 at the end, the returned string might be shorter. + */ + static std::string consumeString(Context &context, std::size_t maximumSize); + + static std::string bytesToString(std::span bytes); + + static std::string bytesToHexString(std::span bytes); + + static std::string bytesToHexString(std::vector const &bytes); + + template + static std::string bytesToHexString(std::array const &bytes) + { + return bytesToHexString(std::span(bytes.data(), S)); + } + + template + static std::string bytesToHexString(T const &bytes) + { + auto const raw = std::span((std::uint8_t const *const)&bytes, sizeof(T)); + return std::endian::native == std::endian::big + ? bytesToHexString(raw) + : bytesToHexString(std::vector(raw.rbegin(), raw.rend())); + } + }; +} diff --git a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp new file mode 100644 index 00000000..314130bd --- /dev/null +++ b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: (C) 2026 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../include/DateTimeDecoder.h" +#include "../include/StringDecoder.h" +#include "../include/InterpreterUtility.h" + +#include +#include + +namespace interpreter::detail::common +{ + + std::string DateTimeDecoder::consumeDateTimeCompact4(Context &context) + { + auto const date = common::consumeInteger2(context); + auto const time = common::consumeInteger2(context); + // TODO Use chrono parse or apply validation for all values + std::ostringstream os; + os << std::setw(4) << std::setfill('0') << std::to_string(((date & 0xFE00) >> 9) + 1990) << "-" + << std::setw(2) << std::setfill('0') << std::to_string(((date & 0x01E0) >> 5)) << "-" + << std::setw(2) << std::setfill('0') << std::to_string(((date & 0x001F) >> 0)) << "T" + << std::setw(2) << std::setfill('0') << std::to_string(((time & 0xF800) >> 11)) << ":" + << std::setw(2) << std::setfill('0') << std::to_string(((time & 0x07E0) >> 5)) << ":" + << std::setw(2) << std::setfill('0') << std::to_string(((time & 0x001F) >> 0)); + return os.str(); + } + + std::string DateTimeDecoder::consumeDateTime3(Context &context) + { + auto const input = StringDecoder::consumeString(context, 12); + auto const p = input.begin(); + // TODO Use chrono parse or apply validation for all values + std::ostringstream os; // DDMMYYYYHHMM + os << std::string(p + 4, p + 8) << "-" + << std::string(p + 2, p + 4) << "-" + << std::string(p + 0, p + 2) << "T" + << std::string(p + 8, p + 10) << ":" + << std::string(p + 10, p + 12) << ":" + << "00"; + return os.str(); + } + + std::string DateTimeDecoder::consumeDate8(Context &context) + { + auto const input = StringDecoder::consumeString(context, 8); + auto const p = input.begin(); + // TODO Use chrono parse or apply validation for all values + std::ostringstream os; // DDMMYYYY + os << std::string(p + 4, p + 8) << "-" + << std::string(p + 2, p + 4) << "-" + << std::string(p + 0, p + 2); + return os.str(); + } + +} diff --git a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp index 715c5857..2470eabd 100644 --- a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp +++ b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp @@ -3,21 +3,15 @@ #include "../include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" +#include "lib/interpreter/detail/common/include/Context.h" + #include #include -#include -#include -#include -#include -#include +#include namespace interpreter::detail::common { - std::string consumeString(Context &context, std::size_t size) - { - auto const data = context.consumeMaximalBytes(size); - return bytesToString(data); - } template T getInteger(Context &context, std::size_t sourceLength = sizeof(T)) @@ -62,77 +56,4 @@ namespace interpreter::detail::common { return context.consumeByte(); } - - std::string consumeDateTimeCompact4(Context &context) - { - auto const date = common::consumeInteger2(context); - auto const time = common::consumeInteger2(context); - // TODO Use chrono parse or apply validation for all values - std::ostringstream os; - os << std::setw(4) << std::setfill('0') << std::to_string(((date & 0xFE00) >> 9) + 1990) << "-" - << std::setw(2) << std::setfill('0') << std::to_string(((date & 0x01E0) >> 5)) << "-" - << std::setw(2) << std::setfill('0') << std::to_string(((date & 0x001F) >> 0)) << "T" - << std::setw(2) << std::setfill('0') << std::to_string(((time & 0xF800) >> 11)) << ":" - << std::setw(2) << std::setfill('0') << std::to_string(((time & 0x07E0) >> 5)) << ":" - << std::setw(2) << std::setfill('0') << std::to_string(((time & 0x001F) >> 0)); - return os.str(); - } - - std::string consumeDateTime3(Context &context) - { - auto const input = consumeString(context, 12); - auto const p = input.begin(); - // TODO Use chrono parse or apply validation for all values - std::ostringstream os; // DDMMYYYYHHMM - os << std::string(p + 4, p + 8) << "-" - << std::string(p + 2, p + 4) << "-" - << std::string(p + 0, p + 2) << "T" - << std::string(p + 8, p + 10) << ":" - << std::string(p + 10, p + 12) << ":" - << "00"; - return os.str(); - } - - std::string consumeDate8(Context &context) - { - auto const input = consumeString(context, 8); - auto const p = input.begin(); - // TODO Use chrono parse or apply validation for all values - std::ostringstream os; // DDMMYYYY - os << std::string(p + 4, p + 8) << "-" - << std::string(p + 2, p + 4) << "-" - << std::string(p + 0, p + 2); - return os.str(); - } - - std::string bytesToString(std::span bytes) - { - auto ascii = std::vector(); - std::transform(std::begin(bytes), std::end(bytes), std::back_inserter(ascii), [](std::uint8_t const &v) - { return v >= 128 ? ' ' : v; }); - auto result = std::string{std::begin(ascii), std::find(std::begin(ascii), std::end(ascii), '\0')}; - result.erase(std::find_if(std::rbegin(result), std::rend(result), [](unsigned char ch) - { return !std::isspace(ch); }) - .base(), - std::end(result)); - return result; - } - - std::string bytesToHexString(std::span bytes) - { - if (bytes.empty()) - { - return ""; - } - - std::stringstream os; - std::for_each(std::begin(bytes), std::end(bytes), [&](auto const &byte) - { os << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (int)byte; }); - return os.str(); - } - - std::string bytesToHexString(std::vector const &bytes) - { - return bytesToHexString(std::span(bytes.data(), bytes.size())); - } } \ No newline at end of file diff --git a/source/lib/interpreter/detail/common/source/StringDecoder.cpp b/source/lib/interpreter/detail/common/source/StringDecoder.cpp new file mode 100644 index 00000000..68af3095 --- /dev/null +++ b/source/lib/interpreter/detail/common/source/StringDecoder.cpp @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: (C) 2026 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../include/StringDecoder.h" + +#include "lib/interpreter/detail/common/include/Context.h" + +#include +#include +#include +#include + +namespace interpreter::detail::common +{ + std::string StringDecoder::consumeString(Context &context, std::size_t size) + { + auto const data = context.consumeMaximalBytes(size); + return bytesToString(data); + } + + std::string StringDecoder::bytesToString(std::span bytes) + { + // auto ascii = std::vector(); + // std::transform(std::begin(bytes), std::end(bytes), std::back_inserter(ascii), [](std::uint8_t const &v) + // { return v >= 128 ? ' ' : v; }); + auto result = std::string{std::begin(bytes), std::find(std::begin(bytes), std::end(bytes), '\0')}; + result.erase(std::find_if(std::rbegin(result), std::rend(result), [](unsigned char ch) + { return !std::isspace(ch); }) + .base(), + std::end(result)); + return result; + } + + std::string StringDecoder::bytesToHexString(std::span bytes) + { + if (bytes.empty()) + { + return ""; + } + + std::stringstream os; + std::for_each(std::begin(bytes), std::end(bytes), [&](auto const &byte) + { os << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (int)byte; }); + return os.str(); + } + + std::string StringDecoder::bytesToHexString(std::vector const &bytes) + { + return bytesToHexString(std::span(bytes.data(), bytes.size())); + } +} diff --git a/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp b/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp index 51841001..a72af14a 100644 --- a/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp +++ b/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp @@ -4,6 +4,8 @@ #include "../include/Record0080BL.h" #include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" +#include "lib/interpreter/detail/common/include/DateTimeDecoder.h" #include "lib/interpreter/detail/common/include/Record.h" #include "lib/utility/include/JsonBuilder.h" @@ -74,16 +76,16 @@ namespace interpreter::detail::uic auto const certificate2 = context.consumeBytes(11); builder - .add("validFrom", common::consumeDate8(context)) - .add("validTo", common::consumeDate8(context)) - .add("serial", common::consumeString(context, 8)); + .add("validFrom", common::DateTimeDecoder::consumeDate8(context)) + .add("validTo", common::DateTimeDecoder::consumeDate8(context)) + .add("serial", common::StringDecoder::consumeString(context, 8)); }}, {std::string("03"), [](auto &context, auto &builder) { builder - .add("validFrom", common::consumeDate8(context)) - .add("validTo", common::consumeDate8(context)) - .add("serial", common::consumeString(context, 10)); + .add("validFrom", common::DateTimeDecoder::consumeDate8(context)) + .add("validTo", common::DateTimeDecoder::consumeDate8(context)) + .add("serial", common::StringDecoder::consumeString(context, 10)); }}}; Record0080BL::Record0080BL(infrastructure::LoggerFactory &loggerFactory, RecordHeader &&h) @@ -98,14 +100,14 @@ namespace interpreter::detail::uic auto recordJson = ::utility::JsonBuilder::object(); // clang-format off recordJson - .add("ticketType", common::consumeString(context, 2)) - .add("trips", ::utility::toArray(std::stoi(common::consumeString(context, 1)), [&](auto &builder) + .add("ticketType", common::StringDecoder::consumeString(context, 2)) + .add("trips", ::utility::toArray(std::stoi(common::StringDecoder::consumeString(context, 1)), [&](auto &builder) { tripInterpreter(context, builder); })) - .add("fields", ::utility::toObject(std::stoi(common::consumeString(context, 2)), [&](auto & builder) + .add("fields", ::utility::toObject(std::stoi(common::StringDecoder::consumeString(context, 2)), [&](auto & builder) { - auto const type = common::consumeString(context, 4); - auto const length = std::stoi(common::consumeString(context, 4)); - auto const content = common::consumeString(context, length); + auto const type = common::StringDecoder::consumeString(context, 4); + auto const length = std::stoi(common::StringDecoder::consumeString(context, 4)); + auto const content = common::StringDecoder::consumeString(context, length); auto const annotation = annotationMap.find(type); builder diff --git a/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp b/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp index d420eec2..6a1b1478 100644 --- a/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp +++ b/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp @@ -4,6 +4,7 @@ #include "../include/Record0080VU.h" #include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/DateTimeDecoder.h" #include "lib/interpreter/detail/common/include/Record.h" #include "lib/utility/include/JsonBuilder.h" @@ -43,8 +44,8 @@ namespace interpreter::detail::uic .add("pvProduktnummer", std::to_string(pvProduktnummer)) .add("pvProduktbezeichnung", pvProduktbezeichnung) .add("pvOrganisationsId", std::to_string(pvOrganisationsId)) - .add("gueltigAb", common::consumeDateTimeCompact4(context)) - .add("gueltigBis", common::consumeDateTimeCompact4(context)) + .add("gueltigAb", common::DateTimeDecoder::consumeDateTimeCompact4(context)) + .add("gueltigBis", common::DateTimeDecoder::consumeDateTimeCompact4(context)) .add("preis", common::consumeInteger3(context)) .add("samSequenznummer", std::to_string(common::consumeInteger4(context))) .add("flaechenelemente", ::utility::toDynamicArray(common::consumeInteger1(context), [&context](auto &builder) diff --git a/source/lib/interpreter/detail/uic918/source/Record118199.cpp b/source/lib/interpreter/detail/uic918/source/Record118199.cpp index e4ef4feb..c6473d14 100644 --- a/source/lib/interpreter/detail/uic918/source/Record118199.cpp +++ b/source/lib/interpreter/detail/uic918/source/Record118199.cpp @@ -3,7 +3,7 @@ #include "../include/Record118199.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" #include "lib/interpreter/detail/common/include/Record.h" #include "lib/utility/include/JsonBuilder.h" @@ -20,7 +20,7 @@ namespace interpreter::detail::uic common::Context Record118199::interpret(common::Context &&context) { - auto const jsonString = common::consumeString(context, context.getRemainingSize()); + auto const jsonString = common::StringDecoder::consumeString(context, context.getRemainingSize()); context.addRecord(common::Record(header.recordId, header.recordVersion, ::utility::JsonBuilder(jsonString))); return std::move(context); diff --git a/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp b/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp index a9e4fa74..5a44229d 100644 --- a/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp +++ b/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp @@ -3,7 +3,7 @@ #include "../include/RecordHeader.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" #include #include @@ -12,9 +12,9 @@ namespace interpreter::detail::uic { RecordHeader::RecordHeader(common::Context &context) : start(context.getPosition()), - recordId(common::consumeString(context, 6)), - recordVersion(common::consumeString(context, 2)), - recordLength(std::stoi(common::consumeString(context, 4))) + recordId(common::StringDecoder::consumeString(context, 6)), + recordVersion(common::StringDecoder::consumeString(context, 2)), + recordLength(std::stoi(common::StringDecoder::consumeString(context, 4))) { context.addField(recordId + ".recordId", recordId); context.addField(recordId + ".recordVersion", recordVersion); diff --git a/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp b/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp index ebb4f5ef..de537193 100644 --- a/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp +++ b/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp @@ -3,7 +3,8 @@ #include "../include/RecordU_HEAD.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" +#include "lib/interpreter/detail/common/include/DateTimeDecoder.h" #include "lib/interpreter/detail/common/include/Record.h" #include "lib/utility/include/JsonBuilder.h" @@ -21,12 +22,12 @@ namespace interpreter::detail::uic { auto recordJson = ::utility::JsonBuilder::object(); recordJson - .add("companyCode", common::consumeString(context, 4)) - .add("uniqueTicketKey", common::consumeString(context, 20)) - .add("editionTime", common::consumeDateTime3(context)) - .add("flags", common::consumeString(context, 1)) - .add("editionLanguageOfTicket", common::consumeString(context, 2)) - .add("secondLanguageOfContract", common::consumeString(context, 2)); + .add("companyCode", common::StringDecoder::consumeString(context, 4)) + .add("uniqueTicketKey", common::StringDecoder::consumeString(context, 20)) + .add("editionTime", common::DateTimeDecoder::consumeDateTime3(context)) + .add("flags", common::StringDecoder::consumeString(context, 1)) + .add("editionLanguageOfTicket", common::StringDecoder::consumeString(context, 2)) + .add("secondLanguageOfContract", common::StringDecoder::consumeString(context, 2)); context.addRecord(common::Record(header.recordId, header.recordVersion, std::move(recordJson))); return std::move(context); diff --git a/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp b/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp index 5bf47bd4..d2311d51 100644 --- a/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp +++ b/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp @@ -3,7 +3,7 @@ #include "../include/RecordU_TLAY.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" #include "lib/interpreter/detail/common/include/Record.h" #include "lib/utility/include/JsonBuilder.h" @@ -24,7 +24,7 @@ namespace interpreter::detail::uic common::Context RecordU_TLAY::interpret(common::Context &&context) { - auto const layoutStandard = common::consumeString(context, 4); + auto const layoutStandard = common::StringDecoder::consumeString(context, 4); context.addField("U_TLAY.layoutStandard", layoutStandard); if (layoutStandard.compare("RCT2") != 0 && layoutStandard.compare("PLAI") != 0) { @@ -35,17 +35,17 @@ namespace interpreter::detail::uic auto recordJson = ::utility::JsonBuilder::object(); // clang-format off recordJson - .add("fields", ::utility::toArray(std::stoi(common::consumeString(context, 4)), [&](auto &builder) + .add("fields", ::utility::toArray(std::stoi(common::StringDecoder::consumeString(context, 4)), [&](auto &builder) { builder - .add("line", std::stoi(common::consumeString(context, 2))) - .add("column", std::stoi(common::consumeString(context, 2))) - .add("height", std::stoi(common::consumeString(context, 2))) - .add("width", std::stoi(common::consumeString(context, 2))) - .add("formatting", common::consumeString(context, 1)); + .add("line", std::stoi(common::StringDecoder::consumeString(context, 2))) + .add("column", std::stoi(common::StringDecoder::consumeString(context, 2))) + .add("height", std::stoi(common::StringDecoder::consumeString(context, 2))) + .add("width", std::stoi(common::StringDecoder::consumeString(context, 2))) + .add("formatting", common::StringDecoder::consumeString(context, 1)); - auto const length = std::stoi(common::consumeString(context, 4)); + auto const length = std::stoi(common::StringDecoder::consumeString(context, 4)); builder - .add("text", common::consumeString(context, length)); + .add("text", common::StringDecoder::consumeString(context, length)); })); // clang-format on context.addRecord(common::Record(header.recordId, header.recordVersion, std::move(recordJson))); diff --git a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp index 897a269b..c81dc6d0 100644 --- a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp +++ b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp @@ -12,7 +12,7 @@ #include "../include/Record118199.h" #include "../include/Deflator.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" #include "lib/interpreter/detail/common/include/Field.h" #include "lib/interpreter/detail/common/include/Record.h" @@ -57,7 +57,7 @@ namespace interpreter::detail::uic auto const tid = context.consumeBytes(typeId.size()); if (Uic918Interpreter::TypeIdType(tid.begin(), tid.end()) != typeId) { - throw std::runtime_error("Unexpected UIC918 type ID, expecting 0x" + common::bytesToHexString(typeId) + ", got: 0x" + common::bytesToHexString(tid)); + throw std::runtime_error("Unexpected UIC918 type ID, expecting 0x" + common::StringDecoder::bytesToHexString(typeId) + ", got: 0x" + common::StringDecoder::bytesToHexString(tid)); } if (context.getRemainingSize() < 2) @@ -66,7 +66,7 @@ namespace interpreter::detail::uic return std::move(context); } - auto const messageTypeVersion = common::consumeString(context, 2); + auto const messageTypeVersion = common::StringDecoder::consumeString(context, 2); auto const version = std::stoi(messageTypeVersion); // Might be "OTI" as well if (version != 1 && version != 2) @@ -78,15 +78,15 @@ namespace interpreter::detail::uic context.addField("raw", context.getAllBase64Encoded()); context.addField("uniqueMessageTypeId", "#UT"); context.addField("messageTypeVersion", messageTypeVersion); - auto const ricsCode = common::consumeString(context, 4); + auto const ricsCode = common::StringDecoder::consumeString(context, 4); context.addField("companyCode", ricsCode); - auto const keyId = common::consumeString(context, 5); + auto const keyId = common::StringDecoder::consumeString(context, 5); context.addField("signatureKeyId", keyId); auto const signatureLength = version == 2 ? 64 : 50; auto const signature = context.consumeBytes(signatureLength); auto const consumed = context.getConsumedSize(); - auto const messageLengthString = common::consumeString(context, 4); + auto const messageLengthString = common::StringDecoder::consumeString(context, 4); auto const messageLength = std::stoi(messageLengthString); context.addField("compressedMessageLength", std::to_string(messageLength)); if (messageLength < 0 || messageLength > context.getRemainingSize()) diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp index d267b264..d25b4673 100644 --- a/source/lib/interpreter/detail/vdv/source/Certificate.cpp +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -6,6 +6,7 @@ #include "lib/interpreter/detail/common/include/InterpreterUtility.h" #include "lib/interpreter/detail/common/include/TLVDecoder.h" #include "lib/interpreter/detail/common/include/BCDDecoder.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" #include #include @@ -39,7 +40,7 @@ namespace interpreter::detail::vdv Certificate Certificate::consumeFromEnvelope(common::Context &context) { auto const signatureData = common::TLVDecoder::consumeExpectedElement(context, {0x7f, 0x21}); - auto authority = common::bytesToHexString(common::TLVDecoder::consumeExpectedElement(context, {0x42})); + auto authority = common::StringDecoder::bytesToHexString(common::TLVDecoder::consumeExpectedElement(context, {0x42})); auto signatureContext = common::Context(signatureData); auto signature = Signature::consumeFrom(signatureContext); @@ -91,8 +92,8 @@ namespace interpreter::detail::vdv CertificateParticipant CertificateParticipant::consumeFrom(common::Context &context) { - auto region = common::bytesToString(context.consumeBytes(2)); - auto name = common::bytesToString(context.consumeBytes(3)); + auto region = common::StringDecoder::bytesToString(context.consumeBytes(2)); + auto name = common::StringDecoder::bytesToString(context.consumeBytes(3)); auto serviceIdenticator = common::consumeInteger1(context); auto algorithmReference = common::consumeInteger1(context); auto year = std::to_string(1990 + common::consumeInteger1(context)); @@ -173,7 +174,7 @@ namespace interpreter::detail::vdv CertificateAuthorization CertificateAuthorization::consumeFrom(common::Context &context) { - auto name = common::consumeString(context, 6); + auto name = common::StringDecoder::consumeString(context, 6); auto const serviceIndicator = common::consumeInteger1(context); return CertificateAuthorization{std::move(name), serviceIndicator}; } diff --git a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp index f14b310d..aaf9bf83 100644 --- a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp +++ b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp @@ -4,7 +4,7 @@ #include "../include/LDIFFileCertificateProvider.h" #include "lib/interpreter/detail/common/include/Context.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" #include "lib/interpreter/detail/common/include/TLVDecoder.h" #include "lib/utility/include/Base64.h" @@ -61,7 +61,7 @@ namespace interpreter::detail::vdv } else { - throw std::runtime_error(std::string("Unexpected tag found: ") + common::bytesToHexString(tag)); + throw std::runtime_error(std::string("Unexpected tag found: ") + common::StringDecoder::bytesToHexString(tag)); } } diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 010f155a..84289ccc 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -8,6 +8,8 @@ #include "lib/interpreter/detail/common/include/InterpreterUtility.h" #include "lib/interpreter/detail/common/include/TLVDecoder.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" +#include "lib/interpreter/detail/common/include/DateTimeDecoder.h" #include "lib/utility/include/Base64.h" @@ -50,8 +52,8 @@ namespace interpreter::detail::vdv context.ensureEmpty(); auto const remainderTail = common::Context(signature.remainder).consumeBytesEnd(5); - auto const signatureIdent = common::bytesToString(remainderTail.subspan(0, 3)); - auto const signatureVersion = common::bytesToHexString(remainderTail.subspan(3, 2)); + auto const signatureIdent = common::StringDecoder::bytesToString(remainderTail.subspan(0, 3)); + auto const signatureVersion = common::StringDecoder::bytesToHexString(remainderTail.subspan(3, 2)); auto jsonBuilder = utility::JsonBuilder::object(); jsonBuilder @@ -64,22 +66,30 @@ namespace interpreter::detail::vdv { auto messageContext = common::Context(*message); auto const messageTail = messageContext.consumeBytesEnd(5); - auto const messageIdent = common::bytesToString(messageTail.subspan(0, 3)); - auto const messageVersion = common::bytesToHexString(messageTail.subspan(3, 2)); + auto const messageIdent = common::StringDecoder::bytesToString(messageTail.subspan(0, 3)); + auto const messageVersion = common::StringDecoder::bytesToHexString(messageTail.subspan(3, 2)); jsonBuilder .add("ticketId", std::to_string(common::consumeInteger4(messageContext))) .add("ticketOrganisationId", std::to_string(common::consumeInteger2(messageContext))) .add("productNumber", std::to_string(common::consumeInteger2(messageContext))) .add("productOrganisationId", std::to_string(common::consumeInteger2(messageContext))) - .add("validFrom", common::consumeDateTimeCompact4(messageContext)) - .add("validTo", common::consumeDateTimeCompact4(messageContext)); + .add("validFrom", common::DateTimeDecoder::consumeDateTimeCompact4(messageContext)) + .add("validTo", common::DateTimeDecoder::consumeDateTimeCompact4(messageContext)); auto const product = common::TLVDecoder::consumeExpectedElement(messageContext, {0x85}); { auto productContext = common::Context(product); auto const unknown1 = common::TLVDecoder::consumeExpectedElement(productContext, {0xda}); auto const passenger = common::TLVDecoder::consumeExpectedElement(productContext, {0xdb}); - jsonBuilder; + { + auto passengerContext = common::Context(passenger); + auto const gender = std::to_string(passengerContext.consumeByte()); + auto const birthDate = common::DateTimeDecoder::consumeDateTimeCompact4(passengerContext); + auto const name = common::StringDecoder::bytesToString(passengerContext.consumeRemainingBytes()); + jsonBuilder + .add("name", name) + .add("gender", gender); + } } } diff --git a/source/test/interpreter/source/DateTimeDecoderTest.cpp b/source/test/interpreter/source/DateTimeDecoderTest.cpp new file mode 100644 index 00000000..6c611fe1 --- /dev/null +++ b/source/test/interpreter/source/DateTimeDecoderTest.cpp @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: (C) 2022 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "lib/interpreter/detail/common/include/Context.h" +#include "lib/interpreter/detail/common/include/DateTimeDecoder.h" + +namespace interpreter::detail::common +{ + TEST(consumeDateTimeCompact4, initial) + { + auto context = Context({0x28, 0x39, 0x70, 0x62}); + EXPECT_EQ(DateTimeDecoder::consumeDateTimeCompact4(context), "2010-01-25T14:03:02"); + } + + TEST(consumeDateTime3, initial) + { + auto context = Context({'2', '7', '1', '0', '2', '0', '2', '0', '1', '3', '4', '5'}); + EXPECT_EQ(DateTimeDecoder::consumeDateTime3(context), "2020-10-27T13:45:00"); + } + + TEST(consumeDate8, initial) + { + auto context = Context({'1', '3', '0', '1', '2', '0', '2', '1'}); + EXPECT_EQ(DateTimeDecoder::consumeDate8(context), "2021-01-13"); + } +} diff --git a/source/test/interpreter/source/InterpreterUtilityTest.cpp b/source/test/interpreter/source/InterpreterUtilityTest.cpp index 057d22db..3d8fa6bc 100644 --- a/source/test/interpreter/source/InterpreterUtilityTest.cpp +++ b/source/test/interpreter/source/InterpreterUtilityTest.cpp @@ -3,161 +3,64 @@ #include +#include "lib/interpreter/detail/common/include/Context.h" #include "lib/interpreter/detail/common/include/InterpreterUtility.h" namespace interpreter::detail::common { - TEST(consumeString, readAndStopAtNull) - { - auto const source = std::vector{'R', 'P', 'E', 'X', '4', 'F', '-', '4', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - auto context = Context(source, ""); - EXPECT_EQ(consumeString(context, 20), std::string("RPEX4F-4")); - } - - TEST(consumeString, readAndTrimTrailingSpaces) - { - auto const source = std::vector{'A', 'B', 'C', ' ', '\n', ' ', ' ', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - auto context = Context(source, ""); - EXPECT_EQ(consumeString(context, 20), std::string("ABC")); - } - - TEST(consumeString, readAll) - { - auto const source = std::vector{'R', 'P', 'E', 'X', '4', 'F', '-', '4'}; - auto context = Context(source, ""); - EXPECT_EQ(consumeString(context, 8), std::string("RPEX4F-4")); - } - - TEST(consumeString, readEmpty) - { - auto const source = std::vector{0}; - auto context = Context(source, ""); - EXPECT_EQ(consumeString(context, 8), std::string("")); - } - TEST(getNumeric, min8) { - auto const source = std::vector{0xff, 1, 0xff}; - auto context = Context(source, ""); + auto context = Context({0xff, 1, 0xff}); context.ignoreBytes(1); EXPECT_EQ(consumeInteger1(context), 1); } TEST(getNumeric, max8) { - auto const source = std::vector{0xfe, 0xff, 0xfe}; - auto context = Context(source, ""); + auto context = Context({0xfe, 0xff, 0xfe}); context.ignoreBytes(1); EXPECT_EQ(consumeInteger1(context), 255); } TEST(getNumeric, min16) { - auto const source = std::vector{0xff, 0, 1, 0xff}; - auto context = Context(source, ""); + auto context = Context({0xff, 0, 1, 0xff}); context.ignoreBytes(1); EXPECT_EQ(consumeInteger2(context), 1); } TEST(getNumeric, max16) { - auto const source = std::vector{0xfe, 0xff, 0xff, 0xfe}; - auto context = Context(source, ""); + auto context = Context({0xfe, 0xff, 0xff, 0xfe}); context.ignoreBytes(1); EXPECT_EQ(consumeInteger2(context), 65535); } TEST(getNumeric, min24) { - auto const source = std::vector{0xff, 0, 0, 1, 0xff}; // big endian 1 - auto context = Context(source, ""); + auto context = Context({0xff, 0, 0, 1, 0xff}); // big endian 1 context.ignoreBytes(1); EXPECT_EQ(consumeInteger3(context), 1); } TEST(getNumeric, max24) { - auto const source = std::vector{0xfe, 0xff, 0xff, 0xff, 0xfe}; - auto context = Context(source, ""); + auto context = Context({0xfe, 0xff, 0xff, 0xff, 0xfe}); context.ignoreBytes(1); EXPECT_EQ(consumeInteger3(context), 16777215); } TEST(getNumeric, min32) { - auto const source = std::vector{0xff, 0, 0, 0, 1, 0xff}; // big endian 1 - auto context = Context(source, ""); + auto context = Context({0xff, 0, 0, 0, 1, 0xff}); // big endian 1 context.ignoreBytes(1); EXPECT_EQ(consumeInteger4(context), 1); } TEST(getNumeric, max32) { - auto const source = std::vector{0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe}; - auto context = Context(source, ""); + auto context = Context({0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe}); context.ignoreBytes(1); EXPECT_EQ(consumeInteger4(context), 4294967295); } - - TEST(consumeDateTimeCompact4, initial) - { - auto const source = std::vector{0x28, 0x39, 0x70, 0x62}; - auto context = Context(source, ""); - EXPECT_EQ(consumeDateTimeCompact4(context), "2010-01-25T14:03:02"); - } - - TEST(consumeDateTime3, initial) - { - auto const source = std::vector{'2', '7', '1', '0', '2', '0', '2', '0', '1', '3', '4', '5'}; - auto context = Context(source, ""); - EXPECT_EQ(consumeDateTime3(context), "2020-10-27T13:45:00"); - } - - TEST(consumeDate8, initial) - { - auto const source = std::vector{'1', '3', '0', '1', '2', '0', '2', '1'}; - auto context = Context(source, ""); - EXPECT_EQ(consumeDate8(context), "2021-01-13"); - } - - TEST(bytesToHexString, filled) - { - auto const source = std::vector{0x12, 0x0A, 0xAB, 0x00, 0xFF}; - EXPECT_EQ(bytesToHexString(source), "120AAB00FF"); - } - - TEST(bytesToHexString, empty) - { - auto const source = std::vector{}; - EXPECT_EQ(bytesToHexString(source), ""); - } - - TEST(bytesToHexString, array) - { - EXPECT_EQ(bytesToHexString(std::array{0x12, 0x0A, 0xAB}), "120AAB"); - EXPECT_EQ(bytesToHexString(std::array{0x12, 0x0A}), "120A00"); - EXPECT_EQ(bytesToHexString(std::array{}), "000000"); - EXPECT_EQ(bytesToHexString(std::array{0x11, 0x22, 0x33, 0x44, 0x55}), "1122334455"); - } - - TEST(bytesToHexString, integer4) - { - EXPECT_EQ(bytesToHexString(std::uint32_t(0)), "00000000"); - EXPECT_EQ(bytesToHexString(std::uint32_t(1)), "00000001"); - EXPECT_EQ(bytesToHexString(std::uint32_t(0xffffffff)), "FFFFFFFF"); - } - - TEST(bytesToHexString, integer2) - { - EXPECT_EQ(bytesToHexString(std::uint16_t()), "0000"); - EXPECT_EQ(bytesToHexString(std::uint16_t(1)), "0001"); - EXPECT_EQ(bytesToHexString(std::uint16_t(0xffff)), "FFFF"); - } - - TEST(bytesToHexString, integer1) - { - EXPECT_EQ(bytesToHexString(std::uint8_t()), "00"); - EXPECT_EQ(bytesToHexString(std::uint8_t(1)), "01"); - EXPECT_EQ(bytesToHexString(std::uint8_t(0xff)), "FF"); - } } diff --git a/source/test/interpreter/source/StringDecoderTest.cpp b/source/test/interpreter/source/StringDecoderTest.cpp new file mode 100644 index 00000000..6dc26f6a --- /dev/null +++ b/source/test/interpreter/source/StringDecoderTest.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: (C) 2026 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "lib/interpreter/detail/common/include/Context.h" +#include "lib/interpreter/detail/common/include/StringDecoder.h" + +namespace interpreter::detail::common +{ + TEST(consumeString, readAndStopAtNull) + { + auto context = Context({'R', 'P', 'E', 'X', '4', 'F', '-', '4', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + EXPECT_EQ(StringDecoder::consumeString(context, 20), std::string("RPEX4F-4")); + } + + TEST(consumeString, readAndTrimTrailingSpaces) + { + auto context = Context({'A', 'B', 'C', ' ', '\n', ' ', ' ', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + EXPECT_EQ(StringDecoder::consumeString(context, 20), std::string("ABC")); + } + + TEST(consumeString, readAll) + { + auto context = Context({'R', 'P', 'E', 'X', '4', 'F', '-', '4'}); + EXPECT_EQ(StringDecoder::consumeString(context, 8), std::string("RPEX4F-4")); + } + + TEST(consumeString, readEmpty) + { + auto context = Context({0}); + EXPECT_EQ(StringDecoder::consumeString(context, 8), std::string("")); + } + + TEST(bytesToHexString, filled) + { + auto const source = std::vector{0x12, 0x0A, 0xAB, 0x00, 0xFF}; + EXPECT_EQ(StringDecoder::bytesToHexString(source), "120AAB00FF"); + } + + TEST(bytesToHexString, empty) + { + auto const source = std::vector{}; + EXPECT_EQ(StringDecoder::bytesToHexString(source), ""); + } + + TEST(bytesToHexString, array) + { + EXPECT_EQ(StringDecoder::bytesToHexString(std::array{}), "00"); + EXPECT_EQ(StringDecoder::bytesToHexString(std::array{0x12, 0x0A, 0xAB}), "120AAB"); + EXPECT_EQ(StringDecoder::bytesToHexString(std::array{0x12, 0x0A}), "120A00"); + EXPECT_EQ(StringDecoder::bytesToHexString(std::array{}), "000000"); + EXPECT_EQ(StringDecoder::bytesToHexString(std::array{0x11, 0x22, 0x33, 0x44, 0x55}), "1122334455"); + } + + TEST(bytesToHexString, integer4) + { + EXPECT_EQ(StringDecoder::bytesToHexString(std::uint32_t(0)), "00000000"); + EXPECT_EQ(StringDecoder::bytesToHexString(std::uint32_t(1)), "00000001"); + EXPECT_EQ(StringDecoder::bytesToHexString(std::uint32_t(0xffffffff)), "FFFFFFFF"); + } + + TEST(bytesToHexString, integer2) + { + EXPECT_EQ(StringDecoder::bytesToHexString(std::uint16_t()), "0000"); + EXPECT_EQ(StringDecoder::bytesToHexString(std::uint16_t(1)), "0001"); + EXPECT_EQ(StringDecoder::bytesToHexString(std::uint16_t(0xffff)), "FFFF"); + } + + TEST(bytesToHexString, integer1) + { + EXPECT_EQ(StringDecoder::bytesToHexString(std::uint8_t()), "00"); + EXPECT_EQ(StringDecoder::bytesToHexString(std::uint8_t(1)), "01"); + EXPECT_EQ(StringDecoder::bytesToHexString(std::uint8_t(0xff)), "FF"); + } +} From 9b7431f67891fc79a8dcb6ccc327f2d2c29c3bfc Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 21 Feb 2026 16:56:18 +0100 Subject: [PATCH 32/41] Add utf8 test case 4 StringDecoder --- .../detail/common/include/StringDecoder.h | 4 ++-- .../interpreter/source/StringDecoderTest.cpp | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/source/lib/interpreter/detail/common/include/StringDecoder.h b/source/lib/interpreter/detail/common/include/StringDecoder.h index 455bf89a..89938171 100644 --- a/source/lib/interpreter/detail/common/include/StringDecoder.h +++ b/source/lib/interpreter/detail/common/include/StringDecoder.h @@ -18,10 +18,10 @@ namespace interpreter::detail::common class StringDecoder { public: - /* Consumes maximumSize or less bytes and returns a 0 terminated string. + /* Consumes maximumBytes or less bytes (NOT chars) and returns a 0 terminated string. When the buffer is filled by multiple 0 at the end, the returned string might be shorter. */ - static std::string consumeString(Context &context, std::size_t maximumSize); + static std::string consumeString(Context &context, std::size_t maximumBytes); static std::string bytesToString(std::span bytes); diff --git a/source/test/interpreter/source/StringDecoderTest.cpp b/source/test/interpreter/source/StringDecoderTest.cpp index 6dc26f6a..10de5382 100644 --- a/source/test/interpreter/source/StringDecoderTest.cpp +++ b/source/test/interpreter/source/StringDecoderTest.cpp @@ -8,25 +8,36 @@ namespace interpreter::detail::common { - TEST(consumeString, readAndStopAtNull) + TEST(StringDecoder, consumeUtf8) + { + auto const buffer = std::string("öäü"); + auto context = Context(std::span{(std::uint8_t const *)buffer.data(), 6}); + EXPECT_EQ(StringDecoder::consumeString(context, 6), std::string("öäü")); + + context = Context({0xC3, 0xBC, 0xC3, 0xB6, 0xC3, 0xA4, 0xC3, 0x9F, 0xC3, 0x9C, 0xC3, 0x96, 0xC3, 0x84}); + EXPECT_EQ(StringDecoder::consumeString(context, 6), std::string("üöä")); + EXPECT_EQ(StringDecoder::consumeString(context, 8), std::string("ßÜÖÄ")); + } + + TEST(StringDecoder, consumeAndStopAtNull) { auto context = Context({'R', 'P', 'E', 'X', '4', 'F', '-', '4', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); EXPECT_EQ(StringDecoder::consumeString(context, 20), std::string("RPEX4F-4")); } - TEST(consumeString, readAndTrimTrailingSpaces) + TEST(StringDecoder, consumeAndTrimTrailingSpaces) { auto context = Context({'A', 'B', 'C', ' ', '\n', ' ', ' ', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); EXPECT_EQ(StringDecoder::consumeString(context, 20), std::string("ABC")); } - TEST(consumeString, readAll) + TEST(StringDecoder, consumeAll) { auto context = Context({'R', 'P', 'E', 'X', '4', 'F', '-', '4'}); EXPECT_EQ(StringDecoder::consumeString(context, 8), std::string("RPEX4F-4")); } - TEST(consumeString, readEmpty) + TEST(StringDecoder, consumeEmpty) { auto context = Context({0}); EXPECT_EQ(StringDecoder::consumeString(context, 8), std::string("")); From 8c5e3270537907096793e1a14bdc0b2d2408783f Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 21 Feb 2026 18:56:30 +0100 Subject: [PATCH 33/41] Separate utf8 string decoding from iso 8859-1/latin-1 string decoding --- .../detail/common/include/StringDecoder.h | 19 ++++- .../detail/common/source/DateTimeDecoder.cpp | 4 +- .../detail/common/source/StringDecoder.cpp | 69 +++++++++++++++---- .../detail/uic918/source/Record0080BL.cpp | 16 ++--- .../detail/uic918/source/Record118199.cpp | 2 +- .../detail/uic918/source/RecordHeader.cpp | 6 +- .../detail/uic918/source/RecordU_HEAD.cpp | 10 +-- .../detail/uic918/source/RecordU_TLAY.cpp | 18 ++--- .../uic918/source/Uic918Interpreter.cpp | 8 +-- .../detail/vdv/source/Certificate.cpp | 6 +- .../detail/vdv/source/VDVInterpreter.cpp | 12 ++-- .../interpreter/source/StringDecoderTest.cpp | 56 ++++++++++++--- 12 files changed, 159 insertions(+), 67 deletions(-) diff --git a/source/lib/interpreter/detail/common/include/StringDecoder.h b/source/lib/interpreter/detail/common/include/StringDecoder.h index 89938171..8151821d 100644 --- a/source/lib/interpreter/detail/common/include/StringDecoder.h +++ b/source/lib/interpreter/detail/common/include/StringDecoder.h @@ -18,12 +18,25 @@ namespace interpreter::detail::common class StringDecoder { public: - /* Consumes maximumBytes or less bytes (NOT chars) and returns a 0 terminated string. + /* Consumes maximumBytes or less bytes (NOT chars) and returns a 0 terminated UTF8 string. When the buffer is filled by multiple 0 at the end, the returned string might be shorter. + In case you are sure the buffer contains ASCII only, the method works fine and is the + preferred method over consumeLatin1 since it avoids additional conversion. */ - static std::string consumeString(Context &context, std::size_t maximumBytes); + static std::string consumeUTF8(Context &context, std::size_t maximumBytes); - static std::string bytesToString(std::span bytes); + static std::string decodeUTF8(std::span bytes); + + /* Consumes maximumBytes or less ISO 8859-1 (Latin-1) encoded bytes and returns a 0 + terminated UTF8 string. + When the buffer is filled by multiple 0 at the end, the returned string might be shorter. + */ + static std::string consumeLatin1(Context &context, std::size_t maximumBytes); + + static std::string decodeLatin1(std::span bytes); + + /* + */ static std::string bytesToHexString(std::span bytes); diff --git a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp index 314130bd..4778e304 100644 --- a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp @@ -28,7 +28,7 @@ namespace interpreter::detail::common std::string DateTimeDecoder::consumeDateTime3(Context &context) { - auto const input = StringDecoder::consumeString(context, 12); + auto const input = StringDecoder::consumeUTF8(context, 12); auto const p = input.begin(); // TODO Use chrono parse or apply validation for all values std::ostringstream os; // DDMMYYYYHHMM @@ -43,7 +43,7 @@ namespace interpreter::detail::common std::string DateTimeDecoder::consumeDate8(Context &context) { - auto const input = StringDecoder::consumeString(context, 8); + auto const input = StringDecoder::consumeUTF8(context, 8); auto const p = input.begin(); // TODO Use chrono parse or apply validation for all values std::ostringstream os; // DDMMYYYY diff --git a/source/lib/interpreter/detail/common/source/StringDecoder.cpp b/source/lib/interpreter/detail/common/source/StringDecoder.cpp index 68af3095..7350512b 100644 --- a/source/lib/interpreter/detail/common/source/StringDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/StringDecoder.cpp @@ -12,23 +12,66 @@ namespace interpreter::detail::common { - std::string StringDecoder::consumeString(Context &context, std::size_t size) + static std::string latin1ToUtf8(std::string &&latin1) { - auto const data = context.consumeMaximalBytes(size); - return bytesToString(data); + auto utf8 = std::string{}; + utf8.reserve(latin1.capacity()); + for (std::uint8_t const &ch : latin1) + { + if (ch < 0x80) + { + utf8.push_back(ch); + } + else + { + utf8.push_back(0xc0 | ch >> 6); + utf8.push_back(0x80 | (ch & 0x3f)); + } + } + return utf8; + } + + static constexpr std::string toString(std::span bytes) + { + auto const first = std::begin(bytes); + return std::string{first, std::find(first, std::end(bytes), '\0')}; + } + + static std::string removeTrailingSpaces(std::string &&input) + { + auto const last = std::rbegin(input); + auto const position = std::find_if(last, std::rend(input), [](unsigned char ch) + { return !std::isspace(ch); }) + .base(); + + auto const end = std::end(input); + if (position != end) + { + input.erase(position, end); + } + return input; + } + + std::string StringDecoder::consumeUTF8(Context &context, std::size_t maximumBytes) + { + auto const data = context.consumeMaximalBytes(maximumBytes); + return decodeUTF8(data); + } + + std::string StringDecoder::decodeUTF8(std::span bytes) + { + return removeTrailingSpaces(toString(bytes)); + } + + std::string StringDecoder::consumeLatin1(Context &context, std::size_t maximumBytes) + { + auto const data = context.consumeMaximalBytes(maximumBytes); + return decodeLatin1(data); } - std::string StringDecoder::bytesToString(std::span bytes) + std::string StringDecoder::decodeLatin1(std::span bytes) { - // auto ascii = std::vector(); - // std::transform(std::begin(bytes), std::end(bytes), std::back_inserter(ascii), [](std::uint8_t const &v) - // { return v >= 128 ? ' ' : v; }); - auto result = std::string{std::begin(bytes), std::find(std::begin(bytes), std::end(bytes), '\0')}; - result.erase(std::find_if(std::rbegin(result), std::rend(result), [](unsigned char ch) - { return !std::isspace(ch); }) - .base(), - std::end(result)); - return result; + return removeTrailingSpaces(latin1ToUtf8(toString(bytes))); } std::string StringDecoder::bytesToHexString(std::span bytes) diff --git a/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp b/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp index a72af14a..5b0f2a71 100644 --- a/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp +++ b/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp @@ -78,14 +78,14 @@ namespace interpreter::detail::uic builder .add("validFrom", common::DateTimeDecoder::consumeDate8(context)) .add("validTo", common::DateTimeDecoder::consumeDate8(context)) - .add("serial", common::StringDecoder::consumeString(context, 8)); + .add("serial", common::StringDecoder::consumeUTF8(context, 8)); }}, {std::string("03"), [](auto &context, auto &builder) { builder .add("validFrom", common::DateTimeDecoder::consumeDate8(context)) .add("validTo", common::DateTimeDecoder::consumeDate8(context)) - .add("serial", common::StringDecoder::consumeString(context, 10)); + .add("serial", common::StringDecoder::consumeUTF8(context, 10)); }}}; Record0080BL::Record0080BL(infrastructure::LoggerFactory &loggerFactory, RecordHeader &&h) @@ -100,14 +100,14 @@ namespace interpreter::detail::uic auto recordJson = ::utility::JsonBuilder::object(); // clang-format off recordJson - .add("ticketType", common::StringDecoder::consumeString(context, 2)) - .add("trips", ::utility::toArray(std::stoi(common::StringDecoder::consumeString(context, 1)), [&](auto &builder) + .add("ticketType", common::StringDecoder::consumeUTF8(context, 2)) + .add("trips", ::utility::toArray(std::stoi(common::StringDecoder::consumeUTF8(context, 1)), [&](auto &builder) { tripInterpreter(context, builder); })) - .add("fields", ::utility::toObject(std::stoi(common::StringDecoder::consumeString(context, 2)), [&](auto & builder) + .add("fields", ::utility::toObject(std::stoi(common::StringDecoder::consumeUTF8(context, 2)), [&](auto & builder) { - auto const type = common::StringDecoder::consumeString(context, 4); - auto const length = std::stoi(common::StringDecoder::consumeString(context, 4)); - auto const content = common::StringDecoder::consumeString(context, length); + auto const type = common::StringDecoder::consumeUTF8(context, 4); + auto const length = std::stoi(common::StringDecoder::consumeUTF8(context, 4)); + auto const content = common::StringDecoder::consumeUTF8(context, length); auto const annotation = annotationMap.find(type); builder diff --git a/source/lib/interpreter/detail/uic918/source/Record118199.cpp b/source/lib/interpreter/detail/uic918/source/Record118199.cpp index c6473d14..b681122b 100644 --- a/source/lib/interpreter/detail/uic918/source/Record118199.cpp +++ b/source/lib/interpreter/detail/uic918/source/Record118199.cpp @@ -20,7 +20,7 @@ namespace interpreter::detail::uic common::Context Record118199::interpret(common::Context &&context) { - auto const jsonString = common::StringDecoder::consumeString(context, context.getRemainingSize()); + auto const jsonString = common::StringDecoder::consumeUTF8(context, context.getRemainingSize()); context.addRecord(common::Record(header.recordId, header.recordVersion, ::utility::JsonBuilder(jsonString))); return std::move(context); diff --git a/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp b/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp index 5a44229d..d190ff24 100644 --- a/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp +++ b/source/lib/interpreter/detail/uic918/source/RecordHeader.cpp @@ -12,9 +12,9 @@ namespace interpreter::detail::uic { RecordHeader::RecordHeader(common::Context &context) : start(context.getPosition()), - recordId(common::StringDecoder::consumeString(context, 6)), - recordVersion(common::StringDecoder::consumeString(context, 2)), - recordLength(std::stoi(common::StringDecoder::consumeString(context, 4))) + recordId(common::StringDecoder::consumeUTF8(context, 6)), + recordVersion(common::StringDecoder::consumeUTF8(context, 2)), + recordLength(std::stoi(common::StringDecoder::consumeUTF8(context, 4))) { context.addField(recordId + ".recordId", recordId); context.addField(recordId + ".recordVersion", recordVersion); diff --git a/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp b/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp index de537193..4187dda7 100644 --- a/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp +++ b/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp @@ -22,12 +22,12 @@ namespace interpreter::detail::uic { auto recordJson = ::utility::JsonBuilder::object(); recordJson - .add("companyCode", common::StringDecoder::consumeString(context, 4)) - .add("uniqueTicketKey", common::StringDecoder::consumeString(context, 20)) + .add("companyCode", common::StringDecoder::consumeUTF8(context, 4)) + .add("uniqueTicketKey", common::StringDecoder::consumeUTF8(context, 20)) .add("editionTime", common::DateTimeDecoder::consumeDateTime3(context)) - .add("flags", common::StringDecoder::consumeString(context, 1)) - .add("editionLanguageOfTicket", common::StringDecoder::consumeString(context, 2)) - .add("secondLanguageOfContract", common::StringDecoder::consumeString(context, 2)); + .add("flags", common::StringDecoder::consumeUTF8(context, 1)) + .add("editionLanguageOfTicket", common::StringDecoder::consumeUTF8(context, 2)) + .add("secondLanguageOfContract", common::StringDecoder::consumeUTF8(context, 2)); context.addRecord(common::Record(header.recordId, header.recordVersion, std::move(recordJson))); return std::move(context); diff --git a/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp b/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp index d2311d51..8e07e9f1 100644 --- a/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp +++ b/source/lib/interpreter/detail/uic918/source/RecordU_TLAY.cpp @@ -24,7 +24,7 @@ namespace interpreter::detail::uic common::Context RecordU_TLAY::interpret(common::Context &&context) { - auto const layoutStandard = common::StringDecoder::consumeString(context, 4); + auto const layoutStandard = common::StringDecoder::consumeUTF8(context, 4); context.addField("U_TLAY.layoutStandard", layoutStandard); if (layoutStandard.compare("RCT2") != 0 && layoutStandard.compare("PLAI") != 0) { @@ -35,17 +35,17 @@ namespace interpreter::detail::uic auto recordJson = ::utility::JsonBuilder::object(); // clang-format off recordJson - .add("fields", ::utility::toArray(std::stoi(common::StringDecoder::consumeString(context, 4)), [&](auto &builder) + .add("fields", ::utility::toArray(std::stoi(common::StringDecoder::consumeUTF8(context, 4)), [&](auto &builder) { builder - .add("line", std::stoi(common::StringDecoder::consumeString(context, 2))) - .add("column", std::stoi(common::StringDecoder::consumeString(context, 2))) - .add("height", std::stoi(common::StringDecoder::consumeString(context, 2))) - .add("width", std::stoi(common::StringDecoder::consumeString(context, 2))) - .add("formatting", common::StringDecoder::consumeString(context, 1)); + .add("line", std::stoi(common::StringDecoder::consumeUTF8(context, 2))) + .add("column", std::stoi(common::StringDecoder::consumeUTF8(context, 2))) + .add("height", std::stoi(common::StringDecoder::consumeUTF8(context, 2))) + .add("width", std::stoi(common::StringDecoder::consumeUTF8(context, 2))) + .add("formatting", common::StringDecoder::consumeUTF8(context, 1)); - auto const length = std::stoi(common::StringDecoder::consumeString(context, 4)); + auto const length = std::stoi(common::StringDecoder::consumeUTF8(context, 4)); builder - .add("text", common::StringDecoder::consumeString(context, length)); + .add("text", common::StringDecoder::consumeUTF8(context, length)); })); // clang-format on context.addRecord(common::Record(header.recordId, header.recordVersion, std::move(recordJson))); diff --git a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp index c81dc6d0..8cf5c0f2 100644 --- a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp +++ b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp @@ -66,7 +66,7 @@ namespace interpreter::detail::uic return std::move(context); } - auto const messageTypeVersion = common::StringDecoder::consumeString(context, 2); + auto const messageTypeVersion = common::StringDecoder::consumeUTF8(context, 2); auto const version = std::stoi(messageTypeVersion); // Might be "OTI" as well if (version != 1 && version != 2) @@ -78,15 +78,15 @@ namespace interpreter::detail::uic context.addField("raw", context.getAllBase64Encoded()); context.addField("uniqueMessageTypeId", "#UT"); context.addField("messageTypeVersion", messageTypeVersion); - auto const ricsCode = common::StringDecoder::consumeString(context, 4); + auto const ricsCode = common::StringDecoder::consumeUTF8(context, 4); context.addField("companyCode", ricsCode); - auto const keyId = common::StringDecoder::consumeString(context, 5); + auto const keyId = common::StringDecoder::consumeUTF8(context, 5); context.addField("signatureKeyId", keyId); auto const signatureLength = version == 2 ? 64 : 50; auto const signature = context.consumeBytes(signatureLength); auto const consumed = context.getConsumedSize(); - auto const messageLengthString = common::StringDecoder::consumeString(context, 4); + auto const messageLengthString = common::StringDecoder::consumeUTF8(context, 4); auto const messageLength = std::stoi(messageLengthString); context.addField("compressedMessageLength", std::to_string(messageLength)); if (messageLength < 0 || messageLength > context.getRemainingSize()) diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp index d25b4673..be97aaf8 100644 --- a/source/lib/interpreter/detail/vdv/source/Certificate.cpp +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -92,8 +92,8 @@ namespace interpreter::detail::vdv CertificateParticipant CertificateParticipant::consumeFrom(common::Context &context) { - auto region = common::StringDecoder::bytesToString(context.consumeBytes(2)); - auto name = common::StringDecoder::bytesToString(context.consumeBytes(3)); + auto region = common::StringDecoder::decodeLatin1(context.consumeBytes(2)); + auto name = common::StringDecoder::decodeLatin1(context.consumeBytes(3)); auto serviceIdenticator = common::consumeInteger1(context); auto algorithmReference = common::consumeInteger1(context); auto year = std::to_string(1990 + common::consumeInteger1(context)); @@ -174,7 +174,7 @@ namespace interpreter::detail::vdv CertificateAuthorization CertificateAuthorization::consumeFrom(common::Context &context) { - auto name = common::StringDecoder::consumeString(context, 6); + auto name = common::StringDecoder::consumeUTF8(context, 6); auto const serviceIndicator = common::consumeInteger1(context); return CertificateAuthorization{std::move(name), serviceIndicator}; } diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 84289ccc..ae20bab7 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -10,6 +10,7 @@ #include "lib/interpreter/detail/common/include/TLVDecoder.h" #include "lib/interpreter/detail/common/include/StringDecoder.h" #include "lib/interpreter/detail/common/include/DateTimeDecoder.h" +#include "lib/interpreter/detail/common/include/BCDDecoder.h" #include "lib/utility/include/Base64.h" @@ -39,6 +40,7 @@ namespace interpreter::detail::vdv { // Documentation (not fully matching anymore): // - https://www.kcd-nrw.de/fileadmin/03_KC_Seiten/KCD/Downloads/Technische_Dokumente/Archiv/2010_02_12_kompendiumvrrfa2dvdv_1_4.pdf + // - https://www.kcd-nrw.de/fileadmin/user_upload/Abbildung_und_Kontrolle_in_NRW_1_5_5.pdf // Quite old reference impl for encoding: // - https://sourceforge.net/projects/dbuic2vdvbc/ // Some more up-to-date hints: @@ -52,8 +54,8 @@ namespace interpreter::detail::vdv context.ensureEmpty(); auto const remainderTail = common::Context(signature.remainder).consumeBytesEnd(5); - auto const signatureIdent = common::StringDecoder::bytesToString(remainderTail.subspan(0, 3)); - auto const signatureVersion = common::StringDecoder::bytesToHexString(remainderTail.subspan(3, 2)); + auto const signatureIdent = common::StringDecoder::decodeLatin1(remainderTail.subspan(0, 3)); + auto const signatureVersion = std::to_string(common::BCDDecoder::decodePackedInteger2(remainderTail.subspan(3, 2))); auto jsonBuilder = utility::JsonBuilder::object(); jsonBuilder @@ -66,8 +68,8 @@ namespace interpreter::detail::vdv { auto messageContext = common::Context(*message); auto const messageTail = messageContext.consumeBytesEnd(5); - auto const messageIdent = common::StringDecoder::bytesToString(messageTail.subspan(0, 3)); - auto const messageVersion = common::StringDecoder::bytesToHexString(messageTail.subspan(3, 2)); + auto const messageIdent = common::StringDecoder::decodeLatin1(messageTail.subspan(0, 3)); + auto const messageVersion = common::BCDDecoder::decodePackedInteger2(messageTail.subspan(3, 2)); jsonBuilder .add("ticketId", std::to_string(common::consumeInteger4(messageContext))) .add("ticketOrganisationId", std::to_string(common::consumeInteger2(messageContext))) @@ -85,7 +87,7 @@ namespace interpreter::detail::vdv auto passengerContext = common::Context(passenger); auto const gender = std::to_string(passengerContext.consumeByte()); auto const birthDate = common::DateTimeDecoder::consumeDateTimeCompact4(passengerContext); - auto const name = common::StringDecoder::bytesToString(passengerContext.consumeRemainingBytes()); + auto const name = common::StringDecoder::decodeLatin1(passengerContext.consumeRemainingBytes()); jsonBuilder .add("name", name) .add("gender", gender); diff --git a/source/test/interpreter/source/StringDecoderTest.cpp b/source/test/interpreter/source/StringDecoderTest.cpp index 10de5382..2907c448 100644 --- a/source/test/interpreter/source/StringDecoderTest.cpp +++ b/source/test/interpreter/source/StringDecoderTest.cpp @@ -12,35 +12,69 @@ namespace interpreter::detail::common { auto const buffer = std::string("öäü"); auto context = Context(std::span{(std::uint8_t const *)buffer.data(), 6}); - EXPECT_EQ(StringDecoder::consumeString(context, 6), std::string("öäü")); + EXPECT_EQ(StringDecoder::consumeUTF8(context, 6), std::string("öäü")); context = Context({0xC3, 0xBC, 0xC3, 0xB6, 0xC3, 0xA4, 0xC3, 0x9F, 0xC3, 0x9C, 0xC3, 0x96, 0xC3, 0x84}); - EXPECT_EQ(StringDecoder::consumeString(context, 6), std::string("üöä")); - EXPECT_EQ(StringDecoder::consumeString(context, 8), std::string("ßÜÖÄ")); + EXPECT_EQ(StringDecoder::consumeUTF8(context, 6), std::string("üöä")); + EXPECT_EQ(StringDecoder::consumeUTF8(context, 8), std::string("ßÜÖÄ")); } - TEST(StringDecoder, consumeAndStopAtNull) + TEST(StringDecoder, consumeUtf8StopAtNull) { auto context = Context({'R', 'P', 'E', 'X', '4', 'F', '-', '4', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); - EXPECT_EQ(StringDecoder::consumeString(context, 20), std::string("RPEX4F-4")); + EXPECT_EQ(StringDecoder::consumeUTF8(context, 20), std::string("RPEX4F-4")); } - TEST(StringDecoder, consumeAndTrimTrailingSpaces) + TEST(StringDecoder, consumeUtf8TrimTrailingSpaces) { auto context = Context({'A', 'B', 'C', ' ', '\n', ' ', ' ', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); - EXPECT_EQ(StringDecoder::consumeString(context, 20), std::string("ABC")); + EXPECT_EQ(StringDecoder::consumeUTF8(context, 20), std::string("ABC")); } - TEST(StringDecoder, consumeAll) + TEST(StringDecoder, consumeUtf8All) { auto context = Context({'R', 'P', 'E', 'X', '4', 'F', '-', '4'}); - EXPECT_EQ(StringDecoder::consumeString(context, 8), std::string("RPEX4F-4")); + EXPECT_EQ(StringDecoder::consumeUTF8(context, 8), std::string("RPEX4F-4")); } - TEST(StringDecoder, consumeEmpty) + TEST(StringDecoder, consumeUtf8Empty) { auto context = Context({0}); - EXPECT_EQ(StringDecoder::consumeString(context, 8), std::string("")); + EXPECT_EQ(StringDecoder::consumeUTF8(context, 8), std::string("")); + } + + TEST(StringDecoder, consumeLatin1) + { + auto context = Context({0xf6, 0xe4, 0xfc}); + EXPECT_EQ(StringDecoder::consumeLatin1(context, 6), std::string("öäü")); + + context = Context({0xfc, 0xf6, 0xe4, 0xdf, 0xdc, 0xd6, 0xc4}); + EXPECT_EQ(StringDecoder::consumeLatin1(context, 3), std::string("üöä")); + EXPECT_EQ(StringDecoder::consumeLatin1(context, 4), std::string("ßÜÖÄ")); + } + + TEST(StringDecoder, consumeLatin1StopAtNull) + { + auto context = Context({'R', 'P', 'E', 'X', '4', 'F', '-', '4', 0}); + EXPECT_EQ(StringDecoder::consumeLatin1(context, 20), std::string("RPEX4F-4")); + } + + TEST(StringDecoder, consumeLatin1TrimTrailingSpaces) + { + auto context = Context({'A', 'B', 'C', ' ', '\n', ' ', ' ', ' ', 0}); + EXPECT_EQ(StringDecoder::consumeLatin1(context, 20), std::string("ABC")); + } + + TEST(StringDecoder, consumeLatin1All) + { + auto context = Context({'R', 'P', 'E', 'X', '4', 'F', '-', '4'}); + EXPECT_EQ(StringDecoder::consumeLatin1(context, 8), std::string("RPEX4F-4")); + } + + TEST(StringDecoder, consumeLatin1Empty) + { + auto context = Context({0}); + EXPECT_EQ(StringDecoder::consumeLatin1(context, 8), std::string("")); } TEST(bytesToHexString, filled) From e2abf3c0a16597f4c96f61cc8371dd2da462635c Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 22 Feb 2026 01:06:28 +0100 Subject: [PATCH 34/41] Improve StringDecoder + testing and ascii support --- .../interpreter/api/source/Interpreter.cpp | 2 +- .../detail/common/include/DateTimeDecoder.h | 6 +- .../detail/common/include/StringDecoder.h | 22 +++-- .../detail/common/include/TLVDecoder.h | 12 +++ .../detail/common/source/DateTimeDecoder.cpp | 7 +- .../detail/common/source/StringDecoder.cpp | 38 +++++-- .../detail/common/source/TLVDecoder.cpp | 22 +++++ .../detail/sbb/source/SBBInterpreter.cpp | 2 +- .../detail/uic918/source/RecordU_HEAD.cpp | 2 +- .../uic918/source/Uic918Interpreter.cpp | 2 +- .../detail/vdv/source/Certificate.cpp | 2 +- .../source/LDIFFileCertificateProvider.cpp | 2 +- .../detail/vdv/source/VDVInterpreter.cpp | 2 +- .../source/DateTimeDecoderTest.cpp | 14 ++- .../interpreter/source/StringDecoderTest.cpp | 98 ++++++++++++++----- 15 files changed, 174 insertions(+), 59 deletions(-) diff --git a/source/lib/interpreter/api/source/Interpreter.cpp b/source/lib/interpreter/api/source/Interpreter.cpp index 06fecca5..679bb100 100644 --- a/source/lib/interpreter/api/source/Interpreter.cpp +++ b/source/lib/interpreter/api/source/Interpreter.cpp @@ -60,7 +60,7 @@ namespace interpreter::api auto const interpreter = interpreterMap.find(detail::common::Interpreter::TypeIdType(typeId.begin(), typeId.end())); if (interpreter == interpreterMap.end()) { - LOG_WARN(logger) << "Unknown message type: 0x" << detail::common::StringDecoder::bytesToHexString(typeId); + LOG_WARN(logger) << "Unknown message type: 0x" << detail::common::StringDecoder::toHexString(typeId); return std::move(context); } diff --git a/source/lib/interpreter/detail/common/include/DateTimeDecoder.h b/source/lib/interpreter/detail/common/include/DateTimeDecoder.h index 727308f7..7829ccfa 100644 --- a/source/lib/interpreter/detail/common/include/DateTimeDecoder.h +++ b/source/lib/interpreter/detail/common/include/DateTimeDecoder.h @@ -16,11 +16,11 @@ namespace interpreter::detail::common */ static std::string consumeDateTimeCompact4(Context &context); - /* Consumes 3 bytes and decodes date-time to ISO-8601 format + /* Consumes 12 bytes (ASCII) and decodes date-time to ISO-8601 format */ - static std::string consumeDateTime3(Context &context); + static std::string consumeDateTime12(Context &context); - /* Consumes 8 bytes and decodes date to ISO-8601 format + /* Consumes 8 bytes (ASCII) and decodes date to ISO-8601 format */ static std::string consumeDate8(Context &context); }; diff --git a/source/lib/interpreter/detail/common/include/StringDecoder.h b/source/lib/interpreter/detail/common/include/StringDecoder.h index 8151821d..2fb7c247 100644 --- a/source/lib/interpreter/detail/common/include/StringDecoder.h +++ b/source/lib/interpreter/detail/common/include/StringDecoder.h @@ -35,26 +35,32 @@ namespace interpreter::detail::common static std::string decodeLatin1(std::span bytes); - /* + /* Consumes maximumBytes or less bytes and ensures it's plain ASCII (<128) and returns a + 0 terminated UTF8 string (which is the same as ASCII in this case). */ + static std::string consumeASCII(Context &context, std::size_t maximumBytes, bool ensurePrintable = false); - static std::string bytesToHexString(std::span bytes); + static std::string decodeASCII(std::span bytes, bool ensurePrintable = false); - static std::string bytesToHexString(std::vector const &bytes); + /* Returns a string containing the hexadecimal representation of the given byte buffer. + */ + static std::string toHexString(std::span bytes); + + static std::string toHexString(std::vector const &bytes); template - static std::string bytesToHexString(std::array const &bytes) + static std::string toHexString(std::array const &bytes) { - return bytesToHexString(std::span(bytes.data(), S)); + return toHexString(std::span(bytes.data(), S)); } template - static std::string bytesToHexString(T const &bytes) + static std::string toHexString(T const &bytes) { auto const raw = std::span((std::uint8_t const *const)&bytes, sizeof(T)); return std::endian::native == std::endian::big - ? bytesToHexString(raw) - : bytesToHexString(std::vector(raw.rbegin(), raw.rend())); + ? toHexString(raw) + : toHexString(std::vector(raw.rbegin(), raw.rend())); } }; } diff --git a/source/lib/interpreter/detail/common/include/TLVDecoder.h b/source/lib/interpreter/detail/common/include/TLVDecoder.h index 33c2ccb2..88a5d0b6 100644 --- a/source/lib/interpreter/detail/common/include/TLVDecoder.h +++ b/source/lib/interpreter/detail/common/include/TLVDecoder.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include namespace interpreter::detail::common { @@ -58,6 +60,8 @@ namespace interpreter::detail::common constexpr bool operator!=(TLVTag const &rhs) const { return currentSize != rhs.currentSize || value != rhs.value; } + constexpr bool operator<(TLVTag const &rhs) const { return currentSize < rhs.currentSize && value < rhs.value; }; + void ensureEqual(TLVTag const &rhs) const; std::string toHexString() const; @@ -65,7 +69,15 @@ namespace interpreter::detail::common class TLVDecoder { + using TagMapType = std::map)>>; + + TagMapType const tagMap; + public: + TLVDecoder(TagMapType tagMap); + + std::size_t consume(common::Context &context) const; + static TLVTag consumeTag(common::Context &context); static common::Context &consumeExpectedTag(common::Context &context, common::TLVTag const &expectedTag); diff --git a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp index 4778e304..ab1c1b46 100644 --- a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp @@ -26,9 +26,9 @@ namespace interpreter::detail::common return os.str(); } - std::string DateTimeDecoder::consumeDateTime3(Context &context) + std::string DateTimeDecoder::consumeDateTime12(Context &context) { - auto const input = StringDecoder::consumeUTF8(context, 12); + auto const input = StringDecoder::consumeASCII(context, 12, true); auto const p = input.begin(); // TODO Use chrono parse or apply validation for all values std::ostringstream os; // DDMMYYYYHHMM @@ -43,7 +43,7 @@ namespace interpreter::detail::common std::string DateTimeDecoder::consumeDate8(Context &context) { - auto const input = StringDecoder::consumeUTF8(context, 8); + auto const input = StringDecoder::consumeASCII(context, 8, true); auto const p = input.begin(); // TODO Use chrono parse or apply validation for all values std::ostringstream os; // DDMMYYYY @@ -52,5 +52,4 @@ namespace interpreter::detail::common << std::string(p + 0, p + 2); return os.str(); } - } diff --git a/source/lib/interpreter/detail/common/source/StringDecoder.cpp b/source/lib/interpreter/detail/common/source/StringDecoder.cpp index 7350512b..3bc8bc02 100644 --- a/source/lib/interpreter/detail/common/source/StringDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/StringDecoder.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace interpreter::detail::common { @@ -54,8 +55,7 @@ namespace interpreter::detail::common std::string StringDecoder::consumeUTF8(Context &context, std::size_t maximumBytes) { - auto const data = context.consumeMaximalBytes(maximumBytes); - return decodeUTF8(data); + return decodeUTF8(context.consumeMaximalBytes(maximumBytes)); } std::string StringDecoder::decodeUTF8(std::span bytes) @@ -65,8 +65,7 @@ namespace interpreter::detail::common std::string StringDecoder::consumeLatin1(Context &context, std::size_t maximumBytes) { - auto const data = context.consumeMaximalBytes(maximumBytes); - return decodeLatin1(data); + return decodeLatin1(context.consumeMaximalBytes(maximumBytes)); } std::string StringDecoder::decodeLatin1(std::span bytes) @@ -74,21 +73,42 @@ namespace interpreter::detail::common return removeTrailingSpaces(latin1ToUtf8(toString(bytes))); } - std::string StringDecoder::bytesToHexString(std::span bytes) + std::string StringDecoder::consumeASCII(Context &context, std::size_t maximumBytes, bool ensurePrintable) + { + return decodeASCII(context.consumeMaximalBytes(maximumBytes), ensurePrintable); + } + + std::string StringDecoder::decodeASCII(std::span bytes, bool ensurePrintable) + { + auto const comparator = ensurePrintable // clang-format off + ? [](std::uint8_t const &ch) -> bool { return ch > 0x7E || ch < 0x20; } + : [](std::uint8_t const &ch) -> bool { return ch > 0x7F; }; // clang-format on + + auto const end = std::end(bytes); + auto const match = std::find_if(std::begin(bytes), end, comparator); + if (match != end) + { + throw std::runtime_error(std::string("Unexpected (non-ascii or non-printable) character found: ") + toHexString(*match)); + } + + return removeTrailingSpaces(toString(bytes)); + } + + std::string StringDecoder::toHexString(std::span bytes) { if (bytes.empty()) { - return ""; + return {}; } std::stringstream os; std::for_each(std::begin(bytes), std::end(bytes), [&](auto const &byte) - { os << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (int)byte; }); + { os << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (std::uint32_t)byte; }); return os.str(); } - std::string StringDecoder::bytesToHexString(std::vector const &bytes) + std::string StringDecoder::toHexString(std::vector const &bytes) { - return bytesToHexString(std::span(bytes.data(), bytes.size())); + return toHexString(std::span(bytes.data(), bytes.size())); } } diff --git a/source/lib/interpreter/detail/common/source/TLVDecoder.cpp b/source/lib/interpreter/detail/common/source/TLVDecoder.cpp index 9589a1bd..1e7a335e 100644 --- a/source/lib/interpreter/detail/common/source/TLVDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/TLVDecoder.cpp @@ -26,6 +26,28 @@ namespace interpreter::detail::common return os.str(); } + TLVDecoder::TLVDecoder(TagMapType tm) + : tagMap(std::move(tm)) + { + } + + std::size_t TLVDecoder::consume(common::Context &context) const + { + auto matchCount = std::size_t{0}; + while (!context.isEmpty()) + { + auto const tag = consumeTag(context); + auto const entry = tagMap.find(tag); + if (entry != tagMap.end()) + { + auto const value = context.consumeBytes(consumeLength(context)); + entry->second(value); + matchCount++; + } + } + return matchCount; + } + TLVTag TLVDecoder::consumeTag(common::Context &context) { auto tag = TLVTag{}; diff --git a/source/lib/interpreter/detail/sbb/source/SBBInterpreter.cpp b/source/lib/interpreter/detail/sbb/source/SBBInterpreter.cpp index 230312a7..cbe8b711 100644 --- a/source/lib/interpreter/detail/sbb/source/SBBInterpreter.cpp +++ b/source/lib/interpreter/detail/sbb/source/SBBInterpreter.cpp @@ -32,7 +32,7 @@ namespace interpreter::detail::sbb LOG_WARN(logger) << "Failed to parse SBB protobuf message, trying to continue..."; // return context; } - std::string json; + auto json = std::string{}; auto const status = google::protobuf::util::MessageToJsonString(sbb, &json); if (!status.ok()) { diff --git a/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp b/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp index 4187dda7..c6aea043 100644 --- a/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp +++ b/source/lib/interpreter/detail/uic918/source/RecordU_HEAD.cpp @@ -24,7 +24,7 @@ namespace interpreter::detail::uic recordJson .add("companyCode", common::StringDecoder::consumeUTF8(context, 4)) .add("uniqueTicketKey", common::StringDecoder::consumeUTF8(context, 20)) - .add("editionTime", common::DateTimeDecoder::consumeDateTime3(context)) + .add("editionTime", common::DateTimeDecoder::consumeDateTime12(context)) .add("flags", common::StringDecoder::consumeUTF8(context, 1)) .add("editionLanguageOfTicket", common::StringDecoder::consumeUTF8(context, 2)) .add("secondLanguageOfContract", common::StringDecoder::consumeUTF8(context, 2)); diff --git a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp index 8cf5c0f2..9c78d417 100644 --- a/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp +++ b/source/lib/interpreter/detail/uic918/source/Uic918Interpreter.cpp @@ -57,7 +57,7 @@ namespace interpreter::detail::uic auto const tid = context.consumeBytes(typeId.size()); if (Uic918Interpreter::TypeIdType(tid.begin(), tid.end()) != typeId) { - throw std::runtime_error("Unexpected UIC918 type ID, expecting 0x" + common::StringDecoder::bytesToHexString(typeId) + ", got: 0x" + common::StringDecoder::bytesToHexString(tid)); + throw std::runtime_error("Unexpected UIC918 type ID, expecting 0x" + common::StringDecoder::toHexString(typeId) + ", got: 0x" + common::StringDecoder::toHexString(tid)); } if (context.getRemainingSize() < 2) diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp index be97aaf8..a966e8a3 100644 --- a/source/lib/interpreter/detail/vdv/source/Certificate.cpp +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -40,7 +40,7 @@ namespace interpreter::detail::vdv Certificate Certificate::consumeFromEnvelope(common::Context &context) { auto const signatureData = common::TLVDecoder::consumeExpectedElement(context, {0x7f, 0x21}); - auto authority = common::StringDecoder::bytesToHexString(common::TLVDecoder::consumeExpectedElement(context, {0x42})); + auto authority = common::StringDecoder::toHexString(common::TLVDecoder::consumeExpectedElement(context, {0x42})); auto signatureContext = common::Context(signatureData); auto signature = Signature::consumeFrom(signatureContext); diff --git a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp index aaf9bf83..2d9e6ffb 100644 --- a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp +++ b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp @@ -61,7 +61,7 @@ namespace interpreter::detail::vdv } else { - throw std::runtime_error(std::string("Unexpected tag found: ") + common::StringDecoder::bytesToHexString(tag)); + throw std::runtime_error(std::string("Unexpected tag found: ") + common::StringDecoder::toHexString(tag)); } } diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index ae20bab7..1c929a0d 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -81,7 +81,7 @@ namespace interpreter::detail::vdv auto const product = common::TLVDecoder::consumeExpectedElement(messageContext, {0x85}); { auto productContext = common::Context(product); - auto const unknown1 = common::TLVDecoder::consumeExpectedElement(productContext, {0xda}); + auto const primary = common::TLVDecoder::consumeExpectedElement(productContext, {0xda}); auto const passenger = common::TLVDecoder::consumeExpectedElement(productContext, {0xdb}); { auto passengerContext = common::Context(passenger); diff --git a/source/test/interpreter/source/DateTimeDecoderTest.cpp b/source/test/interpreter/source/DateTimeDecoderTest.cpp index 6c611fe1..34b6bc28 100644 --- a/source/test/interpreter/source/DateTimeDecoderTest.cpp +++ b/source/test/interpreter/source/DateTimeDecoderTest.cpp @@ -8,21 +8,27 @@ namespace interpreter::detail::common { - TEST(consumeDateTimeCompact4, initial) + TEST(DateTimeDecoder, consumeDateTimeCompact4) { auto context = Context({0x28, 0x39, 0x70, 0x62}); EXPECT_EQ(DateTimeDecoder::consumeDateTimeCompact4(context), "2010-01-25T14:03:02"); } - TEST(consumeDateTime3, initial) + TEST(DateTimeDecoder, consumeDateTime12) { auto context = Context({'2', '7', '1', '0', '2', '0', '2', '0', '1', '3', '4', '5'}); - EXPECT_EQ(DateTimeDecoder::consumeDateTime3(context), "2020-10-27T13:45:00"); + EXPECT_EQ(DateTimeDecoder::consumeDateTime12(context), "2020-10-27T13:45:00"); } - TEST(consumeDate8, initial) + TEST(DateTimeDecoder, consumeDate8) { auto context = Context({'1', '3', '0', '1', '2', '0', '2', '1'}); EXPECT_EQ(DateTimeDecoder::consumeDate8(context), "2021-01-13"); } + + TEST(DateTimeDecoder, consumeDate8Null) + { + auto context = Context({0, 0, 0, 0, 0, 0, 0, 0}); + EXPECT_EQ(DateTimeDecoder::consumeDate8(context), "2021-01-13"); + } } diff --git a/source/test/interpreter/source/StringDecoderTest.cpp b/source/test/interpreter/source/StringDecoderTest.cpp index 2907c448..6146d0eb 100644 --- a/source/test/interpreter/source/StringDecoderTest.cpp +++ b/source/test/interpreter/source/StringDecoderTest.cpp @@ -77,45 +77,95 @@ namespace interpreter::detail::common EXPECT_EQ(StringDecoder::consumeLatin1(context, 8), std::string("")); } - TEST(bytesToHexString, filled) + TEST(StringDecoder, consumeASCII) { - auto const source = std::vector{0x12, 0x0A, 0xAB, 0x00, 0xFF}; - EXPECT_EQ(StringDecoder::bytesToHexString(source), "120AAB00FF"); + auto context = Context({'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'}); + EXPECT_EQ(StringDecoder::consumeASCII(context, 100), std::string("hello world!")); } - TEST(bytesToHexString, empty) + TEST(StringDecoder, consumeASCIIInvalid) { - auto const source = std::vector{}; - EXPECT_EQ(StringDecoder::bytesToHexString(source), ""); + auto context = Context({'h', 'e', 'l', 'l', 0xf6, ' ', 'w', 'o', 'r', 'l', 'd', '!'}); + EXPECT_THROW(StringDecoder::consumeASCII(context, 100), std::runtime_error); } - TEST(bytesToHexString, array) + TEST(StringDecoder, consumeASCIIPrintable) { - EXPECT_EQ(StringDecoder::bytesToHexString(std::array{}), "00"); - EXPECT_EQ(StringDecoder::bytesToHexString(std::array{0x12, 0x0A, 0xAB}), "120AAB"); - EXPECT_EQ(StringDecoder::bytesToHexString(std::array{0x12, 0x0A}), "120A00"); - EXPECT_EQ(StringDecoder::bytesToHexString(std::array{}), "000000"); - EXPECT_EQ(StringDecoder::bytesToHexString(std::array{0x11, 0x22, 0x33, 0x44, 0x55}), "1122334455"); + auto context = Context({0x20, '!', 'a', 'A', '0', '9', 'z', 'Z', 0x7E}); + EXPECT_EQ(StringDecoder::consumeASCII(context, 9, true), std::string(" !aA09zZ~")); } - TEST(bytesToHexString, integer4) + TEST(StringDecoder, consumeASCIIEnsurePrintableTrue) { - EXPECT_EQ(StringDecoder::bytesToHexString(std::uint32_t(0)), "00000000"); - EXPECT_EQ(StringDecoder::bytesToHexString(std::uint32_t(1)), "00000001"); - EXPECT_EQ(StringDecoder::bytesToHexString(std::uint32_t(0xffffffff)), "FFFFFFFF"); + auto context = Context({0x00, 0x1F, 0x20, 0x7E, 0x7F, 0xFF}); + EXPECT_THROW(StringDecoder::consumeASCII(context, 1, true), std::runtime_error); + EXPECT_THROW(StringDecoder::consumeASCII(context, 1, true), std::runtime_error); + EXPECT_NO_THROW(StringDecoder::consumeASCII(context, 1, true)); + EXPECT_NO_THROW(StringDecoder::consumeASCII(context, 1, true)); + EXPECT_THROW(StringDecoder::consumeASCII(context, 1, true), std::runtime_error); + EXPECT_THROW(StringDecoder::consumeASCII(context, 1, true), std::runtime_error); + EXPECT_TRUE(context.isEmpty()); } - TEST(bytesToHexString, integer2) + TEST(StringDecoder, consumeASCIIEnsurePrintableFalse) { - EXPECT_EQ(StringDecoder::bytesToHexString(std::uint16_t()), "0000"); - EXPECT_EQ(StringDecoder::bytesToHexString(std::uint16_t(1)), "0001"); - EXPECT_EQ(StringDecoder::bytesToHexString(std::uint16_t(0xffff)), "FFFF"); + auto context = Context({0x00, 0x1F, 0x20, 0x7E, 0x7F, 0x80, 0xFF}); + EXPECT_NO_THROW(StringDecoder::consumeASCII(context, 1, false)); + EXPECT_NO_THROW(StringDecoder::consumeASCII(context, 1, false)); + EXPECT_NO_THROW(StringDecoder::consumeASCII(context, 1, false)); + EXPECT_NO_THROW(StringDecoder::consumeASCII(context, 1, false)); + EXPECT_NO_THROW(StringDecoder::consumeASCII(context, 1, false)); + EXPECT_THROW(StringDecoder::consumeASCII(context, 1, false), std::runtime_error); + EXPECT_THROW(StringDecoder::consumeASCII(context, 1, false), std::runtime_error); + EXPECT_TRUE(context.isEmpty()); } - TEST(bytesToHexString, integer1) + TEST(StringDecoder, filledVectorToHexString) { - EXPECT_EQ(StringDecoder::bytesToHexString(std::uint8_t()), "00"); - EXPECT_EQ(StringDecoder::bytesToHexString(std::uint8_t(1)), "01"); - EXPECT_EQ(StringDecoder::bytesToHexString(std::uint8_t(0xff)), "FF"); + EXPECT_EQ(StringDecoder::toHexString(std::vector{0x23}), "23"); + EXPECT_EQ(StringDecoder::toHexString(std::vector(4, ' ')), "20202020"); + EXPECT_EQ(StringDecoder::toHexString(std::vector(3)), "000000"); + EXPECT_EQ(StringDecoder::toHexString(std::vector{0x12, 0x0A, 0xAB, 0x00, 0xFF}), "120AAB00FF"); + } + + TEST(StringDecoder, emptyVectorToHexString) + { + EXPECT_EQ(StringDecoder::toHexString(std::vector{}), ""); + } + + TEST(StringDecoder, filledArrayToHexString) + { + EXPECT_EQ(StringDecoder::toHexString(std::array{}), "00"); + EXPECT_EQ(StringDecoder::toHexString(std::array{0x12, 0x0A, 0xAB}), "120AAB"); + EXPECT_EQ(StringDecoder::toHexString(std::array{0x12, 0x0A}), "120A00"); + EXPECT_EQ(StringDecoder::toHexString(std::array{}), "000000"); + EXPECT_EQ(StringDecoder::toHexString(std::array{0x11, 0x22, 0x33, 0x44, 0x55}), "1122334455"); + } + + TEST(StringDecoder, emptyArrayToHexString) + { + EXPECT_EQ(StringDecoder::toHexString(std::array{}), ""); + } + + TEST(StringDecoder, integer4ToHexString) + { + EXPECT_EQ(StringDecoder::toHexString(std::uint32_t()), "00000000"); + EXPECT_EQ(StringDecoder::toHexString(std::uint32_t(1)), "00000001"); + EXPECT_EQ(StringDecoder::toHexString(std::uint32_t(0xffffffff)), "FFFFFFFF"); + EXPECT_EQ(StringDecoder::toHexString(2323u), "00000913"); + } + + TEST(StringDecoder, integer2ToHexString) + { + EXPECT_EQ(StringDecoder::toHexString(std::uint16_t()), "0000"); + EXPECT_EQ(StringDecoder::toHexString(std::uint16_t(1)), "0001"); + EXPECT_EQ(StringDecoder::toHexString(std::uint16_t(0xffff)), "FFFF"); + } + + TEST(StringDecoder, integer1ToHexString) + { + EXPECT_EQ(StringDecoder::toHexString(std::uint8_t()), "00"); + EXPECT_EQ(StringDecoder::toHexString(std::uint8_t(1)), "01"); + EXPECT_EQ(StringDecoder::toHexString(std::uint8_t(0xff)), "FF"); } } From 7bf00d45f934798bfddef3985372852638edc568 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 22 Feb 2026 15:31:47 +0100 Subject: [PATCH 35/41] Implement and test TLV instance decoder using tag-registration --- .../detail/common/include/DateTimeDecoder.h | 3 +++ .../detail/common/include/TLVDecoder.h | 10 ++++++++-- .../detail/common/source/DateTimeDecoder.cpp | 7 +++++++ .../detail/common/source/TLVDecoder.cpp | 13 +++++++++--- .../interpreter/source/TLVDecoderTest.cpp | 20 +++++++++++++++++++ 5 files changed, 48 insertions(+), 5 deletions(-) diff --git a/source/lib/interpreter/detail/common/include/DateTimeDecoder.h b/source/lib/interpreter/detail/common/include/DateTimeDecoder.h index 7829ccfa..0ff7f263 100644 --- a/source/lib/interpreter/detail/common/include/DateTimeDecoder.h +++ b/source/lib/interpreter/detail/common/include/DateTimeDecoder.h @@ -4,6 +4,8 @@ #pragma once #include +#include +#include namespace interpreter::detail::common { @@ -15,6 +17,7 @@ namespace interpreter::detail::common /* Consumes 4 bytes and decodes date-time to ISO-8601 format */ static std::string consumeDateTimeCompact4(Context &context); + static std::string decodeDateTimeCompact4(std::span bytes); /* Consumes 12 bytes (ASCII) and decodes date-time to ISO-8601 format */ diff --git a/source/lib/interpreter/detail/common/include/TLVDecoder.h b/source/lib/interpreter/detail/common/include/TLVDecoder.h index 88a5d0b6..dc863347 100644 --- a/source/lib/interpreter/detail/common/include/TLVDecoder.h +++ b/source/lib/interpreter/detail/common/include/TLVDecoder.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace interpreter::detail::common { @@ -60,7 +61,10 @@ namespace interpreter::detail::common constexpr bool operator!=(TLVTag const &rhs) const { return currentSize != rhs.currentSize || value != rhs.value; } - constexpr bool operator<(TLVTag const &rhs) const { return currentSize < rhs.currentSize && value < rhs.value; }; + constexpr bool operator<(TLVTag const &rhs) const + { + return std::tie(value, currentSize) < std::tie(rhs.value, rhs.currentSize); + }; void ensureEqual(TLVTag const &rhs) const; @@ -69,14 +73,16 @@ namespace interpreter::detail::common class TLVDecoder { + public: using TagMapType = std::map)>>; + private: TagMapType const tagMap; public: TLVDecoder(TagMapType tagMap); - std::size_t consume(common::Context &context) const; + std::tuple consume(common::Context &context) const; static TLVTag consumeTag(common::Context &context); diff --git a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp index ab1c1b46..4e37b866 100644 --- a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp @@ -4,6 +4,7 @@ #include "../include/DateTimeDecoder.h" #include "../include/StringDecoder.h" #include "../include/InterpreterUtility.h" +#include "../include/Context.h" #include #include @@ -26,6 +27,12 @@ namespace interpreter::detail::common return os.str(); } + std::string DateTimeDecoder::decodeDateTimeCompact4(std::span bytes) + { + auto context = Context(bytes); + return consumeDateTimeCompact4(context); + } + std::string DateTimeDecoder::consumeDateTime12(Context &context) { auto const input = StringDecoder::consumeASCII(context, 12, true); diff --git a/source/lib/interpreter/detail/common/source/TLVDecoder.cpp b/source/lib/interpreter/detail/common/source/TLVDecoder.cpp index 1e7a335e..aa31f600 100644 --- a/source/lib/interpreter/detail/common/source/TLVDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/TLVDecoder.cpp @@ -31,21 +31,28 @@ namespace interpreter::detail::common { } - std::size_t TLVDecoder::consume(common::Context &context) const + std::tuple TLVDecoder::consume(common::Context &context) const { auto matchCount = std::size_t{0}; + auto ignoreCount = std::size_t{0}; while (!context.isEmpty()) { auto const tag = consumeTag(context); + auto const length = consumeLength(context); auto const entry = tagMap.find(tag); if (entry != tagMap.end()) { - auto const value = context.consumeBytes(consumeLength(context)); + auto const value = context.consumeBytes(length); entry->second(value); matchCount++; } + else + { + context.ignoreBytes(length); + ignoreCount++; + } } - return matchCount; + return std::make_tuple(matchCount, ignoreCount); } TLVTag TLVDecoder::consumeTag(common::Context &context) diff --git a/source/test/interpreter/source/TLVDecoderTest.cpp b/source/test/interpreter/source/TLVDecoderTest.cpp index 57ca12d9..c864f370 100644 --- a/source/test/interpreter/source/TLVDecoderTest.cpp +++ b/source/test/interpreter/source/TLVDecoderTest.cpp @@ -6,6 +6,8 @@ #include "lib/interpreter/detail/common/include/Context.h" #include "lib/interpreter/detail/common/include/TLVDecoder.h" +#include "lib/interpreter/detail/common/include/BCDDecoder.h" +#include "lib/interpreter/detail/common/include/DateTimeDecoder.h" namespace interpreter::detail::common { @@ -234,4 +236,22 @@ namespace interpreter::detail::common auto context = Context(std::vector{0x9a, 0x81, 0x02, 0x23, 0x42}); EXPECT_THROW(TLVDecoder::consumeExpectedElement(context, {0x9b}), std::runtime_error); } + + TEST(TLVDecoder, consumeSelectedTags) + { + auto a1 = std::uint16_t{0}; + auto a2 = std::string{}; + auto const decoder = TLVDecoder({// clang-format off + {{0xA1}, [&](std::span bytes) { a1 = BCDDecoder::decodePackedInteger2(bytes); }}, + {{0xA2}, [&](std::span bytes) { a2 = DateTimeDecoder::decodeDateTimeCompact4(bytes); }} + }); // clang-format on + auto context = Context({0xA0, 0x00, 0xA1, 0x81, 0x02, 0x42, 0x23, 0xA2, 0x04, 0x44, 0xF2, 0x00, 0x01}); + auto const [matches, ignores] = decoder.consume(context); + EXPECT_EQ(matches, 2); + EXPECT_EQ(ignores, 1); + EXPECT_TRUE(context.isEmpty()); + + EXPECT_EQ(a1, 4223u); + EXPECT_EQ(a2, "2024-07-18T00:00:01"); + } } From 322f8505d32cc141be3ee67cf3d1acd344d117f0 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 22 Feb 2026 22:28:02 +0100 Subject: [PATCH 36/41] Improve testing 4 DateTimeDecoder --- .../detail/common/include/StringDecoder.h | 4 ---- .../interpreter/source/DateTimeDecoderTest.cpp | 18 +++++++++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/source/lib/interpreter/detail/common/include/StringDecoder.h b/source/lib/interpreter/detail/common/include/StringDecoder.h index 2fb7c247..6a428763 100644 --- a/source/lib/interpreter/detail/common/include/StringDecoder.h +++ b/source/lib/interpreter/detail/common/include/StringDecoder.h @@ -24,7 +24,6 @@ namespace interpreter::detail::common preferred method over consumeLatin1 since it avoids additional conversion. */ static std::string consumeUTF8(Context &context, std::size_t maximumBytes); - static std::string decodeUTF8(std::span bytes); /* Consumes maximumBytes or less ISO 8859-1 (Latin-1) encoded bytes and returns a 0 @@ -32,20 +31,17 @@ namespace interpreter::detail::common When the buffer is filled by multiple 0 at the end, the returned string might be shorter. */ static std::string consumeLatin1(Context &context, std::size_t maximumBytes); - static std::string decodeLatin1(std::span bytes); /* Consumes maximumBytes or less bytes and ensures it's plain ASCII (<128) and returns a 0 terminated UTF8 string (which is the same as ASCII in this case). */ static std::string consumeASCII(Context &context, std::size_t maximumBytes, bool ensurePrintable = false); - static std::string decodeASCII(std::span bytes, bool ensurePrintable = false); /* Returns a string containing the hexadecimal representation of the given byte buffer. */ static std::string toHexString(std::span bytes); - static std::string toHexString(std::vector const &bytes); template diff --git a/source/test/interpreter/source/DateTimeDecoderTest.cpp b/source/test/interpreter/source/DateTimeDecoderTest.cpp index 34b6bc28..870bf99d 100644 --- a/source/test/interpreter/source/DateTimeDecoderTest.cpp +++ b/source/test/interpreter/source/DateTimeDecoderTest.cpp @@ -14,21 +14,33 @@ namespace interpreter::detail::common EXPECT_EQ(DateTimeDecoder::consumeDateTimeCompact4(context), "2010-01-25T14:03:02"); } + TEST(DateTimeDecoder, consumeDateTimeCompact4Minimal) + { + auto context = Context({0, 0, 0, 0}); + EXPECT_EQ(DateTimeDecoder::consumeDateTimeCompact4(context), "1990-00-00T00:00:00"); + } + TEST(DateTimeDecoder, consumeDateTime12) { auto context = Context({'2', '7', '1', '0', '2', '0', '2', '0', '1', '3', '4', '5'}); EXPECT_EQ(DateTimeDecoder::consumeDateTime12(context), "2020-10-27T13:45:00"); } + TEST(DateTimeDecoder, consumeDateTime12Minimal) + { + auto context = Context({'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'}); + EXPECT_EQ(DateTimeDecoder::consumeDateTime12(context), "0000-00-00T00:00:00"); + } + TEST(DateTimeDecoder, consumeDate8) { auto context = Context({'1', '3', '0', '1', '2', '0', '2', '1'}); EXPECT_EQ(DateTimeDecoder::consumeDate8(context), "2021-01-13"); } - TEST(DateTimeDecoder, consumeDate8Null) + TEST(DateTimeDecoder, consumeDate8Minimal) { - auto context = Context({0, 0, 0, 0, 0, 0, 0, 0}); - EXPECT_EQ(DateTimeDecoder::consumeDate8(context), "2021-01-13"); + auto context = Context({'0', '0', '0', '0', '0', '0', '0', '0'}); + EXPECT_EQ(DateTimeDecoder::consumeDate8(context), "0000-00-00"); } } From 950d4e169e6c6cc0ccde51b609055c849d9b6aa8 Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 22 Feb 2026 23:25:29 +0100 Subject: [PATCH 37/41] Separate different efs tag into responsible methods by using TLVDecoder --- .../detail/common/include/TLVDecoder.h | 2 ++ .../detail/common/source/DateTimeDecoder.cpp | 6 ++++ .../detail/common/source/TLVDecoder.cpp | 6 ++++ .../detail/vdv/source/VDVInterpreter.cpp | 36 +++++++++++-------- .../source/DateTimeDecoderTest.cpp | 2 +- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/source/lib/interpreter/detail/common/include/TLVDecoder.h b/source/lib/interpreter/detail/common/include/TLVDecoder.h index dc863347..ad89895d 100644 --- a/source/lib/interpreter/detail/common/include/TLVDecoder.h +++ b/source/lib/interpreter/detail/common/include/TLVDecoder.h @@ -84,6 +84,8 @@ namespace interpreter::detail::common std::tuple consume(common::Context &context) const; + std::tuple consume(std::span bytes) const; + static TLVTag consumeTag(common::Context &context); static common::Context &consumeExpectedTag(common::Context &context, common::TLVTag const &expectedTag); diff --git a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp index 4e37b866..9b8abb39 100644 --- a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp @@ -16,6 +16,12 @@ namespace interpreter::detail::common { auto const date = common::consumeInteger2(context); auto const time = common::consumeInteger2(context); + + if (date == 0 && time == 0) + { + return {"0000-00-00T00:00:00"}; + } + // TODO Use chrono parse or apply validation for all values std::ostringstream os; os << std::setw(4) << std::setfill('0') << std::to_string(((date & 0xFE00) >> 9) + 1990) << "-" diff --git a/source/lib/interpreter/detail/common/source/TLVDecoder.cpp b/source/lib/interpreter/detail/common/source/TLVDecoder.cpp index aa31f600..ab2107f0 100644 --- a/source/lib/interpreter/detail/common/source/TLVDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/TLVDecoder.cpp @@ -55,6 +55,12 @@ namespace interpreter::detail::common return std::make_tuple(matchCount, ignoreCount); } + std::tuple TLVDecoder::consume(std::span bytes) const + { + auto context = common::Context(bytes); + return consume(context); + } + TLVTag TLVDecoder::consumeTag(common::Context &context) { auto tag = TLVTag{}; diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 1c929a0d..392147c0 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -36,6 +36,22 @@ namespace interpreter::detail::vdv { } + static void decodePrimaryData(std::span bytes, utility::JsonBuilder &jsonResult) + { + } + + static void decodePassengerData(std::span bytes, utility::JsonBuilder &jsonResult) + { + auto context = common::Context(bytes); + auto const gender = std::to_string(context.consumeByte()); + auto const dateOfBirth = common::DateTimeDecoder::consumeDateTimeCompact4(context); + auto const name = common::StringDecoder::decodeLatin1(context.consumeRemainingBytes()); + jsonResult + .add("gender", gender) + .add("name", name) + .add("dateOfBirth", dateOfBirth); + } + common::Context VDVInterpreter::interpret(common::Context &&context) { // Documentation (not fully matching anymore): @@ -78,21 +94,11 @@ namespace interpreter::detail::vdv .add("validFrom", common::DateTimeDecoder::consumeDateTimeCompact4(messageContext)) .add("validTo", common::DateTimeDecoder::consumeDateTimeCompact4(messageContext)); - auto const product = common::TLVDecoder::consumeExpectedElement(messageContext, {0x85}); - { - auto productContext = common::Context(product); - auto const primary = common::TLVDecoder::consumeExpectedElement(productContext, {0xda}); - auto const passenger = common::TLVDecoder::consumeExpectedElement(productContext, {0xdb}); - { - auto passengerContext = common::Context(passenger); - auto const gender = std::to_string(passengerContext.consumeByte()); - auto const birthDate = common::DateTimeDecoder::consumeDateTimeCompact4(passengerContext); - auto const name = common::StringDecoder::decodeLatin1(passengerContext.consumeRemainingBytes()); - jsonBuilder - .add("name", name) - .add("gender", gender); - } - } + auto const efsDecoder = common::TLVDecoder({// clang-format off + {{0xDA}, [&](auto bytes) { decodePrimaryData(std::move(bytes), jsonBuilder); }}, + {{0xDB}, [&](auto bytes) { decodePassengerData(std::move(bytes), jsonBuilder); }} + }); // clang-format on + efsDecoder.consume(common::TLVDecoder::consumeExpectedElement(messageContext, {0x85})); } context.addField("validated", message ? "true" : "false"); diff --git a/source/test/interpreter/source/DateTimeDecoderTest.cpp b/source/test/interpreter/source/DateTimeDecoderTest.cpp index 870bf99d..19022c09 100644 --- a/source/test/interpreter/source/DateTimeDecoderTest.cpp +++ b/source/test/interpreter/source/DateTimeDecoderTest.cpp @@ -17,7 +17,7 @@ namespace interpreter::detail::common TEST(DateTimeDecoder, consumeDateTimeCompact4Minimal) { auto context = Context({0, 0, 0, 0}); - EXPECT_EQ(DateTimeDecoder::consumeDateTimeCompact4(context), "1990-00-00T00:00:00"); + EXPECT_EQ(DateTimeDecoder::consumeDateTimeCompact4(context), "0000-00-00T00:00:00"); } TEST(DateTimeDecoder, consumeDateTime12) From 6e2f4cb2170899ed6bf04651630d573dedef788a Mon Sep 17 00:00:00 2001 From: sascha Date: Sun, 22 Feb 2026 23:38:01 +0100 Subject: [PATCH 38/41] Simplify certificate TLV extraction by using TLVDecoder instance --- .../source/LDIFFileCertificateProvider.cpp | 27 ++++--------------- .../detail/vdv/source/VDVInterpreter.cpp | 5 ++-- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp index 2d9e6ffb..3ac217ca 100644 --- a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp +++ b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp @@ -42,28 +42,11 @@ namespace interpreter::detail::vdv auto signature = std::span{}; auto remainder = std::span{}; - auto payloadContext = common::Context(payload); - while (!payloadContext.isEmpty()) - { - auto const tag = common::TLVDecoder::consumeTag(payloadContext); - auto const value = payloadContext.consumeBytes(common::TLVDecoder::consumeLength(payloadContext)); - if (tag == common::TLVTag{0x5f, 0x4e}) - { - content = value; - } - else if (tag == common::TLVTag{0x5f, 0x37}) - { - signature = value; - } - else if (tag == common::TLVTag{0x5f, 0x38}) - { - remainder = value; - } - else - { - throw std::runtime_error(std::string("Unexpected tag found: ") + common::StringDecoder::toHexString(tag)); - } - } + common::TLVDecoder({// clang-format off + {{0x5f, 0x4e}, [&](auto bytes) { content = bytes; }}, + {{0x5f, 0x37}, [&](auto bytes) { signature = bytes; }}, + {{0x5f, 0x38}, [&](auto bytes) { remainder = bytes; }} + }).consume(payload); // clang-format on certificate = std::make_optional(Certificate{commonName, description, Signature{signature, remainder}, content}); return certificate; diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 392147c0..4c8680f2 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -94,11 +94,10 @@ namespace interpreter::detail::vdv .add("validFrom", common::DateTimeDecoder::consumeDateTimeCompact4(messageContext)) .add("validTo", common::DateTimeDecoder::consumeDateTimeCompact4(messageContext)); - auto const efsDecoder = common::TLVDecoder({// clang-format off + common::TLVDecoder({// clang-format off {{0xDA}, [&](auto bytes) { decodePrimaryData(std::move(bytes), jsonBuilder); }}, {{0xDB}, [&](auto bytes) { decodePassengerData(std::move(bytes), jsonBuilder); }} - }); // clang-format on - efsDecoder.consume(common::TLVDecoder::consumeExpectedElement(messageContext, {0x85})); + }).consume(common::TLVDecoder::consumeExpectedElement(messageContext, {0x85})); // clang-format on } context.addField("validated", message ? "true" : "false"); From 6c5be51a7a062530daa859ffd2c3e756404f25ef Mon Sep 17 00:00:00 2001 From: sascha Date: Mon, 23 Feb 2026 20:54:36 +0100 Subject: [PATCH 39/41] Move integer consumers into NumberDecoder class --- .../common/include/InterpreterUtility.h | 28 ++++---- .../detail/common/source/DateTimeDecoder.cpp | 4 +- .../common/source/InterpreterUtility.cpp | 8 +-- .../detail/uic918/source/Record0080VU.cpp | 43 +++++++------ .../detail/vdv/source/Certificate.cpp | 64 ++++++++++--------- .../detail/vdv/source/VDVInterpreter.cpp | 52 +++++++++------ .../source/InterpreterUtilityTest.cpp | 16 ++--- 7 files changed, 118 insertions(+), 97 deletions(-) diff --git a/source/lib/interpreter/detail/common/include/InterpreterUtility.h b/source/lib/interpreter/detail/common/include/InterpreterUtility.h index acd82d30..066d814c 100644 --- a/source/lib/interpreter/detail/common/include/InterpreterUtility.h +++ b/source/lib/interpreter/detail/common/include/InterpreterUtility.h @@ -9,19 +9,23 @@ namespace interpreter::detail::common { class Context; - /* Consumes 4 bytes and converts from big-endian to system byte order - */ - std::uint32_t consumeInteger4(Context &context); + class NumberDecoder + { + public: + /* Consumes 4 bytes and converts from big-endian to system byte order + */ + static std::uint32_t consumeInteger4(Context &context); - /* Consumes 3 bytes and converts from big-endian to system byte order - */ - std::uint32_t consumeInteger3(Context &context); + /* Consumes 3 bytes and converts from big-endian to system byte order + */ + static std::uint32_t consumeInteger3(Context &context); - /* Consumes 2 bytes and converts from big-endian to system byte order - */ - std::uint16_t consumeInteger2(Context &context); + /* Consumes 2 bytes and converts from big-endian to system byte order + */ + static std::uint16_t consumeInteger2(Context &context); - /* Consumes 1 byte - */ - std::uint8_t consumeInteger1(Context &context); + /* Consumes 1 byte + */ + static std::uint8_t consumeInteger1(Context &context); + }; } diff --git a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp index 9b8abb39..9f8fe88e 100644 --- a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp @@ -14,8 +14,8 @@ namespace interpreter::detail::common std::string DateTimeDecoder::consumeDateTimeCompact4(Context &context) { - auto const date = common::consumeInteger2(context); - auto const time = common::consumeInteger2(context); + auto const date = NumberDecoder::consumeInteger2(context); + auto const time = NumberDecoder::consumeInteger2(context); if (date == 0 && time == 0) { diff --git a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp index 2470eabd..35579cea 100644 --- a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp +++ b/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp @@ -37,22 +37,22 @@ namespace interpreter::detail::common return result; } - std::uint32_t consumeInteger4(Context &context) + std::uint32_t NumberDecoder::consumeInteger4(Context &context) { return getInteger(context); } - std::uint32_t consumeInteger3(Context &context) + std::uint32_t NumberDecoder::consumeInteger3(Context &context) { return getInteger(context, 3); } - std::uint16_t consumeInteger2(Context &context) + std::uint16_t NumberDecoder::consumeInteger2(Context &context) { return getInteger(context); } - std::uint8_t consumeInteger1(Context &context) + std::uint8_t NumberDecoder::consumeInteger1(Context &context) { return context.consumeByte(); } diff --git a/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp b/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp index 6a1b1478..42fbebc1 100644 --- a/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp +++ b/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp @@ -14,6 +14,9 @@ namespace interpreter::detail::uic { + + using namespace common; + Record0080VU::Record0080VU(infrastructure::LoggerFactory &loggerFactory, RecordHeader &&h) : AbstractRecord(CREATE_LOGGER(loggerFactory), std::move(h)) { @@ -24,16 +27,16 @@ namespace interpreter::detail::uic { auto recordJson = ::utility::JsonBuilder::object(); // clang-format off recordJson - .add("terminalNummer", std::to_string(common::consumeInteger2(context))) - .add("samNummer", std::to_string(common::consumeInteger3(context))) - .add("anzahlPersonen", std::to_string(common::consumeInteger1(context))) - .add("efs", ::utility::toArray(common::consumeInteger1(context), [&context](auto &builder) + .add("terminalNummer", std::to_string(NumberDecoder::consumeInteger2(context))) + .add("samNummer", std::to_string(NumberDecoder::consumeInteger3(context))) + .add("anzahlPersonen", std::to_string(NumberDecoder::consumeInteger1(context))) + .add("efs", ::utility::toArray(NumberDecoder::consumeInteger1(context), [&context](auto &builder) { // TODO Unsure if numeric is the proper interpretation of berechtigungsNummer - auto const berechtigungsNummer = common::consumeInteger4(context); - auto const kvpOrganisationsId = common::consumeInteger2(context); - auto const pvProduktnummer = common::consumeInteger2(context); - auto const pvOrganisationsId = common::consumeInteger2(context); + auto const berechtigungsNummer = NumberDecoder::consumeInteger4(context); + auto const kvpOrganisationsId = NumberDecoder::consumeInteger2(context); + auto const pvProduktnummer = NumberDecoder::consumeInteger2(context); + auto const pvOrganisationsId = NumberDecoder::consumeInteger2(context); auto const pvProduktbezeichnung = getProduktbezeichnung(pvOrganisationsId, pvProduktnummer); builder @@ -44,27 +47,27 @@ namespace interpreter::detail::uic .add("pvProduktnummer", std::to_string(pvProduktnummer)) .add("pvProduktbezeichnung", pvProduktbezeichnung) .add("pvOrganisationsId", std::to_string(pvOrganisationsId)) - .add("gueltigAb", common::DateTimeDecoder::consumeDateTimeCompact4(context)) - .add("gueltigBis", common::DateTimeDecoder::consumeDateTimeCompact4(context)) - .add("preis", common::consumeInteger3(context)) - .add("samSequenznummer", std::to_string(common::consumeInteger4(context))) - .add("flaechenelemente", ::utility::toDynamicArray(common::consumeInteger1(context), [&context](auto &builder) + .add("gueltigAb", DateTimeDecoder::consumeDateTimeCompact4(context)) + .add("gueltigBis", DateTimeDecoder::consumeDateTimeCompact4(context)) + .add("preis", NumberDecoder::consumeInteger3(context)) + .add("samSequenznummer", std::to_string(NumberDecoder::consumeInteger4(context))) + .add("flaechenelemente", ::utility::toDynamicArray(NumberDecoder::consumeInteger1(context), [&context](auto &builder) { auto tagStream = std::ostringstream(); - auto const tagValue = int(common::consumeInteger1(context)); + auto const tagValue = int(NumberDecoder::consumeInteger1(context)); tagStream << std::hex << std::noshowbase << tagValue; auto const tag = tagStream.str(); - auto const elementLength = common::consumeInteger1(context); - auto const typ = std::to_string(common::consumeInteger1(context)); - auto const organisationsId = std::to_string(common::consumeInteger2(context)); + auto const elementLength = NumberDecoder::consumeInteger1(context); + auto const typ = std::to_string(NumberDecoder::consumeInteger1(context)); + auto const organisationsId = std::to_string(NumberDecoder::consumeInteger2(context)); auto const flaechenIdLength = elementLength - 3; if (flaechenIdLength != 2 && flaechenIdLength != 3) { throw std::runtime_error(std::string("Unexpected FlaechenelementId length: ") + std::to_string(flaechenIdLength)); } auto const flaechenId = std::to_string(flaechenIdLength == 2 - ? common::consumeInteger2(context) - : common::consumeInteger3(context)); + ? NumberDecoder::consumeInteger2(context) + : NumberDecoder::consumeInteger3(context)); builder .add("tag", tag) @@ -74,7 +77,7 @@ namespace interpreter::detail::uic return elementLength + 2; })); })); // clang-format on - context.addRecord(common::Record(header.recordId, header.recordVersion, std::move(recordJson))); + context.addRecord(Record(header.recordId, header.recordVersion, std::move(recordJson))); return std::move(context); } diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp index a966e8a3..9205e8ec 100644 --- a/source/lib/interpreter/detail/vdv/source/Certificate.cpp +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -16,17 +16,19 @@ namespace interpreter::detail::vdv { + using namespace common; + Signature Signature::consumeFrom(common::Context &context) { - auto value = common::TLVDecoder::consumeExpectedElement(context, {0x5f, 0x37}); - auto remainder = common::TLVDecoder::consumeExpectedElement(context, {0x5f, 0x38}); + auto value = TLVDecoder::consumeExpectedElement(context, {0x5f, 0x37}); + auto remainder = TLVDecoder::consumeExpectedElement(context, {0x5f, 0x38}); return Signature{std::move(value), std::move(remainder)}; } Signature Signature::consumeFromEnvelope(common::Context &context) { - auto value = common::TLVDecoder::consumeExpectedElement(context, {0x9e}); - auto remainder = common::TLVDecoder::consumeExpectedElement(context, {0x9a}); + auto value = TLVDecoder::consumeExpectedElement(context, {0x9e}); + auto remainder = TLVDecoder::consumeExpectedElement(context, {0x9a}); return Signature{std::move(value), std::move(remainder)}; } @@ -39,10 +41,10 @@ namespace interpreter::detail::vdv Certificate Certificate::consumeFromEnvelope(common::Context &context) { - auto const signatureData = common::TLVDecoder::consumeExpectedElement(context, {0x7f, 0x21}); - auto authority = common::StringDecoder::toHexString(common::TLVDecoder::consumeExpectedElement(context, {0x42})); + auto const signatureData = TLVDecoder::consumeExpectedElement(context, {0x7f, 0x21}); + auto authority = StringDecoder::toHexString(TLVDecoder::consumeExpectedElement(context, {0x42})); - auto signatureContext = common::Context(signatureData); + auto signatureContext = Context(signatureData); auto signature = Signature::consumeFrom(signatureContext); context.ensureEmpty(); @@ -55,10 +57,10 @@ namespace interpreter::detail::vdv So it should be a fixed length or there is a termination indicator, but the specification does not mention a terminator. */ - auto context = common::Context(inputContext.consumeBytes(length)); + auto context = Context(inputContext.consumeBytes(length)); auto parts = std::vector{}; - auto const header = common::consumeInteger1(context); + auto const header = NumberDecoder::consumeInteger1(context); if (header < 40) { // ITU-T parts.insert(parts.begin(), {0, header}); } else if (header < 80) { // ISO @@ -71,7 +73,7 @@ namespace interpreter::detail::vdv { auto part = std::uint32_t{0}; auto chunk = std::uint32_t{0}; // MSB = 1 means more bytes - for (chunk = common::consumeInteger1(context); chunk & 0x80; chunk = common::consumeInteger1(context)) + for (chunk = NumberDecoder::consumeInteger1(context); chunk & 0x80; chunk = NumberDecoder::consumeInteger1(context)) { part = (part | (chunk & 0x7f)) << 7; // Drop MSB, OR it to what we have already and shift left to ensure space 4 next chunk } @@ -92,11 +94,11 @@ namespace interpreter::detail::vdv CertificateParticipant CertificateParticipant::consumeFrom(common::Context &context) { - auto region = common::StringDecoder::decodeLatin1(context.consumeBytes(2)); - auto name = common::StringDecoder::decodeLatin1(context.consumeBytes(3)); - auto serviceIdenticator = common::consumeInteger1(context); - auto algorithmReference = common::consumeInteger1(context); - auto year = std::to_string(1990 + common::consumeInteger1(context)); + auto region = StringDecoder::decodeLatin1(context.consumeBytes(2)); + auto name = StringDecoder::decodeLatin1(context.consumeBytes(3)); + auto serviceIdenticator = NumberDecoder::consumeInteger1(context); + auto algorithmReference = NumberDecoder::consumeInteger1(context); + auto year = std::to_string(1990 + NumberDecoder::consumeInteger1(context)); return CertificateParticipant{std::move(region), std::move(name), serviceIdenticator, algorithmReference, std::move(year)}; } @@ -124,11 +126,11 @@ namespace interpreter::detail::vdv CertificateReference CertificateReference::consumeFrom(common::Context &context) { - auto orgId = std::to_string(common::consumeInteger2(context)); + auto orgId = std::to_string(NumberDecoder::consumeInteger2(context)); auto samValidUntil = CertificateDate::consumeFrom2(context); auto samValidFrom = CertificateDate::consumeFrom3(context); - auto ownerOrgId = std::to_string(common::consumeInteger2(context)); - auto samId = std::to_string(common::consumeInteger3(context)); + auto ownerOrgId = std::to_string(NumberDecoder::consumeInteger2(context)); + auto samId = std::to_string(NumberDecoder::consumeInteger3(context)); return CertificateReference{std::move(orgId), std::move(samValidUntil), std::move(samValidFrom), std::move(ownerOrgId), std::move(samId)}; } @@ -143,24 +145,24 @@ namespace interpreter::detail::vdv CertificateDate CertificateDate::consumeFrom4(common::Context &context) { - auto const year = common::BCDDecoder::consumePackedInteger2(context); - auto const month = common::BCDDecoder::consumePackedInteger1(context); - auto const day = common::BCDDecoder::consumePackedInteger1(context); + auto const year = BCDDecoder::consumePackedInteger2(context); + auto const month = BCDDecoder::consumePackedInteger1(context); + auto const day = BCDDecoder::consumePackedInteger1(context); return CertificateDate{year, month, day}; } CertificateDate CertificateDate::consumeFrom3(common::Context &context) { - auto const year = static_cast(2000 + common::BCDDecoder::consumePackedInteger1(context)); - auto const month = common::BCDDecoder::consumePackedInteger1(context); - auto const day = common::BCDDecoder::consumePackedInteger1(context); + auto const year = static_cast(2000 + BCDDecoder::consumePackedInteger1(context)); + auto const month = BCDDecoder::consumePackedInteger1(context); + auto const day = BCDDecoder::consumePackedInteger1(context); return CertificateDate{year, month, day}; } CertificateDate CertificateDate::consumeFrom2(common::Context &context) { - auto const year = static_cast(2000 + common::BCDDecoder::consumePackedInteger1(context)); - auto const month = common::BCDDecoder::consumePackedInteger1(context); + auto const year = static_cast(2000 + BCDDecoder::consumePackedInteger1(context)); + auto const month = BCDDecoder::consumePackedInteger1(context); return CertificateDate{year, month, 1}; } @@ -174,8 +176,8 @@ namespace interpreter::detail::vdv CertificateAuthorization CertificateAuthorization::consumeFrom(common::Context &context) { - auto name = common::StringDecoder::consumeUTF8(context, 6); - auto const serviceIndicator = common::consumeInteger1(context); + auto name = StringDecoder::consumeUTF8(context, 6); + auto const serviceIndicator = NumberDecoder::consumeInteger1(context); return CertificateAuthorization{std::move(name), serviceIndicator}; } @@ -186,7 +188,7 @@ namespace interpreter::detail::vdv CertificateProfile CertificateProfile::consumeFrom(common::Context &context) { - auto identifier = std::to_string(common::consumeInteger1(context)); + auto identifier = std::to_string(NumberDecoder::consumeInteger1(context)); return CertificateProfile{std::move(identifier)}; } @@ -231,7 +233,7 @@ namespace interpreter::detail::vdv DecodedCertificate DecodedCertificate::decodeRootFrom(std::span content) { - auto context = common::Context(content); + auto context = Context(content); auto identity = CertificateIdentity::consumeFrom(context, 9); auto publicKey = PublicKey::consumeFrom(context); context.ensureEmpty(); @@ -240,7 +242,7 @@ namespace interpreter::detail::vdv DecodedCertificate DecodedCertificate::decodeFrom(std::vector &&content) { - auto context = common::Context(content); + auto context = Context(content); auto identity = CertificateIdentity::consumeFrom(context, 7); // TODO OID length is probably not always 7 for all sub-certificates auto publicKey = PublicKey::consumeFrom(context); context.ensureEmpty(); diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 4c8680f2..952b8f68 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -18,6 +18,7 @@ namespace interpreter::detail::vdv { + using namespace common; /* This is actually not a fixed ident. 0x9e is a BER-TLV tag (signature) and 0x81+0x80 a length (128 bytes). But it should be sufficient for now to identify VDV tickets. @@ -38,20 +39,29 @@ namespace interpreter::detail::vdv static void decodePrimaryData(std::span bytes, utility::JsonBuilder &jsonResult) { + auto context = Context(bytes); + context.ignoreBytes(7); + auto const price = NumberDecoder::consumeInteger4(context); + jsonResult + .add("price", price); } static void decodePassengerData(std::span bytes, utility::JsonBuilder &jsonResult) { - auto context = common::Context(bytes); + auto context = Context(bytes); auto const gender = std::to_string(context.consumeByte()); - auto const dateOfBirth = common::DateTimeDecoder::consumeDateTimeCompact4(context); - auto const name = common::StringDecoder::decodeLatin1(context.consumeRemainingBytes()); + auto const dateOfBirth = DateTimeDecoder::consumeDateTimeCompact4(context); + auto const name = StringDecoder::decodeLatin1(context.consumeRemainingBytes()); jsonResult .add("gender", gender) .add("name", name) .add("dateOfBirth", dateOfBirth); } + static void decodeIdentificationData(std::span bytes, utility::JsonBuilder &jsonResult) + { + } + common::Context VDVInterpreter::interpret(common::Context &&context) { // Documentation (not fully matching anymore): @@ -69,9 +79,9 @@ namespace interpreter::detail::vdv auto const certificate = Certificate::consumeFromEnvelope(context); context.ensureEmpty(); - auto const remainderTail = common::Context(signature.remainder).consumeBytesEnd(5); - auto const signatureIdent = common::StringDecoder::decodeLatin1(remainderTail.subspan(0, 3)); - auto const signatureVersion = std::to_string(common::BCDDecoder::decodePackedInteger2(remainderTail.subspan(3, 2))); + auto const remainderTail = Context(signature.remainder).consumeBytesEnd(5); + auto const signatureIdent = StringDecoder::decodeLatin1(remainderTail.subspan(0, 3)); + auto const signatureVersion = std::to_string(BCDDecoder::decodePackedInteger2(remainderTail.subspan(3, 2))); auto jsonBuilder = utility::JsonBuilder::object(); jsonBuilder @@ -82,27 +92,29 @@ namespace interpreter::detail::vdv auto message = messageDecoder->decodeMessage(certificate, signature); if (message) { - auto messageContext = common::Context(*message); + auto messageContext = Context(*message); auto const messageTail = messageContext.consumeBytesEnd(5); - auto const messageIdent = common::StringDecoder::decodeLatin1(messageTail.subspan(0, 3)); - auto const messageVersion = common::BCDDecoder::decodePackedInteger2(messageTail.subspan(3, 2)); + auto const messageIdent = StringDecoder::decodeLatin1(messageTail.subspan(0, 3)); + auto const messageVersion = BCDDecoder::decodePackedInteger2(messageTail.subspan(3, 2)); jsonBuilder - .add("ticketId", std::to_string(common::consumeInteger4(messageContext))) - .add("ticketOrganisationId", std::to_string(common::consumeInteger2(messageContext))) - .add("productNumber", std::to_string(common::consumeInteger2(messageContext))) - .add("productOrganisationId", std::to_string(common::consumeInteger2(messageContext))) - .add("validFrom", common::DateTimeDecoder::consumeDateTimeCompact4(messageContext)) - .add("validTo", common::DateTimeDecoder::consumeDateTimeCompact4(messageContext)); - - common::TLVDecoder({// clang-format off + .add("ticketId", std::to_string(NumberDecoder::consumeInteger4(messageContext))) + .add("ticketOrganisationId", std::to_string(NumberDecoder::consumeInteger2(messageContext))) + .add("productNumber", std::to_string(NumberDecoder::consumeInteger2(messageContext))) + .add("productOrganisationId", std::to_string(NumberDecoder::consumeInteger2(messageContext))) + .add("validFrom", DateTimeDecoder::consumeDateTimeCompact4(messageContext)) + .add("validTo", DateTimeDecoder::consumeDateTimeCompact4(messageContext)); + + auto const efsDecoder = TLVDecoder({// clang-format off {{0xDA}, [&](auto bytes) { decodePrimaryData(std::move(bytes), jsonBuilder); }}, - {{0xDB}, [&](auto bytes) { decodePassengerData(std::move(bytes), jsonBuilder); }} - }).consume(common::TLVDecoder::consumeExpectedElement(messageContext, {0x85})); // clang-format on + {{0xDB}, [&](auto bytes) { decodePassengerData(std::move(bytes), jsonBuilder); }}, + {{0xD7}, [&](auto bytes) { decodeIdentificationData(std::move(bytes), jsonBuilder); }} + }); // clang-format on + efsDecoder.consume(TLVDecoder::consumeExpectedElement(messageContext, {0x85})); } context.addField("validated", message ? "true" : "false"); - context.addRecord(common::Record(signatureIdent, signatureVersion, std::move(jsonBuilder))); + context.addRecord(Record(signatureIdent, signatureVersion, std::move(jsonBuilder))); return std::move(context); } } diff --git a/source/test/interpreter/source/InterpreterUtilityTest.cpp b/source/test/interpreter/source/InterpreterUtilityTest.cpp index 3d8fa6bc..ee0cd13a 100644 --- a/source/test/interpreter/source/InterpreterUtilityTest.cpp +++ b/source/test/interpreter/source/InterpreterUtilityTest.cpp @@ -12,55 +12,55 @@ namespace interpreter::detail::common { auto context = Context({0xff, 1, 0xff}); context.ignoreBytes(1); - EXPECT_EQ(consumeInteger1(context), 1); + EXPECT_EQ(NumberDecoder::consumeInteger1(context), 1); } TEST(getNumeric, max8) { auto context = Context({0xfe, 0xff, 0xfe}); context.ignoreBytes(1); - EXPECT_EQ(consumeInteger1(context), 255); + EXPECT_EQ(NumberDecoder::consumeInteger1(context), 255); } TEST(getNumeric, min16) { auto context = Context({0xff, 0, 1, 0xff}); context.ignoreBytes(1); - EXPECT_EQ(consumeInteger2(context), 1); + EXPECT_EQ(NumberDecoder::consumeInteger2(context), 1); } TEST(getNumeric, max16) { auto context = Context({0xfe, 0xff, 0xff, 0xfe}); context.ignoreBytes(1); - EXPECT_EQ(consumeInteger2(context), 65535); + EXPECT_EQ(NumberDecoder::consumeInteger2(context), 65535); } TEST(getNumeric, min24) { auto context = Context({0xff, 0, 0, 1, 0xff}); // big endian 1 context.ignoreBytes(1); - EXPECT_EQ(consumeInteger3(context), 1); + EXPECT_EQ(NumberDecoder::consumeInteger3(context), 1); } TEST(getNumeric, max24) { auto context = Context({0xfe, 0xff, 0xff, 0xff, 0xfe}); context.ignoreBytes(1); - EXPECT_EQ(consumeInteger3(context), 16777215); + EXPECT_EQ(NumberDecoder::consumeInteger3(context), 16777215); } TEST(getNumeric, min32) { auto context = Context({0xff, 0, 0, 0, 1, 0xff}); // big endian 1 context.ignoreBytes(1); - EXPECT_EQ(consumeInteger4(context), 1); + EXPECT_EQ(NumberDecoder::consumeInteger4(context), 1); } TEST(getNumeric, max32) { auto context = Context({0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe}); context.ignoreBytes(1); - EXPECT_EQ(consumeInteger4(context), 4294967295); + EXPECT_EQ(NumberDecoder::consumeInteger4(context), 4294967295); } } From 66cd6903da2200c2fb7d22c3d297c57ca172d35a Mon Sep 17 00:00:00 2001 From: sascha Date: Mon, 23 Feb 2026 21:18:09 +0100 Subject: [PATCH 40/41] Move integer consumers into NumberDecoder class --- .../common/include/{InterpreterUtility.h => NumberDecoder.h} | 0 source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp | 2 +- .../common/source/{InterpreterUtility.cpp => NumberDecoder.cpp} | 2 +- source/lib/interpreter/detail/uic918/source/Record0080BL.cpp | 2 +- source/lib/interpreter/detail/uic918/source/Record0080VU.cpp | 2 +- source/lib/interpreter/detail/uic918/source/RecordU_FLEX.cpp | 2 +- .../lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp | 2 +- source/lib/interpreter/detail/vdv/source/Certificate.cpp | 2 +- source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp | 2 +- .../{InterpreterUtilityTest.cpp => NumberDecoderTest.cpp} | 2 +- source/test/interpreter/source/UicInterpreterTest.cpp | 2 +- 11 files changed, 10 insertions(+), 10 deletions(-) rename source/lib/interpreter/detail/common/include/{InterpreterUtility.h => NumberDecoder.h} (100%) rename source/lib/interpreter/detail/common/source/{InterpreterUtility.cpp => NumberDecoder.cpp} (97%) rename source/test/interpreter/source/{InterpreterUtilityTest.cpp => NumberDecoderTest.cpp} (96%) diff --git a/source/lib/interpreter/detail/common/include/InterpreterUtility.h b/source/lib/interpreter/detail/common/include/NumberDecoder.h similarity index 100% rename from source/lib/interpreter/detail/common/include/InterpreterUtility.h rename to source/lib/interpreter/detail/common/include/NumberDecoder.h diff --git a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp index 9f8fe88e..e365857d 100644 --- a/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp +++ b/source/lib/interpreter/detail/common/source/DateTimeDecoder.cpp @@ -3,7 +3,7 @@ #include "../include/DateTimeDecoder.h" #include "../include/StringDecoder.h" -#include "../include/InterpreterUtility.h" +#include "../include/NumberDecoder.h" #include "../include/Context.h" #include diff --git a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp b/source/lib/interpreter/detail/common/source/NumberDecoder.cpp similarity index 97% rename from source/lib/interpreter/detail/common/source/InterpreterUtility.cpp rename to source/lib/interpreter/detail/common/source/NumberDecoder.cpp index 35579cea..2c685815 100644 --- a/source/lib/interpreter/detail/common/source/InterpreterUtility.cpp +++ b/source/lib/interpreter/detail/common/source/NumberDecoder.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: (C) 2022 user4223 and (other) contributors to ticket-decoder // SPDX-License-Identifier: GPL-3.0-or-later -#include "../include/InterpreterUtility.h" +#include "../include/NumberDecoder.h" #include "lib/interpreter/detail/common/include/StringDecoder.h" #include "lib/interpreter/detail/common/include/Context.h" diff --git a/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp b/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp index 5b0f2a71..f782ba23 100644 --- a/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp +++ b/source/lib/interpreter/detail/uic918/source/Record0080BL.cpp @@ -3,7 +3,7 @@ #include "../include/Record0080BL.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/NumberDecoder.h" #include "lib/interpreter/detail/common/include/StringDecoder.h" #include "lib/interpreter/detail/common/include/DateTimeDecoder.h" #include "lib/interpreter/detail/common/include/Record.h" diff --git a/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp b/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp index 42fbebc1..f6d792c9 100644 --- a/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp +++ b/source/lib/interpreter/detail/uic918/source/Record0080VU.cpp @@ -3,7 +3,7 @@ #include "../include/Record0080VU.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/NumberDecoder.h" #include "lib/interpreter/detail/common/include/DateTimeDecoder.h" #include "lib/interpreter/detail/common/include/Record.h" diff --git a/source/lib/interpreter/detail/uic918/source/RecordU_FLEX.cpp b/source/lib/interpreter/detail/uic918/source/RecordU_FLEX.cpp index 2cd28baf..166501de 100644 --- a/source/lib/interpreter/detail/uic918/source/RecordU_FLEX.cpp +++ b/source/lib/interpreter/detail/uic918/source/RecordU_FLEX.cpp @@ -3,7 +3,7 @@ #include "../include/RecordU_FLEX.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/NumberDecoder.h" #include "lib/interpreter/detail/common/include/Record.h" #include "../u_flex/v1.3/include/RecordU_FLEX_13.h" diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 9f5d7664..88c500f0 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -5,7 +5,7 @@ #include "../include/VDVUtility.h" #include "lib/interpreter/detail/common/include/Context.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/NumberDecoder.h" #include "lib/infrastructure/include/Logging.h" diff --git a/source/lib/interpreter/detail/vdv/source/Certificate.cpp b/source/lib/interpreter/detail/vdv/source/Certificate.cpp index 9205e8ec..9c76f441 100644 --- a/source/lib/interpreter/detail/vdv/source/Certificate.cpp +++ b/source/lib/interpreter/detail/vdv/source/Certificate.cpp @@ -3,7 +3,7 @@ #include "../include/Certificate.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/NumberDecoder.h" #include "lib/interpreter/detail/common/include/TLVDecoder.h" #include "lib/interpreter/detail/common/include/BCDDecoder.h" #include "lib/interpreter/detail/common/include/StringDecoder.h" diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index 952b8f68..ec8719df 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -6,7 +6,7 @@ #include "../include/BotanMessageDecoder.h" #include "../include/VDVUtility.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/NumberDecoder.h" #include "lib/interpreter/detail/common/include/TLVDecoder.h" #include "lib/interpreter/detail/common/include/StringDecoder.h" #include "lib/interpreter/detail/common/include/DateTimeDecoder.h" diff --git a/source/test/interpreter/source/InterpreterUtilityTest.cpp b/source/test/interpreter/source/NumberDecoderTest.cpp similarity index 96% rename from source/test/interpreter/source/InterpreterUtilityTest.cpp rename to source/test/interpreter/source/NumberDecoderTest.cpp index ee0cd13a..d645339f 100644 --- a/source/test/interpreter/source/InterpreterUtilityTest.cpp +++ b/source/test/interpreter/source/NumberDecoderTest.cpp @@ -4,7 +4,7 @@ #include #include "lib/interpreter/detail/common/include/Context.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/NumberDecoder.h" namespace interpreter::detail::common { diff --git a/source/test/interpreter/source/UicInterpreterTest.cpp b/source/test/interpreter/source/UicInterpreterTest.cpp index a9927eb4..634d794c 100644 --- a/source/test/interpreter/source/UicInterpreterTest.cpp +++ b/source/test/interpreter/source/UicInterpreterTest.cpp @@ -13,7 +13,7 @@ #include #include "lib/interpreter/detail/uic918/include/Uic918Interpreter.h" -#include "lib/interpreter/detail/common/include/InterpreterUtility.h" +#include "lib/interpreter/detail/common/include/NumberDecoder.h" #include "lib/utility/include/Base64.h" From 1efbe7362081dad5b3716c4b317ea35f69254137 Mon Sep 17 00:00:00 2001 From: sascha Date: Tue, 24 Feb 2026 22:00:17 +0100 Subject: [PATCH 41/41] Make configuration 2 certificate input file a parameter 4 decoder/analyzer/python binding --- source/app/source/analyzer.cpp | 11 +++-- source/app/source/decoder.cpp | 11 +++-- source/lib/api/include/DecoderFacade.h | 6 +-- source/lib/api/source/DecoderFacade.cpp | 30 ++++++++----- .../api/include/CertificateProvider.h | 42 +++++++++++++++++++ .../lib/interpreter/api/include/Interpreter.h | 3 +- .../api/include/NopCertificateProvider.h | 27 ++++++++++++ .../api/include/SignatureVerifier.h | 2 +- .../api/source/CertificateProvider.cpp | 23 ++++++++++ .../interpreter/api/source/Interpreter.cpp | 14 ++++--- .../api/source/NopCertificateProvider.cpp | 31 ++++++++++++++ .../api/source/NopSignatureVerifier.cpp | 11 +++-- .../api/source/SignatureVerifier.cpp | 4 +- .../detail/vdv/include/BotanMessageDecoder.h | 5 ++- .../detail/vdv/include/CertificateProvider.h | 23 ---------- .../vdv/include/LDIFFileCertificateProvider.h | 7 ++-- .../detail/vdv/include/VDVInterpreter.h | 6 +-- .../detail/vdv/source/BotanMessageDecoder.cpp | 6 +-- .../source/LDIFFileCertificateProvider.cpp | 10 ++--- .../detail/vdv/source/VDVInterpreter.cpp | 7 ++-- .../verifier/include/BotanSignatureVerifier.h | 6 +-- .../source/BotanSignatureVerifier.cpp | 8 ++-- source/python/interpret_only.py | 2 +- source/python/run.py | 2 +- source/python/source/Binding.cpp | 14 ++++--- 25 files changed, 218 insertions(+), 93 deletions(-) create mode 100644 source/lib/interpreter/api/include/CertificateProvider.h create mode 100644 source/lib/interpreter/api/include/NopCertificateProvider.h create mode 100644 source/lib/interpreter/api/source/CertificateProvider.cpp create mode 100644 source/lib/interpreter/api/source/NopCertificateProvider.cpp delete mode 100644 source/lib/interpreter/detail/vdv/include/CertificateProvider.h diff --git a/source/app/source/analyzer.cpp b/source/app/source/analyzer.cpp index 97566037..7b1c8767 100644 --- a/source/app/source/analyzer.cpp +++ b/source/app/source/analyzer.cpp @@ -36,10 +36,14 @@ int main(int argc, char **argv) "o", "output-folder", "Path to folder to take intermediate image and raw data files and json result files", false, "out/", "Directory path", cmd); - auto const publicKeyFilePathArg = TCLAP::ValueArg( - "k", "keys-file", + auto const uicPublicKeyXmlFileArg = TCLAP::ValueArg( + "K", "keys-file", "Path to file containing public keys from UIC for signature validation", false, "cert/UIC_PublicKeys.xml", "File path [xml]", cmd); + auto const vdvCertificateLdifFileArg = TCLAP::ValueArg( + "C", "certificates-file", + "Path to file containing certificates from VDV for message decoding and signature validation", + false, "cert/VDV_Certificates.ldif", "File path [ldif]", cmd); auto const cameraEnabledArg = TCLAP::SwitchArg( "c", "camera-enabled", "Enable camera at start and try to detect aztec codes in delivered images", @@ -78,7 +82,8 @@ int main(int argc, char **argv) auto decoderFacade = api::DecoderFacade::create(context) .withAsynchronousLoad(true) - .withPublicKeyFile(publicKeyFilePathArg.getValue()) + .withUicPublicKeyXmlFile(uicPublicKeyXmlFileArg.getValue()) + .withVdvCertificateLdifFile(vdvCertificateLdifFileArg.getValue()) .withImageRotation(imageRotationArg.getValue()) .withImageSplit(imageSplitArg.getValue()) .withDetector(detector::api::DetectorType::NOP_DETECTOR) diff --git a/source/app/source/decoder.cpp b/source/app/source/decoder.cpp index b7bbb4e7..4d29259c 100644 --- a/source/app/source/decoder.cpp +++ b/source/app/source/decoder.cpp @@ -49,10 +49,14 @@ int main(int argc, char **argv) "R", "output-base64-raw-data", "Decode aztec code and dump raw data to output after base64 encoding", cmd, false); - auto const publicKeyFilePathArg = TCLAP::ValueArg( - "k", "keys-file", + auto const uicPublicKeyXmlFileArg = TCLAP::ValueArg( + "K", "keys-file", "Path to file containing public keys from UIC for signature validation", false, "cert/UIC_PublicKeys.xml", "File path [xml]", cmd); + auto const vdvCertificateLdifFileArg = TCLAP::ValueArg( + "C", "certificates-file", + "Path to file containing certificates from VDV for message decoding and signature validation", + false, "cert/VDV_Certificates.ldif", "File path [ldif]", cmd); auto const pureBarcodeArg = TCLAP::ValueArg( "P", "pure-barcode", "Input contains the barcode only", @@ -103,7 +107,8 @@ int main(int argc, char **argv) auto decoderFacade = api::DecoderFacade::create(context) .withPureBarcode(pureBarcodeArg.getValue()) .withLocalBinarizer(binarizerEnabledArg.getValue()) - .withPublicKeyFile(publicKeyFilePathArg.getValue()) + .withUicPublicKeyXmlFile(uicPublicKeyXmlFileArg.getValue()) + .withVdvCertificateLdifFile(vdvCertificateLdifFileArg.getValue()) .withImageRotation(imageRotationArg.getValue()) .withImageScale(imageScaleArg.getValue()) .withImageSplit(imageSplitArg.getValue()) diff --git a/source/lib/api/include/DecoderFacade.h b/source/lib/api/include/DecoderFacade.h index 111f336b..35f806dc 100644 --- a/source/lib/api/include/DecoderFacade.h +++ b/source/lib/api/include/DecoderFacade.h @@ -70,9 +70,9 @@ namespace api DecoderFacadeBuilder &withDetector(detector::api::DetectorType type); - /* TODO Fix naming 2 withUicPublicKeyXmlFile 2 allow withVdvPublicKeyLdifFile without confusion - */ - DecoderFacadeBuilder &withPublicKeyFile(std::filesystem::path publicKeyFilePath); + DecoderFacadeBuilder &withUicPublicKeyXmlFile(std::filesystem::path uicPublicKeyXmlFile); + + DecoderFacadeBuilder &withVdvCertificateLdifFile(std::filesystem::path vdvCertificateLdifFile); DecoderFacadeBuilder &withPureBarcode(bool pureBarcode); diff --git a/source/lib/api/source/DecoderFacade.cpp b/source/lib/api/source/DecoderFacade.cpp index d7e06ae9..7af62005 100644 --- a/source/lib/api/source/DecoderFacade.cpp +++ b/source/lib/api/source/DecoderFacade.cpp @@ -22,6 +22,7 @@ #include "lib/interpreter/api/include/Interpreter.h" #include "lib/interpreter/api/include/SignatureVerifier.h" +#include "lib/interpreter/api/include/CertificateProvider.h" namespace api { @@ -30,7 +31,8 @@ namespace api public: friend DecoderFacadeBuilder; - std::optional publicKeyFilePath; + std::optional uicPublicKeyXmlFile; + std::optional vdvCertificateLdifFile; std::optional classifierFile; std::optional> preProcessorResultVisitor; std::optional> detectorResultVisitor; @@ -159,11 +161,15 @@ namespace api return *this; } - /* TODO Fix naming 2 withUicPublicKeyXmlFile 2 allow withVdvPublicKeyLdifFile without confusion - */ - DecoderFacadeBuilder &DecoderFacadeBuilder::withPublicKeyFile(std::filesystem::path publicKeyFilePath) + DecoderFacadeBuilder &DecoderFacadeBuilder::withUicPublicKeyXmlFile(std::filesystem::path uicPublicKeyXmlFile) { - options->publicKeyFilePath = std::make_optional(publicKeyFilePath); + options->uicPublicKeyXmlFile = std::make_optional(uicPublicKeyXmlFile); + return *this; + } + + DecoderFacadeBuilder &DecoderFacadeBuilder::withVdvCertificateLdifFile(std::filesystem::path vdvCertificateLdifFile) + { + options->vdvCertificateLdifFile = std::make_optional(vdvCertificateLdifFile); return *this; } @@ -268,6 +274,7 @@ namespace api std::shared_ptr detector; std::unique_ptr const decoder; std::unique_ptr const signatureChecker; + std::unique_ptr certificateProvider; std::unique_ptr const interpreter; Internal(infrastructure::Context &context, std::shared_ptr o) @@ -284,13 +291,16 @@ namespace api decoder(decoder::api::Decoder::create( context, options->getDecoderOptions())), - signatureChecker( - options->publicKeyFilePath - ? interpreter::api::SignatureVerifier::create(context, *options->publicKeyFilePath) - : interpreter::api::SignatureVerifier::createDummy(context)), + signatureChecker(options->uicPublicKeyXmlFile + ? interpreter::api::SignatureVerifier::create(context, *options->uicPublicKeyXmlFile) + : interpreter::api::SignatureVerifier::createDummy(context)), + certificateProvider(options->vdvCertificateLdifFile + ? interpreter::api::CertificateProvider::create(context, *options->vdvCertificateLdifFile) + : interpreter::api::CertificateProvider::createDummy(context)), interpreter(interpreter::api::Interpreter::create( context, - *signatureChecker)) + *signatureChecker, + *certificateProvider)) { } diff --git a/source/lib/interpreter/api/include/CertificateProvider.h b/source/lib/interpreter/api/include/CertificateProvider.h new file mode 100644 index 00000000..51c19280 --- /dev/null +++ b/source/lib/interpreter/api/include/CertificateProvider.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "lib/infrastructure/include/ContextFwd.h" + +#include "lib/interpreter/detail/vdv/include/Certificate.h" // TODO Replace and remove + +#include +#include +#include +#include +#include +#include + +namespace interpreter::api +{ + + /*struct CertificateData + { + std::string name; + std::string description; + std::span data; + };*/ + + class CertificateProvider + { + public: + virtual ~CertificateProvider() = default; + + static std::unique_ptr create(infrastructure::Context &context, std::filesystem::path const &vdvCertificateLdifFile); + + static std::unique_ptr createDummy(infrastructure::Context &context); + + virtual std::vector getAuthorities() = 0; + + /* TODO This should not use the type out of detail::vdv and should use a locally defined type, see above + */ + virtual std::optional get(std::string authority) = 0; + }; +} diff --git a/source/lib/interpreter/api/include/Interpreter.h b/source/lib/interpreter/api/include/Interpreter.h index 075a88ef..46e1f2ad 100644 --- a/source/lib/interpreter/api/include/Interpreter.h +++ b/source/lib/interpreter/api/include/Interpreter.h @@ -14,6 +14,7 @@ namespace interpreter::api { class SignatureVerifier; + class CertificateProvider; class Interpreter { @@ -24,6 +25,6 @@ namespace interpreter::api */ virtual std::optional interpret(std::vector const &input, std::string origin, int indent = -1) const = 0; - static std::unique_ptr create(infrastructure::Context &context, SignatureVerifier const &signatureChecker); + static std::unique_ptr create(infrastructure::Context &context, SignatureVerifier const &signatureChecker, CertificateProvider &certificateProvider); }; } diff --git a/source/lib/interpreter/api/include/NopCertificateProvider.h b/source/lib/interpreter/api/include/NopCertificateProvider.h new file mode 100644 index 00000000..da617e76 --- /dev/null +++ b/source/lib/interpreter/api/include/NopCertificateProvider.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "CertificateProvider.h" + +#include "lib/infrastructure/include/ContextFwd.h" +#include "lib/infrastructure/include/Logger.h" + +namespace interpreter::api +{ + + class NopCertificateProvider : public CertificateProvider + { + infrastructure::Logger logger; + + public: + NopCertificateProvider(infrastructure::Context &context); + + static std::unique_ptr create(infrastructure::Context &context); + + virtual std::vector getAuthorities() override; + + virtual std::optional get(std::string authority) override; + }; +} diff --git a/source/lib/interpreter/api/include/SignatureVerifier.h b/source/lib/interpreter/api/include/SignatureVerifier.h index b11dbbb5..1c8c2593 100644 --- a/source/lib/interpreter/api/include/SignatureVerifier.h +++ b/source/lib/interpreter/api/include/SignatureVerifier.h @@ -22,7 +22,7 @@ namespace interpreter::api Successful }; - static std::unique_ptr create(infrastructure::Context &context, std::filesystem::path const &uicSignatureXml); + static std::unique_ptr create(infrastructure::Context &context, std::filesystem::path const &uicPublicKeyXmlFile); /* Creates a dummy implementation returning always KeyNotFound */ diff --git a/source/lib/interpreter/api/source/CertificateProvider.cpp b/source/lib/interpreter/api/source/CertificateProvider.cpp new file mode 100644 index 00000000..a0edd15f --- /dev/null +++ b/source/lib/interpreter/api/source/CertificateProvider.cpp @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../include/CertificateProvider.h" +#include "../include/NopCertificateProvider.h" + +#include "lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h" + +#include "lib/infrastructure/include/Context.h" + +namespace interpreter::api +{ + + std::unique_ptr CertificateProvider::create(infrastructure::Context &context, std::filesystem::path const &vdvCertificateLdifFile) + { + return std::make_unique(context, vdvCertificateLdifFile); + } + + std::unique_ptr CertificateProvider::createDummy(infrastructure::Context &context) + { + return std::make_unique(context); + } +} diff --git a/source/lib/interpreter/api/source/Interpreter.cpp b/source/lib/interpreter/api/source/Interpreter.cpp index 679bb100..4ef41516 100644 --- a/source/lib/interpreter/api/source/Interpreter.cpp +++ b/source/lib/interpreter/api/source/Interpreter.cpp @@ -3,6 +3,8 @@ #include "../include/Interpreter.h" +#include "lib/interpreter/api/include/CertificateProvider.h" + #include "lib/interpreter/detail/common/include/Context.h" #include "lib/interpreter/detail/common/include/StringDecoder.h" @@ -28,12 +30,12 @@ namespace interpreter::api } template - static decltype(interpreterMap)::value_type create(auto &loggerFactory) + static decltype(interpreterMap)::value_type create(auto &loggerFactory, auto &certificateProvider) { - return std::make_pair(T::getTypeId(), decltype(interpreterMap)::mapped_type{new T(loggerFactory)}); + return std::make_pair(T::getTypeId(), decltype(interpreterMap)::mapped_type{new T(loggerFactory, certificateProvider)}); } - Internal(infrastructure::Context &c, SignatureVerifier const &signatureChecker) + Internal(infrastructure::Context &c, SignatureVerifier const &signatureChecker, CertificateProvider &certificateProvider) : logger(CREATE_LOGGER(c.getLoggerFactory())), interpreterMap() { @@ -41,7 +43,7 @@ namespace interpreter::api interpreterMap.emplace(create(c.getLoggerFactory(), signatureChecker)); #endif #ifdef WITH_VDV_INTERPRETER - interpreterMap.emplace(create(c.getLoggerFactory())); + interpreterMap.emplace(create(c.getLoggerFactory(), certificateProvider)); #endif #ifdef WITH_SBB_INTERPRETER interpreterMap.emplace(create(c.getLoggerFactory(), signatureChecker)); @@ -83,8 +85,8 @@ namespace interpreter::api } }; - std::unique_ptr Interpreter::create(infrastructure::Context &context, SignatureVerifier const &signatureChecker) + std::unique_ptr Interpreter::create(infrastructure::Context &context, SignatureVerifier const &signatureChecker, CertificateProvider &certificateProvider) { - return std::make_unique(context, signatureChecker); + return std::make_unique(context, signatureChecker, certificateProvider); } } diff --git a/source/lib/interpreter/api/source/NopCertificateProvider.cpp b/source/lib/interpreter/api/source/NopCertificateProvider.cpp new file mode 100644 index 00000000..29f33e28 --- /dev/null +++ b/source/lib/interpreter/api/source/NopCertificateProvider.cpp @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../include/NopCertificateProvider.h" + +#include "lib/infrastructure/include/Context.h" +#include "lib/infrastructure/include/Logging.h" + +namespace interpreter::api +{ + NopCertificateProvider::NopCertificateProvider(infrastructure::Context &context) + : logger(CREATE_LOGGER(context.getLoggerFactory())) + { + LOG_WARN(logger) << "Using dummy certificate provider"; + } + + std::unique_ptr NopCertificateProvider::create(infrastructure::Context &context) + { + return std::make_unique(context); + } + + std::vector NopCertificateProvider::getAuthorities() + { + return {}; + } + + std::optional NopCertificateProvider::get(std::string authority) + { + return {}; + } +} diff --git a/source/lib/interpreter/api/source/NopSignatureVerifier.cpp b/source/lib/interpreter/api/source/NopSignatureVerifier.cpp index 6fa310ca..16786580 100644 --- a/source/lib/interpreter/api/source/NopSignatureVerifier.cpp +++ b/source/lib/interpreter/api/source/NopSignatureVerifier.cpp @@ -4,7 +4,6 @@ #include "../include/NopSignatureVerifier.h" #include "lib/infrastructure/include/Context.h" - #include "lib/infrastructure/include/Logging.h" namespace interpreter::api @@ -15,6 +14,11 @@ namespace interpreter::api LOG_WARN(logger) << "Using dummy signature checker"; } + std::unique_ptr NopSignatureVerifier::create(infrastructure::Context &context) + { + return std::make_unique(context); + } + NopSignatureVerifier::Result NopSignatureVerifier::check( std::string const &ricsCode, std::string const &keyId, std::span message, @@ -22,9 +26,4 @@ namespace interpreter::api { return Result::KeyNotFound; } - - std::unique_ptr NopSignatureVerifier::create(infrastructure::Context &context) - { - return std::make_unique(context); - } } diff --git a/source/lib/interpreter/api/source/SignatureVerifier.cpp b/source/lib/interpreter/api/source/SignatureVerifier.cpp index 6cc54f6f..a4844b91 100644 --- a/source/lib/interpreter/api/source/SignatureVerifier.cpp +++ b/source/lib/interpreter/api/source/SignatureVerifier.cpp @@ -15,10 +15,10 @@ namespace interpreter::api { std::unique_ptr SignatureVerifier::create( infrastructure::Context &context, - std::filesystem::path const &uicSignatureXml) + std::filesystem::path const &uicPublicKeyXmlFile) { #ifdef WITH_SIGNATURE_VERIFIER - return std::make_unique(context, uicSignatureXml); + return std::make_unique(context, uicPublicKeyXmlFile); #else return createDummy(context); #endif diff --git a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h index 7b84f7e9..a77ac2aa 100644 --- a/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h +++ b/source/lib/interpreter/detail/vdv/include/BotanMessageDecoder.h @@ -5,7 +5,8 @@ #include "MessageDecoder.h" #include "Certificate.h" -#include "CertificateProvider.h" + +#include "lib/interpreter/api/include/CertificateProvider.h" #include "lib/infrastructure/include/LoggingFwd.h" @@ -26,7 +27,7 @@ namespace interpreter::detail::vdv std::optional getIssuingCertificate(std::string authority); public: - BotanMessageDecoder(infrastructure::LoggerFactory &loggerFactory, CertificateProvider &certificateProvider); + BotanMessageDecoder(infrastructure::LoggerFactory &loggerFactory, api::CertificateProvider &certificateProvider); virtual std::optional> decodeMessage( Certificate const &envelopeCertificate, diff --git a/source/lib/interpreter/detail/vdv/include/CertificateProvider.h b/source/lib/interpreter/detail/vdv/include/CertificateProvider.h deleted file mode 100644 index 2d225f59..00000000 --- a/source/lib/interpreter/detail/vdv/include/CertificateProvider.h +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: (C) 2025 user4223 and (other) contributors to ticket-decoder -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include "Certificate.h" - -#include -#include -#include - -namespace interpreter::detail::vdv -{ - class CertificateProvider - { - public: - virtual ~CertificateProvider() = default; - - virtual std::vector getAuthorities() = 0; - - virtual std::optional get(std::string authority) = 0; - }; -} diff --git a/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h index fdb927a0..e1b42d77 100644 --- a/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h +++ b/source/lib/interpreter/detail/vdv/include/LDIFFileCertificateProvider.h @@ -3,8 +3,9 @@ #pragma once -#include "CertificateProvider.h" +#include "lib/interpreter/api/include/CertificateProvider.h" +#include "lib/infrastructure/include/ContextFwd.h" #include "lib/infrastructure/include/Logger.h" #include @@ -25,14 +26,14 @@ namespace interpreter::detail::vdv See: https://www.telesec.de/assets/downloads/Public-Key-Service/PKS-LDAP_Schnittstelle-V2.1-DE.pdf */ - class LDIFFileCertificateProvider : public CertificateProvider + class LDIFFileCertificateProvider : public api::CertificateProvider { infrastructure::Logger logger; struct Internal; std::shared_ptr internal; public: - LDIFFileCertificateProvider(infrastructure::LoggerFactory &loggerFactory, std::filesystem::path file = "cert/VDV_Certificates.ldif"); + LDIFFileCertificateProvider(infrastructure::Context &context, std::filesystem::path vdvCertificateLdifFile); virtual std::vector getAuthorities() override; diff --git a/source/lib/interpreter/detail/vdv/include/VDVInterpreter.h b/source/lib/interpreter/detail/vdv/include/VDVInterpreter.h index e207724a..f80fb8d4 100644 --- a/source/lib/interpreter/detail/vdv/include/VDVInterpreter.h +++ b/source/lib/interpreter/detail/vdv/include/VDVInterpreter.h @@ -3,11 +3,11 @@ #pragma once +#include "lib/interpreter/api/include/CertificateProvider.h" #include "lib/interpreter/detail/common/include/Interpreter.h" #include "lib/infrastructure/include/Logger.h" -#include "CertificateProvider.h" #include "MessageDecoder.h" #include @@ -17,13 +17,13 @@ namespace interpreter::detail::vdv class VDVInterpreter : public common::Interpreter { infrastructure::Logger logger; - std::unique_ptr certificateProvider; + api::CertificateProvider &certificateProvider; std::unique_ptr messageDecoder; public: static TypeIdType getTypeId(); - VDVInterpreter(infrastructure::LoggerFactory &loggerFactory); + VDVInterpreter(infrastructure::LoggerFactory &loggerFactory, api::CertificateProvider &certificateProvider); virtual common::Context interpret(common::Context &&context) override; }; diff --git a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp index 88c500f0..e3b84b15 100644 --- a/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp +++ b/source/lib/interpreter/detail/vdv/source/BotanMessageDecoder.cpp @@ -20,11 +20,11 @@ namespace interpreter::detail::vdv { std::unique_ptr const sha1HashFunction = Botan::HashFunction::create_or_throw("SHA-1"); - CertificateProvider &certificateProvider; + api::CertificateProvider &certificateProvider; std::optional rootCertificate; public: - Internal(CertificateProvider &cp) + Internal(api::CertificateProvider &cp) : certificateProvider(cp), rootCertificate() { @@ -78,7 +78,7 @@ namespace interpreter::detail::vdv } }; - BotanMessageDecoder::BotanMessageDecoder(infrastructure::LoggerFactory &lf, CertificateProvider &cp) + BotanMessageDecoder::BotanMessageDecoder(infrastructure::LoggerFactory &lf, api::CertificateProvider &cp) : logger(CREATE_LOGGER(lf)), internal(std::make_shared(cp)), issuingCertificates() diff --git a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp index 3ac217ca..db23d77b 100644 --- a/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp +++ b/source/lib/interpreter/detail/vdv/source/LDIFFileCertificateProvider.cpp @@ -4,12 +4,12 @@ #include "../include/LDIFFileCertificateProvider.h" #include "lib/interpreter/detail/common/include/Context.h" -#include "lib/interpreter/detail/common/include/StringDecoder.h" #include "lib/interpreter/detail/common/include/TLVDecoder.h" #include "lib/utility/include/Base64.h" #include "lib/infrastructure/include/Logging.h" +#include "lib/infrastructure/include/Context.h" #include #include @@ -133,13 +133,13 @@ namespace interpreter::detail::vdv } }; - LDIFFileCertificateProvider::LDIFFileCertificateProvider(infrastructure::LoggerFactory &loggerFactory, std::filesystem::path file) - : logger(CREATE_LOGGER(loggerFactory)), - internal(std::make_shared(Internal::import(file))) + LDIFFileCertificateProvider::LDIFFileCertificateProvider(infrastructure::Context &context, std::filesystem::path vdvCertificateLdifFile) + : logger(CREATE_LOGGER(context.getLoggerFactory())), + internal(std::make_shared(Internal::import(vdvCertificateLdifFile))) { if (!internal->entries) { - LOG_INFO(logger) << "Failed to import certificates from given LDIF file: " << file.string(); + LOG_WARN(logger) << "Failed to import certificates from given LDIF file: " << vdvCertificateLdifFile; } else { diff --git a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp index ec8719df..8797deb6 100644 --- a/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp +++ b/source/lib/interpreter/detail/vdv/source/VDVInterpreter.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "../include/VDVInterpreter.h" -#include "../include/LDIFFileCertificateProvider.h" #include "../include/BotanMessageDecoder.h" #include "../include/VDVUtility.h" @@ -30,10 +29,10 @@ namespace interpreter::detail::vdv return typeId; } - VDVInterpreter::VDVInterpreter(infrastructure::LoggerFactory &lf) + VDVInterpreter::VDVInterpreter(infrastructure::LoggerFactory &lf, api::CertificateProvider &cp) : logger(CREATE_LOGGER(lf)), - certificateProvider(std::make_unique(lf)), - messageDecoder(std::make_unique(lf, *certificateProvider)) + certificateProvider(cp), + messageDecoder(std::make_unique(lf, certificateProvider)) { } diff --git a/source/lib/interpreter/detail/verifier/include/BotanSignatureVerifier.h b/source/lib/interpreter/detail/verifier/include/BotanSignatureVerifier.h index 2fce0f00..872d2aba 100644 --- a/source/lib/interpreter/detail/verifier/include/BotanSignatureVerifier.h +++ b/source/lib/interpreter/detail/verifier/include/BotanSignatureVerifier.h @@ -5,11 +5,11 @@ #include "Certificate.h" +#include "lib/interpreter/api/include/SignatureVerifier.h" + #include "lib/infrastructure/include/ContextFwd.h" #include "lib/infrastructure/include/Logger.h" -#include "lib/interpreter/api/include/SignatureVerifier.h" - #include #include #include @@ -22,7 +22,7 @@ namespace interpreter::detail::verifier std::map keys; public: - BotanSignatureVerifier(infrastructure::Context &context, std::filesystem::path const &uicSignatureXml); + BotanSignatureVerifier(infrastructure::Context &context, std::filesystem::path const &uicPublicKeyXmlFile); virtual api::SignatureVerifier::Result check( std::string const &ricsCode, std::string const &keyId, diff --git a/source/lib/interpreter/detail/verifier/source/BotanSignatureVerifier.cpp b/source/lib/interpreter/detail/verifier/source/BotanSignatureVerifier.cpp index 6548773d..3ee08103 100644 --- a/source/lib/interpreter/detail/verifier/source/BotanSignatureVerifier.cpp +++ b/source/lib/interpreter/detail/verifier/source/BotanSignatureVerifier.cpp @@ -10,17 +10,17 @@ namespace interpreter::detail::verifier { - BotanSignatureVerifier::BotanSignatureVerifier(infrastructure::Context &context, std::filesystem::path const &uicSignatureXml) + BotanSignatureVerifier::BotanSignatureVerifier(infrastructure::Context &context, std::filesystem::path const &uicPublicKeyXmlFile) : logger(CREATE_LOGGER(context.getLoggerFactory())) { - if (!std::filesystem::exists(uicSignatureXml) || !std::filesystem::is_regular_file(uicSignatureXml)) + if (!std::filesystem::exists(uicPublicKeyXmlFile) || !std::filesystem::is_regular_file(uicPublicKeyXmlFile)) { - LOG_WARN(logger) << "UIC signature file not found or not a regular file: " << uicSignatureXml; + LOG_WARN(logger) << "UIC signature file not found or not a regular file: " << uicPublicKeyXmlFile; return; } auto doc = pugi::xml_document{}; - auto const result = doc.load_file(uicSignatureXml.c_str()); + auto const result = doc.load_file(uicPublicKeyXmlFile.c_str()); if (!result) { LOG_WARN(logger) << "Loading UIC signature file failed with: " << result.description(); diff --git a/source/python/interpret_only.py b/source/python/interpret_only.py index 9c71ec77..93151310 100644 --- a/source/python/interpret_only.py +++ b/source/python/interpret_only.py @@ -12,5 +12,5 @@ print("No barcodes found") exit(1) -decoder_facade = DecoderFacade(fail_on_interpreter_error = False, public_key_file = "cert/UIC_PublicKeys.xml") +decoder_facade = DecoderFacade(fail_on_interpreter_error = False, uic_public_key_xml_file = "cert/UIC_PublicKeys.xml", vdv_certificate_ldif_file = "cert/VDV_Certificates.ldif") print(decoder_facade.decode_uic918(b64encode(barcodes[0].bytes))) diff --git a/source/python/run.py b/source/python/run.py index 1e24a69b..a2f9ce49 100644 --- a/source/python/run.py +++ b/source/python/run.py @@ -16,7 +16,7 @@ def get_source_and_details(result: Tuple[str,str]) -> str: return result[0] + ": " + get_details(result[1]) -decoder_facade = DecoderFacade(fail_on_interpreter_error = False, public_key_file = "cert/UIC_PublicKeys.xml") +decoder_facade = DecoderFacade(fail_on_interpreter_error = False, uic_public_key_xml_file = "cert/UIC_PublicKeys.xml", vdv_certificate_ldif_file = "cert/VDV_Certificates.ldif") print("\n### UIC918-9") for result in decoder_facade.decode_files("images/Muster-UIC918-9"): diff --git a/source/python/source/Binding.cpp b/source/python/source/Binding.cpp index ed3ef637..d7d97f66 100644 --- a/source/python/source/Binding.cpp +++ b/source/python/source/Binding.cpp @@ -34,10 +34,11 @@ class DecoderFacadeWrapper std::shared_ptr context; api::DecoderFacade facade; - Instance(std::string publicKeyFile, bool const failOnDecoderError, bool const failOnInterpreterError) + Instance(std::string uicPublicKeyXmlFile, std::string vdvCertificateLdifFile, bool const failOnDecoderError, bool const failOnInterpreterError) : context(getContext()), facade(api::DecoderFacade::create(*context) - .withPublicKeyFile(std::move(publicKeyFile)) + .withUicPublicKeyXmlFile(std::move(uicPublicKeyXmlFile)) + .withVdvCertificateLdifFile(std::move(vdvCertificateLdifFile)) .withFailOnDecoderError(failOnDecoderError) .withFailOnInterpreterError(failOnInterpreterError) .build()) @@ -50,8 +51,8 @@ class DecoderFacadeWrapper api::DecoderFacade &get() { return instance->facade; } public: - DecoderFacadeWrapper(std::string publicKeyFile, bool const failOnDecoderError, bool const failOnInterpreterError) - : instance(std::make_shared(std::move(publicKeyFile), failOnDecoderError, failOnInterpreterError)) {} + DecoderFacadeWrapper(std::string uicPublicKeyXmlFile, std::string vdvCertificateLdifFile, bool const failOnDecoderError, bool const failOnInterpreterError) + : instance(std::make_shared(std::move(uicPublicKeyXmlFile), std::move(vdvCertificateLdifFile), failOnDecoderError, failOnInterpreterError)) {} DecoderFacadeWrapper(DecoderFacadeWrapper const &) = default; DecoderFacadeWrapper &operator=(DecoderFacadeWrapper const &) = default; @@ -85,8 +86,9 @@ BOOST_PYTHON_MODULE(ticket_decoder) boost::python::register_exception_translator(errorTranslator); - boost::python::class_("DecoderFacade", boost::python::init( - (boost::python::arg("public_key_file") = "cert/UIC_PublicKeys.xml", + boost::python::class_("DecoderFacade", boost::python::init( + (boost::python::arg("uic_public_key_xml_file") = "cert/UIC_PublicKeys.xml", + boost::python::arg("vdv_certificate_ldif_file") = "cert/VDV_Certificates.ldif", boost::python::arg("fail_on_decoder_error") = false, boost::python::arg("fail_on_interpreter_error") = true))) .def("decode_uic918", &DecoderFacadeWrapper::decodeUIC918, "Decode base64-encoded raw UIC918 data into structured json",