diff --git a/include/openthread/dataset.h b/include/openthread/dataset.h index 9f91deb18..1b12dd407 100644 --- a/include/openthread/dataset.h +++ b/include/openthread/dataset.h @@ -567,6 +567,24 @@ otError otDatasetGeneratePskc(const char *aPassPhrase, */ otError otNetworkNameFromString(otNetworkName *aNetworkName, const char *aNameString); +/** + * Indicates whether or not the given Operational Dataset TLVs is a valid Active or Pending Dataset. + * + * A valid Active Dataset MUST contain all the required TLVs (Active Timestamp, Channel, Channel Mask, Extended PAN ID, + * Mesh-Local Prefix, Network Key, Network Name, PAN ID, PSKc, and Security Policy). + * + * A valid Pending Dataset MUST contain all the required TLVs for an Active Dataset and additionally MUST contain + * Pending Timestamp and Delay Timer TLVs. + * + * This method also checks whether there are duplicated TLVs or the TLVs are not well-formed in the @p aDatasetTlvs. + * + * @param[in] aDatasetTlvs A pointer to dataset TLVs. + * @param[in] aActive TRUE for Active Dataset, FALSE for Pending Dataset. + * + * @returns TRUE if @p aDatasetTlvs is a valid Dataset, FALSE otherwise. + */ +bool otDatasetIsValid(const otOperationalDatasetTlvs *aDatasetTlvs, bool aActive); + /** * Parses an Operational Dataset from a given `otOperationalDatasetTlvs`. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 4f2dd3b45..56b266c1e 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 (584) +#define OPENTHREAD_API_VERSION (585) /** * @addtogroup api-instance diff --git a/src/core/api/dataset_api.cpp b/src/core/api/dataset_api.cpp index c8f902e89..17d6f7e59 100644 --- a/src/core/api/dataset_api.cpp +++ b/src/core/api/dataset_api.cpp @@ -153,6 +153,21 @@ otError otNetworkNameFromString(otNetworkName *aNetworkName, const char *aNameSt return (error == OT_ERROR_ALREADY) ? OT_ERROR_NONE : error; } +bool otDatasetIsValid(const otOperationalDatasetTlvs *aDatasetTlvs, bool aActive) +{ + bool isValid = false; + MeshCoP::Dataset dataset; + + AssertPointerIsNotNull(aDatasetTlvs); + + SuccessOrExit(dataset.SetFrom(*aDatasetTlvs)); + SuccessOrExit(dataset.ValidateTlvs()); + isValid = dataset.ContainsAllRequiredTlvsFor(aActive ? MeshCoP::Dataset::kActive : MeshCoP::Dataset::kPending); + +exit: + return isValid; +} + otError otDatasetParseTlvs(const otOperationalDatasetTlvs *aDatasetTlvs, otOperationalDataset *aDataset) { Error error = kErrorNone; diff --git a/tests/gtest/dataset_test.cpp b/tests/gtest/dataset_test.cpp index bd786506d..c2c401070 100644 --- a/tests/gtest/dataset_test.cpp +++ b/tests/gtest/dataset_test.cpp @@ -77,3 +77,88 @@ TEST(otDatasetSetActiveTlvs, shouldTriggerStateCallbackOnSuccess) fakePlatform.GoInMs(10000); } + +TEST(otDatasetIsValid, shouldReturnTrueForValidActiveDataset) +{ + FakePlatform fakePlatform; + otOperationalDataset dataset; + otOperationalDatasetTlvs datasetTlvs; + + EXPECT_EQ(otDatasetCreateNewNetwork(FakePlatform::CurrentInstance(), &dataset), OT_ERROR_NONE); + otDatasetConvertToTlvs(&dataset, &datasetTlvs); + + EXPECT_TRUE(otDatasetIsValid(&datasetTlvs, true)); +} + +TEST(otDatasetIsValid, shouldReturnFalseForIncompleteActiveDataset) +{ + FakePlatform fakePlatform; + otOperationalDataset dataset; + otOperationalDatasetTlvs datasetTlvs; + + EXPECT_EQ(otDatasetCreateNewNetwork(FakePlatform::CurrentInstance(), &dataset), OT_ERROR_NONE); + + // Remove a required field, e.g., Network Key + dataset.mComponents.mIsNetworkKeyPresent = false; + + otDatasetConvertToTlvs(&dataset, &datasetTlvs); + + EXPECT_FALSE(otDatasetIsValid(&datasetTlvs, true)); +} + +TEST(otDatasetIsValid, shouldReturnFalseForDuplicatedTlvs) +{ + FakePlatform fakePlatform; + otOperationalDataset dataset; + otOperationalDatasetTlvs datasetTlvs; + + EXPECT_EQ(otDatasetCreateNewNetwork(FakePlatform::CurrentInstance(), &dataset), OT_ERROR_NONE); + otDatasetConvertToTlvs(&dataset, &datasetTlvs); + + // Duplicate a TLV (e.g. Network Name TLV) + // TLV format: Type (1 byte), Length (1 byte), Value (n bytes) + datasetTlvs.mTlvs[datasetTlvs.mLength] = OT_MESHCOP_TLV_NETWORKNAME; // Type: Network Name + datasetTlvs.mTlvs[datasetTlvs.mLength + 1] = 1; // Length: 1 + datasetTlvs.mTlvs[datasetTlvs.mLength + 2] = 'A'; + datasetTlvs.mLength += 3; + + EXPECT_FALSE(otDatasetIsValid(&datasetTlvs, true)); +} + +TEST(otDatasetIsValid, shouldReturnTrueForValidPendingDataset) +{ + FakePlatform fakePlatform; + otOperationalDataset dataset; + otOperationalDatasetTlvs datasetTlvs; + + EXPECT_EQ(otDatasetCreateNewNetwork(FakePlatform::CurrentInstance(), &dataset), OT_ERROR_NONE); + + // Pending Dataset MUST contain Pending Timestamp and Delay Timer. + dataset.mPendingTimestamp.mSeconds = 100; + dataset.mPendingTimestamp.mTicks = 0; + dataset.mComponents.mIsPendingTimestampPresent = true; + dataset.mDelay = 30000; + dataset.mComponents.mIsDelayPresent = true; + + otDatasetConvertToTlvs(&dataset, &datasetTlvs); + + EXPECT_TRUE(otDatasetIsValid(&datasetTlvs, false)); +} + +TEST(otDatasetIsValid, shouldReturnFalseForIncompletePendingDataset) +{ + FakePlatform fakePlatform; + otOperationalDataset dataset; + otOperationalDatasetTlvs datasetTlvs; + + EXPECT_EQ(otDatasetCreateNewNetwork(FakePlatform::CurrentInstance(), &dataset), OT_ERROR_NONE); + + // Provide Pending Timestamp but omit Delay Timer. + dataset.mPendingTimestamp.mSeconds = 100; + dataset.mPendingTimestamp.mTicks = 0; + dataset.mComponents.mIsPendingTimestampPresent = true; + + otDatasetConvertToTlvs(&dataset, &datasetTlvs); + + EXPECT_FALSE(otDatasetIsValid(&datasetTlvs, false)); +}