From c34311ff50ecf9f53c386fcbc5fd961777f50a63 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 23 Jun 2026 11:59:38 -0700 Subject: [PATCH] [vendor-info] generalize vendor OUI to support variable lengths (#13258) This commit generalizes the Vendor OUI (Organizationally Unique Identifier) handling to support IEEE MAC Address Block Large (MA-L, 24-bit), Medium(MA-M, 28-bit), and Small (MA-S, 36-bit) assignments. Key enhancements: - Backward-Compatible Config Parsing: The `OPENTHREAD_CONFIG_NET_DIAG_VENDOR_OUI` configuration remains fully backward compatible. If the macro is set to a traditional 24-bit integer (e.g., `0x641666`), it is implicitly parsed as a 24-bit OUI. For larger or variable-length OUIs, a new explicit 48-bit format is supported: `0x[BitLengthInHex][5 OUI Bytes]` (e.g. `0x1c001a2b3000ULL` for 28-bit, and `0x24001a2b3c40ULL` for 36-bit). This format encodes both prefix length and value into a single integer, making it trivial to pass through command-line flags (e.g. `-D...`). - Compile-Time Validation: Introduced `VendorInfo::OuiParser` template class to parse and validate the configured OUI at compile-time (`static_assert`). It ensures invalid lengths, out-of-range configurations, or non-zero trailing bits fail the build immediately. - New APIs: Replaced legacy `uint32_t` representations with the `otThreadVendorOui` containing `mBitLength` and a 5-byte buffer `mBytes`. Added new public APIs `otThreadGetVendorOuiInfo()` and `otThreadSetVendorOuiInfo()`, while deprecating the older 24-bit OUI getters and setters. - Border Agent & CLI Updates: - Updated Border Agent TXT data generation (`vo` key) and parsing to correctly handling of new OUI lengths. - Updated CLI command utilities to output variable-length OUI bytes formatted correctly. - Added new `test_vendor_oui.cpp` unit tests to thoroughly validate compile-time parsing and runtime class logic. --- include/openthread/border_agent_txt_data.h | 4 +- include/openthread/instance.h | 2 +- include/openthread/netdiag.h | 125 ++++++++-- src/cli/cli.cpp | 19 +- src/cli/cli_utils.cpp | 14 ++ src/cli/cli_utils.hpp | 15 ++ src/core/api/netdiag_api.cpp | 40 ++- src/core/config/net_diag.h | 18 +- src/core/meshcop/border_agent_txt_data.cpp | 16 +- src/core/meshcop/border_agent_txt_data.hpp | 2 +- src/core/thread/vendor_info.cpp | 172 ++++++++++++- src/core/thread/vendor_info.hpp | 271 ++++++++++++++++++--- tests/nexus/test_border_agent.cpp | 7 +- tests/unit/CMakeLists.txt | 1 + tests/unit/test_vendor_oui.cpp | 207 ++++++++++++++++ 15 files changed, 829 insertions(+), 84 deletions(-) create mode 100644 tests/unit/test_vendor_oui.cpp diff --git a/include/openthread/border_agent_txt_data.h b/include/openthread/border_agent_txt_data.h index a8552df55..6547d1f24 100644 --- a/include/openthread/border_agent_txt_data.h +++ b/include/openthread/border_agent_txt_data.h @@ -42,6 +42,7 @@ #include #include #include +#include #include #ifdef __cplusplus @@ -61,7 +62,6 @@ extern "C" { #define OT_BORDER_AGENT_THREAD_VERSION_SIZE (16) ///< Max size of Thread Version string in `otBorderAgentTxtDataInfo`. #define OT_BORDER_AGENT_VENDOR_NAME_SIZE (32) ///< Max size of Vendor Name string in `otBorderAgentTxtDataInfo`. #define OT_BORDER_AGENT_MODEL_NAME_SIZE (32) ///< Max size of Model Name string in `otBorderAgentTxtDataInfo`. -#define OT_BORDER_AGENT_VENDOR_OUI_SIZE (3) ///< Size of Vendor OUI (in bytes) in `otBorderAgentTxtDataInfo`. /** * Represents the Connection Mode in a Border Agent State Bitmap. @@ -169,7 +169,7 @@ typedef struct otBorderAgentTxtDataInfo otExtAddress mExtAddress; ///< Extended Address. char mVendorName[OT_BORDER_AGENT_VENDOR_NAME_SIZE]; ///< Vendor Name string. char mModelName[OT_BORDER_AGENT_MODEL_NAME_SIZE]; ///< Model Name string. - uint8_t mVendorOui[OT_BORDER_AGENT_VENDOR_OUI_SIZE]; ///< Vendor OUI (24-bit). + otThreadVendorOui mVendorOui; ///< Vendor OUI. } otBorderAgentTxtDataInfo; /** diff --git a/include/openthread/instance.h b/include/openthread/instance.h index bce260b5e..01539afcc 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 (607) +#define OPENTHREAD_API_VERSION (608) /** * @addtogroup api-instance diff --git a/include/openthread/netdiag.h b/include/openthread/netdiag.h index 2dd8d6120..5a8511585 100644 --- a/include/openthread/netdiag.h +++ b/include/openthread/netdiag.h @@ -273,6 +273,50 @@ typedef struct otNetworkDiagChildTable */ typedef otBorderRoutingState otNetworkDiagBrState; +/** + * Specifies the maximum size of a Thread Vendor OUI in bytes. + */ +#define OT_THREAD_VENDOR_OUI_MAX_SIZE 5 + +/** + * Specifies the bit length of a MAC Address Block Large (MA-L) Vendor OUI. + */ +#define OT_THREAD_VENDOR_OUI_MA_L_BIT_LENGTH 24 + +/** + * Specifies the bit length of a MAC Address Block Medium (MA-M) Vendor OUI. + */ +#define OT_THREAD_VENDOR_OUI_MA_M_BIT_LENGTH 28 + +/** + * Specifies the bit length of a MAC Address Block Small (MA-S) Vendor OUI. + */ +#define OT_THREAD_VENDOR_OUI_MA_S_BIT_LENGTH 36 + +/** + * Represents a Thread Vendor OUI (Organizationally Unique Identifier) which can have different lengths. + * + * A Vendor OUI can be assigned in one of the following formats: + * + * - 24-bit Prefix (MA-L): Exactly 3 bytes (24 bits). + * Example: `00-1A-2B` is represented with `mBitLength = 24` and `mBytes = [0x00, 0x1A, 0x2B, 0x00, 0x00]`. + * + * - 28-bit Prefix (MA-M): Exactly 3.5 bytes (28 bits). + * The half-byte (4 bits) at the end of the prefix occupies the Most Significant Nibble of the 4th byte. + * The Least Significant Nibble of the 4th byte is set to zero. + * Example: `00-1A-2B-3` is represented with `mBitLength = 28` and `mBytes = [0x00, 0x1A, 0x2B, 0x30, 0x00]`. + * + * - 36-bit Prefix (MA-S): Exactly 4.5 bytes (36 bits). + * The half-byte (4 bits) at the end of the prefix occupies the Most Significant Nibble of the 5th byte. + * The Least Significant Nibble of the 5th byte is set to zero. + * Example: `00-1A-2B-3C-4` is represented with `mBitLength = 36` and `mBytes = [0x00, 0x1A, 0x2B, 0x3C, 0x40]`. + */ +typedef struct otThreadVendorOui +{ + uint8_t mBitLength; ///< The OUI prefix length in bits (24, 28, or 36). + uint8_t mBytes[OT_THREAD_VENDOR_OUI_MAX_SIZE]; ///< The OUI bytes in big-endian order. +} otThreadVendorOui; + /** * Represents a Network Diagnostic TLV. */ @@ -426,19 +470,78 @@ const char *otThreadGetVendorSwVersion(otInstance *aInstance); const char *otThreadGetVendorAppUrl(otInstance *aInstance); /** - * Represents an unspecified Vendor OUI. + * Gets the vendor OUI. + * + * If no vendor OUI is yet set/configured on device, the `mBitLength` in @p aOui will be zero. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[out] aOui A pointer to an `otThreadVendorOui` to return the vendor OUI. */ -#define OT_THREAD_UNSPECIFIED_VENDOR_OUI (0xffffffff) +void otThreadGetVendorOuiInfo(otInstance *aInstance, otThreadVendorOui *aOui); /** - * Get the vendor OUI-24 + * Sets the vendor OUI. + * + * Requires `OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aOui A pointer to the `otThreadVendorOui` to set. + * + * @retval OT_ERROR_NONE Successfully set the vendor OUI. + * @retval OT_ERROR_INVALID_ARGS @p aOui has an invalid length. + */ +otError otThreadSetVendorOuiInfo(otInstance *aInstance, const otThreadVendorOui *aOui); + +#define OT_THREAD_VENDOR_OUI_STRING_SIZE 16 ///< Recommended size for string representation of a vendor OUI. + +/** + * Converts a given vendor OUI to a human-readable string. + * + * The generated string format is hyphen-separated uppercase hexadecimal bytes (e.g., "00-1A-2B" for a 24-bit OUI). + * For 28-bit and 36-bit OUIs, the trailing 4-bit nibble is appended as a single hexadecimal digit (e.g., "00-1A-2B-3" + * for a 28-bit OUI). If @p aOui is invalid or unspecified, the string "unspecified" is returned. + * + * If the resulting string does not fit in @p aBuffer (within its @p aSize characters), the string will be truncated + * but the outputted string is always null-terminated. + * + * @param[in] aOui The vendor OUI to convert. + * @param[out] aBuffer A pointer to a char array to output the string (MUST NOT be NULL). + * @param[in] aSize The size of @p aBuffer (in bytes). Recommended to use `OT_THREAD_VENDOR_OUI_STRING_SIZE`. + */ +void otThreadVendorOuiToString(const otThreadVendorOui *aOui, char *aBuffer, uint16_t aSize); + +#define OT_THREAD_UNSPECIFIED_VENDOR_OUI (0xffffffff) ///< Represents an unspecified Vendor OUI. + +/** + * Gets the vendor OUI-24. + * + * @deprecated This function is deprecated. Use `otThreadGetVendorOuiInfo()` instead. + * + * If the configured Vendor OUI has a prefix length greater than 24 bits, this function returns the most significant + * 24 bits (first 3 bytes) of the OUI to maintain backward compatibility. * * @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. + * @returns The vendor OUI-24 value, or `OT_THREAD_UNSPECIFIED_VENDOR_OUI` if not specified. */ uint32_t otThreadGetVendorOui(otInstance *aInstance); +/** + * Sets the vendor OUI-24. + * + * @deprecated This function is deprecated. Use `otThreadSetVendorOuiInfo()` instead. + * + * 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); + /** * Set the vendor name string. * @@ -508,20 +611,6 @@ 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 bcfd81f19..67a6f4450 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -756,8 +756,8 @@ void Interpreter::OutputBorderAgentTxtDataInfo(uint8_t aIndentSize, const otBord if (aInfo.mHasVendorOui) { - OutputLine(aIndentSize, "VendorOui: %02X-%02X-%02X", aInfo.mVendorOui[0], aInfo.mVendorOui[1], - aInfo.mVendorOui[2]); + OutputFormat(aIndentSize, "VendorOui: "); + OutputVendorOuiLine(aInfo.mVendorOui); } if (aInfo.mHasStateBitmap) @@ -7600,23 +7600,16 @@ template <> otError Interpreter::Process(Arg aArgs[]) * Done * @endcode * @par api_copy - * #otThreadGetVendorOui + * #otThreadGetVendorOuiInfo */ else if (aArgs[0] == "oui") { if (aArgs[1].IsEmpty()) { - uint32_t oui = otThreadGetVendorOui(GetInstancePtr()); + otThreadVendorOui oui; - 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)); - } + otThreadGetVendorOuiInfo(GetInstancePtr(), &oui); + OutputVendorOuiLine(oui); error = OT_ERROR_NONE; } diff --git a/src/cli/cli_utils.cpp b/src/cli/cli_utils.cpp index faeaad57e..db00fc090 100644 --- a/src/cli/cli_utils.cpp +++ b/src/cli/cli_utils.cpp @@ -231,6 +231,20 @@ void Utils::OutputSockAddrLine(const otSockAddr &aSockAddr) OutputNewLine(); } +void Utils::OutputVendorOui(const otThreadVendorOui &aOui) +{ + char string[OT_THREAD_VENDOR_OUI_STRING_SIZE]; + + otThreadVendorOuiToString(&aOui, string, sizeof(string)); + OutputFormat("%s", string); +} + +void Utils::OutputVendorOuiLine(const otThreadVendorOui &aOui) +{ + OutputVendorOui(aOui); + OutputNewLine(); +} + void Utils::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength) { OutputDnsTxtData(/* aKeyValuePerLine */ false, 0, aTxtData, aTxtDataLength); diff --git a/src/cli/cli_utils.hpp b/src/cli/cli_utils.hpp index a7acb074b..6055d03c7 100644 --- a/src/cli/cli_utils.hpp +++ b/src/cli/cli_utils.hpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include "cli_config.h" @@ -431,6 +432,20 @@ public: */ void OutputSockAddrLine(const otSockAddr &aSockAddr); + /** + * Outputs the Vendor OUI to the CLI console. + * + * @param[in] aOui A reference to the Vendor OUI. + */ + void OutputVendorOui(const otThreadVendorOui &aOui); + + /** + * Outputs the Vendor OUI to the CLI console and appends a newline. + * + * @param[in] aOui A reference to the Vendor OUI. + */ + void OutputVendorOuiLine(const otThreadVendorOui &aOui); + /** * Outputs DNS TXT data to the CLI console. * diff --git a/src/core/api/netdiag_api.cpp b/src/core/api/netdiag_api.cpp index df39dd26f..0b607ff9a 100644 --- a/src/core/api/netdiag_api.cpp +++ b/src/core/api/netdiag_api.cpp @@ -85,7 +85,24 @@ const char *otThreadGetVendorAppUrl(otInstance *aInstance) return AsCoreType(aInstance).Get().GetAppUrl(); } -uint32_t otThreadGetVendorOui(otInstance *aInstance) { return AsCoreType(aInstance).Get().GetOui(); } +void otThreadGetVendorOuiInfo(otInstance *aInstance, otThreadVendorOui *aOui) +{ + AssertPointerIsNotNull(aOui); + + *aOui = AsCoreType(aInstance).Get().GetOui(); +} + +void otThreadVendorOuiToString(const otThreadVendorOui *aOui, char *aBuffer, uint16_t aSize) +{ + AsCoreType(aOui).ToString(aBuffer, aSize); +} + +uint32_t otThreadGetVendorOui(otInstance *aInstance) +{ + // This API is deprecated. Use `otThreadGetVendorOuiInfo()` instead + + return AsCoreType(aInstance).Get().GetOui().GetAsOui24(); +} #if OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE @@ -109,9 +126,28 @@ otError otThreadSetVendorAppUrl(otInstance *aInstance, const char *aVendorAppUrl return AsCoreType(aInstance).Get().SetAppUrl(aVendorAppUrl); } +otError otThreadSetVendorOuiInfo(otInstance *aInstance, const otThreadVendorOui *aOui) +{ + return AsCoreType(aInstance).Get().SetOui(AsCoreType(aOui)); +} + otError otThreadSetVendorOui(otInstance *aInstance, uint32_t aVendorOui) { - return AsCoreType(aInstance).Get().SetOui(aVendorOui); + // This API is deprecated, use `otThreadSetVendorOuiInfo()` instead + + Error error; + VendorInfo::Oui oui; + + VerifyOrExit(aVendorOui <= 0xffffff, error = kErrorInvalidArgs); + + oui.Clear(); + oui.mBitLength = 24; + BigEndian::WriteUint24(aVendorOui, oui.mBytes); + + error = AsCoreType(aInstance).Get().SetOui(oui); + +exit: + return error; } #endif // OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE diff --git a/src/core/config/net_diag.h b/src/core/config/net_diag.h index 824895dae..0b83a55a7 100644 --- a/src/core/config/net_diag.h +++ b/src/core/config/net_diag.h @@ -91,10 +91,22 @@ /** * @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`). + * Specifies the default Vendor OUI (Organizationally Unique Identifier) value. * - * The value of `0xffffffff` (UINT32_MAX) is used to indicate OUI is not specified. + * This configuration supports multiple layout formats to maintain backward compatibility: + * + * - 24-bit OUI (MA-L): Hexadecimal representation of a 24-bit value (e.g., OUI 64-16-66 is represented as + * `0x641666`). Values <= `0xffffff` are implicitly treated as a 24-bit OUI. + * + * - Explicit OUI with various lengths (24, 28, or 36 bits): A 48-bit hexadecimal value where the most significant + * byte (bits 40-47) represents the prefix bit-length, and the lower 5 bytes (bits 0-39) represent the OUI bytes + * in big-endian order: + * - 28-bit OUI `00-1A-2B-3` is represented as `0x1c001a2b3000ULL`. + * - 36-bit OUI `00-1A-2B-3C-4` is represented as `0x24001a2b3c40ULL`. + * + * The value of `0xffffffff` (UINT32_MAX) is used to indicate the Vendor OUI is not specified. + * + * The configured value is validated at compile-time to ensure it conforms to one of the supported prefix lengths. */ #ifndef OPENTHREAD_CONFIG_NET_DIAG_VENDOR_OUI #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_OUI (0xffffffff) diff --git a/src/core/meshcop/border_agent_txt_data.cpp b/src/core/meshcop/border_agent_txt_data.cpp index f4ecb68df..f832c8600 100644 --- a/src/core/meshcop/border_agent_txt_data.cpp +++ b/src/core/meshcop/border_agent_txt_data.cpp @@ -171,12 +171,11 @@ Error TxtData::Prepare(uint8_t *aBuffer, SuccessOrExit(error = encoder.AppendStringEntry(Key::kModelName, Get().GetModel())); } - if (aAddVendorOui && Get().IsOuiSpecified()) + if (aAddVendorOui && Get().GetOui().IsValid()) { - uint8_t ouiData[kVendorOuiSize]; + const VendorInfo::Oui &oui = Get().GetOui(); - BigEndian::WriteUint24(Get().GetOui(), ouiData); - SuccessOrExit(error = encoder.AppendEntry(Key::kVendorOui, ouiData)); + SuccessOrExit(error = encoder.AppendBytesEntry(Key::kVendorOui, oui.GetBytes(), oui.GetSize())); } aLength = encoder.GetLength(); @@ -248,7 +247,7 @@ void TxtData::PrepareWithVendorData(Heap::Data &aTxtData) if (mShouldAddVendorOui) { - size += kVendorOuiSize + sizeof(Key::kVendorOui) + sizeof('='); + size += VendorInfo::Oui::kMaxSize + sizeof(Key::kVendorOui) + sizeof('='); } buffer = reinterpret_cast(Heap::CAlloc(size, sizeof(uint8_t))); @@ -509,7 +508,7 @@ void TxtData::Info::ProcessTxtEntry(const Dns::TxtEntry &aEntry) } else if (aEntry.MatchesKey(Key::kVendorOui)) { - mHasVendorOui = ReadValue(aEntry, mVendorOui); + mHasVendorOui = ReadVendorOui(aEntry, AsCoreType(&mVendorOui)); } } @@ -551,6 +550,11 @@ exit: return didRead; } +bool TxtData::Info::ReadVendorOui(const Dns::TxtEntry &aEntry, VendorInfo::Oui &aOui) +{ + return (aOui.SetFrom(aEntry.mValue, aEntry.mValueLength) == kErrorNone); +} + void TxtData::StateBitmap::Parse(uint32_t aBitmap, Info &aInfo) { ClearAllBytes(aInfo); diff --git a/src/core/meshcop/border_agent_txt_data.hpp b/src/core/meshcop/border_agent_txt_data.hpp index ed2677b23..23cade3e5 100644 --- a/src/core/meshcop/border_agent_txt_data.hpp +++ b/src/core/meshcop/border_agent_txt_data.hpp @@ -125,6 +125,7 @@ public: static bool ReadValue(const Dns::TxtEntry &aEntry, void *aBuffer, uint16_t aSize); static void ReadStringValue(const Dns::TxtEntry &aEntry, char *aString, uint16_t aStringSize); static bool ReadOmrPrefix(const Dns::TxtEntry &aEntry, Ip6::Prefix &aPrefix); + static bool ReadVendorOui(const Dns::TxtEntry &aEntry, VendorInfo::Oui &aOui); template bool ReadValue(const Dns::TxtEntry &aEntry, ObjectType &aObject) { @@ -279,7 +280,6 @@ 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[]; diff --git a/src/core/thread/vendor_info.cpp b/src/core/thread/vendor_info.cpp index 431cb75e3..dfa536029 100644 --- a/src/core/thread/vendor_info.cpp +++ b/src/core/thread/vendor_info.cpp @@ -37,6 +37,9 @@ namespace ot { +//---------------------------------------------------------------------------------------------------------------------- +// VendorInfo + const char VendorInfo::kName[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME; const char VendorInfo::kModel[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL; const char VendorInfo::kSwVersion[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_SW_VERSION; @@ -49,6 +52,19 @@ static_assert(CheckConstStringPrefix(OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME, kNa "VENDOR_NAME MUST start with 'RD:' prefix for a reference device."); #endif +static constexpr uint64_t kDefaultOuiConfig = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_OUI; + +const otThreadVendorOui VendorInfo::kOui = { + OuiParser::GetBitLength(), + { + OuiParser::GetByte0(), + OuiParser::GetByte1(), + OuiParser::GetByte2(), + OuiParser::GetByte3(), + OuiParser::GetByte4(), + }, +}; + VendorInfo::VendorInfo(Instance &aInstance) : InstanceLocator(aInstance) { @@ -57,14 +73,15 @@ 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"); + static_assert(OuiParser::IsValid(), + "VENDOR_OUI format is not valid - see the config documentation"); #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; + mOui = AsCoreType(&kOui); #endif } @@ -113,14 +130,14 @@ Error VendorInfo::SetSwVersion(const char *aSwVersion) Error VendorInfo::SetAppUrl(const char *aAppUrl) { return StringCopy(mAppUrl, aAppUrl, kStringCheckUtf8Encoding); } -Error VendorInfo::SetOui(uint32_t aOui) +Error VendorInfo::SetOui(const Oui &aOui) { Error error = kErrorNone; - VerifyOrExit((aOui & ~kOuiMask) == 0, error = kErrorInvalidArgs); + VerifyOrExit(aOui.IsValid(), error = kErrorInvalidArgs); - VerifyOrExit(aOui != mOui); - mOui = aOui; + VerifyOrExit(mOui != aOui); + mOui.SetFrom(aOui); #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_ENABLE Get().HandleVendorOuiChange(); @@ -132,4 +149,147 @@ exit: #endif // OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE +//---------------------------------------------------------------------------------------------------------------------- +// VendorInfo::Oui + +const uint8_t VendorInfo::Oui::kValidBitLengths[] = {24, 28, 36}; + +bool VendorInfo::Oui::IsValid(void) const { return DoesArrayContain(kValidBitLengths, GetBitLength()); } + +void VendorInfo::Oui::SetFrom(const Oui &aOther) +{ + *this = aOther; + Tidy(); +} + +Error VendorInfo::Oui::SetBitLengthFromSize(uint16_t aSize) +{ + Error error = kErrorNone; + + VerifyOrExit(IsValueInRange(aSize, kMinSize, kMaxSize), error = kErrorInvalidArgs); + + Clear(); + mBitLength = kValidBitLengths[aSize - kMinSize]; + +exit: + return error; +} + +Error VendorInfo::Oui::SetFrom(const uint8_t *aData, uint16_t aSize) +{ + Error error; + + SuccessOrExit(error = SetBitLengthFromSize(aSize)); + memcpy(mBytes, aData, aSize); + Tidy(); + +exit: + return error; +} + +void VendorInfo::Oui::Tidy(void) +{ + uint8_t index; + + if (!IsValid()) + { + Clear(); + ExitNow(); + } + + index = GetBitLength() / kBitsPerByte; + + if (GetBitLength() > index * kBitsPerByte) + { + mBytes[index] &= kTopNibbleMask; + index++; + } + + for (; index < kMaxSize; index++) + { + mBytes[index] = 0; + } + +exit: + return; +} + +uint32_t VendorInfo::Oui::GetAsOui24(void) const +{ + uint32_t oui24; + + if (!IsValid()) + { + oui24 = VendorInfo::Oui::kUnspecified; + ExitNow(); + } + + oui24 = BigEndian::ReadUint24(GetBytes()); + +exit: + return oui24; +} + +bool VendorInfo::Oui::operator==(const Oui &aOther) const +{ + bool isEqual = false; + Oui tidyOther; + + VerifyOrExit(GetBitLength() == aOther.GetBitLength()); + + tidyOther = aOther; + tidyOther.Tidy(); + + VerifyOrExit(memcmp(GetBytes(), tidyOther.GetBytes(), GetSize()) == 0); + isEqual = true; + +exit: + return isEqual; +} + +VendorInfo::Oui::InfoString VendorInfo::Oui::ToString(void) const +{ + InfoString string; + + ToString(string); + + return string; +} + +void VendorInfo::Oui::ToString(char *aBuffer, uint16_t aSize) const +{ + StringWriter writer(aBuffer, aSize); + ToString(writer); +} + +void VendorInfo::Oui::ToString(StringWriter &aWriter) const +{ + uint8_t index = 0; + uint8_t numFullBytes = GetBitLength() / kBitsPerByte; + + if (!IsValid()) + { + aWriter.Append("unspecified"); + ExitNow(); + } + + for (index = 0; index < numFullBytes; index++) + { + if (index != 0) + { + aWriter.Append("-"); + } + + aWriter.Append("%02X", mBytes[index]); + } + + if (GetBitLength() > numFullBytes * kBitsPerByte) + { + aWriter.Append("-%1X", ReadBits(mBytes[index])); + } + +exit: + return; +} + } // namespace ot diff --git a/src/core/thread/vendor_info.hpp b/src/core/thread/vendor_info.hpp index 39c2db736..f6e3cc1c2 100644 --- a/src/core/thread/vendor_info.hpp +++ b/src/core/thread/vendor_info.hpp @@ -38,23 +38,165 @@ #include +#include "common/as_core_type.hpp" +#include "common/bit_utils.hpp" +#include "common/clearable.hpp" +#include "common/equatable.hpp" #include "common/error.hpp" #include "common/locator.hpp" #include "common/non_copyable.hpp" +#include "common/string.hpp" #include "thread/net_diag_tlvs.hpp" namespace ot { +class UnitTester; + /** * Represents the vendor information. */ class VendorInfo : public InstanceLocator, private NonCopyable { + friend class UnitTester; + public: /** - * Unspecified Vendor OUI value. + * Represents a Thread Vendor OUI (Organizationally Unique Identifier). */ - static constexpr uint32_t kUnspecifiedOui = OT_THREAD_UNSPECIFIED_VENDOR_OUI; + class Oui : public otThreadVendorOui, public Clearable, public Unequatable + { + friend class UnitTester; + + public: + /** + * The minimum size of the OUI in bytes. + */ + static constexpr uint8_t kMinSize = 3; + + /** + * The maximum size of the OUI in bytes. + */ + static constexpr uint8_t kMaxSize = OT_THREAD_VENDOR_OUI_MAX_SIZE; + + /** + * The unspecified Vendor OUI value. + */ + static constexpr uint32_t kUnspecified = OT_THREAD_UNSPECIFIED_VENDOR_OUI; + + /** + * The size of `InfoString` (used in `ToString()`). + */ + static constexpr uint16_t kInfoStringSize = OT_THREAD_VENDOR_OUI_STRING_SIZE; + + /** + * Defines the fixed-length `String` returned from `ToString()`. + */ + typedef String InfoString; + + /** + * Indicates whether or not the OUI value is valid. + * + * @retval TRUE The OUI is valid. + * @retval FALSE The OUI is not valid. + */ + bool IsValid(void) const; + + /** + * Returns the OUI prefix length in bits (24, 28, or 36) + * + * @returns The OUI prefix length in bits. + */ + uint8_t GetBitLength(void) const { return mBitLength; } + + /** + * Returns a pointer to the OUI bytes. + * + * @returns A pointer to the OUI bytes. + */ + const uint8_t *GetBytes(void) const { return mBytes; } + + /** + * Returns the OUI size in bytes. + * + * @returns The OUI size in bytes. + */ + uint8_t GetSize(void) const { return BytesForBitSize(mBitLength); } + + /** + * Sets the OUI from another OUI, tidying unused bits. + * + * @param[in] aOther The other OUI to set from. + */ + void SetFrom(const Oui &aOther); + + /** + * Sets the OUI from a byte buffer. + * + * @param[in] aData A pointer to the buffer. + * @param[in] aSize The size of the buffer in bytes. + * + * @retval kErrorNone Successfully set the OUI. + * @retval kErrorInvalidArgs The @p aSize is not valid and not within valid range [kMinSize, kMaxSize]. + */ + Error SetFrom(const uint8_t *aData, uint16_t aSize); + + /** + * Returns the OUI as a 24-bit OUI value. + * + * @deprecated This method is deprecated. + * + * If the configured Vendor OUI has a prefix length greater than 24 bits, this method returns the most + * significant 24 bits (first 3 bytes) of the OUI. + * + * @returns The 24-bit OUI value, or `kUnspecified` if it is not valid. + */ + uint32_t GetAsOui24(void) const; + + /** + * Overloads operator== to check for equality with another OUI. + * + * @param[in] aOther The other OUI to compare with. + * + * @retval TRUE The OUIs are equal. + * @retval FALSE The OUIs are not equal. + */ + bool operator==(const Oui &aOther) const; + + /** + * Converts the OUI to a human-readable string + * + * The generated string format is hyphen-separated uppercase hexadecimal bytes (e.g., "00-1A-2B" for a 24-bit + * OUI). For 28-bit and 36-bit OUIs, the trailing 4-bit nibble is appended as a single hexadecimal digit + * (e.g., "00-1A-2B-3" for a 28-bit OUI). If @p aOui is invalid, the string "unspecified" is returned. + * + * @returns An `InfoString` containing the string representation of the OUI. + */ + InfoString ToString(void) const; + + /** + * Converts the OUI to a human-readable string. + * + * The generated string format is hyphen-separated uppercase hexadecimal bytes (e.g., "00-1A-2B" for a 24-bit + * OUI). For 28-bit and 36-bit OUIs, the trailing 4-bit nibble is appended as a single hexadecimal digit + * (e.g., "00-1A-2B-3" for a 28-bit OUI). If @p aOui is invalid, the string "unspecified" is returned. + * + * If the resulting string does not fit in @p aBuffer (within its @p aSize characters), the string will be + * truncated but the outputted string is always null-terminated. + * + * @param[out] aBuffer A pointer to a character buffer to output the string. + * @param[in] aSize The size of @p aBuffer in bytes. + */ + void ToString(char *aBuffer, uint16_t aSize) const; + + private: + static constexpr uint8_t kTopNibbleMask = 0xf0; + + static const uint8_t kValidBitLengths[]; + + Error SetBitLengthFromSize(uint16_t aSize); + void Tidy(void); + void ToString(StringWriter &aWriter) const; + }; /** * Initializes the `VendorInfo`. @@ -138,64 +280,137 @@ public: Error SetAppUrl(const char *aAppUrl); /** - * Indicates whether the Vendor OUI is specified. + * Returns the Vendor OUI. * - * @retval TRUE The Vendor OUI is specified. - * @retval FALSE The Vendor OUI is not specified. + * If the Vendor OUI is not set, the returned `Oui` object's `IsValid()` will return `false`. + * + * @returns The Vendor OUI. */ - bool IsOuiSpecified(void) const { return mOui != kUnspecifiedOui; } + const Oui &GetOui(void) const { return mOui; } /** - * Returns the Vendor OUI-24. + * Sets the Vendor OUI. * - * @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. + * @param[in] aOui The Vendor OUI. * * @retval kErrorNone Successfully set the Vendor OUI. - * @retval kErrorInvalidArgs @p aOui is not a valid 24-bit value. + * @retval kErrorInvalidArgs @p aOui is not valid. */ - Error SetOui(uint32_t aOui); + Error SetOui(const Oui &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; } + const Oui &GetOui(void) const { return static_cast(kOui); } #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 NetDiag::VendorNameTlv::StringType NameStringType; typedef NetDiag::VendorModelTlv::StringType ModelStringType; typedef NetDiag::VendorSwVersionTlv::StringType SwVersionStringType; typedef NetDiag::VendorAppUrlTlv::StringType AppUrlStringType; - static const char kName[]; - static const char kModel[]; - static const char kSwVersion[]; - static const char kAppUrl[]; + template class OuiParser + { + // Helper template class to parse and validate the compile-time configured OUI. + // It supports both the legacy 24-bit OUI (implicit length of 24) and the new 48-bit + // format (explicit length in the high byte) to maintain backward compatibility. + + public: + static constexpr bool IsSpecified(void) { return (kOuiConfig != VendorInfo::Oui::kUnspecified); } + + static constexpr uint8_t GetBitLength(void) + { + return !IsSpecified() ? 0 : (IsShort24BitLayout() ? 24 : ReadBits(kLenMask)); + } + + static constexpr bool IsValid(void) + { + return !IsSpecified() || IsShort24BitLayout() || Is24Bit() || Is28Bit() || Is36Bit(); + } + + static constexpr uint8_t GetByte0(void) + { + return !IsSpecified() ? 0 : IsShort24BitLayout() ? ReadBits(kShortLayoutByte0Mask) : ReadBits(kByte0Mask); + } + + static constexpr uint8_t GetByte1(void) + { + return !IsSpecified() ? 0 : IsShort24BitLayout() ? ReadBits(kShortLayoutByte1Mask) : ReadBits(kByte1Mask); + } + + static constexpr uint8_t GetByte2(void) + { + return !IsSpecified() ? 0 : IsShort24BitLayout() ? ReadBits(kShortLayoutByte2Mask) : ReadBits(kByte2Mask); + } + + static constexpr uint8_t GetByte3(void) + { + return !IsSpecified() ? 0 : IsShort24BitLayout() ? 0 : ReadBits(kByte3Mask); + } + + static constexpr uint8_t GetByte4(void) + { + return !IsSpecified() ? 0 : IsShort24BitLayout() ? 0 : ReadBits(kByte4Mask); + } + + private: + static constexpr uint64_t kMaxUint48 = 0xffffffffffffULL; // 6 bytes + static constexpr uint64_t kLenMask = 0xff0000000000ULL; + static constexpr uint64_t kByte0Mask = 0x00ff00000000ULL; + static constexpr uint64_t kByte1Mask = 0x0000ff000000ULL; + static constexpr uint64_t kByte2Mask = 0x000000ff0000ULL; + static constexpr uint64_t kByte3Mask = 0x00000000ff00ULL; + static constexpr uint64_t kByte4Mask = 0x0000000000ffULL; + static constexpr uint64_t kMaxUint24 = 0x000000ffffffULL; // 3 bytes + static constexpr uint64_t kShortLayoutByte0Mask = 0x000000ff0000ULL; + static constexpr uint64_t kShortLayoutByte1Mask = 0x00000000ff00ULL; + static constexpr uint64_t kShortLayoutByte2Mask = 0x0000000000ffULL; + + static constexpr bool IsShort24BitLayout(void) { return (kOuiConfig <= kMaxUint24); } + static constexpr bool IsConfigValueValid(void) { return (kOuiConfig <= kMaxUint48); } + + static constexpr bool Is24Bit(void) + { + return IsConfigValueValid() && (GetBitLength() == 24) && (GetByte3() == 0) && (GetByte4() == 0); + } + + static constexpr bool Is28Bit(void) + { + return IsConfigValueValid() && GetBitLength() == 28 && ((GetByte3() & 0x0f) == 0) && (GetByte4() == 0); + } + + static constexpr bool Is36Bit(void) + { + return IsConfigValueValid() && GetBitLength() == 36 && ((GetByte4() & 0x0f) == 0); + } + + static constexpr uint8_t ReadBits(uint64_t aMask) + { + return static_cast((kOuiConfig & aMask) >> BitOffsetOfMask(aMask)); + } + }; + + static const char kName[]; + static const char kModel[]; + static const char kSwVersion[]; + static const char kAppUrl[]; + static const otThreadVendorOui kOui; #if OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE NameStringType mName; ModelStringType mModel; SwVersionStringType mSwVersion; AppUrlStringType mAppUrl; - uint32_t mOui; + Oui mOui; #endif }; +DefineCoreType(otThreadVendorOui, VendorInfo::Oui); + } // namespace ot #endif // OT_CORE_THREAD_VENDOR_INFO_HPP_ diff --git a/tests/nexus/test_border_agent.cpp b/tests/nexus/test_border_agent.cpp index 297868c6e..3d70d2032 100644 --- a/tests/nexus/test_border_agent.cpp +++ b/tests/nexus/test_border_agent.cpp @@ -1495,12 +1495,11 @@ void ValidateMeshCoPTxtData(TxtData &aTxtData, Node &aNode, bool aExpectVendorIn VerifyOrQuit(info.mHasModelName); VerifyOrQuit(StringMatch(info.mModelName, expectedModelName)); - if (aNode.Get().IsOuiSpecified()) + if (aNode.Get().GetOui().IsValid()) { - uint8_t ouiData[3]; + const VendorInfo::Oui &oui = aNode.Get().GetOui(); - BigEndian::WriteUint24(aNode.Get().GetOui(), ouiData); - aTxtData.ValidateKey("vo", ouiData); + aTxtData.ValidateKey("vo", oui.GetBytes(), oui.GetSize()); } } else diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 35d6adb16..bf274951e 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -271,6 +271,7 @@ ot_unit_test(tlv) ot_unit_test(toolchain test_toolchain_c.c) ot_unit_test(trickle_timer) ot_unit_test(url) +ot_unit_test(vendor_oui) ot_unit_ncp_test(cli) ot_unit_ncp_test(dnssd) diff --git a/tests/unit/test_vendor_oui.cpp b/tests/unit/test_vendor_oui.cpp new file mode 100644 index 000000000..b1e68f8bb --- /dev/null +++ b/tests/unit/test_vendor_oui.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2026, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test_platform.h" +#include "test_util.h" + +#include + +#include "thread/vendor_info.hpp" + +namespace ot { + +class UnitTester +{ +public: + template + static void TestParserCase(bool aIsSpecified, bool aIsValid, uint8_t aBitLength, const uint8_t aExpectedBytes[5]) + { + using Parser = VendorInfo::OuiParser; + + VerifyOrQuit(Parser::IsSpecified() == aIsSpecified, "IsSpecified() failed"); + VerifyOrQuit(Parser::IsValid() == aIsValid, "IsValid() failed"); + + if (aIsValid && aIsSpecified) + { + VerifyOrQuit(Parser::GetBitLength() == aBitLength, "GetBitLength() failed"); + + VerifyOrQuit(Parser::GetByte0() == aExpectedBytes[0]); + VerifyOrQuit(Parser::GetByte1() == aExpectedBytes[1]); + VerifyOrQuit(Parser::GetByte2() == aExpectedBytes[2]); + VerifyOrQuit(Parser::GetByte3() == aExpectedBytes[3]); + VerifyOrQuit(Parser::GetByte4() == aExpectedBytes[4]); + } + } + + static void TestCompileTimeOuiParser(void) + { + printf("TestCompileTimeOuiParser\n"); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // 1. Unspecified cases + { + uint8_t expected[5] = {0}; + TestParserCase<0xffffffffULL>(/* aIsSpecified */ false, /* aIsValid */ true, 0, expected); + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // 2. Implicit 24-bit (legacy OUI format) + { + uint8_t expected[5] = {0x64, 0x16, 0x66, 0, 0}; + TestParserCase<0x641666>(/* aIsSpecified */ true, /* aIsValid */ true, 24, expected); + } + + { + uint8_t expected[5] = {0x00, 0x00, 0x01, 0, 0}; + TestParserCase<0x000001>(/* aIsSpecified */ true, /* aIsValid */ true, 24, expected); + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // 3. Explicit 24-bit OUI (length in bits 40-47, bytes in bits 0-39) + { + uint8_t expected[5] = {0x00, 0x1a, 0x2b, 0, 0}; + TestParserCase<0x18001a2b0000ULL>(/* aIsSpecified */ true, /* aIsValid */ true, 24, expected); + } + + // Invalid: non-zero in byte index 3 + { + uint8_t expected[5] = {0}; + TestParserCase<0x18001a2b0300ULL>(/* aIsSpecified */ true, /* aIsValid */ false, 24, expected); + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // 4. Explicit 28-bit OUI + { + uint8_t expected[5] = {0x00, 0x1a, 0x2b, 0x30, 0}; + TestParserCase<0x1c001a2b3000ULL>(/* aIsSpecified */ true, /* aIsValid */ true, 28, expected); + } + + // Invalid: non-zero in byte index 4 + { + uint8_t expected[5] = {0}; + TestParserCase<0x1c001a2b3005ULL>(/* aIsSpecified */ true, /* aIsValid */ false, 28, expected); + } + + // Invalid: non-zero in lower nibble of byte index 3 + { + uint8_t expected[5] = {0}; + TestParserCase<0x1c001a2b3500ULL>(/* aIsSpecified */ true, /* aIsValid */ false, 28, expected); + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // 5. Explicit 36-bit OUI + { + uint8_t expected[5] = {0x00, 0x1a, 0x2b, 0x3c, 0x40}; + TestParserCase<0x24001a2b3c40ULL>(/* aIsSpecified */ true, /* aIsValid */ true, 36, expected); + } + // Invalid: non-zero in lower nibble of byte index 4 + { + uint8_t expected[5] = {0}; + TestParserCase<0x24001a2b3c43ULL>(/* aIsSpecified */ true, /* aIsValid */ false, 36, expected); + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // 6. Invalid Bit Length (e.g. 32 bits) + { + uint8_t expected[5] = {0}; + TestParserCase<0x20001a2b3c40ULL>(/* aIsSpecified */ true, /* aIsValid */ false, 32, expected); + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // 7. Value out of valid range (exceeds 6 bytes) + { + uint8_t expected[5] = {0}; + TestParserCase<0x1c11001a2b3000ULL>(/* aIsSpecified */ true, /* aIsValid */ false, 28, expected); + } + } + + static void TestOuiClass(void) + { + VendorInfo::Oui oui; + VendorInfo::Oui oui1; + VendorInfo::Oui oui2; + VendorInfo::Oui oui3; + uint8_t bytes24[] = {0x64, 0x16, 0x66}; + uint8_t bytes28[] = {0x00, 0x1a, 0x2b, 0x35}; + uint8_t bytes36[] = {0x00, 0x1a, 0x2b, 0x3c, 0x42}; + + printf("TestOuiClass\n"); + + // Test ToString() on unspecified OUI + oui.Clear(); + VerifyOrQuit(StringMatch(oui.ToString().AsCString(), "unspecified")); + + // Test `SetFrom()` with bytes (24-bit) + SuccessOrQuit(oui.SetFrom(bytes24, sizeof(bytes24))); + VerifyOrQuit(oui.IsValid()); + VerifyOrQuit(oui.GetBitLength() == 24); + VerifyOrQuit(oui.GetSize() == 3); + VerifyOrQuit(oui.GetAsOui24() == 0x641666); + VerifyOrQuit(StringMatch(oui.ToString().AsCString(), "64-16-66")); + + // Test `SetFrom()` with non-tidy 28-bit + SuccessOrQuit(oui.SetFrom(bytes28, sizeof(bytes28))); + VerifyOrQuit(oui.IsValid()); + VerifyOrQuit(oui.GetBitLength() == 28); + VerifyOrQuit(oui.GetSize() == 4); + VerifyOrQuit(oui.GetBytes()[3] == 0x30); + VerifyOrQuit(StringMatch(oui.ToString().AsCString(), "00-1A-2B-3")); + + // Test `SetFrom()` with non-tidy 36-bit + SuccessOrQuit(oui.SetFrom(bytes36, sizeof(bytes36))); + VerifyOrQuit(oui.IsValid()); + VerifyOrQuit(oui.GetBitLength() == 36); + VerifyOrQuit(oui.GetSize() == 5); + VerifyOrQuit(oui.GetBytes()[4] == 0x40); + VerifyOrQuit(StringMatch(oui.ToString().AsCString(), "00-1A-2B-3C-4")); + + // Test `operator==` (with tidy check on right-hand side) + SuccessOrQuit(oui1.SetFrom(bytes28, sizeof(bytes28))); // will be tidied + + oui2.Clear(); + oui2.mBitLength = 28; + memcpy(oui2.mBytes, bytes28, sizeof(bytes28)); // manually write untidied bytes + + VerifyOrQuit(oui1 == oui2); + + // Test `SetFrom(const Oui &)` tidying + oui3.SetFrom(oui2); + VerifyOrQuit(oui3.GetBytes()[3] == 0x30, "SetFrom(const Oui&) failed to tidy"); + } +}; + +} // namespace ot + +int main(void) +{ + ot::UnitTester::TestCompileTimeOuiParser(); + ot::UnitTester::TestOuiClass(); + + printf("All tests passed\n"); + return 0; +}