initial support for loading KFDtool EKC files (this is currently unused but is a future stepping-stone for encryption key handling);
parent
69f8fbe884
commit
78f034511f
@ -0,0 +1,571 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Converged FNE Software
|
||||||
|
* GPLv2 Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "common/AESCrypto.h"
|
||||||
|
#include "common/Log.h"
|
||||||
|
#include "common/Timer.h"
|
||||||
|
#include "common/Utils.h"
|
||||||
|
#include "common/zlib/zlib.h"
|
||||||
|
#include "xml/rapidxml.h"
|
||||||
|
#include "CryptoContainer.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(ENABLE_TCP_SSL)
|
||||||
|
#include <openssl/bio.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace crypto;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#define CHUNK 16384
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Global Functions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the length of a decoded base64 string.
|
||||||
|
* @param b64input String containing the base64 encoded data.
|
||||||
|
* @returns int Length of buffer to contain base64 encoded data.
|
||||||
|
*/
|
||||||
|
int calcDecodeLength(const char* b64input)
|
||||||
|
{
|
||||||
|
int len = strlen(b64input);
|
||||||
|
int padding = 0;
|
||||||
|
|
||||||
|
// last two chars are =
|
||||||
|
if (b64input[len-1] == '=' && b64input[len-2] == '=')
|
||||||
|
padding = 2;
|
||||||
|
else if (b64input[len-1] == '=') // last char is =
|
||||||
|
padding = 1;
|
||||||
|
|
||||||
|
return (int)len * 0.75 - padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Decodes a base64 encoded string.
|
||||||
|
* @param b64message String containing the base64 encoded data.
|
||||||
|
* @param buffer Buffer pointer to place encoded data.
|
||||||
|
* @returns int
|
||||||
|
*/
|
||||||
|
int base64Decode(char* b64message, uint8_t** buffer)
|
||||||
|
{
|
||||||
|
int decodeLen = calcDecodeLength(b64message), len = 0;
|
||||||
|
|
||||||
|
*buffer = (uint8_t*)malloc(decodeLen + 1);
|
||||||
|
FILE* stream = ::fmemopen(b64message, ::strlen(b64message), "r");
|
||||||
|
|
||||||
|
BIO* b64 = BIO_new(BIO_f_base64());
|
||||||
|
BIO* bio = BIO_new_fp(stream, BIO_NOCLOSE);
|
||||||
|
bio = BIO_push(b64, bio);
|
||||||
|
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); // do not use newlines to flush buffer
|
||||||
|
len = BIO_read(bio, *buffer, ::strlen(b64message));
|
||||||
|
|
||||||
|
// can test here if len == decodeLen - if not, then return an error
|
||||||
|
(*buffer)[len] = '\0';
|
||||||
|
|
||||||
|
BIO_free_all(bio);
|
||||||
|
::fclose(stream);
|
||||||
|
|
||||||
|
return decodeLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
* @param buffer
|
||||||
|
* @param len
|
||||||
|
* @param target
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
int findFirstChar(const uint8_t* buffer, uint32_t len, char target)
|
||||||
|
{
|
||||||
|
for (uint32_t i = 0U; i < len; i++) {
|
||||||
|
if (buffer[i] == target) {
|
||||||
|
return (int)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
* @param buffer
|
||||||
|
* @param len
|
||||||
|
* @param target
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
int findLastChar(const uint8_t* buffer, uint32_t len, char target)
|
||||||
|
{
|
||||||
|
if (buffer == nullptr) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastIndex = -1;
|
||||||
|
for (uint32_t i = 0U; i < len; i++) {
|
||||||
|
if (buffer[i] == target) {
|
||||||
|
lastIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Static Class Members
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
std::mutex CryptoContainer::m_mutex;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Public Class Members
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/* Initializes a new instance of the CryptoContainer class. */
|
||||||
|
|
||||||
|
CryptoContainer::CryptoContainer(const std::string& filename, const std::string& password, uint32_t reloadTime, bool enabled) : Thread(),
|
||||||
|
m_file(filename),
|
||||||
|
m_password(password),
|
||||||
|
m_reloadTime(reloadTime),
|
||||||
|
#if !defined(ENABLE_TCP_SSL)
|
||||||
|
m_enabled(false),
|
||||||
|
#else
|
||||||
|
m_enabled(enabled),
|
||||||
|
#endif // !ENABLE_TCP_SSL
|
||||||
|
m_stop(false),
|
||||||
|
m_keys()
|
||||||
|
{
|
||||||
|
/* stub */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finalizes a instance of the CryptoContainer class. */
|
||||||
|
|
||||||
|
CryptoContainer::~CryptoContainer() = default;
|
||||||
|
|
||||||
|
/* Thread entry point. This function is provided to run the thread for the lookup table. */
|
||||||
|
|
||||||
|
void CryptoContainer::entry()
|
||||||
|
{
|
||||||
|
if (m_reloadTime == 0U) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer timer(1U, 60U * m_reloadTime);
|
||||||
|
timer.start();
|
||||||
|
|
||||||
|
while (!m_stop) {
|
||||||
|
sleep(1000U);
|
||||||
|
|
||||||
|
timer.clock();
|
||||||
|
if (timer.hasExpired()) {
|
||||||
|
load();
|
||||||
|
timer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stops and unloads this lookup table. */
|
||||||
|
|
||||||
|
void CryptoContainer::stop(bool noDestroy)
|
||||||
|
{
|
||||||
|
if (!m_enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_reloadTime == 0U) {
|
||||||
|
if (!noDestroy)
|
||||||
|
delete this;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_stop = true;
|
||||||
|
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reads the lookup table from the specified lookup table file. */
|
||||||
|
|
||||||
|
bool CryptoContainer::read()
|
||||||
|
{
|
||||||
|
if (!m_enabled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool ret = load();
|
||||||
|
|
||||||
|
if (m_reloadTime > 0U)
|
||||||
|
run();
|
||||||
|
setName("fne:crypto-lookup-tbl");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clears all entries from the lookup table. */
|
||||||
|
|
||||||
|
void CryptoContainer::clear()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
m_keys.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adds a new entry to the lookup table by the specified unique ID. */
|
||||||
|
|
||||||
|
void CryptoContainer::addEntry(KeyItem key)
|
||||||
|
{
|
||||||
|
if (key.isInvalid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
KeyItem entry = key;
|
||||||
|
uint32_t id = entry.id();
|
||||||
|
uint32_t kId = entry.kId();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
auto it = std::find_if(m_keys.begin(), m_keys.end(),
|
||||||
|
[&](KeyItem x)
|
||||||
|
{
|
||||||
|
return x.id() == id && x.kId() == kId;
|
||||||
|
});
|
||||||
|
if (it != m_keys.end()) {
|
||||||
|
m_keys[it - m_keys.begin()] = entry;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_keys.push_back(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Erases an existing entry from the lookup table by the specified unique ID. */
|
||||||
|
|
||||||
|
void CryptoContainer::eraseEntry(uint32_t id)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
auto it = std::find_if(m_keys.begin(), m_keys.end(), [&](KeyItem x) { return x.id() == id; });
|
||||||
|
if (it != m_keys.end()) {
|
||||||
|
m_keys.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finds a table entry in this lookup table. */
|
||||||
|
|
||||||
|
KeyItem CryptoContainer::find(uint32_t kId)
|
||||||
|
{
|
||||||
|
KeyItem entry;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
auto it = std::find_if(m_keys.begin(), m_keys.end(),
|
||||||
|
[&](KeyItem x)
|
||||||
|
{
|
||||||
|
return x.kId() == kId;
|
||||||
|
});
|
||||||
|
if (it != m_keys.end()) {
|
||||||
|
entry = *it;
|
||||||
|
} else {
|
||||||
|
entry = KeyItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Private Class Members
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/* Loads the table from the passed lookup table file. */
|
||||||
|
|
||||||
|
bool CryptoContainer::load()
|
||||||
|
{
|
||||||
|
#if !defined(ENABLE_TCP_SSL)
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
if (!m_enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_file.length() <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_password.length() <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* ekcFile = ::fopen(m_file.c_str(), "rb");
|
||||||
|
if (ekcFile == nullptr) {
|
||||||
|
LogError(LOG_HOST, "Cannot open the crypto container file - %s", m_file.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// inflate file
|
||||||
|
// compression structures
|
||||||
|
z_stream strm;
|
||||||
|
strm.zalloc = Z_NULL;
|
||||||
|
strm.zfree = Z_NULL;
|
||||||
|
strm.opaque = Z_NULL;
|
||||||
|
|
||||||
|
// set input data
|
||||||
|
strm.avail_in = 0U;
|
||||||
|
strm.next_in = Z_NULL;
|
||||||
|
|
||||||
|
// initialize decompression
|
||||||
|
int ret = inflateInit2(&strm, 16 + MAX_WBITS);
|
||||||
|
if (ret != Z_OK) {
|
||||||
|
LogError(LOG_HOST, "Error initializing ZLIB, ret = %d", ret);
|
||||||
|
::fclose(ekcFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip 4 bytes (C# adds a header on the GZIP stream for the decompressed length)
|
||||||
|
::fseek(ekcFile, 4, SEEK_SET);
|
||||||
|
|
||||||
|
// decompress data
|
||||||
|
std::vector<uint8_t> decompressedData;
|
||||||
|
uint8_t inBuffer[CHUNK];
|
||||||
|
uint8_t outBuffer[CHUNK];
|
||||||
|
do {
|
||||||
|
strm.avail_in = fread(inBuffer, 1, CHUNK, ekcFile);
|
||||||
|
if (::ferror(ekcFile)) {
|
||||||
|
inflateEnd(&strm);
|
||||||
|
::fclose(ekcFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strm.avail_in == 0)
|
||||||
|
break;
|
||||||
|
strm.next_in = inBuffer;
|
||||||
|
|
||||||
|
uint32_t have = 0U;
|
||||||
|
do {
|
||||||
|
strm.avail_out = CHUNK;
|
||||||
|
strm.next_out = outBuffer;
|
||||||
|
|
||||||
|
ret = inflate(&strm, Z_NO_FLUSH);
|
||||||
|
if (ret == Z_DATA_ERROR) {
|
||||||
|
// deflate stream invalid
|
||||||
|
LogError(LOG_HOST, "Error decompressing EKC: %s", (strm.msg == NULL) ? "compressed data error" : strm.msg);
|
||||||
|
inflateEnd(&strm);
|
||||||
|
::fclose(ekcFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == Z_STREAM_ERROR || ret < 0) {
|
||||||
|
LogError(LOG_HOST, "Error decompressing EKC, ret = %d", ret);
|
||||||
|
inflateEnd(&strm);
|
||||||
|
::fclose(ekcFile);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
have = CHUNK - strm.avail_out;
|
||||||
|
decompressedData.insert(decompressedData.end(), outBuffer, outBuffer + have);
|
||||||
|
} while (strm.avail_out == 0);
|
||||||
|
} while (ret != Z_STREAM_END);
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
inflateEnd(&strm);
|
||||||
|
::fclose(ekcFile);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// ensure zero termination
|
||||||
|
decompressedData.push_back(0U);
|
||||||
|
|
||||||
|
uint8_t* decompressed = decompressedData.data();
|
||||||
|
|
||||||
|
// parse outer container DOM
|
||||||
|
enum { PARSE_FLAGS = rapidxml::parse_full };
|
||||||
|
rapidxml::xml_document<> ekcOuterContainer;
|
||||||
|
ekcOuterContainer.parse<PARSE_FLAGS>(reinterpret_cast<char*>(decompressed));
|
||||||
|
|
||||||
|
rapidxml::xml_node<>* outerRoot = ekcOuterContainer.first_node("OuterContainer");
|
||||||
|
if (outerRoot != nullptr) {
|
||||||
|
// get EKC version
|
||||||
|
std::string version = "";
|
||||||
|
rapidxml::xml_attribute<>* versionAttr = outerRoot->first_attribute("version");
|
||||||
|
if (versionAttr != nullptr)
|
||||||
|
version = std::string(versionAttr->value());
|
||||||
|
|
||||||
|
// validate EKC version is set and is 1.0
|
||||||
|
if (version == "") {
|
||||||
|
::LogError(LOG_HOST, "Error opening EKC: incorrect version, expected 1.0 got none");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version != "1.0") {
|
||||||
|
::LogError(LOG_HOST, "Error opening EKC: incorrect version, expected 1.0 got %s", version.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get key derivation node
|
||||||
|
rapidxml::xml_node<>* keyDerivation = outerRoot->first_node("KeyDerivation");
|
||||||
|
if (keyDerivation == nullptr) {
|
||||||
|
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// retreive and parse salt
|
||||||
|
uint8_t* salt = nullptr;
|
||||||
|
rapidxml::xml_node<>* saltNode = keyDerivation->first_node("Salt");
|
||||||
|
if (saltNode == nullptr) {
|
||||||
|
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int8_t saltBufLen = base64Decode(saltNode->value(), &salt);
|
||||||
|
|
||||||
|
// retrieve interation count
|
||||||
|
int32_t iterationCount = 0;
|
||||||
|
rapidxml::xml_node<>* iterNode = keyDerivation->first_node("IterationCount");
|
||||||
|
if (iterNode == nullptr) {
|
||||||
|
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
iterationCount = ::strtoul(iterNode->value(), NULL, 10);
|
||||||
|
|
||||||
|
// retrieve key length
|
||||||
|
int32_t keyLength = 0;
|
||||||
|
rapidxml::xml_node<>* keyLenNode = keyDerivation->first_node("KeyLength");
|
||||||
|
if (keyLenNode == nullptr) {
|
||||||
|
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
keyLength = ::strtoul(keyLenNode->value(), NULL, 10);
|
||||||
|
|
||||||
|
// generate crypto key to decrypt inner container
|
||||||
|
uint8_t key[EVP_MAX_KEY_LENGTH];
|
||||||
|
::memset(key, 0x00U, EVP_MAX_KEY_LENGTH);
|
||||||
|
uint8_t iv[EVP_MAX_IV_LENGTH];
|
||||||
|
::memset(iv, 0x00U, EVP_MAX_IV_LENGTH);
|
||||||
|
|
||||||
|
uint8_t keyIv[EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH];
|
||||||
|
::memset(keyIv, 0x00U, EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH);
|
||||||
|
if (PKCS5_PBKDF2_HMAC(m_password.c_str(), m_password.size(), salt, saltBufLen, iterationCount, EVP_sha512(), keyLength + EVP_MAX_IV_LENGTH, keyIv)) {
|
||||||
|
::memcpy(key, keyIv, keyLength);
|
||||||
|
::memcpy(iv, keyIv + keyLength, EVP_MAX_IV_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get inner container encrypted data
|
||||||
|
// bryanb: annoying levels of XML encapsulation...
|
||||||
|
rapidxml::xml_node<>* encryptedDataNode = outerRoot->first_node("EncryptedData");
|
||||||
|
if (encryptedDataNode == nullptr) {
|
||||||
|
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rapidxml::xml_node<>* cipherDataNode = encryptedDataNode->first_node("CipherData");
|
||||||
|
if (cipherDataNode == nullptr) {
|
||||||
|
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rapidxml::xml_node<>* cipherValue = cipherDataNode->first_node("CipherValue");
|
||||||
|
if (cipherValue == nullptr) {
|
||||||
|
::LogError(LOG_HOST, "Error opening EKC: failed to process XML");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* innerContainerCrypted = nullptr;
|
||||||
|
int innerContainerLen = base64Decode(cipherValue->value(), &innerContainerCrypted);
|
||||||
|
|
||||||
|
// decrypt inner container
|
||||||
|
AES aes = AES(AESKeyLength::AES_256);
|
||||||
|
uint8_t* innerContainer = aes.decryptCBC(innerContainerCrypted, innerContainerLen, key, iv);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** bryanb: this is probably slightly error prone...
|
||||||
|
*/
|
||||||
|
|
||||||
|
int xmlFirstTagChar = findFirstChar(innerContainer, innerContainerLen, '<');
|
||||||
|
int xmlLastTagChar = findLastChar(innerContainer, innerContainerLen, '>');
|
||||||
|
// zero all bytes after the last > character
|
||||||
|
::memset(innerContainer + xmlLastTagChar + 1U, 0x00U, innerContainerLen - xmlLastTagChar);
|
||||||
|
|
||||||
|
rapidxml::xml_document<> ekcInnerContainer;
|
||||||
|
ekcInnerContainer.parse<PARSE_FLAGS>(reinterpret_cast<char*>(innerContainer + xmlFirstTagChar));
|
||||||
|
|
||||||
|
rapidxml::xml_node<>* innerRoot = ekcInnerContainer.first_node("InnerContainer");
|
||||||
|
if (innerRoot != nullptr) {
|
||||||
|
// clear table
|
||||||
|
clear();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
// get keys node
|
||||||
|
rapidxml::xml_node<>* keys = innerRoot->first_node("Keys");
|
||||||
|
if (keys != nullptr) {
|
||||||
|
uint32_t i = 0U;
|
||||||
|
for (rapidxml::xml_node<>* keyNode = keys->first_node("KeyItem"); keyNode; keyNode = keyNode->next_sibling()) {
|
||||||
|
KeyItem key = KeyItem();
|
||||||
|
key.id(i);
|
||||||
|
|
||||||
|
// get name
|
||||||
|
rapidxml::xml_node<>* nameNode = keyNode->first_node("Name");
|
||||||
|
if (nameNode == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
key.name(nameNode->value());
|
||||||
|
|
||||||
|
// get keyset ID
|
||||||
|
rapidxml::xml_node<>* keysetIdNode = keyNode->first_node("KeysetId");
|
||||||
|
if (keysetIdNode == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
key.keysetId(::strtoul(keysetIdNode->value(), NULL, 10));
|
||||||
|
|
||||||
|
// get SLN
|
||||||
|
rapidxml::xml_node<>* slnNode = keyNode->first_node("Sln");
|
||||||
|
if (slnNode == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
key.sln(::strtoul(slnNode->value(), NULL, 10));
|
||||||
|
|
||||||
|
// get algorithm ID
|
||||||
|
rapidxml::xml_node<>* algIdNode = keyNode->first_node("AlgorithmId");
|
||||||
|
if (algIdNode == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
key.algId(::strtoul(algIdNode->value(), NULL, 10));
|
||||||
|
|
||||||
|
// get key ID
|
||||||
|
rapidxml::xml_node<>* kIdNode = keyNode->first_node("KeyId");
|
||||||
|
if (kIdNode == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
key.kId(::strtoul(kIdNode->value(), NULL, 10));
|
||||||
|
|
||||||
|
// get key material
|
||||||
|
rapidxml::xml_node<>* keyMatNode = keyNode->first_node("Key");
|
||||||
|
if (keyMatNode == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
key.keyMaterial(keyMatNode->value());
|
||||||
|
|
||||||
|
::LogInfoEx(LOG_HOST, "Key NAME: %s SLN: %u ALGID: $%02X, KID: $%04X", key.name().c_str(), key.sln(), key.algId(), key.kId());
|
||||||
|
|
||||||
|
m_keys.push_back(key);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
::LogError(LOG_HOST, "Error opening EKC: %s", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_keys.size() == 0U) {
|
||||||
|
::LogError(LOG_HOST, "No encryption keys defined!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = m_keys.size();
|
||||||
|
if (size == 0U)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LogInfoEx(LOG_HOST, "Loaded %lu entries into crypto lookup table", size);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#endif // !ENABLE_TCP_SSL
|
||||||
|
}
|
||||||
@ -0,0 +1,246 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Converged FNE Software
|
||||||
|
* GPLv2 Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @file CryptoContainer.h
|
||||||
|
* @ingroup fne
|
||||||
|
* @file CryptoContainer.cpp
|
||||||
|
* @ingroup fne
|
||||||
|
*/
|
||||||
|
#if !defined(__CRYPTO_CONTAINER_H__)
|
||||||
|
#define __CRYPTO_CONTAINER_H__
|
||||||
|
|
||||||
|
#include "common/Defines.h"
|
||||||
|
#include "common/Thread.h"
|
||||||
|
#include "common/Utils.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents an key item.
|
||||||
|
* @ingroup fne
|
||||||
|
*/
|
||||||
|
class HOST_SW_API KeyItem {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the KeyItem class.
|
||||||
|
*/
|
||||||
|
KeyItem() :
|
||||||
|
m_id(0U),
|
||||||
|
m_name(),
|
||||||
|
m_keysetId(0U),
|
||||||
|
m_sln(0U),
|
||||||
|
m_algId(0U),
|
||||||
|
m_kId(0U),
|
||||||
|
m_keyMaterial()
|
||||||
|
{
|
||||||
|
/* stub */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Equals operator. Copies this KeyItem to another KeyItem.
|
||||||
|
* @param data Instance of KeyItem to copy.
|
||||||
|
*/
|
||||||
|
virtual KeyItem& operator= (const KeyItem& data)
|
||||||
|
{
|
||||||
|
if (this != &data) {
|
||||||
|
m_id = data.m_id;
|
||||||
|
m_name = data.m_name;
|
||||||
|
m_keysetId = data.m_keysetId;
|
||||||
|
m_sln = data.m_sln;
|
||||||
|
m_algId = data.m_algId;
|
||||||
|
m_kId = data.m_kId;
|
||||||
|
|
||||||
|
m_keyMaterial = data.m_keyMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper to quickly determine if a key item entry is valid.
|
||||||
|
* @returns bool True, if key item entry is valid, otherwise false.
|
||||||
|
*/
|
||||||
|
bool isInvalid() const
|
||||||
|
{
|
||||||
|
if (m_sln == 0U)
|
||||||
|
return true;
|
||||||
|
if (m_algId == 0U)
|
||||||
|
return true;
|
||||||
|
if (m_kId == 0U)
|
||||||
|
return true;
|
||||||
|
if (m_keyMaterial.size() == 0U)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the encryption key.
|
||||||
|
* @param[out] key Buffer containing the key.
|
||||||
|
*/
|
||||||
|
void getKey(uint8_t* key) const
|
||||||
|
{
|
||||||
|
assert(key != nullptr);
|
||||||
|
|
||||||
|
const char* rawKey = m_keyMaterial.c_str();
|
||||||
|
::memset(key, 0x00U, 32U);
|
||||||
|
|
||||||
|
for (uint8_t i = 0U; i < 32U; i++) {
|
||||||
|
char t[4] = {rawKey[0], rawKey[1], 0};
|
||||||
|
key[i] = (uint8_t)::strtoul(t, NULL, 16);
|
||||||
|
if (i + 2U > m_keyMaterial.size())
|
||||||
|
break;
|
||||||
|
rawKey += 2 * sizeof(char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*/
|
||||||
|
__PROPERTY_PLAIN(uint32_t, id);
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*/
|
||||||
|
__PROPERTY_PLAIN(std::string, name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*/
|
||||||
|
__PROPERTY_PLAIN(uint32_t, keysetId);
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*/
|
||||||
|
__PROPERTY_PLAIN(uint32_t, sln);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encryption algorithm ID.
|
||||||
|
*/
|
||||||
|
__PROPERTY_PLAIN(uint8_t, algId);
|
||||||
|
/**
|
||||||
|
* @brief Encryption key ID.
|
||||||
|
*/
|
||||||
|
__PROPERTY_PLAIN(uint32_t, kId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encryption key material.
|
||||||
|
*/
|
||||||
|
__PROPERTY_PLAIN(std::string, keyMaterial);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Implements a threading lookup table class that contains routing
|
||||||
|
* rules information.
|
||||||
|
* @ingroup lookups_tgid
|
||||||
|
*/
|
||||||
|
class HOST_SW_API CryptoContainer : public Thread {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the CryptoContainer class.
|
||||||
|
* @param filename Full-path to the crypto container file.
|
||||||
|
* @param password Crypto container file access password.
|
||||||
|
* @param reloadTime Interval of time to reload the crypto container.
|
||||||
|
* @param enabled Flag indicating if crypto container is enabled.
|
||||||
|
*/
|
||||||
|
CryptoContainer(const std::string& filename, const std::string& password, uint32_t reloadTime, bool enabled);
|
||||||
|
/**
|
||||||
|
* @brief Finalizes a instance of the CryptoContainer class.
|
||||||
|
*/
|
||||||
|
~CryptoContainer() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thread entry point. This function is provided to run the thread
|
||||||
|
* for the lookup table.
|
||||||
|
*/
|
||||||
|
void entry() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stops and unloads this lookup table.
|
||||||
|
* @param noDestroy Flag indicating the lookup table should remain resident in memory after stopping.
|
||||||
|
*/
|
||||||
|
void stop(bool noDestroy = false);
|
||||||
|
/**
|
||||||
|
* @brief Reads the lookup table from the specified lookup table file.
|
||||||
|
* @returns bool True, if lookup table was read, otherwise false.
|
||||||
|
*/
|
||||||
|
bool read();
|
||||||
|
/**
|
||||||
|
* @brief Reads the lookup table from the specified lookup table file.
|
||||||
|
* @returns bool True, if lookup table was read, otherwise false.
|
||||||
|
*/
|
||||||
|
bool reload() { return load(); }
|
||||||
|
/**
|
||||||
|
* @brief Clears all entries from the lookup table.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adds a new entry to the lookup table.
|
||||||
|
* @param key Key Item.
|
||||||
|
*/
|
||||||
|
void addEntry(KeyItem key);
|
||||||
|
/**
|
||||||
|
* @brief Erases an existing entry from the lookup table by the specified unique ID.
|
||||||
|
* @param id Unique ID to erase.
|
||||||
|
*/
|
||||||
|
void eraseEntry(uint32_t id);
|
||||||
|
/**
|
||||||
|
* @brief Finds a table entry in this lookup table.
|
||||||
|
* @param kId Unique identifier for table entry.
|
||||||
|
* @returns KeyItem Table entry.
|
||||||
|
*/
|
||||||
|
virtual KeyItem find(uint32_t kId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper to return the flag indicating whether or not the crypto container is enabled.
|
||||||
|
* @returns bool
|
||||||
|
*/
|
||||||
|
bool isEnabled() const { return m_enabled; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper to set the reload time of this lookup table.
|
||||||
|
* @param reloadTime Lookup time in seconds.
|
||||||
|
*/
|
||||||
|
void setReloadTime(uint32_t reloadTime) { m_reloadTime = reloadTime; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_file;
|
||||||
|
std::string m_password;
|
||||||
|
uint32_t m_reloadTime;
|
||||||
|
|
||||||
|
bool m_enabled;
|
||||||
|
bool m_stop;
|
||||||
|
|
||||||
|
static std::mutex m_mutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Loads the table from the passed lookup table file.
|
||||||
|
* @return True, if lookup table was loaded, otherwise false.
|
||||||
|
*/
|
||||||
|
bool load();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief List of keys.
|
||||||
|
*/
|
||||||
|
__PROPERTY_PLAIN(std::vector<KeyItem>, keys);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __CRYPTO_CONTAINER_H__
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,467 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Common Library
|
||||||
|
* MIT Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||||
|
* Copyright (C) 2019 https://github.com/Fe-Bell/RapidXML
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @defgroup xml Extensible Markup Langauge
|
||||||
|
* @brief Defines and implements XML handling.
|
||||||
|
* @ingroup common
|
||||||
|
*
|
||||||
|
* @file rapidxml_print.h
|
||||||
|
* @ingroup xml
|
||||||
|
*/
|
||||||
|
#if !defined(__RAPIDXML_PRINT_H__)
|
||||||
|
#define __RAPIDXML_PRINT_H__
|
||||||
|
|
||||||
|
// Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||||
|
// Copyright (C) 2019 https://github.com/Fe-Bell/RapidXML
|
||||||
|
// Version 1.17
|
||||||
|
// Revision $DateTime: 2023/09/19 23:27:00 $
|
||||||
|
//! \file rapidxml_print.h This file contains rapidxml printer implementation
|
||||||
|
|
||||||
|
#include "xml/rapidxml.h"
|
||||||
|
|
||||||
|
// Only include streams if not disabled
|
||||||
|
#ifndef RAPIDXML_NO_STREAMS
|
||||||
|
#include <ostream>
|
||||||
|
#include <iterator>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace rapidxml
|
||||||
|
{
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
// Printing flags
|
||||||
|
|
||||||
|
const int print_no_indenting = 0x1; //!< Printer flag instructing the printer to suppress indenting of XML. See print() function.
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
// Internal
|
||||||
|
|
||||||
|
//! \cond internal
|
||||||
|
namespace internal
|
||||||
|
{
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Internal character operations
|
||||||
|
|
||||||
|
// Copy characters from given range to given output iterator
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out)
|
||||||
|
{
|
||||||
|
while (begin != end)
|
||||||
|
*out++ = *begin++;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy characters from given range to given output iterator and expand
|
||||||
|
// characters into references (< > ' " &)
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out)
|
||||||
|
{
|
||||||
|
while (begin != end)
|
||||||
|
{
|
||||||
|
if (*begin == noexpand)
|
||||||
|
{
|
||||||
|
*out++ = *begin; // No expansion, copy character
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (*begin)
|
||||||
|
{
|
||||||
|
case Ch('<'):
|
||||||
|
*out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||||
|
break;
|
||||||
|
case Ch('>'):
|
||||||
|
*out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||||
|
break;
|
||||||
|
case Ch('\''):
|
||||||
|
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';');
|
||||||
|
break;
|
||||||
|
case Ch('"'):
|
||||||
|
*out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||||
|
break;
|
||||||
|
case Ch('&'):
|
||||||
|
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
*out++ = *begin; // No expansion, copy character
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++begin; // Step to next character
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill given output iterator with repetitions of the same character
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt fill_chars(OutIt out, int n, Ch ch)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
*out++ = ch;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find character
|
||||||
|
template<class Ch, Ch ch>
|
||||||
|
inline bool find_char(const Ch *begin, const Ch *end)
|
||||||
|
{
|
||||||
|
while (begin != end)
|
||||||
|
if (*begin++ == ch)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Internal printing operations
|
||||||
|
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent);
|
||||||
|
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags);
|
||||||
|
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
|
||||||
|
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
|
||||||
|
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
|
||||||
|
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
|
||||||
|
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
|
||||||
|
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
|
||||||
|
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
|
||||||
|
|
||||||
|
// Print node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
// Print proper node type
|
||||||
|
switch (node->type())
|
||||||
|
{
|
||||||
|
|
||||||
|
// Document
|
||||||
|
case node_type::node_document:
|
||||||
|
out = print_children(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Element
|
||||||
|
case node_type::node_element:
|
||||||
|
out = print_element_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Data
|
||||||
|
case node_type::node_data:
|
||||||
|
out = print_data_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// CDATA
|
||||||
|
case node_type::node_cdata:
|
||||||
|
out = print_cdata_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Declaration
|
||||||
|
case node_type::node_declaration:
|
||||||
|
out = print_declaration_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Comment
|
||||||
|
case node_type::node_comment:
|
||||||
|
out = print_comment_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Doctype
|
||||||
|
case node_type::node_doctype:
|
||||||
|
out = print_doctype_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Pi
|
||||||
|
case node_type::node_pi:
|
||||||
|
out = print_pi_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Unknown
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If indenting not disabled, add line break after node
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
*out = Ch('\n'), ++out;
|
||||||
|
|
||||||
|
// Return modified iterator
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print children of the node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
for (xml_node<Ch> *child = node->first_node(); child; child = child->next_sibling())
|
||||||
|
out = print_node(out, child, flags, indent);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print attributes of the node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags)
|
||||||
|
{
|
||||||
|
for (xml_attribute<Ch> *attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute())
|
||||||
|
{
|
||||||
|
if (attribute->name() && attribute->value())
|
||||||
|
{
|
||||||
|
// Print attribute name
|
||||||
|
*out = Ch(' '), ++out;
|
||||||
|
out = copy_chars(attribute->name(), attribute->name() + attribute->name_size(), out);
|
||||||
|
*out = Ch('='), ++out;
|
||||||
|
// Print attribute value using appropriate quote type
|
||||||
|
if (find_char<Ch, Ch('"')>(attribute->value(), attribute->value() + attribute->value_size()))
|
||||||
|
{
|
||||||
|
*out = Ch('\''), ++out;
|
||||||
|
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('"'), out);
|
||||||
|
*out = Ch('\''), ++out;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*out = Ch('"'), ++out;
|
||||||
|
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\''), out);
|
||||||
|
*out = Ch('"'), ++out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print data node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_type::node_data);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print data node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_type::node_cdata);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'); ++out;
|
||||||
|
*out = Ch('!'); ++out;
|
||||||
|
*out = Ch('['); ++out;
|
||||||
|
*out = Ch('C'); ++out;
|
||||||
|
*out = Ch('D'); ++out;
|
||||||
|
*out = Ch('A'); ++out;
|
||||||
|
*out = Ch('T'); ++out;
|
||||||
|
*out = Ch('A'); ++out;
|
||||||
|
*out = Ch('['); ++out;
|
||||||
|
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||||
|
*out = Ch(']'); ++out;
|
||||||
|
*out = Ch(']'); ++out;
|
||||||
|
*out = Ch('>'); ++out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print element node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_type::node_element);
|
||||||
|
|
||||||
|
// Print element name and attributes, if any
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||||
|
out = print_attributes(out, node, flags);
|
||||||
|
|
||||||
|
// If node is childless
|
||||||
|
if (node->value_size() == 0 && !node->first_node())
|
||||||
|
{
|
||||||
|
// Print childless node tag ending
|
||||||
|
*out = Ch('/'), ++out;
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Print normal node tag ending
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
|
||||||
|
// Test if node contains a single data node only (and no other nodes)
|
||||||
|
xml_node<Ch> *child = node->first_node();
|
||||||
|
if (!child)
|
||||||
|
{
|
||||||
|
// If node has no children, only print its value without indenting
|
||||||
|
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
|
||||||
|
}
|
||||||
|
else if (child->next_sibling() == 0 && child->type() == node_type::node_data)
|
||||||
|
{
|
||||||
|
// If node has a sole data child, only print its value without indenting
|
||||||
|
out = copy_and_expand_chars(child->value(), child->value() + child->value_size(), Ch(0), out);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Print all children with full indenting
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
*out = Ch('\n'), ++out;
|
||||||
|
out = print_children(out, node, flags, indent + 1);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print node end
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
*out = Ch('/'), ++out;
|
||||||
|
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print declaration node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
// Print declaration start
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
*out = Ch('?'), ++out;
|
||||||
|
*out = Ch('x'), ++out;
|
||||||
|
*out = Ch('m'), ++out;
|
||||||
|
*out = Ch('l'), ++out;
|
||||||
|
|
||||||
|
// Print attributes
|
||||||
|
out = print_attributes(out, node, flags);
|
||||||
|
|
||||||
|
// Print declaration end
|
||||||
|
*out = Ch('?'), ++out;
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print comment node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_type::node_comment);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
*out = Ch('!'), ++out;
|
||||||
|
*out = Ch('-'), ++out;
|
||||||
|
*out = Ch('-'), ++out;
|
||||||
|
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||||
|
*out = Ch('-'), ++out;
|
||||||
|
*out = Ch('-'), ++out;
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print doctype node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_type::node_doctype);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
*out = Ch('!'), ++out;
|
||||||
|
*out = Ch('D'), ++out;
|
||||||
|
*out = Ch('O'), ++out;
|
||||||
|
*out = Ch('C'), ++out;
|
||||||
|
*out = Ch('T'), ++out;
|
||||||
|
*out = Ch('Y'), ++out;
|
||||||
|
*out = Ch('P'), ++out;
|
||||||
|
*out = Ch('E'), ++out;
|
||||||
|
*out = Ch(' '), ++out;
|
||||||
|
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print pi node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_type::node_pi);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
*out = Ch('?'), ++out;
|
||||||
|
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||||
|
*out = Ch(' '), ++out;
|
||||||
|
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||||
|
*out = Ch('?'), ++out;
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//! \endcond
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Printing
|
||||||
|
|
||||||
|
//! Prints XML to given output iterator.
|
||||||
|
//! \param out Output iterator to print to.
|
||||||
|
//! \param node Node to be printed. Pass xml_document to print entire document.
|
||||||
|
//! \param flags Flags controlling how XML is printed.
|
||||||
|
//! \return Output iterator pointing to position immediately after last character of printed text.
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print(OutIt out, const xml_node<Ch> &node, int flags = 0)
|
||||||
|
{
|
||||||
|
return internal::print_node(out, &node, flags, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef RAPIDXML_NO_STREAMS
|
||||||
|
|
||||||
|
//! Prints XML to given output stream.
|
||||||
|
//! \param out Output stream to print to.
|
||||||
|
//! \param node Node to be printed. Pass xml_document to print entire document.
|
||||||
|
//! \param flags Flags controlling how XML is printed.
|
||||||
|
//! \return Output stream.
|
||||||
|
template<class Ch>
|
||||||
|
inline std::basic_ostream<Ch> &print(std::basic_ostream<Ch> &out, const xml_node<Ch> &node, int flags = 0)
|
||||||
|
{
|
||||||
|
print(std::ostream_iterator<Ch>(out), node, flags);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process.
|
||||||
|
//! \param out Output stream to print to.
|
||||||
|
//! \param node Node to be printed.
|
||||||
|
//! \return Output stream.
|
||||||
|
template<class Ch>
|
||||||
|
inline std::basic_ostream<Ch> &operator <<(std::basic_ostream<Ch> &out, const xml_node<Ch> &node)
|
||||||
|
{
|
||||||
|
return print(out, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __RAPIDXML_PRINT_H__
|
||||||
@ -0,0 +1,141 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Common Library
|
||||||
|
* MIT Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||||
|
* Copyright (C) 2019 https://github.com/Fe-Bell/RapidXML
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @defgroup xml Extensible Markup Langauge
|
||||||
|
* @brief Defines and implements XML handling.
|
||||||
|
* @ingroup common
|
||||||
|
*
|
||||||
|
* @file rapidxml_utils.h
|
||||||
|
* @ingroup xml
|
||||||
|
*/
|
||||||
|
#if !defined(__RAPIDXML_UTILS_H__)
|
||||||
|
#define __RAPIDXML_UTILS_H__
|
||||||
|
|
||||||
|
// Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||||
|
// Copyright (C) 2019 https://github.com/Fe-Bell/RapidXML
|
||||||
|
// Version 1.17
|
||||||
|
// Revision $DateTime: 2023/09/19 23:27:00 $
|
||||||
|
//! \file rapidxml_utils.h This file contains high-level rapidxml utilities that can be useful
|
||||||
|
//! in certain simple scenarios. They should probably not be used if maximizing performance is the main objective.
|
||||||
|
|
||||||
|
#include "xml/rapidxml.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace rapidxml
|
||||||
|
{
|
||||||
|
|
||||||
|
//! Represents data loaded from a file
|
||||||
|
template<class Ch = char>
|
||||||
|
class file
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
//! Loads file into the memory. Data will be automatically destroyed by the destructor.
|
||||||
|
//! \param filename Filename to load.
|
||||||
|
file(const char *filename)
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// Open stream
|
||||||
|
basic_ifstream<Ch> stream(filename, ios::binary);
|
||||||
|
if (!stream)
|
||||||
|
throw runtime_error(string("cannot open file ") + filename);
|
||||||
|
stream.unsetf(ios::skipws);
|
||||||
|
|
||||||
|
// Determine stream size
|
||||||
|
stream.seekg(0, ios::end);
|
||||||
|
size_t size = stream.tellg();
|
||||||
|
stream.seekg(0);
|
||||||
|
|
||||||
|
// Load data and add terminating 0
|
||||||
|
m_data.resize(size + 1);
|
||||||
|
stream.read(&m_data.front(), static_cast<streamsize>(size));
|
||||||
|
m_data[size] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Loads file into the memory. Data will be automatically destroyed by the destructor
|
||||||
|
//! \param stream Stream to load from
|
||||||
|
file(std::basic_istream<Ch> &stream)
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// Load data and add terminating 0
|
||||||
|
stream.unsetf(ios::skipws);
|
||||||
|
m_data.assign(istreambuf_iterator<Ch>(stream), istreambuf_iterator<Ch>());
|
||||||
|
if (stream.fail() || stream.bad())
|
||||||
|
throw runtime_error("error reading stream");
|
||||||
|
m_data.push_back(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Gets file data.
|
||||||
|
//! \return Pointer to data of file.
|
||||||
|
Ch *data()
|
||||||
|
{
|
||||||
|
return &m_data.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Gets file data.
|
||||||
|
//! \return Pointer to data of file.
|
||||||
|
const Ch *data() const
|
||||||
|
{
|
||||||
|
return &m_data.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Gets file data size.
|
||||||
|
//! \return Size of file data, in characters.
|
||||||
|
std::size_t size() const
|
||||||
|
{
|
||||||
|
return m_data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::vector<Ch> m_data; // File data
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Counts children of node. Time complexity is O(n).
|
||||||
|
//! \return Number of children of node
|
||||||
|
template<class Ch>
|
||||||
|
inline std::size_t count_children(xml_node<Ch> *node)
|
||||||
|
{
|
||||||
|
xml_node<Ch> *child = node->first_node();
|
||||||
|
std::size_t count = 0;
|
||||||
|
while (child)
|
||||||
|
{
|
||||||
|
++count;
|
||||||
|
child = child->next_sibling();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Counts attributes of node. Time complexity is O(n).
|
||||||
|
//! \return Number of attributes of node
|
||||||
|
template<class Ch>
|
||||||
|
inline std::size_t count_attributes(xml_node<Ch> *node)
|
||||||
|
{
|
||||||
|
xml_attribute<Ch> *attr = node->first_attribute();
|
||||||
|
std::size_t count = 0;
|
||||||
|
while (attr)
|
||||||
|
{
|
||||||
|
++count;
|
||||||
|
attr = attr->next_attribute();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __RAPIDXML_UTILS_H__
|
||||||
Loading…
Reference in new issue