commit
6812029491
@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* 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 "Defines.h"
|
||||
#include "p25/P25Defines.h"
|
||||
#include "p25/kmm/KMMFrame.h"
|
||||
#include "Log.h"
|
||||
|
||||
using namespace p25;
|
||||
using namespace p25::defines;
|
||||
using namespace p25::kmm;
|
||||
|
||||
#include <cassert>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Initializes a copy instance of the KMMFrame class. */
|
||||
|
||||
KMMFrame::KMMFrame(const KMMFrame& data) : KMMFrame()
|
||||
{
|
||||
copy(data);
|
||||
}
|
||||
|
||||
/* Initializes a new instance of the KMMFrame class. */
|
||||
|
||||
KMMFrame::KMMFrame() :
|
||||
m_messageId(KMM_MessageType::NULL_CMD),
|
||||
m_messageLength(7U),
|
||||
m_respKind(KMM_ResponseKind::NONE),
|
||||
m_complete(true),
|
||||
m_mfMessageNumber(0U),
|
||||
m_mfMac(KMM_MAC::NO_MAC)
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
|
||||
/* Finalizes a instance of the KMMFrame class. */
|
||||
|
||||
KMMFrame::~KMMFrame() = default;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Protected Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Internal helper to decode a SNDCP header. */
|
||||
|
||||
bool KMMFrame::decodeHeader(const uint8_t* data, bool outbound)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
|
||||
m_messageId = data[0U]; // Message ID
|
||||
m_messageLength = __GET_UINT16B(data, 1U); // Message Length
|
||||
|
||||
m_respKind = (data[2U] >> 6U) & 0x03U; // Response Kind
|
||||
m_mfMessageNumber = (data[2U] >> 4U) & 0x03U; // Message Number
|
||||
m_mfMac = (data[2U] >> 2U) & 0x03U; // MAC
|
||||
|
||||
bool done = (data[2U] & 0x01U) == 0x01U; // Done Flag
|
||||
if (!done)
|
||||
m_complete = true;
|
||||
else
|
||||
m_complete = false;
|
||||
|
||||
m_dstLlId = __GET_UINT16(data, 4U); // Destination RSI
|
||||
m_srcLlId = __GET_UINT16(data, 7U); // Source RSI
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Internal helper to encode a SNDCP header. */
|
||||
|
||||
void KMMFrame::encodeHeader(uint8_t* data, bool outbound)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
|
||||
data[0U] = m_messageId; // Message ID
|
||||
__SET_UINT16B(m_messageLength, data, 2U); // Message Length
|
||||
|
||||
data[2U] = ((m_respKind & 0x03U) << 6U) + // Response Kind
|
||||
((m_mfMessageNumber & 0x03U) << 4U) + // Message Number
|
||||
((m_mfMac & 0x03U) << 2U) + // MAC
|
||||
((!m_complete) ? 0x01U : 0x00U); // Done Flag
|
||||
|
||||
__SET_UINT16(m_dstLlId, data, 4U); // Destination RSI
|
||||
__SET_UINT16(m_srcLlId, data, 7U); // Source RSI
|
||||
}
|
||||
|
||||
/* Internal helper to copy the the class. */
|
||||
|
||||
void KMMFrame::copy(const KMMFrame& data)
|
||||
{
|
||||
m_messageId = data.m_messageId;
|
||||
m_messageLength = data.m_messageLength;
|
||||
m_respKind = data.m_respKind;
|
||||
m_complete = data.m_complete;
|
||||
|
||||
m_mfMessageNumber = data.m_mfMessageNumber;
|
||||
m_mfMac = data.m_mfMac;
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @defgroup p25_kmm Key Management Message
|
||||
* @brief Implementation for the data handling of the TIA-102.AACA Project 25 standard.
|
||||
* @ingroup p25
|
||||
*
|
||||
* @file KMMFrame.h
|
||||
* @ingroup p25_kmm
|
||||
* @file KMMFrame.cpp
|
||||
* @ingroup p25_kmm
|
||||
*/
|
||||
#if !defined(__P25_KMM__KMM_FRAME_H__)
|
||||
#define __P25_KMM__KMM_FRAME_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/Utils.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace p25
|
||||
{
|
||||
namespace kmm
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @addtogroup p25_kmm
|
||||
* @{
|
||||
*/
|
||||
|
||||
const uint32_t KMM_FRAME_LENGTH = 9U;
|
||||
|
||||
/** @} */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Represents a KMM frame packet header.
|
||||
* @ingroup p25_kmm
|
||||
*/
|
||||
class HOST_SW_API KMMFrame {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a copy instance of the KMMFrame class.
|
||||
* @param data Instance of KMMFrame to copy/
|
||||
*/
|
||||
KMMFrame(const KMMFrame& data);
|
||||
/**
|
||||
* @brief Initializes a new instance of the KMMFrame class.
|
||||
*/
|
||||
KMMFrame();
|
||||
/**
|
||||
* @brief Finalizes a instance of the KMMFrame class.
|
||||
*/
|
||||
~KMMFrame();
|
||||
|
||||
/**
|
||||
* @brief Gets the byte length of this KMMFrame.
|
||||
* @return uint32_t Length of KMMFrame.
|
||||
*/
|
||||
virtual uint32_t length() const { return KMM_FRAME_LENGTH; }
|
||||
|
||||
/**
|
||||
* @brief Decode a KMM frame.
|
||||
* @param[in] data Buffer containing KMM frame data to decode.
|
||||
* @returns bool True, if decoded, otherwise false.
|
||||
*/
|
||||
virtual bool decode(const uint8_t* data) = 0;
|
||||
/**
|
||||
* @brief Encode a KMM frame.
|
||||
* @param[out] data Buffer to encode KMM frame data to.
|
||||
*/
|
||||
virtual void encode(uint8_t* data) = 0;
|
||||
|
||||
public:
|
||||
// Common Data
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
__PROTECTED_PROPERTY(uint8_t, messageId, MessageId);
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
__PROTECTED_PROPERTY(uint16_t, messageLength, MessageLength);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
__PROTECTED_PROPERTY(uint8_t, respKind, ResponseKind);
|
||||
|
||||
/**
|
||||
* @brief Destination Logical link ID.
|
||||
*/
|
||||
__PROTECTED_PROPERTY(uint32_t, dstLlId, DstLLId);
|
||||
/**
|
||||
* @brief Source Logical link ID.
|
||||
*/
|
||||
__PROTECTED_PROPERTY(uint32_t, srcLlId, SrcLLId);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
__PROTECTED_PROPERTY(bool, complete, Complete);
|
||||
|
||||
protected:
|
||||
uint8_t m_mfMessageNumber;
|
||||
uint8_t m_mfMac;
|
||||
|
||||
/**
|
||||
* @brief Internal helper to decode a KMM header.
|
||||
* @param data Buffer containing KMM packet data to decode.
|
||||
* @param outbound Flag indicating whether the packet is inbound or outbound.
|
||||
* @returns bool True, if decoded, otherwise false.
|
||||
*/
|
||||
bool decodeHeader(const uint8_t* data, bool outbound = false);
|
||||
/**
|
||||
* @brief Internal helper to encode a KMM header.
|
||||
* @param data Buffer to encode KMM packet data to.
|
||||
* @param outbound Flag indicating whether the packet is inbound or outbound.
|
||||
*/
|
||||
void encodeHeader(uint8_t* data, bool outbound = false);
|
||||
|
||||
__PROTECTED_COPY(KMMFrame);
|
||||
};
|
||||
} // namespace kmm
|
||||
} // namespace p25
|
||||
|
||||
#endif // __P25_KMM__KMM_FRAME_H__
|
||||
@ -0,0 +1,171 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* 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 "Defines.h"
|
||||
#include "p25/P25Defines.h"
|
||||
#include "p25/kmm/KMMModifyKey.h"
|
||||
#include "Log.h"
|
||||
|
||||
using namespace p25;
|
||||
using namespace p25::defines;
|
||||
using namespace p25::kmm;
|
||||
|
||||
#include <cassert>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Initializes a new instance of the KMMModifyKey class. */
|
||||
|
||||
KMMModifyKey::KMMModifyKey() : KMMFrame(),
|
||||
m_decryptInfoFmt(KMM_DECRYPT_INSTRUCT_NONE),
|
||||
m_algId(ALGO_UNENCRYPT),
|
||||
m_kId(0U),
|
||||
m_keysetItem(),
|
||||
m_miSet(false),
|
||||
m_mi(nullptr)
|
||||
{
|
||||
m_mi = new uint8_t[MI_LENGTH_BYTES];
|
||||
::memset(m_mi, 0x00U, MI_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
/* Finalizes a instance of the KMMModifyKey class. */
|
||||
|
||||
KMMModifyKey::~KMMModifyKey()
|
||||
{
|
||||
if (m_mi != nullptr) {
|
||||
delete[] m_mi;
|
||||
m_mi = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Gets the byte length of this KMMFrame. */
|
||||
|
||||
uint32_t KMMModifyKey::length() const
|
||||
{
|
||||
uint32_t len = KMM_MODIFY_KEY_LENGTH;
|
||||
if (m_miSet)
|
||||
len += MI_LENGTH_BYTES;
|
||||
len += m_keysetItem.length();
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Decode a KMM modify key. */
|
||||
|
||||
bool KMMModifyKey::decode(const uint8_t* data)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
|
||||
KMMFrame::decodeHeader(data, false);
|
||||
|
||||
m_decryptInfoFmt = data[10U]; // Decryption Instruction Format
|
||||
m_algId = data[11U]; // Algorithm ID
|
||||
m_kId = data[12U]; // Key ID
|
||||
|
||||
uint16_t offset = 0U;
|
||||
if (m_decryptInfoFmt == KMM_DECRYPT_INSTRUCT_MI) {
|
||||
::memset(m_mi, 0x00U, MI_LENGTH_BYTES);
|
||||
::memcpy(m_mi, data + 13U, MI_LENGTH_BYTES);
|
||||
offset += 9U;
|
||||
}
|
||||
|
||||
m_keysetItem.keysetId(data[13U + offset]);
|
||||
m_keysetItem.algId(data[14U + offset]);
|
||||
m_keysetItem.keyLength(data[15U + offset]);
|
||||
|
||||
uint8_t keyCount = data[16U + offset];
|
||||
for (uint8_t i = 0U; i < keyCount; i++) {
|
||||
KeyItem* key = new KeyItem();
|
||||
|
||||
UInt8Array __keyPayload = std::make_unique<uint8_t[]>(m_keysetItem.keyLength());
|
||||
uint8_t* keyPayload = __keyPayload.get();
|
||||
|
||||
uint8_t keyFormat = data[17U + offset];
|
||||
uint8_t keyNameLen = keyFormat & 0x1FU;
|
||||
|
||||
key->keyFormat(keyFormat & 0xE0U);
|
||||
key->sln(data[18U + offset]);
|
||||
key->kId(data[19U + offset]);
|
||||
|
||||
::memcpy(keyPayload, data + (20U + offset), m_keysetItem.keyLength());
|
||||
key->setKey(keyPayload, m_keysetItem.keyLength());
|
||||
|
||||
m_keysetItem.push_back(key);
|
||||
|
||||
offset += 5U + keyNameLen + m_keysetItem.keyLength();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Encode a KMM modify key. */
|
||||
|
||||
void KMMModifyKey::encode(uint8_t* data)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
|
||||
KMMFrame::encodeHeader(data, true);
|
||||
|
||||
if (!m_miSet && m_decryptInfoFmt == KMM_DECRYPT_INSTRUCT_MI) {
|
||||
m_decryptInfoFmt = KMM_DECRYPT_INSTRUCT_NONE;
|
||||
}
|
||||
|
||||
data[10U] = m_decryptInfoFmt; // Decryption Instruction Format
|
||||
data[11U] = m_algId; // Algorithm ID
|
||||
data[12U] = m_kId; // Key ID
|
||||
|
||||
uint16_t offset = 0U;
|
||||
if (m_decryptInfoFmt == KMM_DECRYPT_INSTRUCT_MI) {
|
||||
::memcpy(data + 13U, m_mi, MI_LENGTH_BYTES);
|
||||
offset += 9U;
|
||||
}
|
||||
|
||||
data[13U + offset] = m_keysetItem.keysetId();
|
||||
data[14U + offset] = m_keysetItem.algId();
|
||||
data[15U + offset] = m_keysetItem.keyLength();
|
||||
|
||||
uint8_t keyCount = m_keysetItem.keys().size();
|
||||
data[16U + offset] = keyCount;
|
||||
for (auto key : m_keysetItem.keys()) {
|
||||
uint8_t keyNameLen = key->keyFormat() & 0x1FU;
|
||||
data[17U + offset] = key->keyFormat();
|
||||
data[18U + offset] = key->sln();
|
||||
data[19U + offset] = key->kId();
|
||||
|
||||
UInt8Array __keyPayload = std::make_unique<uint8_t[]>(m_keysetItem.keyLength());
|
||||
uint8_t* keyPayload = __keyPayload.get();
|
||||
key->getKey(keyPayload);
|
||||
|
||||
::memcpy(data + (20U + offset), keyPayload, m_keysetItem.keyLength());
|
||||
|
||||
offset += 5U + keyNameLen + m_keysetItem.keyLength();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Protected Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Internal helper to copy the the class. */
|
||||
|
||||
void KMMModifyKey::copy(const KMMModifyKey& data)
|
||||
{
|
||||
m_decryptInfoFmt = data.m_decryptInfoFmt;
|
||||
m_algId = data.m_algId;
|
||||
m_kId = data.m_kId;
|
||||
|
||||
if (data.m_mi != nullptr) {
|
||||
::memset(m_mi, 0x00U, MI_LENGTH_BYTES);
|
||||
::memcpy(m_mi, data.m_mi, MI_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
m_keysetItem = data.m_keysetItem;
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* 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 KMMModifyKey.h
|
||||
* @ingroup p25_kmm
|
||||
* @file KMMModifyKey.cpp
|
||||
* @ingroup p25_kmm
|
||||
*/
|
||||
#if !defined(__P25_KMM__KMM_MODIFY_KEY_H__)
|
||||
#define __P25_KMM__KMM_MODIFY_KEY_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/p25/kmm/KMMFrame.h"
|
||||
#include "common/p25/kmm/KeysetItem.h"
|
||||
#include "common/Utils.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace p25
|
||||
{
|
||||
namespace kmm
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @addtogroup p25_kmm
|
||||
* @{
|
||||
*/
|
||||
|
||||
const uint32_t KMM_MODIFY_KEY_LENGTH = KMM_FRAME_LENGTH + 4U;
|
||||
|
||||
/** @} */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class HOST_SW_API KMMModifyKey : public KMMFrame {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the KMMModifyKey class.
|
||||
*/
|
||||
KMMModifyKey();
|
||||
/**
|
||||
* @brief Finalizes a instance of the KMMModifyKey class.
|
||||
*/
|
||||
~KMMModifyKey();
|
||||
|
||||
/**
|
||||
* @brief Gets the byte length of this KMMFrame.
|
||||
* @return uint32_t Length of KMMFrame.
|
||||
*/
|
||||
uint32_t length() const override;
|
||||
|
||||
/**
|
||||
* @brief Decode a KMM modify key packet.
|
||||
* @param[in] data Buffer containing KMM packet data to decode.
|
||||
* @returns bool True, if decoded, otherwise false.
|
||||
*/
|
||||
bool decode(const uint8_t* data);
|
||||
/**
|
||||
* @brief Encode a KMM modify key packet.
|
||||
* @param[out] data Buffer to encode KMM packet data to.
|
||||
*/
|
||||
void encode(uint8_t* data);
|
||||
|
||||
/** @name Encryption data */
|
||||
/**
|
||||
* @brief Sets the encryption message indicator.
|
||||
* @param[in] mi Buffer containing the 9-byte Message Indicator.
|
||||
*/
|
||||
void setMI(const uint8_t* mi);
|
||||
/**
|
||||
* @brief Gets the encryption message indicator.
|
||||
* @param[out] mi Buffer containing the 9-byte Message Indicator.
|
||||
*/
|
||||
void getMI(uint8_t* mi) const;
|
||||
/** @} */
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
__PROPERTY(uint8_t, decryptInfoFmt, DecryptInfoFmt);
|
||||
/**
|
||||
* @brief Encryption algorithm ID.
|
||||
*/
|
||||
__PROPERTY(uint8_t, algId, AlgId);
|
||||
/**
|
||||
* @brief Encryption key ID.
|
||||
*/
|
||||
__PROPERTY(uint32_t, kId, KId);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
__PROPERTY(KeysetItem, keysetItem, KeysetItem);
|
||||
|
||||
__COPY(KMMModifyKey);
|
||||
|
||||
private:
|
||||
// Encryption data
|
||||
bool m_miSet;
|
||||
uint8_t* m_mi;
|
||||
};
|
||||
} // namespace kmm
|
||||
} // namespace p25
|
||||
|
||||
#endif // __P25_KMM__KMM_MODIFY_KEY_H__
|
||||
@ -0,0 +1,228 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* 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 KeysetItem.h
|
||||
* @ingroup p25_kmm
|
||||
*/
|
||||
#if !defined(__P25_KMM__KEYSET_ITEM_H__)
|
||||
#define __P25_KMM__KEYSET_ITEM_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/Utils.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
namespace p25
|
||||
{
|
||||
namespace kmm
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Represents a key set item within a KMM frame packet.
|
||||
* @ingroup p25_kmm
|
||||
*/
|
||||
class HOST_SW_API KeyItem {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the KeyItem class.
|
||||
*/
|
||||
KeyItem() :
|
||||
m_keyFormat(0x80U/*P25DEF::KEY_FORMAT_TEK*/),
|
||||
m_sln(0U),
|
||||
m_kId(0U),
|
||||
m_keyLength(0U),
|
||||
m_keyMaterial(nullptr)
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
/**
|
||||
* @brief Finalizes a instance of the KeyItem class.
|
||||
*/
|
||||
~KeyItem()
|
||||
{
|
||||
if (m_keyMaterial != nullptr) {
|
||||
delete[] m_keyMaterial;
|
||||
m_keyMaterial = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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_keyFormat = data.m_keyFormat;
|
||||
m_sln = data.m_sln;
|
||||
m_kId = data.m_kId;
|
||||
|
||||
if (data.m_keyLength > 0U) {
|
||||
if (m_keyMaterial != nullptr) {
|
||||
delete[] m_keyMaterial;
|
||||
}
|
||||
|
||||
m_keyMaterial = new uint8_t[data.m_keyLength];
|
||||
m_keyLength = data.m_keyLength;
|
||||
::memcpy(m_keyMaterial, data.m_keyMaterial, data.m_keyLength);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the key material.
|
||||
* @param key
|
||||
* @param keyLength
|
||||
*/
|
||||
void setKey(const uint8_t* key, uint32_t keyLength)
|
||||
{
|
||||
assert(key != nullptr);
|
||||
m_keyLength = keyLength;
|
||||
::memcpy(m_keyMaterial, key, keyLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the key material.
|
||||
* @param key
|
||||
*/
|
||||
void getKey(uint8_t* key) const
|
||||
{
|
||||
assert(key != nullptr);
|
||||
::memcpy(key, m_keyMaterial, m_keyLength);
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
__PROPERTY_PLAIN(uint8_t, keyFormat);
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
__PROPERTY_PLAIN(uint16_t, sln);
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
__PROPERTY_PLAIN(uint16_t, kId);
|
||||
|
||||
private:
|
||||
uint32_t m_keyLength;
|
||||
uint8_t* m_keyMaterial;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Represents a key set item within a KMM frame packet.
|
||||
* @ingroup p25_kmm
|
||||
*/
|
||||
class HOST_SW_API KeysetItem {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the KeysetItem class.
|
||||
*/
|
||||
KeysetItem() :
|
||||
m_keysetId(0U),
|
||||
m_algId(0U),
|
||||
m_keyLength(0U),
|
||||
m_keys()
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
/**
|
||||
* @brief Finalizes a instance of the KeysetItem class.
|
||||
*/
|
||||
~KeysetItem()
|
||||
{
|
||||
for (auto key : m_keys) {
|
||||
delete key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equals operator. Copies this KeysetItem to another KeysetItem.
|
||||
* @param data Instance of KeysetItem to copy.
|
||||
*/
|
||||
virtual KeysetItem& operator= (const KeysetItem& data)
|
||||
{
|
||||
if (this != &data) {
|
||||
m_keysetId = data.m_keysetId;
|
||||
m_algId = data.m_algId;
|
||||
m_keyLength = data.m_keyLength;
|
||||
|
||||
for (auto key : m_keys) {
|
||||
delete key;
|
||||
}
|
||||
m_keys.clear();
|
||||
|
||||
for (auto key : data.m_keys) {
|
||||
KeyItem* copy = key;
|
||||
m_keys.push_back(copy);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the byte length of this keyset item.
|
||||
* @return uint32_t Length of keyset item.
|
||||
*/
|
||||
uint32_t length() const
|
||||
{
|
||||
uint32_t len = 4U;
|
||||
|
||||
uint32_t keyItemLength = m_keys.size() * 5U;
|
||||
uint32_t combinedKeyLength = m_keys.size() * m_keyLength;
|
||||
len += keyItemLength + combinedKeyLength;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add a key to the key list.
|
||||
* @param key
|
||||
*/
|
||||
void push_back(KeyItem* key)
|
||||
{
|
||||
m_keys.push_back(key);
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
__PROPERTY_PLAIN(uint8_t, keysetId);
|
||||
/**
|
||||
* @brief Encryption algorithm ID.
|
||||
*/
|
||||
__PROPERTY_PLAIN(uint8_t, algId);
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
__PROPERTY_PLAIN(uint8_t, keyLength);
|
||||
|
||||
/**
|
||||
* @brief List of keys.
|
||||
*/
|
||||
__PROPERTY_PLAIN(std::vector<KeyItem*>, keys);
|
||||
};
|
||||
} // namespace kmm
|
||||
} // namespace p25
|
||||
|
||||
#endif // __P25_KMM__KEYSET_ITEM_H__
|
||||
@ -0,0 +1,573 @@
|
||||
// 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 // ENABLE_TCP_SSL
|
||||
|
||||
using namespace crypto;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define CHUNK 16384
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global Functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#if defined(ENABLE_TCP_SSL)
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
#endif // ENABLE_TCP_SSL
|
||||
|
||||
/**
|
||||
* @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__
|
||||
@ -1 +1 @@
|
||||
Subproject commit c45ec691a04741443f390354a366798db841900c
|
||||
Subproject commit 718093aea3a5a6c80f7fceb1bbbc940168b8d98c
|
||||
@ -1 +1 @@
|
||||
Subproject commit ca0882a6de99c0dc05eac5de14a5e096c77232ff
|
||||
Subproject commit 0a4c78445fb0c6929a3f351f3f4e693ec0b6d969
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue