From 2cf0bdae62f154813b4bb51f945f4e1d23d436a0 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Wed, 29 Apr 2026 13:09:43 -0700 Subject: [PATCH] [vendor-info] add vendor OUI support (#12991) This commit introduces support for configuring and retrieving a vendor OUI-24 (Organizationally Unique Identifier). It defines the new `OPENTHREAD_CONFIG_NET_DIAG_VENDOR_OUI` configuration option and adds the `otThreadGetVendorOui()` and `otThreadSetVendorOui()` APIs. When specified, the vendor OUI is included in the `BorderAgent` mDNS/DNS-SD TXT data under the `vo` key. The `VendorInfo` class is updated to manage the OUI value. This commit also adds the `vendor oui` CLI command to get or set this property. Finally, it updates the tests to validate the presence and correctness of the new `vo` key in the TXT data. --- etc/cmake/options.cmake | 1 + include/openthread/instance.h | 2 +- include/openthread/netdiag.h | 28 +++++++++++++ src/cli/cli.cpp | 47 ++++++++++++++++++++++ src/core/api/netdiag_api.cpp | 11 ++++- src/core/config/network_diagnostic.h | 12 ++++++ src/core/meshcop/border_agent_txt_data.cpp | 34 ++++++++++++++-- src/core/meshcop/border_agent_txt_data.hpp | 10 ++++- src/core/thread/vendor_info.cpp | 20 +++++++++ src/core/thread/vendor_info.hpp | 39 ++++++++++++++++++ tests/nexus/openthread-core-nexus-config.h | 1 + tests/nexus/test_border_agent.cpp | 8 ++++ 12 files changed, 207 insertions(+), 6 deletions(-) diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 576365496..b3bfc46cc 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -362,6 +362,7 @@ ot_int_option(OT_MLE_MAX_CHILDREN OPENTHREAD_CONFIG_MLE_MAX_CHILDREN "set maximu ot_int_option(OT_RCP_RESTORATION_MAX_COUNT OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT "set max RCP restoration count") ot_int_option(OT_RCP_TIME_SYNC_INTERVAL OPENTHREAD_SPINEL_CONFIG_RCP_TIME_SYNC_INTERVAL "set host-RCP time sync interval in microseconds") ot_int_option(OT_RCP_TX_WAIT_TIME_SECS OPENTHREAD_SPINEL_CONFIG_RCP_TX_WAIT_TIME_SECS "set RCP TX wait TIME in seconds") +ot_int_option(OT_VENDOR_OUI OPENTHREAD_CONFIG_NET_DIAG_VENDOR_OUI "set the vendor OUI") # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/include/openthread/instance.h b/include/openthread/instance.h index e80b788b0..f47d6bfce 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (592) +#define OPENTHREAD_API_VERSION (593) /** * @addtogroup api-instance diff --git a/include/openthread/netdiag.h b/include/openthread/netdiag.h index 19a6ab100..2dd8d6120 100644 --- a/include/openthread/netdiag.h +++ b/include/openthread/netdiag.h @@ -425,6 +425,20 @@ const char *otThreadGetVendorSwVersion(otInstance *aInstance); */ const char *otThreadGetVendorAppUrl(otInstance *aInstance); +/** + * Represents an unspecified Vendor OUI. + */ +#define OT_THREAD_UNSPECIFIED_VENDOR_OUI (0xffffffff) + +/** + * Get the vendor OUI-24 + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns The vendor OUI-24 value in hex format, or `OT_THREAD_UNSPECIFIED_VENDOR_OUI` is not specified. + */ +uint32_t otThreadGetVendorOui(otInstance *aInstance); + /** * Set the vendor name string. * @@ -494,6 +508,20 @@ otError otThreadSetVendorSwVersion(otInstance *aInstance, const char *aVendorSwV */ otError otThreadSetVendorAppUrl(otInstance *aInstance, const char *aVendorAppUrl); +/** + * Set the vendor OUI-24. + * + * Requires `OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aVendorOui The vendor OUI-24 value in Hexadecimal representation (e.g., OUI 64-16-66 is represented as + * `0x641666`). Must be a 24-bit value. + * + * @retval OT_ERROR_NONE Successfully set the vendor OUI. + * @retval OT_ERROR_INVALID_ARGS @p aVendorOui is not a valid 24-bit value. + */ +otError otThreadSetVendorOui(otInstance *aInstance, uint32_t aVendorOui); + /** * Callback function pointer to notify when a Network Diagnostic Reset request message is received for the * `OT_NETWORK_DIAGNOSTIC_TLV_NON_PREFERRED_CHANNELS` TLV. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index ef8983604..2370df1d0 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -7627,6 +7627,53 @@ template <> otError Interpreter::Process(Arg aArgs[]) error = ProcessGetSet(aArgs, otThreadGetVendorAppUrl, otThreadSetVendorAppUrl); #endif } + /** + * @cli vendor oui + * @code + * vendor oui + * B4-A6-61 + * Done + * @endcode + * @par api_copy + * #otThreadGetVendorOui + */ + else if (aArgs[0] == "oui") + { + if (aArgs[1].IsEmpty()) + { + uint32_t oui = otThreadGetVendorOui(GetInstancePtr()); + + if (oui == OT_THREAD_UNSPECIFIED_VENDOR_OUI) + { + OutputLine("unspecified"); + } + else + { + OutputLine("%02X-%02X-%02X", static_cast((oui >> 16) & 0xff), + static_cast((oui >> 8) & 0xff), static_cast(oui & 0xff)); + } + + error = OT_ERROR_NONE; + } + else + { +#if OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE + /** + * @cli vendor oui (set) + * @code + * vendor oui 0xb4a661 + * Done + * @endcode + * @par api_copy + * #otThreadSetVendorOui + * @cparam vendor oui @ca{oui} + */ + error = ProcessSet(aArgs + 1, otThreadSetVendorOui); +#else + error = OT_ERROR_INVALID_ARGS; +#endif + } + } return error; } diff --git a/src/core/api/netdiag_api.cpp b/src/core/api/netdiag_api.cpp index c18922cd8..ef2c42f73 100644 --- a/src/core/api/netdiag_api.cpp +++ b/src/core/api/netdiag_api.cpp @@ -85,7 +85,10 @@ const char *otThreadGetVendorAppUrl(otInstance *aInstance) return AsCoreType(aInstance).Get().GetAppUrl(); } +uint32_t otThreadGetVendorOui(otInstance *aInstance) { return AsCoreType(aInstance).Get().GetOui(); } + #if OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE + otError otThreadSetVendorName(otInstance *aInstance, const char *aVendorName) { return AsCoreType(aInstance).Get().SetName(aVendorName); @@ -105,7 +108,13 @@ otError otThreadSetVendorAppUrl(otInstance *aInstance, const char *aVendorAppUrl { return AsCoreType(aInstance).Get().SetAppUrl(aVendorAppUrl); } -#endif + +otError otThreadSetVendorOui(otInstance *aInstance, uint32_t aVendorOui) +{ + return AsCoreType(aInstance).Get().SetOui(aVendorOui); +} + +#endif // OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE void otThreadSetNonPreferredChannels(otInstance *aInstance, otChannelMask aChannelMask) { diff --git a/src/core/config/network_diagnostic.h b/src/core/config/network_diagnostic.h index 0502cfe70..ed202398c 100644 --- a/src/core/config/network_diagnostic.h +++ b/src/core/config/network_diagnostic.h @@ -88,6 +88,18 @@ #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_APP_URL "" #endif +/** + * @def OPENTHREAD_CONFIG_NET_DIAG_VENDOR_OUI + * + * Specifies the default Vendor OUI-24 value in Hexadecimal representation (e.g., OUI 64-16-66 is represented as + * `0x641666`). + * + * The value of `0xffffffff` (UINT32_MAX) is used to indicate OUI is not specified. + */ +#ifndef OPENTHREAD_CONFIG_NET_DIAG_VENDOR_OUI +#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_OUI (0xffffffff) +#endif + /** * @def OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE * diff --git a/src/core/meshcop/border_agent_txt_data.cpp b/src/core/meshcop/border_agent_txt_data.cpp index 5e379b6a0..8525fd6ad 100644 --- a/src/core/meshcop/border_agent_txt_data.cpp +++ b/src/core/meshcop/border_agent_txt_data.cpp @@ -69,6 +69,7 @@ TxtData::TxtData(Instance &aInstance) #if OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE , mShouldAddVendorName(true) , mShouldAddVendorModel(true) + , mShouldAddVendorOui(true) #endif { } @@ -79,7 +80,8 @@ Error TxtData::Prepare(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength, bool aAddVendorName, - bool aAddVendorModel) + bool aAddVendorModel, + bool aAddVendorOui) { Error error = kErrorNone; Dns::TxtDataEncoder encoder(aBuffer, aBufferSize); @@ -169,6 +171,14 @@ Error TxtData::Prepare(uint8_t *aBuffer, SuccessOrExit(error = encoder.AppendStringEntry(Key::kModelName, Get().GetModel())); } + if (aAddVendorOui && Get().IsOuiSpecified()) + { + uint8_t ouiData[kVendorOuiSize]; + + BigEndian::WriteUint24(Get().GetOui(), ouiData); + SuccessOrExit(error = encoder.AppendEntry(Key::kVendorOui, ouiData)); + } + aLength = encoder.GetLength(); exit: @@ -178,7 +188,7 @@ exit: Error TxtData::Prepare(ServiceTxtData &aTxtData) { return Prepare(aTxtData.mData, sizeof(aTxtData.mData), aTxtData.mLength, /* aAddVendorName */ false, - /* aAddVendorModel */ false); + /* aAddVendorModel */ false, /* aAddVendorOui */ false); } void TxtData::SetChangedCallback(ChangedCallback aCallback, void *aContext) @@ -236,11 +246,16 @@ void TxtData::PrepareWithVendorData(Heap::Data &aTxtData) size += Tlv::kMaxVendorModelLength + sizeof(Key::kModelName) + sizeof('='); } + if (mShouldAddVendorOui) + { + size += kVendorOuiSize + sizeof(Key::kVendorOui) + sizeof('='); + } + buffer = reinterpret_cast(Heap::CAlloc(size, sizeof(uint8_t))); OT_ASSERT(buffer != nullptr); - SuccessOrAssert(Prepare(buffer, size, length, mShouldAddVendorName, mShouldAddVendorModel)); + SuccessOrAssert(Prepare(buffer, size, length, mShouldAddVendorName, mShouldAddVendorModel, mShouldAddVendorOui)); if (mVendorData.GetLength() != 0) { @@ -266,6 +281,7 @@ void TxtData::SetVendorData(const uint8_t *aVendorData, uint16_t aVendorDataLeng mShouldAddVendorName = true; mShouldAddVendorModel = true; + mShouldAddVendorOui = true; iterator.Init(mVendorData.GetBytes(), mVendorData.GetLength()); @@ -279,6 +295,10 @@ void TxtData::SetVendorData(const uint8_t *aVendorData, uint16_t aVendorDataLeng { mShouldAddVendorModel = false; } + else if (entry.MatchesKey(Key::kVendorOui)) + { + mShouldAddVendorOui = false; + } } Refresh(); @@ -303,6 +323,14 @@ void TxtData::HandleVendorModelChange(void) } } +void TxtData::HandleVendorOuiChange(void) +{ + if (mShouldAddVendorOui) + { + Refresh(); + } +} + #endif // OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE uint32_t TxtData::StateBitmap::Determine(Instance &aInstance) diff --git a/src/core/meshcop/border_agent_txt_data.hpp b/src/core/meshcop/border_agent_txt_data.hpp index b672ef41d..ed2677b23 100644 --- a/src/core/meshcop/border_agent_txt_data.hpp +++ b/src/core/meshcop/border_agent_txt_data.hpp @@ -279,6 +279,7 @@ public: #endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE private: + static constexpr uint8_t kVendorOuiSize = OT_BORDER_AGENT_VENDOR_OUI_SIZE; static constexpr uint16_t kMaxSizeNoVendorData = OT_BORDER_AGENT_MESHCOP_SERVICE_TXT_DATA_MAX_LENGTH; static const char kRecordVersion[]; @@ -358,7 +359,12 @@ private: #endif }; - Error Prepare(uint8_t *aBuffer, uint16_t aBufferSize, uint16_t &aLength, bool aAddVendorName, bool aAddVendorModel); + Error Prepare(uint8_t *aBuffer, + uint16_t aBufferSize, + uint16_t &aLength, + bool aAddVendorName, + bool aAddVendorModel, + bool aAddVendorOui); #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE // Callback from Notifier @@ -366,6 +372,7 @@ private: // Callbacks from VendorInfo void HandleVendorNameChange(void); void HandleVendorModelChange(void); + void HandleVendorOuiChange(void); void HandleChangedTask(void); @@ -377,6 +384,7 @@ private: Heap::Data mVendorData; bool mShouldAddVendorName; bool mShouldAddVendorModel; + bool mShouldAddVendorOui; #endif #endif }; diff --git a/src/core/thread/vendor_info.cpp b/src/core/thread/vendor_info.cpp index 35e69c275..431cb75e3 100644 --- a/src/core/thread/vendor_info.cpp +++ b/src/core/thread/vendor_info.cpp @@ -57,11 +57,14 @@ VendorInfo::VendorInfo(Instance &aInstance) static_assert(sizeof(kSwVersion) <= sizeof(SwVersionStringType), "VENDOR_SW_VERSION is too long"); static_assert(sizeof(kAppUrl) <= sizeof(AppUrlStringType), "VENDOR_APP_URL is too long"); + static_assert((kOui == kUnspecifiedOui) || ((kOui & ~kOuiMask) == 0), "VENDOR_OUI is invalid, MUST be 24-bit"); + #if OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE memcpy(mName, kName, sizeof(kName)); memcpy(mModel, kModel, sizeof(kModel)); memcpy(mSwVersion, kSwVersion, sizeof(kSwVersion)); memcpy(mAppUrl, kAppUrl, sizeof(kAppUrl)); + mOui = kOui; #endif } @@ -110,6 +113,23 @@ Error VendorInfo::SetSwVersion(const char *aSwVersion) Error VendorInfo::SetAppUrl(const char *aAppUrl) { return StringCopy(mAppUrl, aAppUrl, kStringCheckUtf8Encoding); } +Error VendorInfo::SetOui(uint32_t aOui) +{ + Error error = kErrorNone; + + VerifyOrExit((aOui & ~kOuiMask) == 0, error = kErrorInvalidArgs); + + VerifyOrExit(aOui != mOui); + mOui = aOui; + +#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE + Get().HandleVendorOuiChange(); +#endif + +exit: + return error; +} + #endif // OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE } // namespace ot diff --git a/src/core/thread/vendor_info.hpp b/src/core/thread/vendor_info.hpp index ccfe83b0a..98547be27 100644 --- a/src/core/thread/vendor_info.hpp +++ b/src/core/thread/vendor_info.hpp @@ -36,6 +36,8 @@ #include "openthread-core-config.h" +#include + #include "common/error.hpp" #include "common/locator.hpp" #include "common/non_copyable.hpp" @@ -49,6 +51,11 @@ namespace ot { class VendorInfo : public InstanceLocator, private NonCopyable { public: + /** + * Unspecified Vendor OUI value. + */ + static constexpr uint32_t kUnspecifiedOui = OT_THREAD_UNSPECIFIED_VENDOR_OUI; + /** * Initializes the `VendorInfo`. * @@ -130,14 +137,45 @@ public: */ Error SetAppUrl(const char *aAppUrl); + /** + * Indicates whether the Vendor OUI is specified. + * + * @retval TRUE The Vendor OUI is specified. + * @retval FALSE The Vendor OUI is not specified. + */ + bool IsOuiSpecified(void) const { return mOui != kUnspecifiedOui; } + + /** + * Returns the Vendor OUI-24. + * + * @returns The Vendor OUI-24 value. Returns `kUnspecifiedOui` if it is not set. + */ + uint32_t GetOui(void) const { return mOui; } + + /** + * Sets the Vendor OUI-24. + * + * @param[in] aOui The Vendor OUI-24 value in Hexadecimal representation (e.g., OUI 64-16-66 is represented as + * `0x641666`). Must be a 24-bit value. + * + * @retval kErrorNone Successfully set the Vendor OUI. + * @retval kErrorInvalidArgs @p aOui is not a valid 24-bit value. + */ + Error SetOui(uint32_t aOui); + #else const char *GetName(void) const { return kName; } const char *GetModel(void) const { return kModel; } const char *GetSwVersion(void) const { return kSwVersion; } const char *GetAppUrl(void) const { return kAppUrl; } + uint32_t GetOui(void) const { return kOui; } + bool IsOuiSpecified(void) const { return kOui != kUnspecifiedOui; } #endif // OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE private: + static constexpr uint32_t kOui = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_OUI; + static constexpr uint32_t kOuiMask = 0xffffff; + // String buffer types (max size specified by corresponding TLV) typedef NetworkDiagnostic::VendorNameTlv::StringType NameStringType; typedef NetworkDiagnostic::VendorModelTlv::StringType ModelStringType; @@ -154,6 +192,7 @@ private: ModelStringType mModel; SwVersionStringType mSwVersion; AppUrlStringType mAppUrl; + uint32_t mOui; #endif }; diff --git a/tests/nexus/openthread-core-nexus-config.h b/tests/nexus/openthread-core-nexus-config.h index a3a7dbe98..c99fe672b 100644 --- a/tests/nexus/openthread-core-nexus-config.h +++ b/tests/nexus/openthread-core-nexus-config.h @@ -125,6 +125,7 @@ #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL "Nexus Simulation" #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "RD:OpenThread by Google Nest" #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_SW_VERSION "OT-simul-nexus" +#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_OUI 0x020100 #define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE 1 #define OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS 256 #define OPENTHREAD_CONFIG_PARENT_SEARCH_BACKOFF_INTERVAL (10 * 60) diff --git a/tests/nexus/test_border_agent.cpp b/tests/nexus/test_border_agent.cpp index 5aed14dd3..cbee4a5ca 100644 --- a/tests/nexus/test_border_agent.cpp +++ b/tests/nexus/test_border_agent.cpp @@ -1461,6 +1461,14 @@ void ValidateMeshCoPTxtData(TxtData &aTxtData, Node &aNode, bool aExpectVendorIn aTxtData.ValidateKey("mn", expectedModelName); VerifyOrQuit(info.mHasModelName); VerifyOrQuit(StringMatch(info.mModelName, expectedModelName)); + + if (aNode.Get().IsOuiSpecified()) + { + uint8_t ouiData[3]; + + BigEndian::WriteUint24(aNode.Get().GetOui(), ouiData); + aTxtData.ValidateKey("vo", ouiData); + } } else {