diff --git a/src/common/p25/data/ConventionalDataService.h b/src/common/p25/data/ConventionalDataService.h deleted file mode 100644 index 49a506e0..00000000 --- a/src/common/p25/data/ConventionalDataService.h +++ /dev/null @@ -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 -#include -#include - -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 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 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__ diff --git a/src/common/p25/data/DataRouting.h b/src/common/p25/data/DataRouting.h deleted file mode 100644 index 0017d643..00000000 --- a/src/common/p25/data/DataRouting.h +++ /dev/null @@ -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 -#include -#include -#include - -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 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& 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 resolveGroup(const std::vector& 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 m_conventional; - std::unordered_map m_trunked; - std::unordered_set 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__ diff --git a/src/common/p25/data/IPConvergenceService.h b/src/common/p25/data/IPConvergenceService.h deleted file mode 100644 index 6a9bba81..00000000 --- a/src/common/p25/data/IPConvergenceService.h +++ /dev/null @@ -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 - -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__ diff --git a/src/common/p25/data/PacketDataState.h b/src/common/p25/data/PacketDataState.h deleted file mode 100644 index 46d58a8a..00000000 --- a/src/common/p25/data/PacketDataState.h +++ /dev/null @@ -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 -#include - -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 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 m_states; - }; - } // namespace data -} // namespace p25 - -#endif // __P25_DATA__PACKETDATASTATE_H__ diff --git a/src/common/p25/data/PacketScheduler.h b/src/common/p25/data/PacketScheduler.h deleted file mode 100644 index 9bc3dd2a..00000000 --- a/src/common/p25/data/PacketScheduler.h +++ /dev/null @@ -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 -#include -#include - -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 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 m_packets; - }; - } // namespace data -} // namespace p25 - -#endif // __P25_DATA__PACKETSCHEDULER_H__ diff --git a/src/fne/CMakeLists.txt b/src/fne/CMakeLists.txt index f82b91ee..e46d5c40 100644 --- a/src/fne/CMakeLists.txt +++ b/src/fne/CMakeLists.txt @@ -14,6 +14,8 @@ file(GLOB dvmfne_SRC "src/fne/network/callhandler/*.cpp" "src/fne/network/callhandler/packetdata/*.h" "src/fne/network/callhandler/packetdata/*.cpp" + "src/fne/network/callhandler/packetdata/p25/*.h" + "src/fne/network/callhandler/packetdata/p25/*.cpp" "src/fne/network/influxdb/*.h" "src/fne/network/influxdb/*.cpp" "src/fne/network/packet/*.h" diff --git a/src/fne/network/TrafficNetwork.h b/src/fne/network/TrafficNetwork.h index 5bf610de..ab9a675e 100644 --- a/src/fne/network/TrafficNetwork.h +++ b/src/fne/network/TrafficNetwork.h @@ -16,6 +16,10 @@ * @brief Implementation for the FNE call handlers. * @ingroup fne_network * + * @defgroup fne_packetdata Packet Data Handlers + * @brief Implementation for the FNE packet data handlers. + * @ingroup fne_callhandler + * * @file TrafficNetwork.h * @ingroup fne_network * @file TrafficNetwork.cpp diff --git a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp index bfba7840..2def6eee 100644 --- a/src/fne/network/callhandler/packetdata/DMRPacketData.cpp +++ b/src/fne/network/callhandler/packetdata/DMRPacketData.cpp @@ -30,6 +30,7 @@ using namespace dmr::defines; #include #include +#include #if !defined(_WIN32) #include @@ -44,6 +45,7 @@ const uint8_t MAX_PKT_RETRY_CNT = 2U; const uint32_t INTERPACKET_DELAY = 100U; // milliseconds const uint32_t ARP_RETRY_MS = 5000U; // milliseconds +const uint32_t CONVENTIONAL_LOCATION_MAX_AGE_MS = 300000U; // 5 minutes // --------------------------------------------------------------------------- // Public Class Members @@ -54,10 +56,10 @@ const uint32_t ARP_RETRY_MS = 5000U; // milliseconds DMRPacketData::DMRPacketData(TrafficNetwork* network, TagDMRData* tag, bool debug) : m_network(network), m_tag(tag), - m_queuedFrames(), - m_queuedFrameBytes(0U), + m_packetScheduler(network->m_vtunQueueMaxFrames, network->m_vtunQueueMaxBytes), m_status(), - m_arpTable(), + m_neighborCache(), + m_locationRegistry(CONVENTIONAL_LOCATION_MAX_AGE_MS), m_readyForNextPkt(), m_suSendSeq(), m_debug(debug) @@ -70,17 +72,7 @@ DMRPacketData::DMRPacketData(TrafficNetwork* network, TagDMRData* tag, bool debu DMRPacketData::~DMRPacketData() { - while (m_queuedFrames.size() > 0U) { - QueuedDataFrame* frame = m_queuedFrames[0U]; - m_queuedFrames.pop_front(); - if (frame != nullptr) { - if (frame->header != nullptr) - delete frame->header; - if (frame->userData != nullptr) - delete[] frame->userData; - delete frame; - } - } + /* stub */ } /* Process a data frame from the network. */ @@ -248,16 +240,25 @@ bool DMRPacketData::processFrame(const uint8_t* data, uint32_t len, uint32_t pee } } + uint32_t srcId = status->header.getSrcId(); + if (m_locationRegistry.learnFromInbound() && srcId != 0U && srcId != DMRDEF::WUID_ALL) { + ConventionalLocation location; + location.peerId = peerId; + location.slotNo = status->slotNo; + location.lastSeen = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + m_locationRegistry.updateConventional(srcId, location); + } + status->callBusy = true; dispatch(peerId, dmrData, data, len); uint64_t duration = hrc::diff(pktTime, status->callStartTime); bool gi = status->header.getGI(); - uint32_t srcId = status->header.getSrcId(); uint32_t dstId = status->header.getDstId(); LogInfoEx((fromUpstream) ? LOG_PEER : LOG_MASTER, "DMR, Data Call End, peer = %u, slot = %u, srcId = %u, dstId = %u, group = %u, blocks = %u, duration = %u, streamId = %u, fromUpstream = %u", - peerId, srcId, dstId, gi, status->header.getBlocksToFollow(), duration / 1000, streamId, fromUpstream); + peerId, status->slotNo, srcId, dstId, gi, status->header.getBlocksToFollow(), duration / 1000, streamId, fromUpstream); TrafficNetwork::MetricsLogging::incrementCallsProcessed(m_network); @@ -334,77 +335,35 @@ void DMRPacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a srcIpStr.c_str(), DMRDEF::WUID_IPI, tgtIpStr.c_str(), dstId, pktLen, proto); // assemble a DMR PDU frame header for transport... - dmr::data::DataHeader* pktHeader = new dmr::data::DataHeader(); - pktHeader->setDPF(DPF::CONFIRMED_DATA); - pktHeader->setA(true); - pktHeader->setSAP(PDUSAP::PACKET_DATA); - pktHeader->setSrcId(DMRDEF::WUID_IPI); - pktHeader->setDstId(dstId); - pktHeader->setGI(false); - pktHeader->setFullMesage(true); + dmr::data::DataHeader pktHeader; + pktHeader.setDPF(DPF::CONFIRMED_DATA); + pktHeader.setA(true); + pktHeader.setSAP(PDUSAP::PACKET_DATA); + pktHeader.setSrcId(DMRDEF::WUID_IPI); + pktHeader.setDstId(dstId); + pktHeader.setGI(false); + pktHeader.setFullMesage(true); // bryanb: we are always sending data as 3/4 rate? - pktHeader->calculateLength(DataType::RATE_34_DATA, pktLen); + pktHeader.calculateLength(DataType::RATE_34_DATA, pktLen); uint32_t pduLength = pktLen; DECLARE_UINT8_ARRAY(pduUserData, pduLength); ::memcpy(pduUserData, data, pktLen); - if (pduLength > m_network->m_vtunQueueMaxBytes) { - LogWarning(LOG_DMR, "VTUN queue drop, frame too large for queue cap, frameBytes = %u, capBytes = %u", - pduLength, m_network->m_vtunQueueMaxBytes); - return; - } - - uint32_t droppedFrames = 0U; - while (m_queuedFrames.size() >= m_network->m_vtunQueueMaxFrames || - (m_queuedFrameBytes + pduLength) > m_network->m_vtunQueueMaxBytes) { - if (m_queuedFrames.size() == 0U) { - break; - } - - QueuedDataFrame* oldFrame = m_queuedFrames[0U]; - m_queuedFrames.pop_front(); - if (oldFrame != nullptr) { - if (oldFrame->userDataLen <= m_queuedFrameBytes) { - m_queuedFrameBytes -= oldFrame->userDataLen; - } - else { - m_queuedFrameBytes = 0U; - } - - if (oldFrame->header != nullptr) - delete oldFrame->header; - if (oldFrame->userData != nullptr) - delete[] oldFrame->userData; - delete oldFrame; - } - - droppedFrames++; - } + ScheduledDMRDataPacket packet; + packet.header = pktHeader; + packet.dstId = dstId; + packet.targetIPAddress = tgtProtoAddr; + packet.dueAt = now + INTERPACKET_DELAY; + packet.userData.assign(pduUserData, pduUserData + pduLength); + uint32_t droppedFrames = m_packetScheduler.enqueue(std::move(packet)); if (droppedFrames > 0U) { - LogWarning(LOG_DMR, "VTUN queue cap reached, dropped %u oldest frame(s), queuedFrames = %u, queuedBytes = %u", - droppedFrames, (uint32_t)m_queuedFrames.size(), m_queuedFrameBytes); + LogWarning(LOG_DMR, "VTUN queue cap reached, dropped %u frame(s), queuedFrames = %u, queuedBytes = %u", + droppedFrames, uint32_t(m_packetScheduler.size()), m_packetScheduler.byteCount()); } - - // queue frame for dispatch - QueuedDataFrame* qf = new QueuedDataFrame(); - qf->retryCnt = 0U; - qf->extendRetry = false; - qf->timestamp = now + INTERPACKET_DELAY; - - qf->header = pktHeader; - qf->dstId = dstId; - qf->tgtProtoAddr = tgtProtoAddr; - - qf->userData = new uint8_t[pduLength]; - ::memcpy(qf->userData, pduUserData, pduLength); - qf->userDataLen = pduLength; - - m_queuedFrames.push_back(qf); - m_queuedFrameBytes += pduLength; #endif // !defined(_WIN32) } @@ -434,53 +393,51 @@ void DMRPacketData::write_PDU_Ack_Response(uint8_t ackClass, uint8_t ackType, ui void DMRPacketData::clock(uint32_t ms) { uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + m_locationRegistry.expire(now); - if (m_queuedFrames.size() == 0U) { + if (m_packetScheduler.empty()) { return; } - // transmit queued data frames - auto& frame = m_queuedFrames[0]; - if (frame != nullptr) { - if (now >= frame->timestamp) { - // check if we have an ARP entry for the destination - if (!hasARPEntry(frame->dstId)) { - if (frame->retryCnt < MAX_PKT_RETRY_CNT || frame->extendRetry) { - // send ARP request - write_PDU_ARP(frame->tgtProtoAddr); - frame->timestamp = now + ARP_RETRY_MS; - frame->retryCnt++; - } else { - LogWarning(LOG_DMR, "DMR, failed to resolve ARP for dstId %u", frame->dstId); - if (frame->userDataLen <= m_queuedFrameBytes) { - m_queuedFrameBytes -= frame->userDataLen; - } - else { - m_queuedFrameBytes = 0U; - } - delete frame->header; - delete[] frame->userData; - delete frame; - m_queuedFrames.pop_front(); - } - } else { - // transmit the PDU frame - dispatchUserFrameToFNE(*frame->header, frame->userData); + ScheduledDMRDataPacket* packet = m_packetScheduler.front(); + if (packet == nullptr || now < packet->dueAt) + return; - if (frame->userDataLen <= m_queuedFrameBytes) { - m_queuedFrameBytes -= frame->userDataLen; - } - else { - m_queuedFrameBytes = 0U; - } + if (packet->retryCount >= MAX_PKT_RETRY_CNT) { + LogWarning(LOG_DMR, "DMR, max packet retry count exceeded, dropping packet, dstId = %u", + packet->dstId); + m_packetScheduler.pop(); + return; + } - delete frame->header; - delete[] frame->userData; - delete frame; - m_queuedFrames.pop_front(); + // check if we have an ARP entry for the destination + if (!hasARPEntry(packet->dstId)) { + write_PDU_ARP(packet->targetIPAddress); + packet->dueAt = now + ARP_RETRY_MS; + packet->retryCount++; + return; + } + + if (packet->dstId != DMRDEF::WUID_ALL) { + DataRoute route = m_locationRegistry.resolve(packet->dstId, + AccessMode::CONVENTIONAL, now); + if (!route.valid) { + UnknownLocationPolicy policy = m_locationRegistry.unknownLocationPolicy(); + if (policy == UnknownLocationPolicy::DROP) { + m_packetScheduler.pop(); + return; } + if (policy == UnknownLocationPolicy::ARP) + write_PDU_ARP(packet->targetIPAddress); + + packet->dueAt = now + ARP_RETRY_MS; + packet->retryCount++; + return; } } + + dispatchUserFrameToFNE(packet->header, packet->userData.data()); + m_packetScheduler.pop(); } /* Helper to cleanup any call's left in a dangling state without any further updates. */ @@ -576,13 +533,13 @@ void DMRPacketData::dispatch(uint32_t peerId, dmr::data::NetData& dmrData, const if (opcode == DMR_PDU_ARP_REQUEST) { LogInfoEx(LOG_DMR, "DMR, ARP request, who has %s? tell %s (%u)", __IP_FROM_UINT(tgtProtoAddr).c_str(), __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); - m_arpTable[srcHWAddr] = srcProtoAddr; // update ARP table + m_neighborCache.observe(srcHWAddr, srcProtoAddr); // update neighbor cache if (tgtProtoAddr == fneIPv4) { write_PDU_ARP_Reply(fneIPv4, srcHWAddr, srcProtoAddr, DMRDEF::WUID_ALLL); } } else if (opcode == DMR_PDU_ARP_REPLY) { LogInfoEx(LOG_DMR, "DMR, ARP reply, %s is at %u", __IP_FROM_UINT(srcProtoAddr).c_str(), srcHWAddr); - m_arpTable[srcHWAddr] = srcProtoAddr; // update ARP table + m_neighborCache.observe(srcHWAddr, srcProtoAddr); // update neighbor cache } #endif // !defined(_WIN32) } @@ -679,6 +636,34 @@ void DMRPacketData::dispatchToFNE(uint32_t peerId, dmr::data::NetData& dmrData, uint32_t srcId = status->header.getSrcId(); uint32_t dstId = status->header.getDstId(); + std::vector routes; + bool groupDelivery = status->header.getGI() || dstId == DMRDEF::WUID_ALL; + if (groupDelivery) { + std::vector availablePeers; + for (const auto& peer : m_network->m_peers) + availablePeers.push_back(peer.first); + for (const auto& peer : m_network->m_host->m_peerNetworks) { + if (peer.second->isEnabled()) + availablePeers.push_back(peer.second->getPeerId()); + } + routes = m_locationRegistry.resolveGroup(availablePeers); + } + else { + uint64_t now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + DataRoute route = m_locationRegistry.resolve(dstId, AccessMode::CONVENTIONAL, now); + if (!route.valid) { + LogWarning(LOG_DMR, "DMR, no RF route for relayed packet, dstId = %u", dstId); + return; + } + routes.push_back(route); + } + + auto selectedPeer = [&routes](uint32_t candidatePeerId) { + return std::any_of(routes.cbegin(), routes.cend(), + [candidatePeerId](const DataRoute& route) { return route.peerId == candidatePeerId; }); + }; + /* ** MASTER TRAFFIC */ @@ -686,7 +671,7 @@ void DMRPacketData::dispatchToFNE(uint32_t peerId, dmr::data::NetData& dmrData, // repeat traffic to the connected peers if (m_network->m_peers.size() > 0U) { for (auto peer : m_network->m_peers) { - if (peerId != peer.first) { + if (peerId != peer.first && selectedPeer(peer.first)) { // is this peer ignored? if (!m_tag->isPeerPermitted(peer.first, dmrData, streamId)) { continue; @@ -712,7 +697,7 @@ void DMRPacketData::dispatchToFNE(uint32_t peerId, dmr::data::NetData& dmrData, // don't try to repeat traffic to the source peer...if this traffic // is coming from a neighbor FNE peer - if (dstPeerId != peerId) { + if (dstPeerId != peerId && selectedPeer(dstPeerId)) { // skip peer if it isn't enabled if (!peer.second->isEnabled()) { continue; @@ -740,6 +725,26 @@ void DMRPacketData::dispatchUserFrameToFNE(dmr::data::DataHeader& dataHeader, ui uint32_t srcId = dataHeader.getSrcId(); uint32_t dstId = dataHeader.getDstId(); + std::vector routes; + if (dataHeader.getGI() || dstId == DMRDEF::WUID_ALL) { + std::vector availablePeers; + for (const auto& peer : m_network->m_peers) + availablePeers.push_back(peer.first); + routes = m_locationRegistry.resolveGroup(availablePeers); + } + else { + uint64_t now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + DataRoute route = m_locationRegistry.resolve(dstId, AccessMode::CONVENTIONAL, now); + if (route.valid) + routes.push_back(route); + } + + if (routes.empty()) { + LogWarning(LOG_DMR, "DMR, no RF route for downlink, dstId = %u", dstId); + return; + } + // update the sequence number m_suSendSeq[srcId]++; if (m_suSendSeq[srcId] >= 8U) { @@ -768,7 +773,6 @@ void DMRPacketData::dispatchUserFrameToFNE(dmr::data::DataHeader& dataHeader, ui */ dmr::data::NetData dmrData; - dmrData.setSlotNo(1U); dmrData.setSrcId(srcId); dmrData.setDstId(dstId); dmrData.setFLCO(FLCO::PRIVATE); @@ -776,18 +780,29 @@ void DMRPacketData::dispatchUserFrameToFNE(dmr::data::DataHeader& dataHeader, ui dmrData.setSeqNo(0U); dmrData.setDataType((currentBlock == 0U) ? DataType::DATA_HEADER : DataType::RATE_34_DATA); - // create DMR network message - uint32_t messageLength = 0U; - UInt8Array message = m_network->createDMR_Message(messageLength, streamId, dmrData); - if (message != nullptr) { - // copy the DMR frame data + for (const DataRoute& route : routes) { + dmrData.setSlotNo(route.slotNo != 0U ? route.slotNo : 1U); + + uint32_t messageLength = 0U; + UInt8Array message = m_network->createDMR_Message(messageLength, streamId, dmrData); + if (message == nullptr) + continue; ::memcpy(message.get() + 20U, data, len); - // repeat traffic to the connected peers - if (m_network->m_peers.size() > 0U) { - for (auto peer : m_network->m_peers) { - m_network->writePeer(peer.first, m_network->m_peerId, { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, - message.get(), messageLength, pktSeq, streamId); + auto localPeer = m_network->m_peers.find(route.peerId); + if (localPeer != m_network->m_peers.end()) { + m_network->writePeer(route.peerId, m_network->m_peerId, + { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, + message.get(), messageLength, pktSeq, streamId); + } + else { + for (const auto& peer : m_network->m_host->m_peerNetworks) { + if (peer.second->getPeerId() == route.peerId && peer.second->isEnabled()) { + peer.second->writeMaster( + { NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, + message.get(), messageLength, pktSeq, streamId); + break; + } } } } @@ -891,18 +906,8 @@ bool DMRPacketData::hasARPEntry(uint32_t id) const return false; } - // lookup ARP table entry - try { - uint32_t addr = m_arpTable.at(id); - if (addr != 0U) { - return true; - } - else { - return false; - } - } catch (...) { - return false; - } + const RouteNeighbor* neighbor = m_neighborCache.findBySubscriberId(id); + return neighbor != nullptr && neighbor->ipAddress != 0U; } /* Helper to get the IP address for the given radio ID. */ @@ -914,7 +919,8 @@ uint32_t DMRPacketData::getIPAddress(uint32_t id) } if (hasARPEntry(id)) { - return m_arpTable[id]; + const RouteNeighbor* neighbor = m_neighborCache.findBySubscriberId(id); + return neighbor != nullptr ? neighbor->ipAddress : 0U; } else { // do we have a static entry for this ID? lookups::RadioId rid = m_network->m_ridLookup->find(id); @@ -938,11 +944,9 @@ uint32_t DMRPacketData::getRadioIdAddress(uint32_t addr) return 0U; } - for (auto entry : m_arpTable) { - if (entry.second == addr) { - return entry.first; - } - } + const RouteNeighbor* neighbor = m_neighborCache.findByIPAddress(addr); + if (neighbor != nullptr) + return neighbor->subscriberId; // check if we have an entry in the RID lookup std::string ipAddr = __IP_FROM_UINT(addr); @@ -955,7 +959,7 @@ uint32_t DMRPacketData::getRadioIdAddress(uint32_t addr) return false; }); if (it != ridTable.end()) { - m_arpTable[it->first] = addr; + m_neighborCache.observe(it->first, addr); return it->first; } diff --git a/src/fne/network/callhandler/packetdata/DMRPacketData.h b/src/fne/network/callhandler/packetdata/DMRPacketData.h index 82862a96..ef976a27 100644 --- a/src/fne/network/callhandler/packetdata/DMRPacketData.h +++ b/src/fne/network/callhandler/packetdata/DMRPacketData.h @@ -9,24 +9,23 @@ */ /** * @file DMRPacketData.h - * @ingroup fne_callhandler + * @ingroup fne_packetdata * @file DMRPacketData.cpp - * @ingroup fne_callhandler + * @ingroup fne_packetdata */ #if !defined(__PACKETDATA__DMR_PACKET_DATA_H__) #define __PACKETDATA__DMR_PACKET_DATA_H__ #include "fne/Defines.h" #include "common/Clock.h" -#include "common/concurrent/deque.h" #include "common/concurrent/unordered_map.h" #include "common/dmr/DMRDefines.h" #include "common/dmr/data/DataHeader.h" #include "network/TrafficNetwork.h" #include "network/PeerNetwork.h" #include "network/callhandler/TagDMRData.h" - -#include +#include "network/callhandler/packetdata/DataRouting.h" +#include "network/callhandler/packetdata/DMRPacketScheduler.h" namespace network { @@ -40,7 +39,7 @@ namespace network /** * @brief Implements the DMR packet data handler. - * @ingroup fne_callhandler + * @ingroup fne_packetdata */ class HOST_SW_API DMRPacketData { public: @@ -101,24 +100,7 @@ namespace network TrafficNetwork* m_network; TagDMRData *m_tag; - /** - * @brief Represents a queued data frame from the VTUN. - */ - class QueuedDataFrame { - public: - dmr::data::DataHeader* header; //!< Instance of a PDU data header. - uint32_t dstId; //!< Destination Radio ID - uint32_t tgtProtoAddr; //!< Target Protocol Address - - uint8_t* userData; //!< Raw data buffer - uint32_t userDataLen; //!< Length of raw data buffer - - uint64_t timestamp; //!< Timestamp in milliseconds - uint8_t retryCnt; //!< Packet Retry Counter - bool extendRetry; //!< Flag indicating whether or not to extend the retry count for this packet. - }; - concurrent::deque m_queuedFrames; - uint32_t m_queuedFrameBytes; + DMRPacketScheduler m_packetScheduler; /** * @brief Represents the receive status of a call. @@ -186,8 +168,8 @@ namespace network typedef std::pair StatusMapPair; concurrent::unordered_map m_status; - typedef std::pair ArpTablePair; - std::unordered_map m_arpTable; + RouteNeighborCache m_neighborCache; + DataLocationRegistry m_locationRegistry; typedef std::pair ReadyForNextPktPair; std::unordered_map m_readyForNextPkt; std::unordered_map m_suSendSeq; diff --git a/src/fne/network/callhandler/packetdata/DMRPacketScheduler.cpp b/src/fne/network/callhandler/packetdata/DMRPacketScheduler.cpp new file mode 100644 index 00000000..811f0198 --- /dev/null +++ b/src/fne/network/callhandler/packetdata/DMRPacketScheduler.cpp @@ -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 + +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; +} diff --git a/src/fne/network/callhandler/packetdata/DMRPacketScheduler.h b/src/fne/network/callhandler/packetdata/DMRPacketScheduler.h new file mode 100644 index 00000000..240ca1e5 --- /dev/null +++ b/src/fne/network/callhandler/packetdata/DMRPacketScheduler.h @@ -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 +#include +#include + +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 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 m_packets; + }; + } // namespace packetdata + } // namespace callhandler +} // namespace network + +#endif // __PACKETDATA__DMR_PACKET_SCHEDULER_H__ diff --git a/src/common/p25/data/DataRouting.cpp b/src/fne/network/callhandler/packetdata/DataRouting.cpp similarity index 89% rename from src/common/p25/data/DataRouting.cpp rename to src/fne/network/callhandler/packetdata/DataRouting.cpp index 5bc86ef7..79c8a32c 100644 --- a/src/common/p25/data/DataRouting.cpp +++ b/src/fne/network/callhandler/packetdata/DataRouting.cpp @@ -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/DataRouting.h" +#include "network/callhandler/packetdata/DataRouting.h" -using namespace p25::data; +using namespace network::callhandler::packetdata; // --------------------------------------------------------------------------- // Public Class Members @@ -17,22 +17,22 @@ using namespace p25::data; /* Observes a route neighbor with the given link-layer ID and IP address. */ -void RouteNeighborCache::observe(uint32_t llId, uint32_t ipAddress, uint64_t nowMs) +void RouteNeighborCache::observe(uint32_t subscriberId, uint32_t ipAddress, uint64_t nowMs) { - if (llId == 0U || ipAddress == 0U) + if (subscriberId == 0U || ipAddress == 0U) return; - RouteNeighbor& neighbor = m_neighbors[llId]; - neighbor.llId = llId; + RouteNeighbor& neighbor = m_neighbors[subscriberId]; + neighbor.subscriberId = subscriberId; neighbor.ipAddress = ipAddress; neighbor.lastSeen = nowMs; } /* Erases a route neighbor with the given link-layer ID. */ -bool RouteNeighborCache::erase(uint32_t llId) +bool RouteNeighborCache::erase(uint32_t subscriberId) { - return m_neighbors.erase(llId) > 0U; + return m_neighbors.erase(subscriberId) > 0U; } /* Clears all observed route neighbors. */ @@ -44,9 +44,9 @@ void RouteNeighborCache::clear() /* Finds a route neighbor by its link-layer ID. */ -const RouteNeighbor* RouteNeighborCache::findByLLId(uint32_t llId) const +const RouteNeighbor* RouteNeighborCache::findBySubscriberId(uint32_t subscriberId) const { - auto it = m_neighbors.find(llId); + auto it = m_neighbors.find(subscriberId); return it == m_neighbors.end() ? nullptr : &it->second; } @@ -165,6 +165,7 @@ DataRoute DataLocationRegistry::resolve(uint32_t llId, AccessMode mode, uint64_t result.peerId = it->second.peerId; result.channelId = it->second.channelId; result.channelNo = it->second.channelNo; + result.slotNo = it->second.slotNo; result.lastSeen = it->second.lastSeen; result.valid = true; diff --git a/src/fne/network/callhandler/packetdata/DataRouting.h b/src/fne/network/callhandler/packetdata/DataRouting.h new file mode 100644 index 00000000..b73c363b --- /dev/null +++ b/src/fne/network/callhandler/packetdata/DataRouting.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 +#include +#include +#include + +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 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& 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 resolveGroup(const std::vector& 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 m_conventional; + std::unordered_map m_trunked; + std::unordered_set 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__ diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.cpp b/src/fne/network/callhandler/packetdata/P25PacketData.cpp index d2ca6db6..91f2d96b 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketData.cpp @@ -27,6 +27,7 @@ using namespace system_clock; using namespace network; using namespace network::callhandler; using namespace network::callhandler::packetdata; +using namespace network::callhandler::packetdata::p25data; using namespace p25; using namespace p25::defines; using namespace p25::data; @@ -366,7 +367,7 @@ void P25PacketData::processPacketFrame(const uint8_t* data, uint32_t len, bool a Utils::dump(1U, "P25, P25PacketData::processPacketFrame(), pduUserData", pduUserData, pduLength); //#endif - ScheduledDataPacket packet; + ScheduledP25DataPacket packet; packet.header = pktHeader; packet.llId = llId; packet.targetIPAddress = tgtProtoAddr; @@ -460,7 +461,7 @@ void P25PacketData::clock(uint32_t ms) return; } - ScheduledDataPacket* packet = m_packetScheduler.front(); + ScheduledP25DataPacket* packet = m_packetScheduler.front(); if (packet == nullptr || now <= packet->dueAt) return; @@ -1471,7 +1472,7 @@ bool P25PacketData::hasARPEntry(uint32_t llId) const return false; } - const RouteNeighbor* neighbor = m_neighborCache.findByLLId(llId); + const RouteNeighbor* neighbor = m_neighborCache.findBySubscriberId(llId); return neighbor != nullptr && neighbor->ipAddress != 0U; } diff --git a/src/fne/network/callhandler/packetdata/P25PacketData.h b/src/fne/network/callhandler/packetdata/P25PacketData.h index 509e6a07..ffd3f115 100644 --- a/src/fne/network/callhandler/packetdata/P25PacketData.h +++ b/src/fne/network/callhandler/packetdata/P25PacketData.h @@ -9,9 +9,9 @@ */ /** * @file P25PacketData.h - * @ingroup fne_callhandler + * @ingroup fne_packetdata * @file P25PacketData.cpp - * @ingroup fne_callhandler + * @ingroup fne_packetdata */ #if !defined(__PACKETDATA__P25_PACKET_DATA_H__) #define __PACKETDATA__P25_PACKET_DATA_H__ @@ -21,16 +21,16 @@ #include "common/concurrent/unordered_map.h" #include "common/p25/P25Defines.h" #include "common/p25/data/Assembler.h" -#include "common/p25/data/ConventionalDataService.h" -#include "common/p25/data/IPConvergenceService.h" -#include "common/p25/data/PacketDataState.h" -#include "common/p25/data/DataRouting.h" -#include "common/p25/data/PacketScheduler.h" #include "common/p25/data/DataHeader.h" #include "common/p25/data/DataBlock.h" #include "network/TrafficNetwork.h" #include "network/PeerNetwork.h" #include "network/callhandler/TagP25Data.h" +#include "network/callhandler/packetdata/DataRouting.h" +#include "network/callhandler/packetdata/P25PacketScheduler.h" +#include "network/callhandler/packetdata/p25/ConventionalDataService.h" +#include "network/callhandler/packetdata/p25/IPConvergenceService.h" +#include "network/callhandler/packetdata/p25/PacketDataState.h" namespace network { @@ -44,7 +44,7 @@ namespace network /** * @brief Implements the P25 packet data handler. - * @ingroup fne_callhandler + * @ingroup fne_packetdata */ class HOST_SW_API P25PacketData { public: @@ -120,7 +120,7 @@ namespace network TrafficNetwork* m_network; TagP25Data* m_tag; - p25::data::Assembler* m_assembler; + ::p25::data::Assembler* m_assembler; /** * @brief Represents the data required for a PDU assembler custom writer context. @@ -131,12 +131,12 @@ namespace network uint32_t peerId; //!< Peer ID for this request. uint32_t srcPeerId; //!< Source Peer ID for this request. network::PeerNetwork* peerNet; //!< Instance of the peer network for an upstream peer. - p25::data::DataHeader* header; //!< PDU data header. + ::p25::data::DataHeader* header; //!< PDU data header. uint16_t pktSeq; //!< Packet sequence. uint32_t streamId; //!< Stream ID. }; - p25::data::PacketScheduler m_packetScheduler; + P25PacketScheduler m_packetScheduler; /** * @brief Represents the receive status of a call. @@ -150,7 +150,7 @@ namespace network uint32_t peerId; //!< Peer ID std::unordered_map receivedBlocks; - p25::data::Assembler assembler; //!< PDU Assembler Instance + ::p25::data::Assembler assembler; //!< PDU Assembler Instance bool hasRxHeader; //!< Flag indicating whether or not a valid Rx header has been received uint16_t dataBlockCnt; //!< Number of data blocks received uint16_t totalBlocks; //!< Total number of blocks expected @@ -211,12 +211,12 @@ namespace network typedef std::pair StatusMapPair; concurrent::unordered_map m_status; - p25::data::RouteNeighborCache m_neighborCache; - p25::data::DataBindingRegistry m_bindingRegistry; - p25::data::DataLocationRegistry m_locationRegistry; - p25::data::DataLinkManager m_dataLinkManager; - p25::data::ConventionalDataService m_conventionalDataService; - p25::data::SCEPService m_scepService; + RouteNeighborCache m_neighborCache; + p25data::DataBindingRegistry m_bindingRegistry; + DataLocationRegistry m_locationRegistry; + p25data::DataLinkManager m_dataLinkManager; + p25data::ConventionalDataService m_conventionalDataService; + p25data::SCEPService m_scepService; bool m_debug; @@ -237,7 +237,7 @@ namespace network * @param auxiliaryES Flag indicating whether or not an auxiliary ES is included. * @param pduUserData Buffer containing user data to transmit. */ - void dispatchUserFrameToFNE(p25::data::DataHeader& dataHeader, bool extendedAddress, bool auxiliaryES, uint8_t* pduUserData); + void dispatchUserFrameToFNE(::p25::data::DataHeader& dataHeader, bool extendedAddress, bool auxiliaryES, uint8_t* pduUserData); /** * @brief Helper used to process conventional data registration from PDU data. @@ -276,7 +276,7 @@ namespace network * @param auxiliaryES Flag indicating whether or not an auxiliary ES is included. * @param pduUserData Buffer containing user data to transmit. */ - void write_PDU_User(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, p25::data::DataHeader& dataHeader, + void write_PDU_User(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, ::p25::data::DataHeader& dataHeader, bool extendedAddress, bool auxiliaryES, uint8_t* pduUserData); /** @@ -291,7 +291,7 @@ namespace network * @param pktSeq RTP packet sequence. * @param streamId Stream ID. */ - bool writeNetwork(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, const p25::data::DataHeader& dataHeader, const uint8_t currentBlock, + bool writeNetwork(uint32_t peerId, uint32_t srcPeerId, network::PeerNetwork* peerNet, const ::p25::data::DataHeader& dataHeader, const uint8_t currentBlock, const uint8_t* data, uint32_t len, uint16_t pktSeq, uint32_t streamId); /** diff --git a/src/common/p25/data/PacketScheduler.cpp b/src/fne/network/callhandler/packetdata/P25PacketScheduler.cpp similarity index 76% rename from src/common/p25/data/PacketScheduler.cpp rename to src/fne/network/callhandler/packetdata/P25PacketScheduler.cpp index 184985ac..dc049340 100644 --- a/src/common/p25/data/PacketScheduler.cpp +++ b/src/fne/network/callhandler/packetdata/P25PacketScheduler.cpp @@ -1,25 +1,25 @@ // 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/PacketScheduler.h" +#include "network/callhandler/packetdata/P25PacketScheduler.h" #include -using namespace p25::data; +using namespace network::callhandler::packetdata; // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- -/* Initializes a new instance of the PacketScheduler class. */ +/* Initializes a new instance of the P25PacketScheduler class. */ -PacketScheduler::PacketScheduler(uint32_t maxFrames, uint32_t maxBytes) : +P25PacketScheduler::P25PacketScheduler(uint32_t maxFrames, uint32_t maxBytes) : m_maxFrames(maxFrames), m_maxBytes(maxBytes), m_byteCount(0U), @@ -30,7 +30,7 @@ PacketScheduler::PacketScheduler(uint32_t maxFrames, uint32_t maxBytes) : /* Enqueues a scheduled packet into the scheduler. */ -uint32_t PacketScheduler::enqueue(ScheduledDataPacket&& packet) +uint32_t P25PacketScheduler::enqueue(ScheduledP25DataPacket&& packet) { uint32_t dropped = 0U; uint32_t packetBytes = uint32_t(packet.userData.size()); @@ -51,14 +51,14 @@ uint32_t PacketScheduler::enqueue(ScheduledDataPacket&& packet) /* Gets the next queued packet. */ -ScheduledDataPacket* PacketScheduler::front() +ScheduledP25DataPacket* P25PacketScheduler::front() { return m_packets.empty() ? nullptr : &m_packets.front(); } /* Removes the next queued packet. */ -void PacketScheduler::pop() +void P25PacketScheduler::pop() { if (m_packets.empty()) return; @@ -70,7 +70,7 @@ void PacketScheduler::pop() /* Moves the next queued packet to the back of the queue. */ -void PacketScheduler::rotate() +void P25PacketScheduler::rotate() { if (m_packets.size() < 2U) return; @@ -81,7 +81,7 @@ void PacketScheduler::rotate() /* Clears all queued packets from the scheduler. */ -void PacketScheduler::clear() +void P25PacketScheduler::clear() { m_packets.clear(); m_byteCount = 0U; diff --git a/src/fne/network/callhandler/packetdata/P25PacketScheduler.h b/src/fne/network/callhandler/packetdata/P25PacketScheduler.h new file mode 100644 index 00000000..c9351dea --- /dev/null +++ b/src/fne/network/callhandler/packetdata/P25PacketScheduler.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 +#include +#include + +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 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 m_packets; + }; + } // namespace packetdata + } // namespace callhandler +} // namespace network + +#endif // __PACKETDATA__P25_PACKET_SCHEDULER_H__ diff --git a/src/common/p25/data/ConventionalDataService.cpp b/src/fne/network/callhandler/packetdata/p25/ConventionalDataService.cpp similarity index 96% rename from src/common/p25/data/ConventionalDataService.cpp rename to src/fne/network/callhandler/packetdata/p25/ConventionalDataService.cpp index b7bbdf45..24e7c25a 100644 --- a/src/common/p25/data/ConventionalDataService.cpp +++ b/src/fne/network/callhandler/packetdata/p25/ConventionalDataService.cpp @@ -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 diff --git a/src/fne/network/callhandler/packetdata/p25/ConventionalDataService.h b/src/fne/network/callhandler/packetdata/p25/ConventionalDataService.h new file mode 100644 index 00000000..df90ada6 --- /dev/null +++ b/src/fne/network/callhandler/packetdata/p25/ConventionalDataService.h @@ -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 +#include +#include + +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 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 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__ diff --git a/src/common/p25/data/IPConvergenceService.cpp b/src/fne/network/callhandler/packetdata/p25/IPConvergenceService.cpp similarity index 94% rename from src/common/p25/data/IPConvergenceService.cpp rename to src/fne/network/callhandler/packetdata/p25/IPConvergenceService.cpp index 3accd090..2023e785 100644 --- a/src/common/p25/data/IPConvergenceService.cpp +++ b/src/fne/network/callhandler/packetdata/p25/IPConvergenceService.cpp @@ -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 diff --git a/src/fne/network/callhandler/packetdata/p25/IPConvergenceService.h b/src/fne/network/callhandler/packetdata/p25/IPConvergenceService.h new file mode 100644 index 00000000..639667a9 --- /dev/null +++ b/src/fne/network/callhandler/packetdata/p25/IPConvergenceService.h @@ -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 + +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__ diff --git a/src/common/p25/data/PacketDataState.cpp b/src/fne/network/callhandler/packetdata/p25/PacketDataState.cpp similarity index 96% rename from src/common/p25/data/PacketDataState.cpp rename to src/fne/network/callhandler/packetdata/p25/PacketDataState.cpp index f35665ea..86b34f0a 100644 --- a/src/common/p25/data/PacketDataState.cpp +++ b/src/fne/network/callhandler/packetdata/p25/PacketDataState.cpp @@ -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 diff --git a/src/fne/network/callhandler/packetdata/p25/PacketDataState.h b/src/fne/network/callhandler/packetdata/p25/PacketDataState.h new file mode 100644 index 00000000..7f402542 --- /dev/null +++ b/src/fne/network/callhandler/packetdata/p25/PacketDataState.h @@ -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 +#include + +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 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 m_states; + }; + } // namespace p25data + } // namespace packetdata + } // namespace callhandler +} // namespace network + +#endif // __P25_DATA__PACKETDATASTATE_H__ diff --git a/tests/dmr/DMR_PacketScheduler_Tests.cpp b/tests/dmr/DMR_PacketScheduler_Tests.cpp new file mode 100644 index 00000000..02082356 --- /dev/null +++ b/tests/dmr/DMR_PacketScheduler_Tests.cpp @@ -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 + +#include + +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); +} diff --git a/tests/p25/P25_PacketDataState_Tests.cpp b/tests/p25/P25_PacketDataState_Tests.cpp index a326f232..99c7158e 100644 --- a/tests/p25/P25_PacketDataState_Tests.cpp +++ b/tests/p25/P25_PacketDataState_Tests.cpp @@ -10,15 +10,17 @@ #include "host/Defines.h" #include "common/p25/P25Defines.h" #include "common/p25/data/ConventionalRegistration.h" -#include "common/p25/data/ConventionalDataService.h" -#include "common/p25/data/IPConvergenceService.h" #include "common/p25/data/IPv4Packet.h" -#include "common/p25/data/PacketDataState.h" -#include "common/p25/data/DataRouting.h" -#include "common/p25/data/PacketScheduler.h" +#include "network/callhandler/packetdata/p25/PacketDataState.h" +#include "network/callhandler/packetdata/DataRouting.h" +#include "network/callhandler/packetdata/P25PacketScheduler.h" +#include "network/callhandler/packetdata/p25/ConventionalDataService.h" +#include "network/callhandler/packetdata/p25/IPConvergenceService.h" using namespace p25::data; using namespace p25::defines; +using namespace network::callhandler::packetdata::p25data; +using namespace network::callhandler::packetdata; #include @@ -375,6 +377,7 @@ TEST_CASE("P25 conventional locations move, expire, and resolve targeted routes" location.peerId = 10U; location.channelId = 1U; location.channelNo = 101U; + location.slotNo = 2U; location.lastSeen = 1000U; REQUIRE(locations.updateConventional(1001U, location)); @@ -383,6 +386,7 @@ TEST_CASE("P25 conventional locations move, expire, and resolve targeted routes" CHECK(route.peerId == 10U); CHECK(route.channelId == 1U); CHECK(route.channelNo == 101U); + CHECK(route.slotNo == 2U); // Later inbound traffic moves the subscriber to peer B. location.peerId = 20U; @@ -451,8 +455,8 @@ TEST_CASE("P25 neighbor observations do not create bindings or routes", DataLocationRegistry locations; neighbors.observe(1001U, 0x0A000001U, 10U); - REQUIRE(neighbors.findByLLId(1001U) != nullptr); - CHECK(neighbors.findByIPAddress(0x0A000001U)->llId == 1001U); + REQUIRE(neighbors.findBySubscriberId(1001U) != nullptr); + CHECK(neighbors.findByIPAddress(0x0A000001U)->subscriberId == 1001U); CHECK(bindings.findByLLId(1001U) == nullptr); CHECK_FALSE(locations.resolve(1001U, AccessMode::CONVENTIONAL).valid); } @@ -460,13 +464,13 @@ TEST_CASE("P25 neighbor observations do not create bindings or routes", TEST_CASE("P25 packet scheduler bounds and rotates queued downlinks", "[p25][packet-data][scheduler]") { - PacketScheduler scheduler(2U, 8U); - ScheduledDataPacket first; + P25PacketScheduler scheduler(2U, 8U); + ScheduledP25DataPacket first; first.llId = 1U; first.userData.assign(4U, 0x11U); CHECK(scheduler.enqueue(std::move(first)) == 0U); - ScheduledDataPacket second; + ScheduledP25DataPacket second; second.llId = 2U; second.userData.assign(4U, 0x22U); CHECK(scheduler.enqueue(std::move(second)) == 0U); @@ -476,7 +480,7 @@ TEST_CASE("P25 packet scheduler bounds and rotates queued downlinks", scheduler.rotate(); CHECK(scheduler.front()->llId == 2U); - ScheduledDataPacket third; + ScheduledP25DataPacket third; third.llId = 3U; third.userData.assign(4U, 0x33U); CHECK(scheduler.enqueue(std::move(third)) == 1U); @@ -487,7 +491,7 @@ TEST_CASE("P25 packet scheduler bounds and rotates queued downlinks", TEST_CASE("P25 scheduled packet header assignment owns an independent copy", "[p25][packet-data][scheduler][memory]") { - PacketScheduler scheduler(1U, 64U); + P25PacketScheduler scheduler(1U, 64U); std::array originalMI { 0x01U, 0x23U, 0x45U, 0x67U, 0x89U, 0xABU, 0xCDU, 0xEFU, 0x10U }; @@ -498,7 +502,7 @@ TEST_CASE("P25 scheduled packet header assignment owns an independent copy", source.setSAP(PDUSAP::PACKET_DATA); source.setMI(originalMI.data()); - ScheduledDataPacket packet; + ScheduledP25DataPacket packet; packet.header = source; packet.userData.assign(20U, 0x55U); REQUIRE(scheduler.enqueue(std::move(packet)) == 0U);