diff --git a/configs/config.example.yml b/configs/config.example.yml index 2053ed30..c156a096 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -326,8 +326,11 @@ protocols: verifyReg: false # Flag indicating automated 12-hour idle group affiliation timeout is disabled. disableGrpAffTimeout: false - # Flag indicating automated 12-hour idle unit registration timeout is disabled. + # Flag indicating the WUID validity timeout is disabled. When true, the broadcast value is 255. disableUnitRegTimeout: false + # WUID validity advertised in SYS_SRV_BCST. The value adds 30-minute increments to the + # four-hour minimum; 0 is four hours and 255 disables WUID expiration. + wuidValidity: 0 # Flag indicating the host requires LLA verification before allowing unit registration. requireLLAForReg: false # Flag indicating whether verbose dumping of P25 data packets is enabled. diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index 109cbd9d..fd7a9908 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -45,6 +45,7 @@ AffiliationLookup::AffiliationLookup(const std::string name, ChannelLookup* chan m_chLookup(channelLookup), m_disableUnitRegTimeout(false), m_disableGrpAffTimeout(false), + m_unitRegTimeout(UNIT_REG_TIMEOUT), m_verbose(verbose) { m_name = name; @@ -75,7 +76,7 @@ void AffiliationLookup::unitReg(uint32_t srcId, bool log) m_unitRegTable.push_back(srcId); - m_unitRegTimers[srcId] = Timer(1000U, UNIT_REG_TIMEOUT); + m_unitRegTimers[srcId] = Timer(1000U, m_unitRegTimeout); m_unitRegTimers[srcId].start(); if (m_verbose && log) { @@ -218,7 +219,7 @@ void AffiliationLookup::replaceUnitRegistrations(const std::vector& re replacementTimers.emplace(srcId, currentTimer->second); } else { - Timer timer(1000U, UNIT_REG_TIMEOUT); + Timer timer(1000U, m_unitRegTimeout); timer.start(); replacementTimers.emplace(srcId, timer); } diff --git a/src/common/lookups/AffiliationLookup.h b/src/common/lookups/AffiliationLookup.h index 3e45a224..c6092aa3 100644 --- a/src/common/lookups/AffiliationLookup.h +++ b/src/common/lookups/AffiliationLookup.h @@ -287,6 +287,11 @@ namespace lookups * @param disable Flag indicating idle unit registration timeout should be disabled. */ void setDisableUnitRegTimeout(bool disabled) { m_disableUnitRegTimeout = disabled; } + /** + * @brief Sets the idle unit registration timeout. + * @param timeout Timeout in seconds. + */ + void setUnitRegTimeout(uint32_t timeout) { m_unitRegTimeout = timeout; } /** * @brief Helper to determine if the group affiliation timeout is enabled or not. @@ -339,6 +344,7 @@ namespace lookups bool m_disableUnitRegTimeout; bool m_disableGrpAffTimeout; + uint32_t m_unitRegTimeout; bool m_verbose; diff --git a/src/common/p25/SiteData.h b/src/common/p25/SiteData.h index cb827147..fcca31e3 100644 --- a/src/common/p25/SiteData.h +++ b/src/common/p25/SiteData.h @@ -46,6 +46,7 @@ namespace p25 m_callsign("CHANGEME"), m_chCnt(0U), m_netActive(false), + m_wuidValidity(0U), m_lto(0) { /* stub */ @@ -75,6 +76,7 @@ namespace p25 m_callsign("CHANGEME"), m_chCnt(0U), m_netActive(false), + m_wuidValidity(0U), m_lto(0) { // lra clamping @@ -148,6 +150,15 @@ namespace p25 m_netActive = netActive; } + /** + * @brief Helper to set the WUID validity period advertised by this site. + * @param wuidValidity WUID validity field in 30-minute increments beyond four hours; 0xFF disables expiry. + */ + void setWuidValidity(uint8_t wuidValidity) + { + m_wuidValidity = wuidValidity; + } + /** * @brief Helper to set adjacent site data. * @param sysId P25 System ID. @@ -198,6 +209,7 @@ namespace p25 m_callsign = "ADJSITE "; m_chCnt = -1; // don't store channel count for adjacent sites m_netActive = true; // adjacent sites are explicitly network active + m_wuidValidity = 0U; m_lto = 0; } @@ -228,6 +240,8 @@ namespace p25 m_netActive = data.m_netActive; + m_wuidValidity = data.m_wuidValidity; + m_lto = data.m_lto; } @@ -284,6 +298,10 @@ namespace p25 * @brief Flag indicating whether this site is a linked active network member. */ DECLARE_RO_PROPERTY_PLAIN(bool, netActive); + /** + * @brief WUID validity field in 30-minute increments beyond four hours; 0xFF disables expiry. + */ + DECLARE_RO_PROPERTY_PLAIN(uint8_t, wuidValidity); /** * @brief Local Time Offset. */ diff --git a/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.cpp b/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.cpp index 0ede0a2a..added3d6 100644 --- a/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.cpp +++ b/src/common/p25/lc/tdulc/LC_SYS_SRV_BCAST.cpp @@ -45,14 +45,15 @@ void LC_SYS_SRV_BCAST::encode(uint8_t* data) { assert(data != nullptr); - const uint32_t services = (s_siteData.netActive()) ? SystemService::NET_ACTIVE : 0U | SYS_SRV_DEFAULT; + const uint32_t services = SYS_SRV_TRUNK | (s_siteData.netActive() ? SystemService::NET_ACTIVE : 0U); ulong64_t rsValue = 0U; m_implicit = true; - rsValue = 0U; - rsValue = (rsValue << 16) + services; // System Services Available + rsValue = s_siteData.wuidValidity(); // WUID Validity + rsValue = (rsValue << 8); // Request Priority Level + rsValue = (rsValue << 24) + services; // System Services Available rsValue = (rsValue << 24) + services; // System Services Supported std::unique_ptr rs = TDULC::fromValue(rsValue); diff --git a/src/common/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp b/src/common/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp index ff0552b2..a1a1b636 100644 --- a/src/common/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp +++ b/src/common/p25/lc/tsbk/OSP_MOT_CC_BSI.cpp @@ -49,10 +49,12 @@ void OSP_MOT_CC_BSI::encode(uint8_t* data, bool rawTSBK, bool noTrellis) m_mfId = MFG_MOT; - tsbkValue = (s_siteCallsign[0] - 43U) & 0x3F; // Character 0 - for (uint8_t i = 1; i < MOT_CALLSIGN_LENGTH_BYTES; i++) { - tsbkValue = (tsbkValue << 6) + ((s_siteCallsign[i] - 43U) & 0x3F); // Character 1 - 7 + // encode the site callsign into the TSBK value using six-bit codes + for (uint8_t i = 0U; i < MOT_CALLSIGN_LENGTH_BYTES; i++) { + const uint8_t code = s_siteCallsign[i] == 0U || s_siteCallsign[i] == ' ' ? 0U : (s_siteCallsign[i] - 43U) & 0x3FU; + tsbkValue = (tsbkValue << 6) + code; } + tsbkValue = (tsbkValue << 4) + s_siteData.channelId(); // Channel ID tsbkValue = (tsbkValue << 12) + s_siteData.channelNo(); // Channel Number diff --git a/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp b/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp index dee51739..1dcab964 100644 --- a/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp +++ b/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.cpp @@ -23,7 +23,11 @@ using namespace p25::lc::tsbk; /* Initializes a new instance of the OSP_SYS_SRV_BCAST class. */ -OSP_SYS_SRV_BCAST::OSP_SYS_SRV_BCAST() : TSBK() +OSP_SYS_SRV_BCAST::OSP_SYS_SRV_BCAST() : TSBK(), + m_wuidValidity(0U), + m_servicesAvailable(0U), + m_servicesSupported(0U), + m_requestPriority(0U) { m_lco = TSBKO::OSP_SYS_SRV_BCAST; } @@ -34,7 +38,15 @@ bool OSP_SYS_SRV_BCAST::decode(const uint8_t* data, bool rawTSBK) { assert(data != nullptr); - /* stub */ + DECLARE_UINT8_ARRAY(payload, P25_TSBK_LENGTH_BYTES); + if (!TSBK::decode(data, payload, rawTSBK)) + return false; + + const ulong64_t tsbkValue = TSBK::toValue(payload); + m_wuidValidity = (uint8_t)((tsbkValue >> 56) & 0xFFU); // WUID Validity + m_servicesAvailable = (uint32_t)((tsbkValue >> 32) & 0xFFFFFFU); // System Services Available + m_servicesSupported = (uint32_t)((tsbkValue >> 8) & 0xFFFFFFU); // System Services Supported + m_requestPriority = (uint8_t)(tsbkValue & 0xFFU); // Request Priority Level return true; } @@ -45,12 +57,14 @@ void OSP_SYS_SRV_BCAST::encode(uint8_t* data, bool rawTSBK, bool noTrellis) { assert(data != nullptr); - const uint32_t services = (s_siteData.netActive()) ? SystemService::NET_ACTIVE : 0U | SYS_SRV_DEFAULT; + const uint32_t services = SYS_SRV_TRUNK | (s_siteData.netActive() ? SystemService::NET_ACTIVE : 0U); ulong64_t tsbkValue = 0U; - tsbkValue = (tsbkValue << 16) + services; // System Services Available + tsbkValue = s_siteData.wuidValidity(); // WUID Validity + tsbkValue = (tsbkValue << 24) + services; // System Services Available tsbkValue = (tsbkValue << 24) + services; // System Services Supported + tsbkValue = (tsbkValue << 8); // Request Priority Level std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); diff --git a/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.h b/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.h index 10f5a3f9..fba423fa 100644 --- a/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.h +++ b/src/common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.h @@ -60,6 +60,24 @@ namespace p25 * @returns std::string String representation of the TSBK. */ std::string toString(bool isp = false) override; + + public: + /** + * @brief WUID validity field in 30-minute increments beyond four hours; 0xFF disables expiry. + */ + DECLARE_RO_PROPERTY(uint8_t, wuidValidity, WUIDValidity); + /** + * @brief Bitmap of services currently available at the site. + */ + DECLARE_RO_PROPERTY(uint32_t, servicesAvailable, ServicesAvailable); + /** + * @brief Bitmap of services supported by the site. + */ + DECLARE_RO_PROPERTY(uint32_t, servicesSupported, ServicesSupported); + /** + * @brief Lowest service-request priority currently accepted by the site. + */ + DECLARE_RO_PROPERTY(uint8_t, requestPriority, RequestPriority); }; } // namespace tsbk } // namespace lc diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index ccf3e386..412b2fb5 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -402,6 +402,23 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw m_siteData = SiteData(netId, sysId, rfssId, siteId, 0U, channelId, channelNo, serviceClass, lto); m_siteData.setCallsign(cwCallsign); + bool disableUnitRegTimeout = p25Protocol["disableUnitRegTimeout"].as(false); + uint32_t wuidValidity = p25Protocol["wuidValidity"].as(0U); + if (wuidValidity > 0xFFU) { + LogWarning(LOG_P25, "WUID validity value exceeds 8 bits, defaulting to 0 (4 hours)."); + wuidValidity = 0U; + } + disableUnitRegTimeout = disableUnitRegTimeout || wuidValidity == 0xFFU; + if (disableUnitRegTimeout) + wuidValidity = 0xFFU; + + m_siteData.setWuidValidity((uint8_t)wuidValidity); + if (!disableUnitRegTimeout) { + const uint32_t wuidValiditySeconds = (4U * 60U * 60U) + (wuidValidity * 30U * 60U); + m_affiliations->setUnitRegTimeout(wuidValiditySeconds); + } + m_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); + lc::LC::setSiteData(m_siteData); lc::TDULC::setSiteData(m_siteData); @@ -422,9 +439,6 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw bool disableGrpAffTimeout = p25Protocol["disableGrpAffTimeout"].as(false); m_affiliations->setDisableGrpAffTimeout(disableGrpAffTimeout); - bool disableUnitRegTimeout = p25Protocol["disableUnitRegTimeout"].as(false); - m_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); - // set the grant release callback m_affiliations->setReleaseGrantCallback([=](uint32_t chNo, uint32_t srcId, uint32_t dstId, uint8_t slot) { m_control->m_telIntGrants.erase(dstId); diff --git a/tests/p25/TSBK_Tests.cpp b/tests/p25/TSBK_Tests.cpp index e7f8abb1..b040ab53 100644 --- a/tests/p25/TSBK_Tests.cpp +++ b/tests/p25/TSBK_Tests.cpp @@ -9,6 +9,7 @@ */ #include "host/Defines.h" #include "common/p25/lc/tsbk/OSP_SCCB.h" +#include "common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.h" #include "common/p25/lc/tsbk/OSP_TSBK_RAW.h" #include "common/p25/lc/tsbk/TSBKFactory.h" #include "common/p25/data/DataBlock.h" @@ -33,6 +34,36 @@ TEST_CASE("TSBK", "[p25][tsbk]") { REQUIRE(P25_TSBK_FEC_LENGTH_BITS == (P25_TSBK_FEC_LENGTH_BYTES * 8 - 4)); // 196 bits (Trellis) } + SECTION("System_Service_Broadcast_Known_Bytes") { + const SiteData previousSiteData = TSBK::getSiteData(); + SiteData siteData; + siteData.setNetActive(true); + siteData.setWuidValidity(0x02U); + TSBK::setSiteData(siteData); + + OSP_SYS_SRV_BCAST source; + uint8_t raw[P25_TSBK_LENGTH_BYTES]; + ::memset(raw, 0x00U, sizeof(raw)); + source.encode(raw, true, true); + + const uint32_t expectedServices = SYS_SRV_TRUNK | SystemService::NET_ACTIVE; + const uint32_t servicesAvailable = GET_UINT24(raw, 3U) + const uint32_t servicesSupported = GET_UINT24(raw, 6U) + CHECK(raw[2U] == 0x02U); + CHECK(servicesAvailable == expectedServices); + CHECK(servicesSupported == expectedServices); + CHECK(raw[9U] == 0x00U); + + OSP_SYS_SRV_BCAST broadcast; + REQUIRE(broadcast.decode(raw, true)); + CHECK(broadcast.getWUIDValidity() == 0x02U); + CHECK(broadcast.getServicesAvailable() == expectedServices); + CHECK(broadcast.getServicesSupported() == expectedServices); + CHECK(broadcast.getRequestPriority() == 0x00U); + + TSBK::setSiteData(previousSiteData); + } + SECTION("Emergency_Alarm_Request_Roundtrip") { ISP_EMERG_ALRM_REQ source; source.setSpecialInfo1(0x01U);