implement the beginnings of KMM frame handling (for future use);

pull/86/head
Bryan Biedenkapp 11 months ago
parent 676a112313
commit ded260216a

@ -28,6 +28,7 @@ file(GLOB common_SRC
"src/common/p25/lc/tsbk/*.cpp"
"src/common/p25/lc/tsbk/mbt/*.cpp"
"src/common/p25/sndcp/*.cpp"
"src/common/p25/kmm/*.cpp"
"src/common/p25/lookups/*.cpp"
# NXDN module
@ -73,6 +74,7 @@ file(GLOB common_INCLUDE
"src/common/p25/lc/tsbk/*.h"
"src/common/p25/lc/tsbk/mbt/*.h"
"src/common/p25/sndcp/*.h"
"src/common/p25/kmm/*.h"
"src/common/p25/lookups/*.h"
# NXDN module

@ -22,7 +22,9 @@
#include "common/Defines.h"
// Shorthand macro to dmr::defines -- keeps source code that doesn't use "using" concise
#if !defined(DMRDEF)
#define DMRDEF dmr::defines
#endif // DMRDEF
namespace dmr
{
namespace defines

@ -22,7 +22,9 @@
#include "common/Defines.h"
// Shorthand macro to nxdn::defines -- keeps source code that doesn't use "using" concise
#if !defined(NXDDEF)
#define NXDDEF nxdn::defines
#endif // NXDDEF
namespace nxdn
{
namespace defines

@ -22,7 +22,9 @@
#include "common/Defines.h"
// Shorthand macro to p25::defines -- keeps source code that doesn't use "using" concise
#if !defined(P25DEF)
#define P25DEF p25::defines
#endif // P25DEF
namespace p25
{
namespace defines
@ -411,6 +413,58 @@ namespace p25
};
}
/** @brief KMM Message Type */
namespace KMM_MessageType {
enum : uint8_t {
NULL_CMD = 0x00U, //! Null
CHANGE_RSI_CMD = 0x03U, //! Change RSI Command
CHANGE_RSI_RSP = 0x04U, //! Change RSI Response
CHANGEOVER_CMD = 0x05U, //! Changeover Command
CHANGEOVER_RSP = 0x06U, //! Changeover Response
HELLO = 0x0CU, //! Hello
INVENTORY_CMD = 0x0DU, //! Inventory Command
INVENTORY_RSP = 0x0EU, //! Inventory Response
MODIFY_KEY_CMD = 0x13U, //! Modify Key Command
NAK = 0x16U, //! Negative Ack
ZEROIZE_CMD = 0x21U, //! Zeroize Command
ZEROIZE_RSP = 0x22U, //! Zeroize Response
};
}
/** @brief KMM Response Kind */
namespace KMM_ResponseKind {
enum : uint8_t {
NONE = 0x00U, //! Response Kind 1 (None)
DELAYED = 0x01U, //! Response Kind 2 (Delayed)
IMMEDIATE = 0x02U, //! Response Kind 3 (Immediate)
};
}
/** @brief KMM Message Authentication */
namespace KMM_MAC {
enum : uint8_t {
NO_MAC = 0x00U, //! No Message Authentication
ENH_MAC = 0x02U, //! Enhanced Message Authentication
DES_MAC = 0x03U, //! DES Message Authentication
};
}
/** @brief KMM Decryption Instruction - None */
const uint8_t KMM_DECRYPT_INSTRUCT_NONE = 0x00U;
/** @brief KMM Decryption Instruction - Message Indicator */
const uint8_t KMM_DECRYPT_INSTRUCT_MI = 0x40U;
/** @brief KMM Key Format TEK */
const uint8_t KEY_FORMAT_TEK = 0x80U;
/** @brief KMM Key Format Delete Key */
const uint8_t KEY_FORMAT_DELETE = 0x20U;
/** @brief SNDCP version 1 */
const uint8_t SNDCP_VERSION_1 = 0x01U;
/** @brief 296 byte MTU */

@ -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__

@ -1519,9 +1519,9 @@ bool Host::rmtPortModemHandler(Modem* modem, uint32_t ms, modem::RESP_TYPE_DVM r
if (modem->getTrace())
Utils::dump(1U, "TX Remote Data", buffer, len);
// never send less then 3 bytes
if (len < 3U)
return false;
// never send less then 3 bytes
if (len < 3U)
return false;
// send entire modem packet over the remote port
m_modemRemotePort->write(buffer, len);

Loading…
Cancel
Save

Powered by TurnKey Linux.