reorganize source files; add mirror functionality for data router and packet scheduler from P25 to DMR; add tests;
parent
3060769f9b
commit
4d1300826c
@ -1,245 +0,0 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file ConventionalDataService.h
|
||||
* @ingroup p25_pdu
|
||||
* @file ConventionalDataService.cpp
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
#if !defined(__P25_DATA__CONVENTIONALDATASERVICE_H__)
|
||||
#define __P25_DATA__CONVENTIONALDATASERVICE_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/p25/data/ConventionalRegistration.h"
|
||||
#include "common/p25/data/PacketDataState.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace p25
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @addtogroup p25_pdu
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Conventional packet data registration decision.
|
||||
*/
|
||||
enum class RegistrationDecision : uint8_t {
|
||||
ACCEPT, //!< Registration request accepted.
|
||||
DENY, //!< Registration request denied.
|
||||
DISCONNECTED //!< Registration disconnected.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Reason a conventional packet data registration was denied.
|
||||
*/
|
||||
enum class RegistrationDenyReason : uint8_t {
|
||||
NONE, //!< No specific reason.
|
||||
MALFORMED, //!< Registration request was malformed.
|
||||
IDENTITY_MISMATCH, //!< Identity did not match expected value.
|
||||
NOT_PROVISIONED, //!< Device not provisioned.
|
||||
DISABLED, //!< Registration disabled.
|
||||
UNSUPPORTED_OPTIONS, //!< Registration request contained unsupported options.
|
||||
ADDRESS_UNAVAILABLE, //!< Requested address unavailable.
|
||||
ADDRESS_CONFLICT //!< Address conflict detected.
|
||||
};
|
||||
/* @} */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Provisioning inputs used to decide a registration request.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API ConventionalRegistrationProvisioning {
|
||||
/**
|
||||
* @brief Indicates whether the device is known.
|
||||
*/
|
||||
bool known = false;
|
||||
/**
|
||||
* @brief Indicates whether the device is enabled.
|
||||
*/
|
||||
bool enabled = false;
|
||||
/**
|
||||
* @brief Indicates whether dynamic address allocation is allowed.
|
||||
*/
|
||||
bool allowDynamicAddress = false;
|
||||
/**
|
||||
* @brief The static IP address of the device.
|
||||
*/
|
||||
uint32_t staticIPAddress = 0U;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Local policy applied to conventional registration requests.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API ConventionalRegistrationPolicy {
|
||||
/**
|
||||
* @brief Indicates whether CMS scan is allowed.
|
||||
*/
|
||||
bool allowCMSScan = false;
|
||||
/**
|
||||
* @brief The lifetime of a registration in milliseconds.
|
||||
*/
|
||||
uint64_t registrationLifetimeMs = 0U;
|
||||
/**
|
||||
* @brief Function to allocate a dynamic IP address.
|
||||
*/
|
||||
std::function<uint32_t(uint32_t, uint32_t)> allocateDynamicAddress;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Result of a conventional registration transaction.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API ConventionalRegistrationResult {
|
||||
/**
|
||||
* @brief The decision of the registration request.
|
||||
*/
|
||||
RegistrationDecision decision = RegistrationDecision::DENY;
|
||||
/**
|
||||
* @brief The reason for denying the registration request.
|
||||
*/
|
||||
RegistrationDenyReason denyReason = RegistrationDenyReason::MALFORMED;
|
||||
/**
|
||||
* @brief The response to the registration request.
|
||||
*/
|
||||
ConventionalRegistration response;
|
||||
/**
|
||||
* @brief Indicates whether the binding has changed as a result of the registration request.
|
||||
*/
|
||||
bool bindingChanged { false };
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Implements conventional packet data registration and binding state.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
class DVM_COMMON_API ConventionalDataService {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the ConventionalDataService class.
|
||||
* @param bindings The data binding registry.
|
||||
* @param links The data link manager.
|
||||
* @param policy The conventional registration policy.
|
||||
*/
|
||||
ConventionalDataService(DataBindingRegistry& bindings, DataLinkManager& links,
|
||||
const ConventionalRegistrationPolicy& policy = ConventionalRegistrationPolicy());
|
||||
|
||||
/**
|
||||
* @brief Processes a conventional registration request.
|
||||
* @param data The raw registration request data.
|
||||
* @param length The length of the registration request data.
|
||||
* @param headerLlId The logical link identifier from the header.
|
||||
* @param provisioning The conventional registration provisioning information.
|
||||
* @param nowMs The current time in milliseconds.
|
||||
* @return The result of the registration request.
|
||||
*/
|
||||
ConventionalRegistrationResult process(const uint8_t* data, uint32_t length,
|
||||
uint32_t headerLlId, const ConventionalRegistrationProvisioning& provisioning,
|
||||
uint64_t nowMs = 0U);
|
||||
|
||||
/**
|
||||
* @brief Installs a static registration for the specified logical link identifier.
|
||||
* @param llId The logical link identifier.
|
||||
* @param ipAddress The IP address associated with the registration.
|
||||
* @param authorized Indicates whether the registration is authorized.
|
||||
* @return True if the static registration was successfully installed; otherwise, false.
|
||||
*/
|
||||
bool installStatic(uint32_t llId, uint32_t ipAddress, bool authorized = true);
|
||||
/**
|
||||
* @brief Expires registrations that have passed their expiration time.
|
||||
* @param nowMs The current time in milliseconds.
|
||||
*/
|
||||
void expire(uint64_t nowMs);
|
||||
/**
|
||||
* @brief Checks if the specified logical link identifier is registered.
|
||||
* @param llId The logical link identifier.
|
||||
* @return True if the logical link identifier is registered; otherwise, false.
|
||||
*/
|
||||
bool isRegistered(uint32_t llId) const;
|
||||
/**
|
||||
* @brief Gets the number of current registrations.
|
||||
* @return The number of current registrations.
|
||||
*/
|
||||
size_t registrationCount() const noexcept { return m_registrations.size(); }
|
||||
|
||||
/**
|
||||
* @brief Converts a registration deny reason to a string representation.
|
||||
* @param reason The registration deny reason.
|
||||
* @return A string representation of the registration deny reason.
|
||||
*/
|
||||
static std::string denyReasonToString(RegistrationDenyReason reason);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Represents the state of a registration.
|
||||
*/
|
||||
struct RegistrationState {
|
||||
/**
|
||||
* @brief The IP address associated with the registration.
|
||||
*/
|
||||
uint32_t ipAddress = 0U;
|
||||
/**
|
||||
* @brief The options associated with the registration.
|
||||
*/
|
||||
uint8_t options = 0U;
|
||||
/**
|
||||
* @brief The expiration time of the registration in milliseconds.
|
||||
*/
|
||||
uint64_t expiresAtMs = 0U;
|
||||
/**
|
||||
* @brief Indicates whether the registration is dynamic.
|
||||
*/
|
||||
bool dynamic = false;
|
||||
};
|
||||
|
||||
DataBindingRegistry& m_bindings;
|
||||
DataLinkManager& m_links;
|
||||
ConventionalRegistrationPolicy m_policy;
|
||||
std::unordered_map<uint32_t, RegistrationState> m_registrations;
|
||||
|
||||
/**
|
||||
* @brief Denies a registration request.
|
||||
* @param request The registration request to deny.
|
||||
* @param reason The reason for denying the registration.
|
||||
* @return The result of the denial operation.
|
||||
*/
|
||||
ConventionalRegistrationResult deny(const ConventionalRegistration& request,
|
||||
RegistrationDenyReason reason) const;
|
||||
};
|
||||
} // namespace data
|
||||
} // namespace p25
|
||||
|
||||
#endif // __P25_DATA__CONVENTIONALDATASERVICE_H__
|
||||
@ -1,320 +0,0 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file DataRouting.h
|
||||
* @ingroup p25_pdu
|
||||
* @file DataRouting.cpp
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
#if !defined(__P25_DATA__DATAROUTING_H__)
|
||||
#define __P25_DATA__DATAROUTING_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/p25/data/PacketDataState.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace p25
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @addtogroup p25_pdu
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Access mode used to reach a P25 packet data subscriber.
|
||||
*/
|
||||
enum class AccessMode : uint8_t {
|
||||
CONVENTIONAL, //!< Conventional access mode.
|
||||
TRUNKED //!< Trunked access mode.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Policy applied when no current subscriber location is known.
|
||||
*/
|
||||
enum class UnknownLocationPolicy : uint8_t {
|
||||
QUEUE, //!< Queue the packet until location is known.
|
||||
ARP, //!< Attempt ARP resolution.
|
||||
DROP //!< Drop the packet if location is unknown.
|
||||
};
|
||||
/* @} */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Last observed conventional location for a subscriber.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API ConventionalLocation {
|
||||
/**
|
||||
* @brief Peer ID of the subscriber.
|
||||
*/
|
||||
uint32_t peerId = 0U;
|
||||
/**
|
||||
* @brief Channel ID of the subscriber.
|
||||
*/
|
||||
uint8_t channelId = 0U;
|
||||
/**
|
||||
* @brief Channel number of the subscriber.
|
||||
*/
|
||||
uint16_t channelNo = 0U;
|
||||
/**
|
||||
* @brief Timestamp of the last observation.
|
||||
*/
|
||||
uint64_t lastSeen = 0U;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Resolved RF route for a P25 packet data subscriber.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API DataRoute {
|
||||
/**
|
||||
* @brief Access mode used to reach the subscriber.
|
||||
*/
|
||||
AccessMode mode = AccessMode::CONVENTIONAL;
|
||||
/**
|
||||
* @brief Peer ID of the subscriber.
|
||||
*/
|
||||
uint32_t peerId = 0U;
|
||||
/**
|
||||
* @brief Channel ID of the subscriber.
|
||||
*/
|
||||
uint8_t channelId = 0U;
|
||||
/**
|
||||
* @brief Channel number of the subscriber.
|
||||
*/
|
||||
uint16_t channelNo = 0U;
|
||||
/**
|
||||
* @brief Grant ID associated with the subscriber.
|
||||
*/
|
||||
uint32_t grantId = 0U;
|
||||
/**
|
||||
* @brief Timestamp of the last observation.
|
||||
*/
|
||||
uint64_t lastSeen = 0U;
|
||||
/**
|
||||
* @brief Indicates if the route is valid.
|
||||
*/
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Observed ARP neighbor information without authorization semantics.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API RouteNeighbor {
|
||||
/**
|
||||
* @brief Link-layer ID of the neighbor.
|
||||
*/
|
||||
uint32_t llId = 0U;
|
||||
/**
|
||||
* @brief IP address of the neighbor.
|
||||
*/
|
||||
uint32_t ipAddress = 0U;
|
||||
/**
|
||||
* @brief Timestamp of the last observation.
|
||||
*/
|
||||
uint64_t lastSeen = 0U;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Stores ARP observations separately from authorized bindings.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
class DVM_COMMON_API RouteNeighborCache {
|
||||
public:
|
||||
/**
|
||||
* @brief Observes a route neighbor with the given link-layer ID and IP address.
|
||||
* @param llId Link-layer ID of the neighbor.
|
||||
* @param ipAddress IP address of the neighbor.
|
||||
* @param nowMs Timestamp of the observation in milliseconds.
|
||||
*/
|
||||
void observe(uint32_t llId, uint32_t ipAddress, uint64_t nowMs = 0U);
|
||||
/**
|
||||
* @brief Erases a route neighbor with the given link-layer ID.
|
||||
* @param llId Link-layer ID of the neighbor to erase.
|
||||
* @return True if the neighbor was found and erased, false otherwise.
|
||||
*/
|
||||
bool erase(uint32_t llId);
|
||||
/**
|
||||
* @brief Clears all observed route neighbors.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief Finds a route neighbor by its link-layer ID.
|
||||
* @param llId Link-layer ID of the neighbor to find.
|
||||
* @return Pointer to the route neighbor if found, nullptr otherwise.
|
||||
*/
|
||||
const RouteNeighbor* findByLLId(uint32_t llId) const;
|
||||
/**
|
||||
* @brief Finds a route neighbor by its IP address.
|
||||
* @param ipAddress IP address of the neighbor to find.
|
||||
* @return Pointer to the route neighbor if found, nullptr otherwise.
|
||||
*/
|
||||
const RouteNeighbor* findByIPAddress(uint32_t ipAddress) const;
|
||||
/**
|
||||
* @brief Returns the number of observed route neighbors.
|
||||
* @return Number of observed route neighbors.
|
||||
*/
|
||||
size_t size() const noexcept { return m_neighbors.size(); }
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, RouteNeighbor> m_neighbors;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Resolves conventional locations and future trunked data routes.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
class DVM_COMMON_API DataLocationRegistry {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the LocationRegistry class.
|
||||
* @param staleAfterMs Duration in milliseconds after which locations are considered stale.
|
||||
* @param learnFromInbound Whether to learn locations from inbound traffic.
|
||||
* @param unknownLocationPolicy Policy for handling unknown locations.
|
||||
*/
|
||||
explicit DataLocationRegistry(uint64_t staleAfterMs = 0U, bool learnFromInbound = true,
|
||||
UnknownLocationPolicy unknownLocationPolicy = UnknownLocationPolicy::ARP);
|
||||
|
||||
/**
|
||||
* @brief Updates the conventional location for a given link-layer ID.
|
||||
* @param llId Link-layer ID of the neighbor.
|
||||
* @param location Conventional location to update.
|
||||
* @return True if the location was updated, false otherwise.
|
||||
*/
|
||||
bool updateConventional(uint32_t llId, const ConventionalLocation& location);
|
||||
/**
|
||||
* @brief Updates the trunked data route for a given link-layer ID.
|
||||
* @param llId Link-layer ID of the neighbor.
|
||||
* @param route Trunked data route to update.
|
||||
* @return True if the route was updated, false otherwise.
|
||||
*/
|
||||
bool updateTrunked(uint32_t llId, const DataRoute& route);
|
||||
/**
|
||||
* @brief Erases the location or route for a given link-layer ID and access mode.
|
||||
* @param llId Link-layer ID of the neighbor.
|
||||
* @param mode Access mode (conventional or trunked).
|
||||
* @return True if the entry was erased, false otherwise.
|
||||
*/
|
||||
bool erase(uint32_t llId, AccessMode mode);
|
||||
/**
|
||||
* @brief Clears all subscriber and group route state.
|
||||
*/
|
||||
void clear();
|
||||
/**
|
||||
* @brief Expires stale conventional and trunked route state based on the current time.
|
||||
* @param nowMs Current time in milliseconds.
|
||||
*/
|
||||
void expire(uint64_t nowMs);
|
||||
|
||||
/**
|
||||
* @brief Resolves the data route for a given link-layer ID and access mode.
|
||||
* @param llId Link-layer ID of the neighbor.
|
||||
* @param mode Access mode (conventional or trunked).
|
||||
* @param nowMs Current time in milliseconds.
|
||||
* @return Resolved data route.
|
||||
*/
|
||||
DataRoute resolve(uint32_t llId, AccessMode mode, uint64_t nowMs = 0U) const;
|
||||
|
||||
/**
|
||||
* @brief Sets the group peers for group-based routing.
|
||||
* @param peerIds Vector of peer link-layer IDs.
|
||||
*/
|
||||
void setGroupPeers(const std::vector<uint32_t>& peerIds);
|
||||
/**
|
||||
* @brief Resolves the data routes for a group of available peers.
|
||||
* @param availablePeerIds Vector of available peer link-layer IDs.
|
||||
* @return Vector of resolved data routes for the available peers.
|
||||
*/
|
||||
std::vector<DataRoute> resolveGroup(const std::vector<uint32_t>& availablePeerIds) const;
|
||||
|
||||
/**
|
||||
* @brief Sets whether to learn from inbound data routes.
|
||||
* @param enabled True to enable learning from inbound routes, false to disable.
|
||||
*/
|
||||
void setLearnFromInbound(bool enabled) noexcept { m_learnFromInbound = enabled; }
|
||||
/**
|
||||
* @brief Checks whether learning from inbound data routes is enabled.
|
||||
* @return True if learning from inbound routes is enabled, false otherwise.
|
||||
*/
|
||||
bool learnFromInbound() const noexcept { return m_learnFromInbound; }
|
||||
/**
|
||||
* @brief Sets the policy for handling unknown locations.
|
||||
* @param policy The unknown location policy to set.
|
||||
*/
|
||||
void setUnknownLocationPolicy(UnknownLocationPolicy policy) noexcept { m_unknownLocationPolicy = policy; }
|
||||
/**
|
||||
* @brief Retrieves the current policy for handling unknown locations.
|
||||
* @return The current unknown location policy.
|
||||
*/
|
||||
UnknownLocationPolicy unknownLocationPolicy() const noexcept { return m_unknownLocationPolicy; }
|
||||
|
||||
/**
|
||||
* @brief Retrieves the count of conventional locations.
|
||||
* @return The number of conventional locations.
|
||||
*/
|
||||
size_t conventionalCount() const noexcept { return m_conventional.size(); }
|
||||
/**
|
||||
* @brief Retrieves the count of trunked locations.
|
||||
* @return The number of trunked locations.
|
||||
*/
|
||||
size_t trunkedCount() const noexcept { return m_trunked.size(); }
|
||||
|
||||
private:
|
||||
uint64_t m_staleAfterMs;
|
||||
bool m_learnFromInbound;
|
||||
UnknownLocationPolicy m_unknownLocationPolicy;
|
||||
std::unordered_map<uint32_t, ConventionalLocation> m_conventional;
|
||||
std::unordered_map<uint32_t, DataRoute> m_trunked;
|
||||
std::unordered_set<uint32_t> m_groupPeers;
|
||||
|
||||
/**
|
||||
* @brief Checks if a location is considered stale based on the last seen timestamp and the current time.
|
||||
* @param lastSeen The timestamp when the location was last seen.
|
||||
* @param nowMs The current time in milliseconds.
|
||||
* @return True if the location is stale, false otherwise.
|
||||
*/
|
||||
bool stale(uint64_t lastSeen, uint64_t nowMs) const;
|
||||
};
|
||||
|
||||
} // namespace data
|
||||
} // namespace p25
|
||||
|
||||
#endif // __P25_DATA__DATAROUTING_H__
|
||||
@ -1,248 +0,0 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file IPConvergenceService.h
|
||||
* @ingroup p25_pdu
|
||||
* @file IPConvergenceService.cpp
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
#if !defined(__P25_DATA__IPCONVERGENCESERVICE_H__)
|
||||
#define __P25_DATA__IPCONVERGENCESERVICE_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/p25/data/IPv4Packet.h"
|
||||
#include "common/p25/data/PacketDataState.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace p25
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @addtogroup p25_pdu
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Result of an IP convergence operation.
|
||||
*/
|
||||
enum class ConvergenceResult : uint8_t {
|
||||
OK, //!< Operation was successful.
|
||||
MALFORMED, //!< The IP datagram is malformed.
|
||||
NO_BINDING, //!< No binding exists for the IP datagram.
|
||||
SOURCE_MISMATCH, //!< The source address does not match the expected source.
|
||||
MTU_EXCEEDED, //!< The IP datagram exceeds the maximum transmission unit.
|
||||
POLICY_REJECTED //!< The IP datagram was rejected by policy.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief LLC delivery mode selected for an IP datagram.
|
||||
*/
|
||||
enum class DataDeliveryMode : uint8_t {
|
||||
CONFIRMED, //!< The IP datagram requires confirmed delivery.
|
||||
UNCONFIRMED //!< The IP datagram does not require confirmed delivery.
|
||||
};
|
||||
/* @} */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Result returned when decoding an IP datagram.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API IPDecodeResult {
|
||||
/**
|
||||
* @brief The result of the IP convergence operation.
|
||||
*/
|
||||
ConvergenceResult result = ConvergenceResult::MALFORMED;
|
||||
/**
|
||||
* @brief The decoded IP packet.
|
||||
*/
|
||||
IPv4Packet packet;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Result returned when routing an IP datagram for encoding.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API IPEncodeResult {
|
||||
/**
|
||||
* @brief The result of the IP convergence operation.
|
||||
*/
|
||||
ConvergenceResult result = ConvergenceResult::MALFORMED;
|
||||
/**
|
||||
* @brief The logical link identifier for the encoded IP datagram.
|
||||
*/
|
||||
uint32_t llId = 0U;
|
||||
/**
|
||||
* @brief The length of the encoded IP datagram.
|
||||
*/
|
||||
uint16_t length = 0U;
|
||||
/**
|
||||
* @brief The delivery mode selected for the encoded IP datagram.
|
||||
*/
|
||||
DataDeliveryMode delivery = DataDeliveryMode::CONFIRMED;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Policy controlling SCEP routing and delivery.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API SCEPPolicy {
|
||||
/**
|
||||
* @brief The maximum transmission unit for the SCEP policy.
|
||||
*/
|
||||
uint16_t mtu = 510U;
|
||||
/**
|
||||
* @brief Indicates whether broadcast transmission is allowed under the SCEP policy.
|
||||
*/
|
||||
bool allowBroadcast = true;
|
||||
/**
|
||||
* @brief Indicates whether multicast transmission is allowed under the SCEP policy.
|
||||
*/
|
||||
bool allowMulticast = false;
|
||||
/**
|
||||
* @brief The delivery mode selected for unicast transmissions under the SCEP policy.
|
||||
*/
|
||||
DataDeliveryMode unicastDelivery = DataDeliveryMode::CONFIRMED;
|
||||
/**
|
||||
* @brief The delivery mode selected for broadcast transmissions under the SCEP policy.
|
||||
*/
|
||||
DataDeliveryMode broadcastDelivery = DataDeliveryMode::UNCONFIRMED;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Defines an IP convergence service for a P25 packet data bearer.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
class DVM_COMMON_API IPConvergenceService {
|
||||
public:
|
||||
/**
|
||||
* @brief Finalizes a instance of IPConvergenceService.
|
||||
*/
|
||||
virtual ~IPConvergenceService() = default;
|
||||
|
||||
/**
|
||||
* @brief Decodes an IP datagram from the provided data buffer.
|
||||
* @param data Pointer to the buffer containing the IP datagram.
|
||||
* @param length Length of the data buffer.
|
||||
* @return The result of the IP datagram decoding operation.
|
||||
*/
|
||||
virtual IPDecodeResult decode(const uint8_t* data, uint32_t length) const = 0;
|
||||
/**
|
||||
* @brief Encodes an IP datagram into the provided data buffer.
|
||||
* @param data Pointer to the buffer where the IP datagram will be encoded.
|
||||
* @param length Length of the data buffer.
|
||||
* @return The result of the IP datagram encoding operation.
|
||||
*/
|
||||
virtual IPEncodeResult encode(const uint8_t* data, uint32_t length) const = 0;
|
||||
/**
|
||||
* @brief Authorizes an uplink transmission for the specified logical link and IP packet.
|
||||
* @param llId The logical link identifier.
|
||||
* @param packet The IP packet to be transmitted.
|
||||
* @return The result of the uplink authorization operation.
|
||||
*/
|
||||
virtual ConvergenceResult authorizeUplink(uint32_t llId,
|
||||
const IPv4Packet& packet) const = 0;
|
||||
/**
|
||||
* @brief Routes a downlink transmission to the specified destination address.
|
||||
* @param destinationAddress The destination IP address for the downlink transmission.
|
||||
* @return The result of the downlink routing operation.
|
||||
*/
|
||||
virtual uint32_t routeDownlink(uint32_t destinationAddress) const = 0;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Implements the conventional SCEP IP convergence service.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
class DVM_COMMON_API SCEPService : public IPConvergenceService {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes an instance of the SCEPService class.
|
||||
* @param bindings The data binding registry to be used by the service.
|
||||
* @param policy The SCEP policy to be applied by the service.
|
||||
*/
|
||||
explicit SCEPService(const DataBindingRegistry& bindings,
|
||||
const SCEPPolicy& policy = SCEPPolicy());
|
||||
|
||||
/**
|
||||
* @brief Decodes an IP datagram from the provided data buffer.
|
||||
* @param data Pointer to the buffer containing the IP datagram.
|
||||
* @param length Length of the data buffer.
|
||||
* @return The result of the IP datagram decoding operation.
|
||||
*/
|
||||
IPDecodeResult decode(const uint8_t* data, uint32_t length) const override;
|
||||
/**
|
||||
* @brief Encodes an IP datagram into the provided data buffer.
|
||||
* @param data Pointer to the buffer containing the IP datagram.
|
||||
* @param length Length of the data buffer.
|
||||
* @return The result of the IP datagram encoding operation.
|
||||
*/
|
||||
IPEncodeResult encode(const uint8_t* data, uint32_t length) const override;
|
||||
/**
|
||||
* @brief Authorizes an uplink transmission for the specified logical link ID and IP packet.
|
||||
* @param llId The logical link ID for the uplink transmission.
|
||||
* @param packet The IP packet to be transmitted.
|
||||
* @return The result of the uplink authorization operation.
|
||||
*/
|
||||
ConvergenceResult authorizeUplink(uint32_t llId, const IPv4Packet& packet) const override;
|
||||
/**
|
||||
* @brief Routes a downlink transmission to the specified destination address.
|
||||
* @param destinationAddress The destination address for the downlink transmission.
|
||||
* @return The logical link ID to which the downlink transmission should be routed.
|
||||
*/
|
||||
uint32_t routeDownlink(uint32_t destinationAddress) const override;
|
||||
|
||||
/**
|
||||
* @brief Determines if the specified address is a broadcast address.
|
||||
* @param address The address to be checked.
|
||||
* @return True if the address is a broadcast address, false otherwise.
|
||||
*/
|
||||
static bool isBroadcast(uint32_t address);
|
||||
/**
|
||||
* @brief Determines if the specified address is a multicast address.
|
||||
* @param address The address to be checked.
|
||||
* @return True if the address is a multicast address, false otherwise.
|
||||
*/
|
||||
static bool isMulticast(uint32_t address);
|
||||
|
||||
private:
|
||||
const DataBindingRegistry& m_bindings;
|
||||
SCEPPolicy m_policy;
|
||||
};
|
||||
|
||||
} // namespace data
|
||||
} // namespace p25
|
||||
|
||||
#endif // __P25_DATA__IPCONVERGENCESERVICE_H__
|
||||
@ -1,298 +0,0 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file PacketDataState.h
|
||||
* @ingroup p25_pdu
|
||||
* @file PacketDataState.cpp
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
#if !defined(__P25_DATA__PACKETDATASTATE_H__)
|
||||
#define __P25_DATA__PACKETDATASTATE_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace p25
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @addtogroup p25_pdu
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief P25 packet data access mode.
|
||||
*/
|
||||
enum class DataAccessMode : uint8_t {
|
||||
CONVENTIONAL, //!< Conventional access mode
|
||||
TRUNKED //!< Trunked access mode
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief P25 packet data convergence mode.
|
||||
*/
|
||||
enum class DataConvergenceMode : uint8_t {
|
||||
CAI_DATA, //!< CAI data convergence mode
|
||||
SCEP, //!< SCEP data convergence mode
|
||||
SNDCP //!< SNDCP data convergence mode
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Source from which an IP binding was learned.
|
||||
*/
|
||||
enum class DataBindingOrigin : uint8_t {
|
||||
PROVISIONED, //!< Provisioned binding origin
|
||||
CONVENTIONAL_REGISTRATION, //!< Conventional registration binding origin
|
||||
SNDCP_STATIC, //!< SNDCP static binding origin
|
||||
SNDCP_DYNAMIC, //!< SNDCP dynamic binding origin
|
||||
ARP_LEARNED //!< ARP learned binding origin
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Result of LLC receive sequence processing.
|
||||
*/
|
||||
enum class ReceiveSequenceResult : uint8_t {
|
||||
ACCEPTED, //!< Accepted receive sequence result
|
||||
DUPLICATE, //!< Duplicate receive sequence result
|
||||
OUT_OF_SEQUENCE //!< Out of sequence receive sequence result
|
||||
};
|
||||
/* @} */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Identifies a P25 logical data link.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API DataLinkKey {
|
||||
/**
|
||||
* @brief The logical link identifier.
|
||||
*/
|
||||
uint32_t llId = 0U;
|
||||
|
||||
/**
|
||||
* @brief Compares this DataLinkKey with another for equality.
|
||||
* @param other The other DataLinkKey to compare with.
|
||||
* @return True if the DataLinkKeys are equal; otherwise, false.
|
||||
*/
|
||||
bool operator==(const DataLinkKey& other) const noexcept
|
||||
{
|
||||
return llId == other.llId;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Associates a logical data link with an authorized IP address.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API IPBinding {
|
||||
/**
|
||||
* @brief The logical data link associated with this IP binding.
|
||||
*/
|
||||
DataLinkKey link;
|
||||
/**
|
||||
* @brief The IP address associated with this binding.
|
||||
*/
|
||||
uint32_t ipAddress = 0U;
|
||||
/**
|
||||
* @brief The access mode for this binding.
|
||||
*/
|
||||
DataAccessMode accessMode = DataAccessMode::CONVENTIONAL;
|
||||
/**
|
||||
* @brief The convergence mode for this binding.
|
||||
*/
|
||||
DataConvergenceMode convergenceMode = DataConvergenceMode::SCEP;
|
||||
/**
|
||||
* @brief The origin from which this binding was learned.
|
||||
*/
|
||||
DataBindingOrigin origin = DataBindingOrigin::PROVISIONED;
|
||||
/**
|
||||
* @brief The NSAPI associated with this binding.
|
||||
*/
|
||||
uint8_t nsapi = 0U;
|
||||
/**
|
||||
* @brief Indicates whether the NSAPI is valid.
|
||||
*/
|
||||
bool hasNSAPI = false;
|
||||
/**
|
||||
* @brief Indicates whether this binding is authorized.
|
||||
*/
|
||||
bool authorized = false;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief LLC sequencing and readiness state for one logical link.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API DataLinkState {
|
||||
/**
|
||||
* @brief The send sequence number for this logical link.
|
||||
*/
|
||||
uint8_t sendSequence = 0U;
|
||||
/**
|
||||
* @brief The receive sequence number for this logical link.
|
||||
*/
|
||||
uint8_t receiveSequence = 0U;
|
||||
/**
|
||||
* @brief The receive fingerprint for this logical link.
|
||||
*/
|
||||
uint32_t receiveFingerprint = 0U;
|
||||
/**
|
||||
* @brief Indicates whether the send sequence has been initialized.
|
||||
*/
|
||||
bool sendInitialized = false;
|
||||
/**
|
||||
* @brief Indicates whether the receive sequence has been initialized.
|
||||
*/
|
||||
bool receiveInitialized = false;
|
||||
/**
|
||||
* @brief Indicates whether the link is ready for the next packet.
|
||||
*/
|
||||
bool readyForNextPacket = true;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Stores typed P25 packet data IP bindings.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
class DVM_COMMON_API DataBindingRegistry {
|
||||
public:
|
||||
/**
|
||||
* @brief Inserts or updates an IP binding in the registry. Returns true if the operation was successful.
|
||||
* @param binding The IP binding to insert or update.
|
||||
* @return True if the operation was successful, false otherwise.
|
||||
*/
|
||||
bool upsert(const IPBinding& binding);
|
||||
/**
|
||||
* @brief Erases an IP binding from the registry by its logical link ID. Returns true if the operation was successful.
|
||||
* @param llId The logical link ID of the IP binding to erase.
|
||||
* @return True if the operation was successful, false otherwise.
|
||||
*/
|
||||
bool erase(uint32_t llId);
|
||||
/**
|
||||
* @brief Clears all IP bindings from the registry.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief Finds an IP binding by its logical link ID.
|
||||
* @param llId The logical link ID of the IP binding to find.
|
||||
* @return A pointer to the IP binding if found, nullptr otherwise.
|
||||
*/
|
||||
const IPBinding* findByLLId(uint32_t llId) const;
|
||||
/**
|
||||
* @brief Finds an IP binding by its IP address.
|
||||
* @param ipAddress The IP address of the IP binding to find.
|
||||
* @return A pointer to the IP binding if found, nullptr otherwise.
|
||||
*/
|
||||
const IPBinding* findByIPAddress(uint32_t ipAddress) const;
|
||||
/**
|
||||
* @brief Returns the number of IP bindings in the registry.
|
||||
* @return The number of IP bindings.
|
||||
*/
|
||||
size_t size() const noexcept { return m_bindings.size(); }
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, IPBinding> m_bindings;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Stores LLC state independently for each logical link.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
class DVM_COMMON_API DataLinkManager {
|
||||
public:
|
||||
/**
|
||||
* @brief Returns the next send sequence number for the specified logical link.
|
||||
* @param llId The logical link ID.
|
||||
* @param synchronize Indicates whether to synchronize the sequence.
|
||||
* @return The next send sequence number.
|
||||
*/
|
||||
uint8_t nextSendSequence(uint32_t llId, bool& synchronize);
|
||||
/**
|
||||
* @brief Accepts a received sequence number for the specified logical link.
|
||||
* @param llId The logical link ID.
|
||||
* @param sequence The received sequence number.
|
||||
* @param synchronize Indicates whether to synchronize the sequence.
|
||||
* @param packetFingerprint The fingerprint of the received packet.
|
||||
* @param expectedSequence The expected sequence number.
|
||||
* @return The result of the receive sequence acceptance.
|
||||
*/
|
||||
ReceiveSequenceResult acceptReceiveSequence(uint32_t llId, uint8_t sequence,
|
||||
bool synchronize, uint32_t packetFingerprint, uint8_t& expectedSequence);
|
||||
|
||||
/**
|
||||
* @brief Computes the fingerprint of the given data.
|
||||
* @param data Pointer to the data.
|
||||
* @param length Length of the data.
|
||||
* @return The computed fingerprint.
|
||||
*/
|
||||
static uint32_t fingerprint(const uint8_t* data, uint32_t length);
|
||||
|
||||
/**
|
||||
* @brief Sets the readiness state for the specified logical link.
|
||||
* @param llId The logical link ID.
|
||||
* @param ready The readiness state to set.
|
||||
*/
|
||||
void setReady(uint32_t llId, bool ready);
|
||||
/**
|
||||
* @brief Checks if the specified logical link is ready.
|
||||
* @param llId The logical link ID.
|
||||
* @return True if the logical link is ready, false otherwise.
|
||||
*/
|
||||
bool isReady(uint32_t llId) const;
|
||||
/**
|
||||
* @brief Checks if the specified logical link has an associated state.
|
||||
* @param llId The logical link ID.
|
||||
* @return True if the logical link has an associated state, false otherwise.
|
||||
*/
|
||||
bool hasState(uint32_t llId) const;
|
||||
/**
|
||||
* @brief Erases the state associated with the specified logical link.
|
||||
* @param llId The logical link ID.
|
||||
*/
|
||||
void erase(uint32_t llId);
|
||||
/**
|
||||
* @brief Clears all logical link states.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, DataLinkState> m_states;
|
||||
};
|
||||
} // namespace data
|
||||
} // namespace p25
|
||||
|
||||
#endif // __P25_DATA__PACKETDATASTATE_H__
|
||||
@ -1,115 +0,0 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file PacketScheduler.h
|
||||
* @ingroup p25_pdu
|
||||
* @file PacketScheduler.cpp
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
#if !defined(__P25_DATA__PACKETSCHEDULER_H__)
|
||||
#define __P25_DATA__PACKETSCHEDULER_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/p25/data/DataHeader.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
namespace p25
|
||||
{
|
||||
namespace data
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief One queued packet data downlink.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
struct DVM_COMMON_API ScheduledDataPacket {
|
||||
DataHeader header; //!< Header information for the P25 packet.
|
||||
std::vector<uint8_t> userData; //!< User data payload for the P25 packet.
|
||||
uint32_t llId = 0U; //!< Link-layer ID associated with the scheduled packet.
|
||||
uint32_t targetIPAddress = 0U; //!< Target IP address for the scheduled packet.
|
||||
uint64_t dueAt = 0U; //!< Timestamp indicating when the packet is due.
|
||||
uint8_t retryCount = 0U; //!< Number of retry attempts for the packet.
|
||||
bool extendedRetry = false; //!< Indicates if extended retry is enabled.
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Owns bounded queued packet data downlinks.
|
||||
* @ingroup p25_pdu
|
||||
*/
|
||||
class DVM_COMMON_API PacketScheduler {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the PacketScheduler class.
|
||||
* @param maxFrames The maximum number of frames the scheduler can hold.
|
||||
* @param maxBytes The maximum number of bytes the scheduler can hold.
|
||||
*/
|
||||
PacketScheduler(uint32_t maxFrames = 0U, uint32_t maxBytes = 0U);
|
||||
|
||||
/**
|
||||
* @brief Enqueues a scheduled packet into the scheduler.
|
||||
* @param packet The scheduled packet to enqueue.
|
||||
* @return The number of oldest frames dropped to make room.
|
||||
*/
|
||||
uint32_t enqueue(ScheduledDataPacket&& packet);
|
||||
|
||||
/**
|
||||
* @brief Checks if the scheduler is empty.
|
||||
* @return True if the scheduler is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept { return m_packets.empty(); }
|
||||
/**
|
||||
* @brief Gets the number of packets currently in the scheduler.
|
||||
* @return The number of packets in the scheduler.
|
||||
*/
|
||||
size_t size() const noexcept { return m_packets.size(); }
|
||||
/**
|
||||
* @brief Gets the total byte count of packets currently in the scheduler.
|
||||
* @return The total byte count of packets in the scheduler.
|
||||
*/
|
||||
uint32_t byteCount() const noexcept { return m_byteCount; }
|
||||
|
||||
/**
|
||||
* @brief Gets the next queued packet.
|
||||
* @return A pointer to the next scheduled packet, or nullptr if the scheduler is empty.
|
||||
*/
|
||||
ScheduledDataPacket* front();
|
||||
/**
|
||||
* @brief Removes the next queued packet.
|
||||
*/
|
||||
void pop();
|
||||
/**
|
||||
* @brief Moves the next queued packet to the back of the queue.
|
||||
*/
|
||||
void rotate();
|
||||
/**
|
||||
* @brief Clears all queued packets from the scheduler.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
uint32_t m_maxFrames;
|
||||
uint32_t m_maxBytes;
|
||||
uint32_t m_byteCount;
|
||||
std::deque<ScheduledDataPacket> m_packets;
|
||||
};
|
||||
} // namespace data
|
||||
} // namespace p25
|
||||
|
||||
#endif // __P25_DATA__PACKETSCHEDULER_H__
|
||||
@ -0,0 +1,88 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "network/callhandler/packetdata/DMRPacketScheduler.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace network::callhandler::packetdata;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/* Initializes a new instance of the DMRPacketScheduler class. */
|
||||
|
||||
DMRPacketScheduler::DMRPacketScheduler(uint32_t maxFrames, uint32_t maxBytes) :
|
||||
m_maxFrames(maxFrames),
|
||||
m_maxBytes(maxBytes),
|
||||
m_byteCount(0U),
|
||||
m_packets()
|
||||
{
|
||||
/* stub */
|
||||
}
|
||||
|
||||
/* Enqueues a scheduled DMR packet. */
|
||||
|
||||
uint32_t DMRPacketScheduler::enqueue(ScheduledDMRDataPacket&& packet)
|
||||
{
|
||||
uint32_t dropped = 0U;
|
||||
uint32_t packetBytes = uint32_t(packet.userData.size());
|
||||
if (m_maxBytes != 0U && packetBytes > m_maxBytes)
|
||||
return 1U;
|
||||
|
||||
while (!m_packets.empty() &&
|
||||
((m_maxFrames != 0U && m_packets.size() >= m_maxFrames) ||
|
||||
(m_maxBytes != 0U && m_byteCount + packetBytes > m_maxBytes))) {
|
||||
pop();
|
||||
dropped++;
|
||||
}
|
||||
|
||||
m_byteCount += packetBytes;
|
||||
m_packets.emplace_back(std::move(packet));
|
||||
return dropped;
|
||||
}
|
||||
|
||||
/* Gets the next queued packet. */
|
||||
|
||||
ScheduledDMRDataPacket* DMRPacketScheduler::front()
|
||||
{
|
||||
return m_packets.empty() ? nullptr : &m_packets.front();
|
||||
}
|
||||
|
||||
/* Removes the next queued packet. */
|
||||
|
||||
void DMRPacketScheduler::pop()
|
||||
{
|
||||
if (m_packets.empty())
|
||||
return;
|
||||
|
||||
uint32_t packetBytes = uint32_t(m_packets.front().userData.size());
|
||||
m_byteCount = packetBytes <= m_byteCount ? m_byteCount - packetBytes : 0U;
|
||||
m_packets.pop_front();
|
||||
}
|
||||
|
||||
/* Moves the next queued packet to the back of the queue. */
|
||||
|
||||
void DMRPacketScheduler::rotate()
|
||||
{
|
||||
if (m_packets.size() < 2U)
|
||||
return;
|
||||
|
||||
m_packets.emplace_back(std::move(m_packets.front()));
|
||||
m_packets.pop_front();
|
||||
}
|
||||
|
||||
/* Clears all queued packets. */
|
||||
|
||||
void DMRPacketScheduler::clear()
|
||||
{
|
||||
m_packets.clear();
|
||||
m_byteCount = 0U;
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file DMRPacketScheduler.h
|
||||
* @ingroup fne_packetdata
|
||||
* @file DMRPacketScheduler.cpp
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
#if !defined(__PACKETDATA__DMR_PACKET_SCHEDULER_H__)
|
||||
#define __PACKETDATA__DMR_PACKET_SCHEDULER_H__
|
||||
|
||||
#include "fne/Defines.h"
|
||||
#include "common/dmr/DMRDefines.h"
|
||||
#include "common/dmr/data/DataHeader.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
namespace network
|
||||
{
|
||||
namespace callhandler
|
||||
{
|
||||
namespace packetdata
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief One queued DMR packet data downlink.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct HOST_SW_API ScheduledDMRDataPacket {
|
||||
dmr::data::DataHeader header; //!< Header information for the DMR packet.
|
||||
std::vector<uint8_t> userData; //!< User data payload for the DMR packet.
|
||||
uint32_t dstId = 0U; //!< Destination radio ID.
|
||||
uint32_t targetIPAddress = 0U; //!< Target IP address for the scheduled packet.
|
||||
uint64_t dueAt = 0U; //!< Timestamp indicating when the packet is due.
|
||||
uint8_t retryCount = 0U; //!< Number of retry attempts for the packet.
|
||||
bool extendedRetry = false; //!< Indicates if extended retry is enabled.
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Owns bounded queued DMR packet data downlinks.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
class HOST_SW_API DMRPacketScheduler {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the DMRPacketScheduler class.
|
||||
* @param maxFrames The maximum number of frames the scheduler can hold.
|
||||
* @param maxBytes The maximum number of bytes the scheduler can hold.
|
||||
*/
|
||||
DMRPacketScheduler(uint32_t maxFrames = 0U, uint32_t maxBytes = 0U);
|
||||
|
||||
/**
|
||||
* @brief Enqueues a scheduled DMR packet.
|
||||
* @param packet The scheduled packet to enqueue.
|
||||
* @return The number of oldest frames dropped to make room.
|
||||
*/
|
||||
uint32_t enqueue(ScheduledDMRDataPacket&& packet);
|
||||
|
||||
bool empty() const noexcept { return m_packets.empty(); }
|
||||
size_t size() const noexcept { return m_packets.size(); }
|
||||
uint32_t byteCount() const noexcept { return m_byteCount; }
|
||||
|
||||
ScheduledDMRDataPacket* front();
|
||||
void pop();
|
||||
void rotate();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
uint32_t m_maxFrames;
|
||||
uint32_t m_maxBytes;
|
||||
uint32_t m_byteCount;
|
||||
std::deque<ScheduledDMRDataPacket> m_packets;
|
||||
};
|
||||
} // namespace packetdata
|
||||
} // namespace callhandler
|
||||
} // namespace network
|
||||
|
||||
#endif // __PACKETDATA__DMR_PACKET_SCHEDULER_H__
|
||||
@ -0,0 +1,329 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file DataRouting.h
|
||||
* @ingroup fne_packetdata
|
||||
* @file DataRouting.cpp
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
#if !defined(__PACKETDATA__DATA_ROUTING_H__)
|
||||
#define __PACKETDATA__DATA_ROUTING_H__
|
||||
|
||||
#include "fne/Defines.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace network
|
||||
{
|
||||
namespace callhandler
|
||||
{
|
||||
namespace packetdata
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @addtogroup fne_packetdata
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Access mode used to reach a packet data subscriber.
|
||||
*/
|
||||
enum class AccessMode : uint8_t {
|
||||
CONVENTIONAL, //!< Conventional access mode.
|
||||
TRUNKED //!< Trunked access mode.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Policy applied when no current subscriber location is known.
|
||||
*/
|
||||
enum class UnknownLocationPolicy : uint8_t {
|
||||
QUEUE, //!< Queue the packet until location is known.
|
||||
ARP, //!< Attempt ARP resolution.
|
||||
DROP //!< Drop the packet if location is unknown.
|
||||
};
|
||||
/* @} */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Last observed conventional location for a subscriber.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct HOST_SW_API ConventionalLocation {
|
||||
/**
|
||||
* @brief Peer ID of the subscriber.
|
||||
*/
|
||||
uint32_t peerId = 0U;
|
||||
/**
|
||||
* @brief Channel ID of the subscriber.
|
||||
*/
|
||||
uint8_t channelId = 0U;
|
||||
/**
|
||||
* @brief Channel number of the subscriber.
|
||||
*/
|
||||
uint16_t channelNo = 0U;
|
||||
/**
|
||||
* @brief DMR timeslot, or zero when it is not applicable or known.
|
||||
*/
|
||||
uint8_t slotNo = 0U;
|
||||
/**
|
||||
* @brief Timestamp of the last observation.
|
||||
*/
|
||||
uint64_t lastSeen = 0U;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Resolved RF route for a packet data subscriber.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct HOST_SW_API DataRoute {
|
||||
/**
|
||||
* @brief Access mode used to reach the subscriber.
|
||||
*/
|
||||
AccessMode mode = AccessMode::CONVENTIONAL;
|
||||
/**
|
||||
* @brief Peer ID of the subscriber.
|
||||
*/
|
||||
uint32_t peerId = 0U;
|
||||
/**
|
||||
* @brief Channel ID of the subscriber.
|
||||
*/
|
||||
uint8_t channelId = 0U;
|
||||
/**
|
||||
* @brief Channel number of the subscriber.
|
||||
*/
|
||||
uint16_t channelNo = 0U;
|
||||
/**
|
||||
* @brief DMR timeslot, or zero when it is not applicable or known.
|
||||
*/
|
||||
uint8_t slotNo = 0U;
|
||||
/**
|
||||
* @brief Grant ID associated with the subscriber.
|
||||
*/
|
||||
uint32_t grantId = 0U;
|
||||
/**
|
||||
* @brief Timestamp of the last observation.
|
||||
*/
|
||||
uint64_t lastSeen = 0U;
|
||||
/**
|
||||
* @brief Indicates if the route is valid.
|
||||
*/
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Observed ARP neighbor information without authorization semantics.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct HOST_SW_API RouteNeighbor {
|
||||
/**
|
||||
* @brief Protocol subscriber identity of the neighbor.
|
||||
*/
|
||||
uint32_t subscriberId = 0U;
|
||||
/**
|
||||
* @brief IP address of the neighbor.
|
||||
*/
|
||||
uint32_t ipAddress = 0U;
|
||||
/**
|
||||
* @brief Timestamp of the last observation.
|
||||
*/
|
||||
uint64_t lastSeen = 0U;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Stores ARP observations separately from authorized bindings.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
class HOST_SW_API RouteNeighborCache {
|
||||
public:
|
||||
/**
|
||||
* @brief Observes a route neighbor with the given link-layer ID and IP address.
|
||||
* @param subscriberId Protocol subscriber identity of the neighbor.
|
||||
* @param ipAddress IP address of the neighbor.
|
||||
* @param nowMs Timestamp of the observation in milliseconds.
|
||||
*/
|
||||
void observe(uint32_t subscriberId, uint32_t ipAddress, uint64_t nowMs = 0U);
|
||||
/**
|
||||
* @brief Erases a route neighbor with the given link-layer ID.
|
||||
* @param subscriberId Protocol subscriber identity of the neighbor to erase.
|
||||
* @return True if the neighbor was found and erased, false otherwise.
|
||||
*/
|
||||
bool erase(uint32_t subscriberId);
|
||||
/**
|
||||
* @brief Clears all observed route neighbors.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief Finds a route neighbor by its subscriber identity.
|
||||
* @param subscriberId Protocol subscriber identity of the neighbor to find.
|
||||
* @return Pointer to the route neighbor if found, nullptr otherwise.
|
||||
*/
|
||||
const RouteNeighbor* findBySubscriberId(uint32_t subscriberId) const;
|
||||
/**
|
||||
* @brief Finds a route neighbor by its IP address.
|
||||
* @param ipAddress IP address of the neighbor to find.
|
||||
* @return Pointer to the route neighbor if found, nullptr otherwise.
|
||||
*/
|
||||
const RouteNeighbor* findByIPAddress(uint32_t ipAddress) const;
|
||||
/**
|
||||
* @brief Returns the number of observed route neighbors.
|
||||
* @return Number of observed route neighbors.
|
||||
*/
|
||||
size_t size() const noexcept { return m_neighbors.size(); }
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, RouteNeighbor> m_neighbors;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Resolves conventional locations and future trunked data routes.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
class HOST_SW_API DataLocationRegistry {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the LocationRegistry class.
|
||||
* @param staleAfterMs Duration in milliseconds after which locations are considered stale.
|
||||
* @param learnFromInbound Whether to learn locations from inbound traffic.
|
||||
* @param unknownLocationPolicy Policy for handling unknown locations.
|
||||
*/
|
||||
explicit DataLocationRegistry(uint64_t staleAfterMs = 0U, bool learnFromInbound = true,
|
||||
UnknownLocationPolicy unknownLocationPolicy = UnknownLocationPolicy::ARP);
|
||||
|
||||
/**
|
||||
* @brief Updates the conventional location for a given link-layer ID.
|
||||
* @param llId Link-layer ID of the neighbor.
|
||||
* @param location Conventional location to update.
|
||||
* @return True if the location was updated, false otherwise.
|
||||
*/
|
||||
bool updateConventional(uint32_t llId, const ConventionalLocation& location);
|
||||
/**
|
||||
* @brief Updates the trunked data route for a given link-layer ID.
|
||||
* @param llId Link-layer ID of the neighbor.
|
||||
* @param route Trunked data route to update.
|
||||
* @return True if the route was updated, false otherwise.
|
||||
*/
|
||||
bool updateTrunked(uint32_t llId, const DataRoute& route);
|
||||
/**
|
||||
* @brief Erases the location or route for a given link-layer ID and access mode.
|
||||
* @param llId Link-layer ID of the neighbor.
|
||||
* @param mode Access mode (conventional or trunked).
|
||||
* @return True if the entry was erased, false otherwise.
|
||||
*/
|
||||
bool erase(uint32_t llId, AccessMode mode);
|
||||
/**
|
||||
* @brief Clears all subscriber and group route state.
|
||||
*/
|
||||
void clear();
|
||||
/**
|
||||
* @brief Expires stale conventional and trunked route state based on the current time.
|
||||
* @param nowMs Current time in milliseconds.
|
||||
*/
|
||||
void expire(uint64_t nowMs);
|
||||
|
||||
/**
|
||||
* @brief Resolves the data route for a given link-layer ID and access mode.
|
||||
* @param llId Link-layer ID of the neighbor.
|
||||
* @param mode Access mode (conventional or trunked).
|
||||
* @param nowMs Current time in milliseconds.
|
||||
* @return Resolved data route.
|
||||
*/
|
||||
DataRoute resolve(uint32_t llId, AccessMode mode, uint64_t nowMs = 0U) const;
|
||||
|
||||
/**
|
||||
* @brief Sets the group peers for group-based routing.
|
||||
* @param peerIds Vector of peer link-layer IDs.
|
||||
*/
|
||||
void setGroupPeers(const std::vector<uint32_t>& peerIds);
|
||||
/**
|
||||
* @brief Resolves the data routes for a group of available peers.
|
||||
* @param availablePeerIds Vector of available peer link-layer IDs.
|
||||
* @return Vector of resolved data routes for the available peers.
|
||||
*/
|
||||
std::vector<DataRoute> resolveGroup(const std::vector<uint32_t>& availablePeerIds) const;
|
||||
|
||||
/**
|
||||
* @brief Sets whether to learn from inbound data routes.
|
||||
* @param enabled True to enable learning from inbound routes, false to disable.
|
||||
*/
|
||||
void setLearnFromInbound(bool enabled) noexcept { m_learnFromInbound = enabled; }
|
||||
/**
|
||||
* @brief Checks whether learning from inbound data routes is enabled.
|
||||
* @return True if learning from inbound routes is enabled, false otherwise.
|
||||
*/
|
||||
bool learnFromInbound() const noexcept { return m_learnFromInbound; }
|
||||
/**
|
||||
* @brief Sets the policy for handling unknown locations.
|
||||
* @param policy The unknown location policy to set.
|
||||
*/
|
||||
void setUnknownLocationPolicy(UnknownLocationPolicy policy) noexcept { m_unknownLocationPolicy = policy; }
|
||||
/**
|
||||
* @brief Retrieves the current policy for handling unknown locations.
|
||||
* @return The current unknown location policy.
|
||||
*/
|
||||
UnknownLocationPolicy unknownLocationPolicy() const noexcept { return m_unknownLocationPolicy; }
|
||||
|
||||
/**
|
||||
* @brief Retrieves the count of conventional locations.
|
||||
* @return The number of conventional locations.
|
||||
*/
|
||||
size_t conventionalCount() const noexcept { return m_conventional.size(); }
|
||||
/**
|
||||
* @brief Retrieves the count of trunked locations.
|
||||
* @return The number of trunked locations.
|
||||
*/
|
||||
size_t trunkedCount() const noexcept { return m_trunked.size(); }
|
||||
|
||||
private:
|
||||
uint64_t m_staleAfterMs;
|
||||
bool m_learnFromInbound;
|
||||
UnknownLocationPolicy m_unknownLocationPolicy;
|
||||
std::unordered_map<uint32_t, ConventionalLocation> m_conventional;
|
||||
std::unordered_map<uint32_t, DataRoute> m_trunked;
|
||||
std::unordered_set<uint32_t> m_groupPeers;
|
||||
|
||||
/**
|
||||
* @brief Checks if a location is considered stale based on the last seen timestamp and the current time.
|
||||
* @param lastSeen The timestamp when the location was last seen.
|
||||
* @param nowMs The current time in milliseconds.
|
||||
* @return True if the location is stale, false otherwise.
|
||||
*/
|
||||
bool stale(uint64_t lastSeen, uint64_t nowMs) const;
|
||||
};
|
||||
} // namespace packetdata
|
||||
} // namespace callhandler
|
||||
} // namespace network
|
||||
|
||||
#endif // __PACKETDATA__DATA_ROUTING_H__
|
||||
@ -0,0 +1,118 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file P25PacketScheduler.h
|
||||
* @ingroup fne_packetdata
|
||||
* @file P25PacketScheduler.cpp
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
#if !defined(__PACKETDATA__P25_PACKET_SCHEDULER_H__)
|
||||
#define __PACKETDATA__P25_PACKET_SCHEDULER_H__
|
||||
|
||||
#include "fne/Defines.h"
|
||||
#include "common/p25/data/DataHeader.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
namespace network
|
||||
{
|
||||
namespace callhandler
|
||||
{
|
||||
namespace packetdata
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief One queued packet data downlink.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct HOST_SW_API ScheduledP25DataPacket {
|
||||
::p25::data::DataHeader header; //!< Header information for the P25 packet.
|
||||
std::vector<uint8_t> userData; //!< User data payload for the P25 packet.
|
||||
uint32_t llId = 0U; //!< Link-layer ID associated with the scheduled packet.
|
||||
uint32_t targetIPAddress = 0U; //!< Target IP address for the scheduled packet.
|
||||
uint64_t dueAt = 0U; //!< Timestamp indicating when the packet is due.
|
||||
uint8_t retryCount = 0U; //!< Number of retry attempts for the packet.
|
||||
bool extendedRetry = false; //!< Indicates if extended retry is enabled.
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Owns bounded queued packet data downlinks.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
class HOST_SW_API P25PacketScheduler {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the P25PacketScheduler class.
|
||||
* @param maxFrames The maximum number of frames the scheduler can hold.
|
||||
* @param maxBytes The maximum number of bytes the scheduler can hold.
|
||||
*/
|
||||
P25PacketScheduler(uint32_t maxFrames = 0U, uint32_t maxBytes = 0U);
|
||||
|
||||
/**
|
||||
* @brief Enqueues a scheduled packet into the scheduler.
|
||||
* @param packet The scheduled packet to enqueue.
|
||||
* @return The number of oldest frames dropped to make room.
|
||||
*/
|
||||
uint32_t enqueue(ScheduledP25DataPacket&& packet);
|
||||
|
||||
/**
|
||||
* @brief Checks if the scheduler is empty.
|
||||
* @return True if the scheduler is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept { return m_packets.empty(); }
|
||||
/**
|
||||
* @brief Gets the number of packets currently in the scheduler.
|
||||
* @return The number of packets in the scheduler.
|
||||
*/
|
||||
size_t size() const noexcept { return m_packets.size(); }
|
||||
/**
|
||||
* @brief Gets the total byte count of packets currently in the scheduler.
|
||||
* @return The total byte count of packets in the scheduler.
|
||||
*/
|
||||
uint32_t byteCount() const noexcept { return m_byteCount; }
|
||||
|
||||
/**
|
||||
* @brief Gets the next queued packet.
|
||||
* @return A pointer to the next scheduled packet, or nullptr if the scheduler is empty.
|
||||
*/
|
||||
ScheduledP25DataPacket* front();
|
||||
/**
|
||||
* @brief Removes the next queued packet.
|
||||
*/
|
||||
void pop();
|
||||
/**
|
||||
* @brief Moves the next queued packet to the back of the queue.
|
||||
*/
|
||||
void rotate();
|
||||
/**
|
||||
* @brief Clears all queued packets from the scheduler.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
uint32_t m_maxFrames;
|
||||
uint32_t m_maxBytes;
|
||||
uint32_t m_byteCount;
|
||||
std::deque<ScheduledP25DataPacket> m_packets;
|
||||
};
|
||||
} // namespace packetdata
|
||||
} // namespace callhandler
|
||||
} // namespace network
|
||||
|
||||
#endif // __PACKETDATA__P25_PACKET_SCHEDULER_H__
|
||||
@ -1,17 +1,18 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "common/p25/data/ConventionalDataService.h"
|
||||
#include "network/callhandler/packetdata/p25/ConventionalDataService.h"
|
||||
#include "common/p25/P25Defines.h"
|
||||
|
||||
using namespace p25::data;
|
||||
using namespace p25::defines;
|
||||
using namespace network::callhandler::packetdata::p25data;
|
||||
using namespace ::p25::data;
|
||||
using namespace ::p25::defines;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
@ -0,0 +1,251 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file ConventionalDataService.h
|
||||
* @ingroup fne_packetdata
|
||||
* @file ConventionalDataService.cpp
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
#if !defined(__P25_DATA__CONVENTIONALDATASERVICE_H__)
|
||||
#define __P25_DATA__CONVENTIONALDATASERVICE_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/p25/data/ConventionalRegistration.h"
|
||||
#include "network/callhandler/packetdata/p25/PacketDataState.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace network
|
||||
{
|
||||
namespace callhandler
|
||||
{
|
||||
namespace packetdata
|
||||
{
|
||||
namespace p25data
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @addtogroup fne_packetdata
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Conventional packet data registration decision.
|
||||
*/
|
||||
enum class RegistrationDecision : uint8_t {
|
||||
ACCEPT, //!< Registration request accepted.
|
||||
DENY, //!< Registration request denied.
|
||||
DISCONNECTED //!< Registration disconnected.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Reason a conventional packet data registration was denied.
|
||||
*/
|
||||
enum class RegistrationDenyReason : uint8_t {
|
||||
NONE, //!< No specific reason.
|
||||
MALFORMED, //!< Registration request was malformed.
|
||||
IDENTITY_MISMATCH, //!< Identity did not match expected value.
|
||||
NOT_PROVISIONED, //!< Device not provisioned.
|
||||
DISABLED, //!< Registration disabled.
|
||||
UNSUPPORTED_OPTIONS, //!< Registration request contained unsupported options.
|
||||
ADDRESS_UNAVAILABLE, //!< Requested address unavailable.
|
||||
ADDRESS_CONFLICT //!< Address conflict detected.
|
||||
};
|
||||
/* @} */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Provisioning inputs used to decide a registration request.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct ConventionalRegistrationProvisioning {
|
||||
/**
|
||||
* @brief Indicates whether the device is known.
|
||||
*/
|
||||
bool known = false;
|
||||
/**
|
||||
* @brief Indicates whether the device is enabled.
|
||||
*/
|
||||
bool enabled = false;
|
||||
/**
|
||||
* @brief Indicates whether dynamic address allocation is allowed.
|
||||
*/
|
||||
bool allowDynamicAddress = false;
|
||||
/**
|
||||
* @brief The static IP address of the device.
|
||||
*/
|
||||
uint32_t staticIPAddress = 0U;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Local policy applied to conventional registration requests.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct ConventionalRegistrationPolicy {
|
||||
/**
|
||||
* @brief Indicates whether CMS scan is allowed.
|
||||
*/
|
||||
bool allowCMSScan = false;
|
||||
/**
|
||||
* @brief The lifetime of a registration in milliseconds.
|
||||
*/
|
||||
uint64_t registrationLifetimeMs = 0U;
|
||||
/**
|
||||
* @brief Function to allocate a dynamic IP address.
|
||||
*/
|
||||
std::function<uint32_t(uint32_t, uint32_t)> allocateDynamicAddress;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Result of a conventional registration transaction.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct ConventionalRegistrationResult {
|
||||
/**
|
||||
* @brief The decision of the registration request.
|
||||
*/
|
||||
RegistrationDecision decision = RegistrationDecision::DENY;
|
||||
/**
|
||||
* @brief The reason for denying the registration request.
|
||||
*/
|
||||
RegistrationDenyReason denyReason = RegistrationDenyReason::MALFORMED;
|
||||
/**
|
||||
* @brief The response to the registration request.
|
||||
*/
|
||||
::p25::data::ConventionalRegistration response;
|
||||
/**
|
||||
* @brief Indicates whether the binding has changed as a result of the registration request.
|
||||
*/
|
||||
bool bindingChanged { false };
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Implements conventional packet data registration and binding state.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
class ConventionalDataService {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes a new instance of the ConventionalDataService class.
|
||||
* @param bindings The data binding registry.
|
||||
* @param links The data link manager.
|
||||
* @param policy The conventional registration policy.
|
||||
*/
|
||||
ConventionalDataService(DataBindingRegistry& bindings, DataLinkManager& links,
|
||||
const ConventionalRegistrationPolicy& policy = ConventionalRegistrationPolicy());
|
||||
|
||||
/**
|
||||
* @brief Processes a conventional registration request.
|
||||
* @param data The raw registration request data.
|
||||
* @param length The length of the registration request data.
|
||||
* @param headerLlId The logical link identifier from the header.
|
||||
* @param provisioning The conventional registration provisioning information.
|
||||
* @param nowMs The current time in milliseconds.
|
||||
* @return The result of the registration request.
|
||||
*/
|
||||
ConventionalRegistrationResult process(const uint8_t* data, uint32_t length,
|
||||
uint32_t headerLlId, const ConventionalRegistrationProvisioning& provisioning,
|
||||
uint64_t nowMs = 0U);
|
||||
|
||||
/**
|
||||
* @brief Installs a static registration for the specified logical link identifier.
|
||||
* @param llId The logical link identifier.
|
||||
* @param ipAddress The IP address associated with the registration.
|
||||
* @param authorized Indicates whether the registration is authorized.
|
||||
* @return True if the static registration was successfully installed; otherwise, false.
|
||||
*/
|
||||
bool installStatic(uint32_t llId, uint32_t ipAddress, bool authorized = true);
|
||||
/**
|
||||
* @brief Expires registrations that have passed their expiration time.
|
||||
* @param nowMs The current time in milliseconds.
|
||||
*/
|
||||
void expire(uint64_t nowMs);
|
||||
/**
|
||||
* @brief Checks if the specified logical link identifier is registered.
|
||||
* @param llId The logical link identifier.
|
||||
* @return True if the logical link identifier is registered; otherwise, false.
|
||||
*/
|
||||
bool isRegistered(uint32_t llId) const;
|
||||
/**
|
||||
* @brief Gets the number of current registrations.
|
||||
* @return The number of current registrations.
|
||||
*/
|
||||
size_t registrationCount() const noexcept { return m_registrations.size(); }
|
||||
|
||||
/**
|
||||
* @brief Converts a registration deny reason to a string representation.
|
||||
* @param reason The registration deny reason.
|
||||
* @return A string representation of the registration deny reason.
|
||||
*/
|
||||
static std::string denyReasonToString(RegistrationDenyReason reason);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Represents the state of a registration.
|
||||
*/
|
||||
struct RegistrationState {
|
||||
/**
|
||||
* @brief The IP address associated with the registration.
|
||||
*/
|
||||
uint32_t ipAddress = 0U;
|
||||
/**
|
||||
* @brief The options associated with the registration.
|
||||
*/
|
||||
uint8_t options = 0U;
|
||||
/**
|
||||
* @brief The expiration time of the registration in milliseconds.
|
||||
*/
|
||||
uint64_t expiresAtMs = 0U;
|
||||
/**
|
||||
* @brief Indicates whether the registration is dynamic.
|
||||
*/
|
||||
bool dynamic = false;
|
||||
};
|
||||
|
||||
DataBindingRegistry& m_bindings;
|
||||
DataLinkManager& m_links;
|
||||
ConventionalRegistrationPolicy m_policy;
|
||||
std::unordered_map<uint32_t, RegistrationState> m_registrations;
|
||||
|
||||
/**
|
||||
* @brief Denies a registration request.
|
||||
* @param request The registration request to deny.
|
||||
* @param reason The reason for denying the registration.
|
||||
* @return The result of the denial operation.
|
||||
*/
|
||||
ConventionalRegistrationResult deny(const ::p25::data::ConventionalRegistration& request,
|
||||
RegistrationDenyReason reason) const;
|
||||
};
|
||||
} // namespace p25data
|
||||
} // namespace packetdata
|
||||
} // namespace callhandler
|
||||
} // namespace network
|
||||
|
||||
#endif // __P25_DATA__CONVENTIONALDATASERVICE_H__
|
||||
@ -1,17 +1,18 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "common/p25/data/IPConvergenceService.h"
|
||||
#include "network/callhandler/packetdata/p25/IPConvergenceService.h"
|
||||
#include "common/p25/P25Defines.h"
|
||||
|
||||
using namespace p25::data;
|
||||
using namespace p25::defines;
|
||||
using namespace network::callhandler::packetdata::p25data;
|
||||
using namespace ::p25::data;
|
||||
using namespace ::p25::defines;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
@ -0,0 +1,253 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file IPConvergenceService.h
|
||||
* @ingroup fne_packetdata
|
||||
* @file IPConvergenceService.cpp
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
#if !defined(__P25_DATA__IPCONVERGENCESERVICE_H__)
|
||||
#define __P25_DATA__IPCONVERGENCESERVICE_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
#include "common/p25/data/IPv4Packet.h"
|
||||
#include "network/callhandler/packetdata/p25/PacketDataState.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace network
|
||||
{
|
||||
namespace callhandler
|
||||
{
|
||||
namespace packetdata
|
||||
{
|
||||
namespace p25data
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @addtogroup fne_packetdata
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Result of an IP convergence operation.
|
||||
*/
|
||||
enum class ConvergenceResult : uint8_t {
|
||||
OK, //!< Operation was successful.
|
||||
MALFORMED, //!< The IP datagram is malformed.
|
||||
NO_BINDING, //!< No binding exists for the IP datagram.
|
||||
SOURCE_MISMATCH, //!< The source address does not match the expected source.
|
||||
MTU_EXCEEDED, //!< The IP datagram exceeds the maximum transmission unit.
|
||||
POLICY_REJECTED //!< The IP datagram was rejected by policy.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief LLC delivery mode selected for an IP datagram.
|
||||
*/
|
||||
enum class DataDeliveryMode : uint8_t {
|
||||
CONFIRMED, //!< The IP datagram requires confirmed delivery.
|
||||
UNCONFIRMED //!< The IP datagram does not require confirmed delivery.
|
||||
};
|
||||
/* @} */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Result returned when decoding an IP datagram.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct IPDecodeResult {
|
||||
/**
|
||||
* @brief The result of the IP convergence operation.
|
||||
*/
|
||||
ConvergenceResult result = ConvergenceResult::MALFORMED;
|
||||
/**
|
||||
* @brief The decoded IP packet.
|
||||
*/
|
||||
::p25::data::IPv4Packet packet;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Result returned when routing an IP datagram for encoding.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct IPEncodeResult {
|
||||
/**
|
||||
* @brief The result of the IP convergence operation.
|
||||
*/
|
||||
ConvergenceResult result = ConvergenceResult::MALFORMED;
|
||||
/**
|
||||
* @brief The logical link identifier for the encoded IP datagram.
|
||||
*/
|
||||
uint32_t llId = 0U;
|
||||
/**
|
||||
* @brief The length of the encoded IP datagram.
|
||||
*/
|
||||
uint16_t length = 0U;
|
||||
/**
|
||||
* @brief The delivery mode selected for the encoded IP datagram.
|
||||
*/
|
||||
DataDeliveryMode delivery = DataDeliveryMode::CONFIRMED;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Policy controlling SCEP routing and delivery.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct SCEPPolicy {
|
||||
/**
|
||||
* @brief The maximum transmission unit for the SCEP policy.
|
||||
*/
|
||||
uint16_t mtu = 510U;
|
||||
/**
|
||||
* @brief Indicates whether broadcast transmission is allowed under the SCEP policy.
|
||||
*/
|
||||
bool allowBroadcast = true;
|
||||
/**
|
||||
* @brief Indicates whether multicast transmission is allowed under the SCEP policy.
|
||||
*/
|
||||
bool allowMulticast = false;
|
||||
/**
|
||||
* @brief The delivery mode selected for unicast transmissions under the SCEP policy.
|
||||
*/
|
||||
DataDeliveryMode unicastDelivery = DataDeliveryMode::CONFIRMED;
|
||||
/**
|
||||
* @brief The delivery mode selected for broadcast transmissions under the SCEP policy.
|
||||
*/
|
||||
DataDeliveryMode broadcastDelivery = DataDeliveryMode::UNCONFIRMED;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Defines an IP convergence service for a P25 packet data bearer.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
class IPConvergenceService {
|
||||
public:
|
||||
/**
|
||||
* @brief Finalizes a instance of IPConvergenceService.
|
||||
*/
|
||||
virtual ~IPConvergenceService() = default;
|
||||
|
||||
/**
|
||||
* @brief Decodes an IP datagram from the provided data buffer.
|
||||
* @param data Pointer to the buffer containing the IP datagram.
|
||||
* @param length Length of the data buffer.
|
||||
* @return The result of the IP datagram decoding operation.
|
||||
*/
|
||||
virtual IPDecodeResult decode(const uint8_t* data, uint32_t length) const = 0;
|
||||
/**
|
||||
* @brief Encodes an IP datagram into the provided data buffer.
|
||||
* @param data Pointer to the buffer where the IP datagram will be encoded.
|
||||
* @param length Length of the data buffer.
|
||||
* @return The result of the IP datagram encoding operation.
|
||||
*/
|
||||
virtual IPEncodeResult encode(const uint8_t* data, uint32_t length) const = 0;
|
||||
/**
|
||||
* @brief Authorizes an uplink transmission for the specified logical link and IP packet.
|
||||
* @param llId The logical link identifier.
|
||||
* @param packet The IP packet to be transmitted.
|
||||
* @return The result of the uplink authorization operation.
|
||||
*/
|
||||
virtual ConvergenceResult authorizeUplink(uint32_t llId,
|
||||
const ::p25::data::IPv4Packet& packet) const = 0;
|
||||
/**
|
||||
* @brief Routes a downlink transmission to the specified destination address.
|
||||
* @param destinationAddress The destination IP address for the downlink transmission.
|
||||
* @return The result of the downlink routing operation.
|
||||
*/
|
||||
virtual uint32_t routeDownlink(uint32_t destinationAddress) const = 0;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Implements the conventional SCEP IP convergence service.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
class SCEPService : public IPConvergenceService {
|
||||
public:
|
||||
/**
|
||||
* @brief Initializes an instance of the SCEPService class.
|
||||
* @param bindings The data binding registry to be used by the service.
|
||||
* @param policy The SCEP policy to be applied by the service.
|
||||
*/
|
||||
explicit SCEPService(const DataBindingRegistry& bindings,
|
||||
const SCEPPolicy& policy = SCEPPolicy());
|
||||
|
||||
/**
|
||||
* @brief Decodes an IP datagram from the provided data buffer.
|
||||
* @param data Pointer to the buffer containing the IP datagram.
|
||||
* @param length Length of the data buffer.
|
||||
* @return The result of the IP datagram decoding operation.
|
||||
*/
|
||||
IPDecodeResult decode(const uint8_t* data, uint32_t length) const override;
|
||||
/**
|
||||
* @brief Encodes an IP datagram into the provided data buffer.
|
||||
* @param data Pointer to the buffer containing the IP datagram.
|
||||
* @param length Length of the data buffer.
|
||||
* @return The result of the IP datagram encoding operation.
|
||||
*/
|
||||
IPEncodeResult encode(const uint8_t* data, uint32_t length) const override;
|
||||
/**
|
||||
* @brief Authorizes an uplink transmission for the specified logical link ID and IP packet.
|
||||
* @param llId The logical link ID for the uplink transmission.
|
||||
* @param packet The IP packet to be transmitted.
|
||||
* @return The result of the uplink authorization operation.
|
||||
*/
|
||||
ConvergenceResult authorizeUplink(uint32_t llId, const ::p25::data::IPv4Packet& packet) const override;
|
||||
/**
|
||||
* @brief Routes a downlink transmission to the specified destination address.
|
||||
* @param destinationAddress The destination address for the downlink transmission.
|
||||
* @return The logical link ID to which the downlink transmission should be routed.
|
||||
*/
|
||||
uint32_t routeDownlink(uint32_t destinationAddress) const override;
|
||||
|
||||
/**
|
||||
* @brief Determines if the specified address is a broadcast address.
|
||||
* @param address The address to be checked.
|
||||
* @return True if the address is a broadcast address, false otherwise.
|
||||
*/
|
||||
static bool isBroadcast(uint32_t address);
|
||||
/**
|
||||
* @brief Determines if the specified address is a multicast address.
|
||||
* @param address The address to be checked.
|
||||
* @return True if the address is a multicast address, false otherwise.
|
||||
*/
|
||||
static bool isMulticast(uint32_t address);
|
||||
|
||||
private:
|
||||
const DataBindingRegistry& m_bindings;
|
||||
SCEPPolicy m_policy;
|
||||
};
|
||||
} // namespace p25data
|
||||
} // namespace packetdata
|
||||
} // namespace callhandler
|
||||
} // namespace network
|
||||
|
||||
#endif // __P25_DATA__IPCONVERGENCESERVICE_H__
|
||||
@ -1,15 +1,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Common Library
|
||||
* 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "common/p25/data/PacketDataState.h"
|
||||
#include "network/callhandler/packetdata/p25/PacketDataState.h"
|
||||
|
||||
using namespace p25::data;
|
||||
using namespace network::callhandler::packetdata::p25data;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public Class Members
|
||||
@ -0,0 +1,304 @@
|
||||
// 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) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @file PacketDataState.h
|
||||
* @ingroup fne_packetdata
|
||||
* @file PacketDataState.cpp
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
#if !defined(__P25_DATA__PACKETDATASTATE_H__)
|
||||
#define __P25_DATA__PACKETDATASTATE_H__
|
||||
|
||||
#include "common/Defines.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace network
|
||||
{
|
||||
namespace callhandler
|
||||
{
|
||||
namespace packetdata
|
||||
{
|
||||
namespace p25data
|
||||
{
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @addtogroup fne_packetdata
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief P25 packet data access mode.
|
||||
*/
|
||||
enum class DataAccessMode : uint8_t {
|
||||
CONVENTIONAL, //!< Conventional access mode
|
||||
TRUNKED //!< Trunked access mode
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief P25 packet data convergence mode.
|
||||
*/
|
||||
enum class DataConvergenceMode : uint8_t {
|
||||
CAI_DATA, //!< CAI data convergence mode
|
||||
SCEP, //!< SCEP data convergence mode
|
||||
SNDCP //!< SNDCP data convergence mode
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Source from which an IP binding was learned.
|
||||
*/
|
||||
enum class DataBindingOrigin : uint8_t {
|
||||
PROVISIONED, //!< Provisioned binding origin
|
||||
CONVENTIONAL_REGISTRATION, //!< Conventional registration binding origin
|
||||
SNDCP_STATIC, //!< SNDCP static binding origin
|
||||
SNDCP_DYNAMIC, //!< SNDCP dynamic binding origin
|
||||
ARP_LEARNED //!< ARP learned binding origin
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Result of LLC receive sequence processing.
|
||||
*/
|
||||
enum class ReceiveSequenceResult : uint8_t {
|
||||
ACCEPTED, //!< Accepted receive sequence result
|
||||
DUPLICATE, //!< Duplicate receive sequence result
|
||||
OUT_OF_SEQUENCE //!< Out of sequence receive sequence result
|
||||
};
|
||||
/* @} */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Identifies a P25 logical data link.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct DataLinkKey {
|
||||
/**
|
||||
* @brief The logical link identifier.
|
||||
*/
|
||||
uint32_t llId = 0U;
|
||||
|
||||
/**
|
||||
* @brief Compares this DataLinkKey with another for equality.
|
||||
* @param other The other DataLinkKey to compare with.
|
||||
* @return True if the DataLinkKeys are equal; otherwise, false.
|
||||
*/
|
||||
bool operator==(const DataLinkKey& other) const noexcept
|
||||
{
|
||||
return llId == other.llId;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Associates a logical data link with an authorized IP address.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct IPBinding {
|
||||
/**
|
||||
* @brief The logical data link associated with this IP binding.
|
||||
*/
|
||||
DataLinkKey link;
|
||||
/**
|
||||
* @brief The IP address associated with this binding.
|
||||
*/
|
||||
uint32_t ipAddress = 0U;
|
||||
/**
|
||||
* @brief The access mode for this binding.
|
||||
*/
|
||||
DataAccessMode accessMode = DataAccessMode::CONVENTIONAL;
|
||||
/**
|
||||
* @brief The convergence mode for this binding.
|
||||
*/
|
||||
DataConvergenceMode convergenceMode = DataConvergenceMode::SCEP;
|
||||
/**
|
||||
* @brief The origin from which this binding was learned.
|
||||
*/
|
||||
DataBindingOrigin origin = DataBindingOrigin::PROVISIONED;
|
||||
/**
|
||||
* @brief The NSAPI associated with this binding.
|
||||
*/
|
||||
uint8_t nsapi = 0U;
|
||||
/**
|
||||
* @brief Indicates whether the NSAPI is valid.
|
||||
*/
|
||||
bool hasNSAPI = false;
|
||||
/**
|
||||
* @brief Indicates whether this binding is authorized.
|
||||
*/
|
||||
bool authorized = false;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief LLC sequencing and readiness state for one logical link.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
struct DataLinkState {
|
||||
/**
|
||||
* @brief The send sequence number for this logical link.
|
||||
*/
|
||||
uint8_t sendSequence = 0U;
|
||||
/**
|
||||
* @brief The receive sequence number for this logical link.
|
||||
*/
|
||||
uint8_t receiveSequence = 0U;
|
||||
/**
|
||||
* @brief The receive fingerprint for this logical link.
|
||||
*/
|
||||
uint32_t receiveFingerprint = 0U;
|
||||
/**
|
||||
* @brief Indicates whether the send sequence has been initialized.
|
||||
*/
|
||||
bool sendInitialized = false;
|
||||
/**
|
||||
* @brief Indicates whether the receive sequence has been initialized.
|
||||
*/
|
||||
bool receiveInitialized = false;
|
||||
/**
|
||||
* @brief Indicates whether the link is ready for the next packet.
|
||||
*/
|
||||
bool readyForNextPacket = true;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Stores typed P25 packet data IP bindings.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
class DataBindingRegistry {
|
||||
public:
|
||||
/**
|
||||
* @brief Inserts or updates an IP binding in the registry. Returns true if the operation was successful.
|
||||
* @param binding The IP binding to insert or update.
|
||||
* @return True if the operation was successful, false otherwise.
|
||||
*/
|
||||
bool upsert(const IPBinding& binding);
|
||||
/**
|
||||
* @brief Erases an IP binding from the registry by its logical link ID. Returns true if the operation was successful.
|
||||
* @param llId The logical link ID of the IP binding to erase.
|
||||
* @return True if the operation was successful, false otherwise.
|
||||
*/
|
||||
bool erase(uint32_t llId);
|
||||
/**
|
||||
* @brief Clears all IP bindings from the registry.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief Finds an IP binding by its logical link ID.
|
||||
* @param llId The logical link ID of the IP binding to find.
|
||||
* @return A pointer to the IP binding if found, nullptr otherwise.
|
||||
*/
|
||||
const IPBinding* findByLLId(uint32_t llId) const;
|
||||
/**
|
||||
* @brief Finds an IP binding by its IP address.
|
||||
* @param ipAddress The IP address of the IP binding to find.
|
||||
* @return A pointer to the IP binding if found, nullptr otherwise.
|
||||
*/
|
||||
const IPBinding* findByIPAddress(uint32_t ipAddress) const;
|
||||
/**
|
||||
* @brief Returns the number of IP bindings in the registry.
|
||||
* @return The number of IP bindings.
|
||||
*/
|
||||
size_t size() const noexcept { return m_bindings.size(); }
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, IPBinding> m_bindings;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class Declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Stores LLC state independently for each logical link.
|
||||
* @ingroup fne_packetdata
|
||||
*/
|
||||
class DataLinkManager {
|
||||
public:
|
||||
/**
|
||||
* @brief Returns the next send sequence number for the specified logical link.
|
||||
* @param llId The logical link ID.
|
||||
* @param synchronize Indicates whether to synchronize the sequence.
|
||||
* @return The next send sequence number.
|
||||
*/
|
||||
uint8_t nextSendSequence(uint32_t llId, bool& synchronize);
|
||||
/**
|
||||
* @brief Accepts a received sequence number for the specified logical link.
|
||||
* @param llId The logical link ID.
|
||||
* @param sequence The received sequence number.
|
||||
* @param synchronize Indicates whether to synchronize the sequence.
|
||||
* @param packetFingerprint The fingerprint of the received packet.
|
||||
* @param expectedSequence The expected sequence number.
|
||||
* @return The result of the receive sequence acceptance.
|
||||
*/
|
||||
ReceiveSequenceResult acceptReceiveSequence(uint32_t llId, uint8_t sequence,
|
||||
bool synchronize, uint32_t packetFingerprint, uint8_t& expectedSequence);
|
||||
|
||||
/**
|
||||
* @brief Computes the fingerprint of the given data.
|
||||
* @param data Pointer to the data.
|
||||
* @param length Length of the data.
|
||||
* @return The computed fingerprint.
|
||||
*/
|
||||
static uint32_t fingerprint(const uint8_t* data, uint32_t length);
|
||||
|
||||
/**
|
||||
* @brief Sets the readiness state for the specified logical link.
|
||||
* @param llId The logical link ID.
|
||||
* @param ready The readiness state to set.
|
||||
*/
|
||||
void setReady(uint32_t llId, bool ready);
|
||||
/**
|
||||
* @brief Checks if the specified logical link is ready.
|
||||
* @param llId The logical link ID.
|
||||
* @return True if the logical link is ready, false otherwise.
|
||||
*/
|
||||
bool isReady(uint32_t llId) const;
|
||||
/**
|
||||
* @brief Checks if the specified logical link has an associated state.
|
||||
* @param llId The logical link ID.
|
||||
* @return True if the logical link has an associated state, false otherwise.
|
||||
*/
|
||||
bool hasState(uint32_t llId) const;
|
||||
/**
|
||||
* @brief Erases the state associated with the specified logical link.
|
||||
* @param llId The logical link ID.
|
||||
*/
|
||||
void erase(uint32_t llId);
|
||||
/**
|
||||
* @brief Clears all logical link states.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, DataLinkState> m_states;
|
||||
};
|
||||
} // namespace p25data
|
||||
} // namespace packetdata
|
||||
} // namespace callhandler
|
||||
} // namespace network
|
||||
|
||||
#endif // __P25_DATA__PACKETDATASTATE_H__
|
||||
@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Digital Voice Modem - Test Suite
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* Copyright (C) 2026 Bryan Biedenkapp, N2PLL
|
||||
*
|
||||
*/
|
||||
#include "network/callhandler/packetdata/DMRPacketScheduler.h"
|
||||
#include "network/callhandler/packetdata/DataRouting.h"
|
||||
|
||||
using namespace network::callhandler::packetdata;
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
TEST_CASE("DMR packet scheduler bounds queued downlinks",
|
||||
"[dmr][packet-data][scheduler]")
|
||||
{
|
||||
DMRPacketScheduler scheduler(2U, 8U);
|
||||
|
||||
ScheduledDMRDataPacket first;
|
||||
first.dstId = 1U;
|
||||
first.userData.assign(4U, 0x11U);
|
||||
CHECK(scheduler.enqueue(std::move(first)) == 0U);
|
||||
|
||||
ScheduledDMRDataPacket second;
|
||||
second.dstId = 2U;
|
||||
second.userData.assign(4U, 0x22U);
|
||||
CHECK(scheduler.enqueue(std::move(second)) == 0U);
|
||||
REQUIRE(scheduler.front() != nullptr);
|
||||
CHECK(scheduler.front()->dstId == 1U);
|
||||
|
||||
ScheduledDMRDataPacket third;
|
||||
third.dstId = 3U;
|
||||
third.userData.assign(4U, 0x33U);
|
||||
CHECK(scheduler.enqueue(std::move(third)) == 1U);
|
||||
REQUIRE(scheduler.front() != nullptr);
|
||||
CHECK(scheduler.front()->dstId == 2U);
|
||||
CHECK(scheduler.size() == 2U);
|
||||
CHECK(scheduler.byteCount() == 8U);
|
||||
}
|
||||
|
||||
TEST_CASE("DMR scheduled packet owns its header and payload",
|
||||
"[dmr][packet-data][scheduler][memory]")
|
||||
{
|
||||
DMRPacketScheduler scheduler(1U, 64U);
|
||||
|
||||
{
|
||||
dmr::data::DataHeader source;
|
||||
source.setSrcId(1001U);
|
||||
source.setDstId(2002U);
|
||||
|
||||
ScheduledDMRDataPacket packet;
|
||||
packet.header = source;
|
||||
packet.userData.assign(20U, 0x55U);
|
||||
REQUIRE(scheduler.enqueue(std::move(packet)) == 0U);
|
||||
}
|
||||
|
||||
REQUIRE(scheduler.front() != nullptr);
|
||||
CHECK(scheduler.front()->header.getSrcId() == 1001U);
|
||||
CHECK(scheduler.front()->header.getDstId() == 2002U);
|
||||
CHECK(scheduler.front()->userData.size() == 20U);
|
||||
|
||||
scheduler.clear();
|
||||
CHECK(scheduler.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("DMR subscriber locations preserve peer and timeslot",
|
||||
"[dmr][packet-data][routing]")
|
||||
{
|
||||
DataLocationRegistry locations(100U);
|
||||
ConventionalLocation location;
|
||||
location.peerId = 20U;
|
||||
location.slotNo = 2U;
|
||||
location.lastSeen = 1000U;
|
||||
REQUIRE(locations.updateConventional(1001U, location));
|
||||
|
||||
DataRoute route = locations.resolve(1001U, AccessMode::CONVENTIONAL, 1050U);
|
||||
REQUIRE(route.valid);
|
||||
CHECK(route.peerId == 20U);
|
||||
CHECK(route.slotNo == 2U);
|
||||
|
||||
location.peerId = 30U;
|
||||
location.slotNo = 1U;
|
||||
location.lastSeen = 1060U;
|
||||
REQUIRE(locations.updateConventional(1001U, location));
|
||||
route = locations.resolve(1001U, AccessMode::CONVENTIONAL, 1070U);
|
||||
CHECK(route.peerId == 30U);
|
||||
CHECK(route.slotNo == 1U);
|
||||
|
||||
locations.expire(1160U);
|
||||
CHECK_FALSE(locations.resolve(1001U, AccessMode::CONVENTIONAL, 1160U).valid);
|
||||
}
|
||||
Loading…
Reference in new issue