[dataset] add API otDatasetTlvsCompare (#12684)

Thread spec doesn't define the order of TLVs in the dataset, so that
we can't call `memcmp` to compare two dataset.  If we convert the
dataset to otOperationalDataset and then compare each value of
otOperationalDataset, we will met an issue if the new Thread spec
defines new TLVs in the dataset in the future.

This commit add a new dataset API `otDatasetTlvsCompare` to check
whether two dataset contain the exact same set of TLVs (same types and
values).
This commit is contained in:
Zhanglong Xia
2026-03-24 02:04:05 +08:00
committed by GitHub
parent 13776bc05c
commit 366a021076
6 changed files with 133 additions and 1 deletions
+15
View File
@@ -578,6 +578,21 @@ otError otNetworkNameFromString(otNetworkName *aNetworkName, const char *aNameSt
*/
otError otDatasetParseTlvs(const otOperationalDatasetTlvs *aDatasetTlvs, otOperationalDataset *aDataset);
/**
* Compares two Operational Dataset TLVs to determine if they contain the same set of TLVs.
*
* This function performs a deep comparison. It parses both @p aDatasetTlvsA and @p aDatasetTlvsB and checks if
* they contain the exact same set of TLVs (same type and same value). The order of TLVs within the
* `otOperationalDatasetTlvs` does not matter.
*
* @param[in] aDatasetTlvsA A pointer to dataset TLVs A. Must not be NULL.
* @param[in] aDatasetTlvsB A pointer to dataset TLVs B. Must not be NULL.
*
* @returns TRUE if the two Operational Dataset TLVs match, FALSE otherwise (e.g., if any TLV differs,
* is missing, or if the TLVs are not well-formed).
*/
bool otDatasetTlvsCompare(const otOperationalDatasetTlvs *aDatasetTlvsA, const otOperationalDatasetTlvs *aDatasetTlvsB);
/**
* Converts a given Operational Dataset to `otOperationalDatasetTlvs`.
*
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (582)
#define OPENTHREAD_API_VERSION (583)
/**
* @addtogroup api-instance
+21
View File
@@ -168,6 +168,27 @@ exit:
return error;
}
bool otDatasetTlvsCompare(const otOperationalDatasetTlvs *aDatasetTlvsA, const otOperationalDatasetTlvs *aDatasetTlvsB)
{
bool equals = false;
MeshCoP::Dataset datasetA;
MeshCoP::Dataset datasetB;
AssertPointerIsNotNull(aDatasetTlvsA);
AssertPointerIsNotNull(aDatasetTlvsB);
SuccessOrExit(datasetA.SetFrom(*aDatasetTlvsA));
SuccessOrExit(datasetB.SetFrom(*aDatasetTlvsB));
SuccessOrExit(datasetA.ValidateTlvs());
SuccessOrExit(datasetB.ValidateTlvs());
equals = datasetA.Equals(datasetB);
exit:
return equals;
}
void otDatasetConvertToTlvs(const otOperationalDataset *aDataset, otOperationalDatasetTlvs *aDatasetTlvs)
{
MeshCoP::Dataset dataset;
+21
View File
@@ -565,6 +565,27 @@ Error Dataset::WriteTimestamp(Type aType, const Timestamp &aTimestamp)
void Dataset::RemoveTimestamp(Type aType) { RemoveTlv(TimestampTlvFor(aType)); }
bool Dataset::Equals(const Dataset &aOther) const
{
bool equals = false;
VerifyOrExit(mLength == aOther.mLength);
// Ensure every TLV in this dataset is present in `aOther` with the same type and value.
for (const Tlv *tlv = GetTlvsStart(); tlv < GetTlvsEnd(); tlv = tlv->GetNext())
{
const Tlv *otherTlv = aOther.FindTlv(tlv->GetType());
VerifyOrExit(otherTlv != nullptr);
VerifyOrExit(memcmp(tlv, otherTlv, tlv->GetSize()) == 0);
}
equals = true;
exit:
return equals;
}
bool Dataset::IsSubsetOf(const Dataset &aOther) const
{
bool isSubset = false;
+16
View File
@@ -626,6 +626,22 @@ public:
*/
const Tlv *GetTlvsEnd(void) const { return reinterpret_cast<const Tlv *>(mTlvs + mLength); }
/**
* Determines whether this Dataset equals another Dataset.
*
* Two datasets are considered matching if they contain the exact same set of TLVs (same types and values).
* The order of TLVs within the datasets does not matter.
*
* This method assumes that both `this` and `aOther` datasets are valid and do not contain duplicate TLVs of
* the same type. The behavior is undefined if a dataset contains duplicates.
*
* @param[in] aOther The other Dataset to check against.
*
* @retval TRUE The current Dataset equals @p aOther.
* @retval FALSE The current Dataset does not match @p aOther.
*/
bool Equals(const Dataset &aOther) const;
/**
* Determines whether this Dataset is a subset of another Dataset.
*
+59
View File
@@ -259,6 +259,65 @@ void TestDataset(void)
VerifyOrQuit(!dataset2.IsSubsetOf(dataset));
VerifyOrQuit(!dataset.IsSubsetOf(dataset2));
// Validate `Equals()`
SuccessOrQuit(dataset.SetFrom(kTlvBytes, sizeof(kTlvBytes)));
SuccessOrQuit(dataset2.SetFrom(kTlvBytes, sizeof(kTlvBytes)));
VerifyOrQuit(dataset.Equals(dataset2));
VerifyOrQuit(dataset2.Equals(dataset));
// Order of TLVs should not matter for `Equals()`
ChannelTlvValue channel;
channel.SetChannelAndPage(11);
dataset.Clear();
SuccessOrQuit(dataset.Write<PanIdTlv>(0xface));
SuccessOrQuit(dataset.Write<ChannelTlv>(channel));
dataset2.Clear();
SuccessOrQuit(dataset2.Write<ChannelTlv>(channel));
SuccessOrQuit(dataset2.Write<PanIdTlv>(0xface));
VerifyOrQuit(dataset.Equals(dataset2));
VerifyOrQuit(dataset2.Equals(dataset));
// Different TLVs
dataset.Clear();
SuccessOrQuit(dataset.Write<PanIdTlv>(0xface));
dataset2.Clear();
SuccessOrQuit(dataset2.Write<PanIdTlv>(0xface));
SuccessOrQuit(dataset2.Write<ChannelTlv>(channel));
VerifyOrQuit(!dataset.Equals(dataset2));
VerifyOrQuit(!dataset2.Equals(dataset));
// Validate `Equals()` with unknown TLVs
{
const uint8_t kUnknownTlvValue[] = {0x01, 0x02, 0x03, 0x04};
const uint8_t kUnknownTlvValue2[] = {0x01, 0x02, 0x03, 0x05};
const Tlv::Type kUnknownTlvType = static_cast<Tlv::Type>(222);
dataset.Clear();
SuccessOrQuit(dataset.Write<PanIdTlv>(0xface));
SuccessOrQuit(dataset.WriteTlv(kUnknownTlvType, kUnknownTlvValue, sizeof(kUnknownTlvValue)));
dataset2.Clear();
SuccessOrQuit(dataset2.WriteTlv(kUnknownTlvType, kUnknownTlvValue, sizeof(kUnknownTlvValue)));
SuccessOrQuit(dataset2.Write<PanIdTlv>(0xface));
VerifyOrQuit(dataset.Equals(dataset2));
VerifyOrQuit(dataset2.Equals(dataset));
// Different values for the same unknown TLV type
SuccessOrQuit(dataset2.WriteTlv(kUnknownTlvType, kUnknownTlvValue2, sizeof(kUnknownTlvValue2)));
VerifyOrQuit(!dataset.Equals(dataset2));
VerifyOrQuit(!dataset2.Equals(dataset));
}
}
} // namespace MeshCoP