[netdiag] require 'RD:' prefix for vendor name on reference devices (#12233)

When `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is active, this
change mandates that the vendor name string MUST begin with the "RD:"
prefix. This ensures that reference devices are clearly and
consistently identifiable through network diagnostic queries.

The enforcement is applied at two levels:

- A compile-time `static_assert` is added to validate the default
  `OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME` at build time. This uses a
  new `constexpr` helper utility `CheckConstStringPrefix()`.

- A runtime check is added to `otThreadSetVendorName()`, which will
  now return `OT_ERROR_INVALID_ARGS` if an invalid name is provided
  on a reference device build.

All related test configurations (`scan-build`, `toranj`, `nexus`) and
CLI tests are updated to reflect this new requirement and validate
it.
This commit is contained in:
Abtin Keshavarzian
2026-01-16 10:20:13 -08:00
committed by GitHub
parent 5fd62a9acf
commit b3d3b5c3c7
13 changed files with 96 additions and 21 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (572)
#define OPENTHREAD_API_VERSION (573)
/**
* @addtogroup api-instance
+6 -1
View File
@@ -433,11 +433,16 @@ const char *otThreadGetVendorAppUrl(otInstance *aInstance);
* @p aVendorName should be UTF8 with max length of 32 chars (`MAX_VENDOR_NAME_TLV_LENGTH`). Maximum length does not
* include the null `\0` character.
*
* If `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled, @p aVendorName must start with the "RD:" prefix.
* This is enforced to ensure reference devices are identifiable. If @p aVendorName does not follow this pattern,
* the name is rejected, and `OT_ERROR_INVALID_ARGS` is returned.
*
* @param[in] aInstance A pointer to an OpenThread instance.
* @param[in] aVendorName The vendor name string.
*
* @retval OT_ERROR_NONE Successfully set the vendor name.
* @retval OT_ERROR_INVALID_ARGS @p aVendorName is not valid (too long or not UTF8).
* @retval OT_ERROR_INVALID_ARGS @p aVendorName is not valid. It is too long, is not UTF-8, or does not start with
* the "RD:" prefix when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled.
*/
otError otThreadSetVendorName(otInstance *aInstance, const char *aVendorName);
+1 -1
View File
@@ -82,7 +82,7 @@ OT_BUILD_OPTIONS=(
"-DOT_SRP_SERVER=ON"
"-DOT_SRP_SERVER_FAST_START_MODE=ON"
"-DOT_UPTIME=ON"
"-DOT_VENDOR_NAME=OpenThread"
"-DOT_VENDOR_NAME=RD:OpenThread"
"-DOT_VENDOR_MODEL=Scan-build"
"-DOT_VENDOR_SW_VERSION=OT"
)
+19
View File
@@ -396,6 +396,25 @@ inline constexpr bool AreConstStringsEqual(const char *aFirst, const char *aSeco
: false;
}
/**
* This `constexpr` function checks whether a given C string starts with a given prefix string.
*
* This is intended for use in `static_assert`, e.g., checking the hardcoded vendor name on a reference device. It is
* not recommended to use this function in other situations as it uses recursion so that it can be `constexpr`.
*
* @param[in] aString The string to check.
* @param[in] aPrefix The prefix string.
*
* @retval TRUE If @p aString starts with @p aPrefix.
* @retval FALSE If @p aString does not start with @p aPrefix.
*/
inline constexpr bool CheckConstStringPrefix(const char *aString, const char *aPrefix)
{
return (*aPrefix == kNullChar)
? true
: ((*aString == *aPrefix) ? CheckConstStringPrefix(aString + 1, aPrefix + 1) : false);
}
/**
* Implements writing to a string buffer.
*/
+9
View File
@@ -43,14 +43,23 @@
* @{
*/
#include "config/misc.h"
/**
* @def OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME
*
* Specifies the default Vendor Name string.
*
* If `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled, the Vendor Name string MUST start with the "RD:" prefix
* to ensure reference devices are identifiable. This is checked and enforced at build-time (`static_assert`).
*/
#ifndef OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "RD:"
#else
#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME ""
#endif
#endif
/**
* @def OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL
+17 -1
View File
@@ -46,6 +46,13 @@ const char Server::kVendorModel[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL;
const char Server::kVendorSwVersion[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_SW_VERSION;
const char Server::kVendorAppUrl[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_APP_URL;
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
static constexpr char kVendorNamePrefix[] = "RD:";
static_assert(CheckConstStringPrefix(OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME, kVendorNamePrefix),
"VENDOR_NAME MUST start with 'RD:' prefix for a reference device.");
#endif
//---------------------------------------------------------------------------------------------------------------------
// Server
@@ -70,7 +77,16 @@ Server::Server(Instance &aInstance)
Error Server::SetVendorName(const char *aVendorName)
{
return StringCopy(mVendorName, aVendorName, kStringCheckUtf8Encoding);
Error error;
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
VerifyOrExit(aVendorName != nullptr && StringStartsWith(aVendorName, kVendorNamePrefix), error = kErrorInvalidArgs);
#endif
SuccessOrExit(error = StringCopy(mVendorName, aVendorName, kStringCheckUtf8Encoding));
exit:
return error;
}
Error Server::SetVendorModel(const char *aVendorModel)
+6 -1
View File
@@ -131,8 +131,13 @@ public:
*
* @param[in] aVendorName The vendor name string.
*
* If `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled, @p aVendorName must start with the "RD:" prefix.
* This is enforced to ensure reference devices are identifiable. If @p aVendorName does not follow this pattern,
* the name is rejected, and `kErrorInvalidArgs` is returned.
*
* @retval kErrorNone Successfully set the vendor name.
* @retval kErrorInvalidArgs @p aVendorName is not valid (too long or not UTF8).
* @retval kErrorInvalidArgs @p aVendorName is not valid. It is too long, is not UTF-8, or does not start with
* the "RD:" prefix when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled.
*/
Error SetVendorName(const char *aVendorName);
+1 -1
View File
@@ -105,7 +105,7 @@
#define OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS 600
#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE OPENTHREAD_FTD
#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL "Nexus Simulation"
#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "OpenThread by Google Nest"
#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_NETDATA_PUBLISHER_ENABLE 1
#define OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS 256
@@ -71,8 +71,8 @@ expect -re {\tVALUE:\t0x2242([0-9a-fA-F]{132})}
send "diagnostic_tlvs channelpages maxchildtimeout eui64 vendorname vendormodel vendorswversion vendorappurl\n"
expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD"
expect_line "\tLEN:\t27"
expect_line "\tVALUE:\t0x1101001304000000f0170818b430000000000119001a001b002300"
expect_line "\tLEN:\t30"
expect_line "\tVALUE:\t0x1101001304000000f0170818b4300000000001190352443a1a001b002300"
send "diagnostic_tlvs childtable\n"
expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD"
@@ -75,12 +75,12 @@ NON_PREFERRED_CHANNELS_TLV = 36
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Check setting vendor name, model, ans sw version
r1.set_vendor_name('nest')
r1.set_vendor_name('RD:nest')
r1.set_vendor_model('marble')
r1.set_vendor_sw_version('ot-1.4')
r1.set_vendor_app_url('https://example.com/vendor-app')
verify(r1.get_vendor_name() == 'nest')
verify(r1.get_vendor_name() == 'RD:nest')
verify(r1.get_vendor_model() == 'marble')
verify(r1.get_vendor_sw_version() == 'ot-1.4')
verify(r1.get_vendor_app_url() == 'https://example.com/vendor-app')
@@ -90,12 +90,24 @@ verify(r1.get_vendor_app_url() == 'https://example.com/vendor-app')
# Vendor name should accept up to 32 chars
r2.set_vendor_name('01234567890123456789012345678901') # 32 chars
r2.set_vendor_name('RD:01234567890123456789012345678') # 32 chars
errored = False
try:
r2.set_vendor_name('012345678901234567890123456789012') # 33 chars
r2.set_vendor_name('RD:012345678901234567890123456789') # 33 chars
except cli.CliError as e:
verify(e.message == 'InvalidArgs')
errored = True
verify(errored)
# Vendor name under `REFERENCE_DEVICE` build MUST start with "RD:".
errored = False
try:
r2.set_vendor_name('name_without_RD_prefix')
except cli.CliError as e:
verify(e.message == 'InvalidArgs')
errored = True
@@ -143,21 +155,21 @@ r2_rloc = r2.get_rloc_ip_addr()
result = r2.cli('networkdiagnostic get', r1_rloc, VENDOR_NAME_TLV)
verify(len(result) == 2)
verify(result[1].startswith("Vendor Name:"))
verify(result[1].split(':')[1].strip() == r1.get_vendor_name())
verify(result[1].split(':', 1)[1].strip() == r1.get_vendor_name())
# Get vendor model (TLV 28)
result = r2.cli('networkdiagnostic get', r1_rloc, VENDOR_MODEL_TLV)
verify(len(result) == 2)
verify(result[1].startswith("Vendor Model:"))
verify(result[1].split(':')[1].strip() == r1.get_vendor_model())
verify(result[1].split(':', 1)[1].strip() == r1.get_vendor_model())
# Get vendor sw version (TLV 29)
result = r2.cli('networkdiagnostic get', r1_rloc, VENDOR_SW_VERSION_TLV)
verify(len(result) == 2)
verify(result[1].startswith("Vendor SW Version:"))
verify(result[1].split(':')[1].strip() == r1.get_vendor_sw_version())
verify(result[1].split(':', 1)[1].strip() == r1.get_vendor_sw_version())
# Get vendor app URL (TLV 35)
@@ -180,11 +192,11 @@ result = r1.cli('networkdiagnostic get', r2_rloc, VENDOR_NAME_TLV, VENDOR_MODEL_
verify(len(result) == 6)
for line in result[1:]:
if line.startswith("Vendor Name:"):
verify(line.split(':')[1].strip() == r2.get_vendor_name())
verify(line.split(':', 1)[1].strip() == r2.get_vendor_name())
elif line.startswith("Vendor Model:"):
verify(line.split(':')[1].strip() == r2.get_vendor_model())
verify(line.split(':', 1)[1].strip() == r2.get_vendor_model())
elif line.startswith("Vendor SW Version:"):
verify(line.split(':')[1].strip() == r2.get_vendor_sw_version())
verify(line.split(':', 1)[1].strip() == r2.get_vendor_sw_version())
elif line.startswith("Vendor App URL:"):
verify(line.split(':', 1)[1].strip() == r2.get_vendor_app_url())
elif line.startswith("Thread Stack Version:"):
+1 -1
View File
@@ -208,7 +208,7 @@
#define OPENTHREAD_CONFIG_MLE_PARENT_RESPONSE_CALLBACK_API_ENABLE 1
#ifndef OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME
#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "OpenThread by Google Nest"
#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "RD:OpenThread by Google Nest"
#endif
#ifndef OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL
+9
View File
@@ -466,6 +466,15 @@ static_assert(!ot::AreStringsInOrder("z", "abcd"), "AreStringsInOrder() failed")
static_assert(!ot::AreStringsInOrder("0", ""), "AreStringsInOrder() failed");
#endif
static_assert(ot::CheckConstStringPrefix("abc", "a"), "CheckConstStringPrefix() failed");
static_assert(ot::CheckConstStringPrefix("abc", "ab"), "CheckConstStringPrefix() failed");
static_assert(ot::CheckConstStringPrefix("abc", "abc"), "CheckConstStringPrefix() failed");
static_assert(ot::CheckConstStringPrefix("abc", ""), "CheckConstStringPrefix() failed");
static_assert(!ot::CheckConstStringPrefix("abc", "b"), "CheckConstStringPrefix() failed");
static_assert(!ot::CheckConstStringPrefix("abc", "abcd"), "CheckConstStringPrefix() failed");
static_assert(!ot::CheckConstStringPrefix("", "a"), "CheckConstStringPrefix() failed");
} // namespace ot
int main(void)
+2 -2
View File
@@ -353,8 +353,8 @@ class TestOTCI(unittest.TestCase):
logging.info('dataset active -x: %r', leader.get_dataset_bytes('active'))
logging.info('dataset pending -x: %r', leader.get_dataset_bytes('pending'))
leader.set_vendor_name('OpenThread')
self.assertEqual('OpenThread', leader.get_vendor_name())
leader.set_vendor_name('RD:OpenThread')
self.assertEqual('RD:OpenThread', leader.get_vendor_name())
leader.set_vendor_model('some_model')
self.assertEqual('some_model', leader.get_vendor_model())
leader.set_vendor_sw_version('1.0.0')