Addition of a new helper class to deal with WDL maps.
diff --git a/src/lib/profiles/data-management/Current/WdmDictionary.h b/src/lib/profiles/data-management/Current/WdmDictionary.h
new file mode 100644
index 0000000..5685465
--- /dev/null
+++ b/src/lib/profiles/data-management/Current/WdmDictionary.h
@@ -0,0 +1,230 @@
+/*
+ *
+ * Copyright (c) 2018 Google LLC.
+ * Copyright (c) 2013-2017 Nest Labs, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file
+ * This file provides a more logical container representation of a WDM dictionary
+ * that permits both indexing against the dictionary key as well as a more logical primary
+ * key
+ */
+
+#ifndef _WEAVE_DATA_MANAGEMENT_WDM_DICTIONARY_H
+#define _WEAVE_DATA_MANAGEMENT_WDM_DICTIONARY_H
+
+#include <Weave/Profiles/data-management/Current/WdmManagedNamespace.h>
+
+#include <Weave/Core/WeaveCore.h>
+#include <Weave/Core/WeaveMessageLayer.h>
+#include <Weave/Profiles/ProfileCommon.h>
+#include <Weave/Support/CodeUtils.h>
+#include <Weave/Profiles/data-management/MessageDef.h>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/identity.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+
+using namespace boost::multi_index;
+
+namespace nl {
+namespace Weave {
+namespace Profiles {
+namespace WeaveMakeManagedNamespaceIdentifier(DataManagement, kWeaveManagedNamespaceDesignation_Current) {
+
+//
+// This templated class provides a more user-friendly type when interacting with WDL maps.
+// WDL maps are constrained in their keys to having 16-bit numerical keys. As such, these maps
+// cannot be thought of as traditional maps that have the key being the logical/primary key used to
+// index the data in the collection. Rather, the key is merely a unique number assigned to each item,
+// lacking any semantic or logical meaning. The actual logical key is embedded in the item itself, and
+// be any WDL type. In this sense, WDL maps can be thought of as spare arrays.
+//
+// Applications will typically want to interact with the collection locally on devices more naturally as a keyed collection,
+// with the data index using the logical key. This requires them to maintain a two-map solution:
+// one to store the original data in the trait, and another to translate from the logical key to the map key.
+//
+// This templated class utilizes boost's multi_index_container, which permits indexing the data against multiple keys
+// while still retaining efficient storage and look-up of the data. The class wraps the provided boost type and
+// provides helper methods to interact with each key's sub-table.
+//
+// Specializing the template requires passing in both the logical key type as well as the value type as well.
+//
+// Requirements on the types include:
+// Logical Key Type: Has both '<' and '==' operators implemented.
+// Value Type: Has the '==' operator implemented.
+//
+// The class also provides methods to compare against another instance of the collection to easily figure out the
+// set of items added, removed or modified against the logical key. This allows for comparisons
+//
+template <class KeyT, class ValueT>
+class WdmDictionary {
+public:
+ //
+ // This is the Item that is used in the collection, housing both the 16-bit dictionary
+ // key as well as the logical KeyT typed key.
+ // It also contains the value as well.
+ //
+ struct Item {
+ Item(uint16_t dictKey) { _dict_key = dictKey; _logical_key = 0; }
+ Item() { _dict_key = 0; _logical_key = 0; }
+
+ bool operator<(const Item& i1) const {
+ return (_logical_key < i1._logical_key);
+ }
+
+ bool operator ==(const Item& i) const {
+ return (_logical_key == i._logical_key && _dict_key == i._dict_key && _data == i._data);
+ }
+
+ ValueT _data;
+ uint16_t _dict_key;
+ KeyT _logical_key;
+ };
+
+ struct map_key{};
+ struct logical_key{};
+
+ typedef multi_index_container<
+ Item,
+ indexed_by<
+ // Both indexes are of type 'ordered_unique' since it's expected there will and should
+ // not be collisions in either of the key-spaces.
+ ordered_unique<tag<map_key>, member<Item, uint16_t, &Item::_dict_key> >,
+ ordered_unique<tag<logical_key>, member<Item, KeyT, &Item::_logical_key> >
+ >
+ > Container;
+
+ typedef typename Container::template index<map_key>::type DictKeyTableType;
+ typedef typename Container::template index<map_key>::type::iterator DictKeyTableTypeIterator;
+ typedef typename Container::template index<logical_key>::type LogicalKeyTableType;
+ typedef typename Container::template index<logical_key>::type::iterator LogicalKeyTableTypeIterator;
+
+ DictKeyTableType& GetDictKeyTable() { return _container.template get<map_key>(); }
+ LogicalKeyTableType& GetLogicalKeyTable() { return _container.template get<logical_key>(); }
+
+ //
+ // This is a convenience method that automatically creates an element in the collection with a particular
+ // dictionary key, or accesses an existing element that already exists, and allows the caller to mutate a member
+ // through a provided closure.
+ //
+ void ModifyItem(uint16_t dictKey, std::function<void(Item&)> func);
+
+ //
+ // The following methods compare against a provided container and enumerates items that have been added, deleted or modified.
+ // This is specifically done against the logical key table and *not* against the dictionary keys. This is necessary since certain
+ // implementations of dictionaries (like the Nest cloud service) do not provide key stability for the dictionary keys, occasionally
+ // re-key'ing them while retaining the logical key + value content.
+ //
+ void ItemsAdded(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&)> func, bool updateStore=false);
+ void ItemsRemoved(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&)> func, bool updateStore=false);
+ void ItemsModified(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&, LogicalKeyTableTypeIterator&)> func, bool updateStore=false);
+ bool IsEqual(WdmDictionary &dictionary);
+
+private:
+ Container _container;
+};
+
+template <typename KeyT, typename ValueT>
+bool WdmDictionary<KeyT, ValueT>::IsEqual(WdmDictionary &dictionary)
+{
+ return (dictionary._container.size() == _container.size())
+ && std::equal(_container.begin(), _container.end(), dictionary._container.begin());
+}
+
+template <typename KeyT, typename ValueT>
+void WdmDictionary<KeyT, ValueT>::ModifyItem(uint16_t dictKey, std::function<void(Item &)> func)
+{
+ Item item = Item(dictKey);
+ DictKeyTableType& mapkey_tbl = _container.template get<map_key>();
+ mapkey_tbl.modify(mapkey_tbl.insert(item).first, [&func](Item &d) {
+ func(d);
+ });
+}
+
+template <typename KeyT, typename ValueT>
+void WdmDictionary<KeyT, ValueT>::ItemsAdded(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&)> func, bool updateStore)
+{
+ Container addedItems;
+ LogicalKeyTableType &addedItemsLogicalTbl = addedItems.template get<logical_key>();
+
+ std::set_difference(stagedContainer.GetLogicalKeyTable().begin(), stagedContainer.GetLogicalKeyTable().end(),
+ GetLogicalKeyTable().begin(), GetLogicalKeyTable().end(),
+ std::inserter(addedItemsLogicalTbl, addedItemsLogicalTbl.begin()));
+
+ for (auto it = addedItemsLogicalTbl.begin(); it != addedItemsLogicalTbl.end(); it++) {
+ func(it);
+
+ if (updateStore) {
+ GetLogicalKeyTable().insert(*it);
+ }
+ }
+}
+
+template <typename KeyT, typename ValueT>
+void WdmDictionary<KeyT, ValueT>::ItemsRemoved(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&)> func, bool updateStore)
+{
+ Container removedItems;
+ LogicalKeyTableType &removedItemsLogicalTbl = removedItems.template get<logical_key>();
+
+ std::set_difference(GetLogicalKeyTable().begin(), GetLogicalKeyTable().end(),
+ stagedContainer.GetLogicalKeyTable().begin(), stagedContainer.GetLogicalKeyTable().end(),
+ std::inserter(removedItemsLogicalTbl, removedItemsLogicalTbl.begin()));
+
+ for (auto it = removedItemsLogicalTbl.begin(); it != removedItemsLogicalTbl.end(); it++) {
+ func(it);
+
+ if (updateStore) {
+ GetLogicalKeyTable().erase(it->_logical_key);
+ }
+ }
+}
+
+template <typename KeyT, typename ValueT>
+void WdmDictionary<KeyT, ValueT>::ItemsModified(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&, LogicalKeyTableTypeIterator&)> func, bool updateStore)
+{
+ Container modifiedItems;
+ LogicalKeyTableType &modifiedItemsTbl = modifiedItems.template get<logical_key>();
+
+ std::set_intersection(stagedContainer.GetLogicalKeyTable().begin(), stagedContainer.GetLogicalKeyTable().end(),
+ GetLogicalKeyTable().begin(), GetLogicalKeyTable().end(),
+ std::inserter(modifiedItemsTbl, modifiedItemsTbl.begin()));
+
+ for (auto it = modifiedItemsTbl.begin(); it != modifiedItemsTbl.end(); it++) {
+ auto it1 = GetLogicalKeyTable().find(it->_logical_key);
+ auto it2 = stagedContainer.GetLogicalKeyTable().find(it->_logical_key);
+
+ if (!(it1->_data == it2->_data)) {
+ func(it1, it2);
+ }
+
+ if (updateStore) {
+ GetLogicalKeyTable().modify(it1, [it2](auto &d) {
+ d = *it2;
+ });
+ }
+ }
+}
+
+}; // namespace WeaveMakeManagedNamespaceIdentifier(DataManagement, kWeaveManagedNamespaceDesignation_Current)
+}; // namespace Profiles
+}; // namespace Weave
+}; // namespace nl
+
+#endif
diff --git a/src/test-apps/Makefile.am b/src/test-apps/Makefile.am
index 801e077..f1a7719 100644
--- a/src/test-apps/Makefile.am
+++ b/src/test-apps/Makefile.am
@@ -356,11 +356,13 @@
if HAVE_CXX11
check_PROGRAMS += \
TestTDM \
+ TestTDMDictionary \
$(NULL)
endif
check_PROGRAMS += \
TestTDM \
+ TestTDMDictionary \
TestPathStore \
TestWdmUpdateEncoder \
TestWdmUpdateResponse \
@@ -469,6 +471,7 @@
if HAVE_CXX11
local_test_programs += \
TestTDM \
+ TestTDMDictionary \
TestWDM \
$(NULL)
endif
@@ -1220,6 +1223,20 @@
TestTDM_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/src/test-apps/schema
TestTDM_LDFLAGS = $(AM_CPPFLAGS)
TestTDM_LDADD = libWeaveTestCommon.a $(COMMON_LDADD)
+
+TestTDMDictionary_SOURCES = TestTDMDictionary.cpp \
+ schema/nest/test/trait/TestHTrait.cpp \
+ schema/nest/test/trait/TestCTrait.cpp \
+ schema/nest/test/trait/TestBTrait.cpp \
+ schema/nest/test/trait/TestMismatchedCTrait.cpp \
+ schema/nest/test/trait/TestCommon.cpp \
+ MockMismatchedSchemaSinkAndSource.cpp \
+ TestPersistedStorageImplementation.cpp \
+ MockTestBTrait.cpp
+
+TestTDMDictionary_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/src/test-apps/schema
+TestTDMDictionary_LDFLAGS = $(AM_CPPFLAGS)
+TestTDMDictionary_LDADD = libWeaveTestCommon.a $(COMMON_LDADD)
endif
TestWDM_SOURCES = TestWdm.cpp
diff --git a/src/test-apps/TestTDM.cpp b/src/test-apps/TestTDM.cpp
index fd00d05..0170916 100644
--- a/src/test-apps/TestTDM.cpp
+++ b/src/test-apps/TestTDM.cpp
@@ -65,6 +65,116 @@
using namespace nl::Weave::TLV;
using namespace nl::Weave::Profiles::DataManagement;
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/identity.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+
+using namespace boost::multi_index;
+
+template <class KeyT, class ValueT>
+class WdmDictionary {
+public:
+ struct Item {
+ Item(uint16_t dictKey) { _dict_key = dictKey; }
+
+ bool operator<(const Item& i1) const {
+ return (_logical_key < i1._logical_key);
+ }
+
+ ValueT _data;
+ uint16_t _dict_key;
+ KeyT _logical_key;
+ };
+
+ struct map_key{};
+ struct logical_key{};
+
+ typedef multi_index_container<
+ Item,
+ indexed_by<
+ ordered_unique<tag<map_key>, member<Item, uint16_t, &Item::_dict_key> >,
+ ordered_unique<tag<logical_key>, member<Item, KeyT, &Item::_logical_key> >
+ >
+ > Container;
+
+ typedef typename Container::template index<map_key>::type DictKeyTableType;
+ typedef typename Container::template index<logical_key>::type LogicalKeyTableType;
+ typedef typename Container::template index<logical_key>::type::iterator LogicalKeyTableTypeIterator;
+
+ void ModifyItem(uint16_t dictKey, std::function<void(Item&)> func);
+ DictKeyTableType& GetDictKeyTable() { return _container.template get<map_key>(); }
+ LogicalKeyTableType& GetLogicalKeyTable() { return _container.template get<logical_key>(); }
+
+ void ItemsAdded(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&)> func);
+ void ItemsRemoved(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&)> func);
+ void ItemsModified(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&, LogicalKeyTableTypeIterator&)> func);
+
+private:
+ Container _container;
+};
+
+template <typename KeyT, typename ValueT>
+void WdmDictionary<KeyT, ValueT>::ModifyItem(uint16_t dictKey, std::function<void(Item &)> func)
+{
+ Item item = Item(dictKey);
+ DictKeyTableType& mapkey_tbl = _container.template get<map_key>();
+ mapkey_tbl.modify(mapkey_tbl.insert(item).first, [&func](Item &d) {
+ func(d);
+ });
+}
+
+template <typename KeyT, typename ValueT>
+void WdmDictionary<KeyT, ValueT>::ItemsAdded(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&)> func)
+{
+ Container addedItems;
+ LogicalKeyTableType &addedItemsLogicalTbl = addedItems.template get<logical_key>();
+
+ std::set_difference(stagedContainer.GetLogicalKeyTable().begin(), stagedContainer.GetLogicalKeyTable().end(),
+ GetLogicalKeyTable().begin(), GetLogicalKeyTable().end(),
+ std::inserter(addedItemsLogicalTbl, addedItemsLogicalTbl.begin()));
+
+ for (auto it = addedItemsLogicalTbl.begin(); it != addedItemsLogicalTbl.end(); it++) {
+ func(it);
+ }
+}
+
+template <typename KeyT, typename ValueT>
+void WdmDictionary<KeyT, ValueT>::ItemsRemoved(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&)> func)
+{
+ Container removedItems;
+ LogicalKeyTableType &removedItemsLogicalTbl = removedItems.template get<logical_key>();
+
+ std::set_difference(GetLogicalKeyTable().begin(), GetLogicalKeyTable().end(),
+ stagedContainer.GetLogicalKeyTable().begin(), stagedContainer.GetLogicalKeyTable().end(),
+ std::inserter(removedItemsLogicalTbl, removedItemsLogicalTbl.begin()));
+
+ for (auto it = removedItemsLogicalTbl.begin(); it != removedItemsLogicalTbl.end(); it++) {
+ func(it);
+ }
+}
+
+template <typename KeyT, typename ValueT>
+void WdmDictionary<KeyT, ValueT>::ItemsModified(WdmDictionary& stagedContainer, std::function<void(LogicalKeyTableTypeIterator&, LogicalKeyTableTypeIterator&)> func)
+{
+ Container modifiedItems;
+ LogicalKeyTableType &modifiedItemsTbl = modifiedItems.template get<logical_key>();
+
+ std::set_intersection(stagedContainer.GetLogicalKeyTable().begin(), stagedContainer.GetLogicalKeyTable().end(),
+ GetLogicalKeyTable().begin(), GetLogicalKeyTable().end(),
+ std::inserter(modifiedItemsTbl, modifiedItemsTbl.begin()));
+
+ for (auto it = modifiedItemsTbl.begin(); it != modifiedItemsTbl.end(); it++) {
+ auto it1 = GetLogicalKeyTable().find(it->_logical_key);
+ auto it2 = stagedContainer.GetLogicalKeyTable().find(it->_logical_key);
+
+ if (!(it1->_data == it2->_data)) {
+ func(it1, it2);
+ }
+ }
+}
+
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// System/Platform definitions
diff --git a/src/test-apps/TestTDMDictionary.cpp b/src/test-apps/TestTDMDictionary.cpp
new file mode 100644
index 0000000..ccf0e85
--- /dev/null
+++ b/src/test-apps/TestTDMDictionary.cpp
@@ -0,0 +1,765 @@
+/*
+ *
+ * Copyright (c) 2018 Google LLC.
+ * Copyright (c) 2013-2017 Nest Labs, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file
+ * This file implements unit tests for the Weave TLV implementation.
+ *
+ */
+
+#include "ToolCommon.h"
+
+#include <nlbyteorder.h>
+#include <nlunit-test.h>
+
+#include <Weave/Core/WeaveCore.h>
+#include <Weave/Core/WeaveTLV.h>
+#include <Weave/Core/WeaveTLVDebug.hpp>
+#include <Weave/Core/WeaveTLVUtilities.hpp>
+#include <Weave/Core/WeaveTLVData.hpp>
+#include <Weave/Core/WeaveCircularTLVBuffer.h>
+#include <Weave/Support/RandUtils.h>
+
+#include <Weave/Profiles/data-management/Current/WdmManagedNamespace.h>
+#include <Weave/Profiles/data-management/DataManagement.h>
+
+#include <Weave/Profiles/data-management/WdmDictionary.h>
+
+#include <nest/test/trait/TestHTrait.h>
+#include <nest/test/trait/TestCTrait.h>
+#include <nest/test/trait/TestMismatchedCTrait.h>
+
+#include "MockMismatchedSchemaSinkAndSource.h"
+
+#include "MockTestBTrait.h"
+
+#include "MockPlatformClocks.h"
+
+#include <new>
+#include <map>
+#include <set>
+#include <algorithm>
+#include <set>
+#include <string>
+#include <iterator>
+
+#if WEAVE_SYSTEM_CONFIG_USE_LWIP
+#include <lwip/init.h>
+#endif // WEAVE_SYSTEM_CONFIG_USE_LWIP
+
+using namespace nl;
+using namespace nl::Weave::TLV;
+using namespace nl::Weave::Profiles::DataManagement;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// System/Platform definitions
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace Private {
+
+System::Error SetClock_RealTime(uint64_t newCurTime)
+{
+ return WEAVE_SYSTEM_NO_ERROR;
+}
+
+static System::Error GetClock_RealTime(uint64_t & curTime)
+{
+ curTime = 0x42; // arbitrary non-zero value.
+ return WEAVE_SYSTEM_NO_ERROR;
+}
+
+} // namespace Private
+
+
+namespace nl {
+namespace Weave {
+namespace Profiles {
+namespace WeaveMakeManagedNamespaceIdentifier(DataManagement, kWeaveManagedNamespaceDesignation_Current) {
+namespace Platform {
+ // for unit tests, the dummy critical section is sufficient.
+ void CriticalSectionEnter()
+ {
+ return;
+ }
+
+ void CriticalSectionExit()
+ {
+ return;
+ }
+} // Platform
+} // WeaveMakeManagedNamespaceIdentifier(DataManagement, kWeaveManagedNamespaceDesignation_Current)
+}
+}
+}
+
+static SubscriptionEngine *gSubscriptionEngine;
+
+SubscriptionEngine * SubscriptionEngine::GetInstance()
+{
+ return gSubscriptionEngine;
+}
+
+static void TestTdmStatic_SingleLeafHandle(nlTestSuite *inSuite, void *inContext);
+
+// Test Suite
+
+/**
+ * Test Suite that lists all the test functions.
+ */
+static const nlTest sTests[] = {
+ // Tests the static schema portions of TDM
+ NL_TEST_DEF("Test Tdm (Static schema): Single leaf handle", TestTdmStatic_SingleLeafHandle),
+ NL_TEST_SENTINEL()
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Testing NotificationEngine + TraitData
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+namespace nl {
+namespace Weave {
+namespace Profiles {
+namespace WeaveMakeManagedNamespaceIdentifier(DataManagement, kWeaveManagedNamespaceDesignation_Current) {
+
+using namespace Schema::Nest::Test::Trait;
+
+class TestTdmSource : public TraitDataSource {
+public:
+ TestTdmSource();
+ void Reset();
+
+private:
+ WEAVE_ERROR GetLeafData(PropertyPathHandle aLeafHandle, uint64_t aTagToWrite, TLVWriter &aWriter);
+ WEAVE_ERROR GetNextDictionaryItemKey(PropertyPathHandle aDictionaryHandle, uintptr_t &aContext, PropertyDictionaryKey &aKey);
+
+public:
+ //
+ // The logical key here is the field 'da', which is of type uint32.
+ //
+ WdmDictionary<uint32_t, TestHTrait::StructDictionary> _dict;
+};
+
+TestTdmSource::TestTdmSource()
+ : TraitDataSource(&TestHTrait::TraitSchema)
+{
+ // Using the Modify method to insert and modify.
+ _dict.ModifyItem(1, [](auto &item) {
+ item._logical_key = 10;
+ item._data.da = 10;
+ item._data.db = 1;
+ item._data.dc = 2;
+ });
+
+ _dict.ModifyItem(2, [](auto &item) {
+ item._logical_key = 20;
+ item._data.da = 20;
+ item._data.db = 3;
+ item._data.dc = 4;
+ });
+}
+
+void TestTdmSource::Reset()
+{
+}
+
+WEAVE_ERROR TestTdmSource::GetNextDictionaryItemKey(PropertyPathHandle aDictionaryHandle, uintptr_t &aContext, PropertyDictionaryKey &aKey)
+{
+ static WdmDictionary<uint32_t, TestHTrait::StructDictionary>::DictKeyTableTypeIterator it;
+
+ if (aContext == 0) {
+ it = _dict.GetDictKeyTable().begin();
+ }
+ else {
+ it++;
+ }
+
+ aContext = (uintptr_t)⁢
+
+ if (it == _dict.GetDictKeyTable().end()) {
+ return WEAVE_END_OF_INPUT;
+ }
+ else {
+ aKey = it->_dict_key;
+ }
+
+ return WEAVE_NO_ERROR;
+}
+
+WEAVE_ERROR TestTdmSource::GetLeafData(PropertyPathHandle aLeafHandle, uint64_t aTagToWrite, TLVWriter &aWriter)
+{
+ WEAVE_ERROR err = WEAVE_NO_ERROR;
+ PropertyPathHandle dictionaryItemHandle = kNullPropertyPathHandle;
+
+ if (GetSchemaEngine()->IsInDictionary(aLeafHandle, dictionaryItemHandle)) {
+ PropertyPathHandle dictionaryHandle = GetSchemaEngine()->GetParent(dictionaryItemHandle);
+ PropertyDictionaryKey key = GetPropertyDictionaryKey(dictionaryItemHandle);
+
+ if (dictionaryHandle == TestHTrait::kPropertyHandle_L) {
+ auto it = _dict.GetDictKeyTable().find(key);
+
+ if (it != _dict.GetDictKeyTable().end()) {
+ TestHTrait::StructDictionary item = it->_data;
+ uint32_t val;
+
+ switch (GetPropertySchemaHandle(aLeafHandle)) {
+ case TestHTrait::kPropertyHandle_L_Value_Da:
+ WeaveLogDetail(DataManagement, "[TestTdmSource::GetLeafData] >> l[%u].da = %u", key, item.da);
+ val = item.da;
+ break;
+
+ case TestHTrait::kPropertyHandle_L_Value_Db:
+ WeaveLogDetail(DataManagement, "[TestTdmSource::GetLeafData] >> l[%u].db = %u", key, item.da);
+ val = item.db;
+ break;
+
+ case TestHTrait::kPropertyHandle_L_Value_Dc:
+ WeaveLogDetail(DataManagement, "[TestTdmSource::GetLeafData] >> l[%u].dc = %u", key, item.da);
+ val = item.dc;
+ break;
+
+ default:
+ WeaveLogError(DataManagement, "Unknown handle passed in!");
+ return WEAVE_ERROR_INVALID_ARGUMENT;
+ break;
+ }
+
+ err = aWriter.Put(aTagToWrite, val);
+ SuccessOrExit(err);
+ }
+ else {
+ WeaveLogError(DataManagement, "Requested key %u for dictionary handle %u that doesn't exist!", key, dictionaryHandle);
+ return WEAVE_ERROR_INVALID_ARGUMENT;
+ }
+ }
+ }
+
+exit:
+ return err;
+}
+
+
+class TestTdmSink : public TraitDataSink {
+public:
+ TestTdmSink();
+ void Reset();
+ void DumpChangeSets();
+
+private:
+ WEAVE_ERROR OnEvent(uint16_t aType, void *aInParam);
+ WEAVE_ERROR SetLeafData(PropertyPathHandle aLeafHandle, nl::Weave::TLV::TLVReader &aReader);
+
+public:
+ //
+ // Main canonical store of truth
+ //
+ WdmDictionary<uint32_t, TestHTrait::StructDictionary> _dict;
+
+ //
+ // Staged changes that are accrued and applied at the end
+ // of a logical change
+ //
+ WdmDictionary<uint32_t, TestHTrait::StructDictionary> _stagedDict;
+
+ //
+ // Staged set of deleted path handles
+ //
+ std::set<PropertyPathHandle> _deletedDictItems;
+
+ //
+ // Staged item that gets appended to the staged dictionary on completion
+ // of the modification.
+ WdmDictionary<uint32_t, TestHTrait::StructDictionary>::Item _stagedDictItem;
+
+ bool _isReplaceOperation;
+};
+
+TestTdmSink::TestTdmSink()
+ : TraitDataSink(&TestHTrait::TraitSchema)
+{
+}
+
+void TestTdmSink::Reset()
+{
+ ClearVersion();
+ _stagedDict.GetDictKeyTable().clear();
+ _deletedDictItems.clear();
+}
+
+WEAVE_ERROR TestTdmSink::OnEvent(uint16_t aType, void *aInParam)
+{
+ InEventParam *inParam = static_cast<InEventParam *>(aInParam);
+
+ switch (aType) {
+ case kEventChangeBegin:
+ _stagedDict.GetDictKeyTable().clear();
+ _deletedDictItems.clear();
+ _isReplaceOperation = false;
+ break;
+
+ case kEventDictionaryItemDelete:
+ WeaveLogError(DataManagement, "[TestTdmSink::OnEvent] Deleting %u:%u",
+ GetPropertyDictionaryKey(inParam->mDictionaryItemDelete.mTargetHandle), GetPropertySchemaHandle(inParam->mDictionaryItemDelete.mTargetHandle));
+
+
+ if (GetPropertySchemaHandle(inParam->mDictionaryItemDelete.mTargetHandle) == TestHTrait::kPropertyHandle_L) {
+ _deletedDictItems.insert(GetPropertyDictionaryKey(inParam->mDictionaryItemDelete.mTargetHandle));
+ }
+
+ break;
+
+ case kEventDictionaryItemModifyBegin:
+ {
+ auto it = _dict.GetDictKeyTable().find(GetPropertyDictionaryKey(inParam->mDictionaryItemModifyBegin.mTargetHandle));
+
+ WeaveLogError(DataManagement, "[TestTdmSink::OnEvent] Adding/Modifying %u:%u", GetPropertyDictionaryKey(inParam->mDictionaryItemModifyBegin.mTargetHandle), GetPropertySchemaHandle(inParam->mDictionaryItemModifyBegin.mTargetHandle));
+
+ _stagedDictItem._dict_key = GetPropertyDictionaryKey(inParam->mDictionaryItemModifyBegin.mTargetHandle);
+ if (it != _dict.GetDictKeyTable().end()) {
+ _stagedDictItem = *it;
+ }
+
+ break;
+ }
+
+ case kEventDictionaryItemModifyEnd:
+ _stagedDict.GetDictKeyTable().insert(_stagedDictItem);
+ break;
+
+ case kEventDictionaryReplaceBegin:
+ WeaveLogError(DataManagement, "[TestTdmSink::OnEvent] Replacing %u:%u", GetPropertyDictionaryKey(inParam->mDictionaryReplaceBegin.mTargetHandle), GetPropertySchemaHandle(inParam->mDictionaryReplaceBegin.mTargetHandle));
+
+ _isReplaceOperation = true;
+ _stagedDict.GetDictKeyTable().clear();
+ break;
+
+
+ case kEventChangeEnd:
+ WeaveLogError(DataManagement, "[TestTdmSink::OnEvent] Change End");
+
+ //
+ // Delete items
+ //
+ for (auto it = _deletedDictItems.begin(); it != _deletedDictItems.end(); it++) {
+ _dict.GetDictKeyTable().erase(*it);
+ }
+
+ _dict.ItemsAdded(_stagedDict, [](auto &i) {
+ printf("A %u: %u\n", i->_logical_key, i->_data.db);
+ }, true);
+
+ //
+ // Only do the negative intersection if we're doing a full replace on the dictionary. Otherwise, we'll
+ // unintentionally remove elements.
+ //
+ if (_isReplaceOperation) {
+ _dict.ItemsRemoved(_stagedDict, [](auto &i) {
+ printf("R %u: %u\n", i->_logical_key, i->_data.db);
+ }, true);
+ }
+
+ _dict.ItemsModified(_stagedDict, [](auto &oldd, auto &newd) {
+ printf("M %u: %u %u\n", oldd->_logical_key, oldd->_data.db, newd->_data.db);
+ }, true);
+
+ break;
+ }
+
+ return WEAVE_NO_ERROR;
+}
+
+WEAVE_ERROR TestTdmSink::SetLeafData(PropertyPathHandle aHandle, TLVReader &aReader)
+{
+ WEAVE_ERROR err = WEAVE_NO_ERROR;
+ PropertySchemaHandle schemaHandle = GetPropertySchemaHandle(aHandle);
+
+ WeaveLogError(DataManagement, "[TestTdmSink::SetLeafData] << %u:%u", GetPropertyDictionaryKey(aHandle), GetPropertySchemaHandle(aHandle));
+
+ switch (schemaHandle) {
+ case TestHTrait::kPropertyHandle_L_Value_Da:
+ {
+ err = aReader.Get(_stagedDictItem._data.da);
+ SuccessOrExit(err);
+
+ _stagedDictItem._logical_key = _stagedDictItem._data.da;
+ break;
+ }
+
+ case TestHTrait::kPropertyHandle_L_Value_Db:
+ {
+ err = aReader.Get(_stagedDictItem._data.db);
+ SuccessOrExit(err);
+ break;
+ }
+
+ case TestHTrait::kPropertyHandle_L_Value_Dc:
+ {
+ err = aReader.Get(_stagedDictItem._data.dc);
+ SuccessOrExit(err);
+ break;
+ }
+ }
+
+exit:
+ return err;
+}
+
+class TestTdm {
+public:
+ TestTdm();
+
+ int Setup();
+ int Teardown();
+ int Reset();
+ int BuildAndProcessNotify();
+
+ void TestTdmStatic_SingleLeafHandle(nlTestSuite *inSuite);
+
+private:
+ SubscriptionHandler *mSubHandler;
+ SubscriptionClient *mSubClient;
+ NotificationEngine *mNotificationEngine;
+
+ SubscriptionEngine mSubscriptionEngine;
+ WeaveExchangeManager mExchangeMgr;
+ SingleResourceSourceTraitCatalog::CatalogItem mSourceCatalogStore[5];
+ SingleResourceSourceTraitCatalog mSourceCatalog;
+ SingleResourceSinkTraitCatalog::CatalogItem mSinkCatalogStore[5];
+ SingleResourceSinkTraitCatalog mSinkCatalog;
+ TestTdmSource mTestTdmSource;
+ TestTdmSink mTestTdmSink;
+ Binding *mClientBinding;
+ uint32_t mTestCase;
+ WEAVE_ERROR AllocateBuffer(uint32_t desiredSize, uint32_t minSize);
+};
+
+TestTdm::TestTdm()
+ : mSourceCatalog(ResourceIdentifier(ResourceIdentifier::SELF_NODE_ID), mSourceCatalogStore, 5),
+ mSinkCatalog(ResourceIdentifier(ResourceIdentifier::SELF_NODE_ID), mSinkCatalogStore, 5),
+ mClientBinding(NULL)
+{
+ mTestCase = 0;
+}
+
+int TestTdm::Setup()
+{
+ WEAVE_ERROR err = WEAVE_NO_ERROR;
+ TraitDataHandle testTdmSourceHandle;
+ TraitDataHandle testTdmSinkHandle;
+ SubscriptionHandler::TraitInstanceInfo *traitInstance = NULL;
+
+ gSubscriptionEngine = &mSubscriptionEngine;
+
+ // Initialize SubEngine and set it up
+ err = mSubscriptionEngine.Init(&ExchangeMgr, NULL, NULL);
+ SuccessOrExit(err);
+
+ err = mSubscriptionEngine.EnablePublisher(NULL, &mSourceCatalog);
+ SuccessOrExit(err);
+
+ // Get a sub handler and prime it to the right state
+ err = mSubscriptionEngine.NewSubscriptionHandler(&mSubHandler);
+ SuccessOrExit(err);
+
+ mSubHandler->mBinding = ExchangeMgr.NewBinding();
+ mSubHandler->mBinding->BeginConfiguration().Transport_UDP();
+
+ mClientBinding = ExchangeMgr.NewBinding();
+
+ err = mSubscriptionEngine.NewClient(&mSubClient, mClientBinding, NULL, NULL, &mSinkCatalog, 0);
+ SuccessOrExit(err);
+
+ mNotificationEngine = &mSubscriptionEngine.mNotificationEngine;
+
+ mSourceCatalog.Add(0, &mTestTdmSource, testTdmSourceHandle);
+ mSinkCatalog.Add(0, &mTestTdmSink, testTdmSinkHandle);
+
+ traitInstance = mSubscriptionEngine.mTraitInfoPool;
+ mSubHandler->mTraitInstanceList = traitInstance;
+ mSubHandler->mNumTraitInstances++;
+ ++(SubscriptionEngine::GetInstance()->mNumTraitInfosInPool);
+
+ traitInstance->Init();
+ traitInstance->mTraitDataHandle = testTdmSourceHandle;
+ traitInstance->mRequestedVersion = 1;
+
+exit:
+ if (err != WEAVE_NO_ERROR) {
+ WeaveLogError(DataManagement, "Error setting up test: %d", err);
+ }
+
+ return err;
+}
+
+void TestTdm::TestTdmStatic_SingleLeafHandle(nlTestSuite *inSuite)
+{
+ WEAVE_ERROR err = WEAVE_NO_ERROR;
+ bool testPass = false;
+
+ Reset();
+
+ //
+ // Replace
+ //
+ mTestTdmSource.Lock();
+ mTestTdmSource.SetDirty(TestHTrait::kPropertyHandle_L);
+ mTestTdmSource.Unlock();
+
+ err = BuildAndProcessNotify();
+ SuccessOrExit(err);
+
+ printf("Equal: %d\n", mTestTdmSource._dict.IsEqual(mTestTdmSink._dict));
+
+ //
+ // Delete Item
+ //
+ mTestTdmSource.Lock();
+ mTestTdmSource._dict.GetDictKeyTable().erase(0);
+ mTestTdmSource.DeleteKey(CreatePropertyPathHandle(TestHTrait::kPropertyHandle_L_Value, 0));
+ mTestTdmSource.Unlock();
+
+ err = BuildAndProcessNotify();
+ SuccessOrExit(err);
+
+ printf("Equal: %d\n", mTestTdmSource._dict.IsEqual(mTestTdmSink._dict));
+
+ //
+ // Add Item
+ //
+ mTestTdmSource.Lock();
+ mTestTdmSource._dict.ModifyItem(10, [](auto &i) {
+ i._logical_key = 300;
+ i._data.da = 300;
+ i._data.db = 30;
+ i._data.dc = 30;
+ });
+
+ mTestTdmSource.Lock();
+ mTestTdmSource.SetDirty(CreatePropertyPathHandle(TestHTrait::kPropertyHandle_L_Value, 10));
+ mTestTdmSource.Unlock();
+
+ err = BuildAndProcessNotify();
+ SuccessOrExit(err);
+
+ printf("Equal: %d\n", mTestTdmSource._dict.IsEqual(mTestTdmSink._dict));
+
+ //
+ // Change dictionary keys, but keep logical keys + data stable.
+ //
+ mTestTdmSource.Lock();
+ mTestTdmSource._dict.ModifyItem(10, [](auto &i) {
+ i._dict_key = 100;
+ });
+
+ mTestTdmSource._dict.ModifyItem(1, [](auto &i) {
+ i._dict_key = 1000;
+ });
+
+ mTestTdmSource.SetDirty(TestHTrait::kPropertyHandle_L);
+ mTestTdmSource.Unlock();
+
+ err = BuildAndProcessNotify();
+ SuccessOrExit(err);
+
+ printf("Equal: %d\n", mTestTdmSource._dict.IsEqual(mTestTdmSink._dict));
+
+
+exit:
+ NL_TEST_ASSERT(inSuite, testPass);
+}
+
+int TestTdm::Teardown()
+{
+ WEAVE_ERROR err = WEAVE_NO_ERROR;
+
+ if (mClientBinding != NULL)
+ {
+ mClientBinding->Release();
+ mClientBinding = NULL;
+ }
+
+ return err;
+}
+
+int TestTdm::Reset()
+{
+ WEAVE_ERROR err = WEAVE_NO_ERROR;
+
+ mSubHandler->MoveToState(SubscriptionHandler::kState_SubscriptionEstablished_Idle);
+
+ mTestTdmSink.Reset();
+ mTestTdmSource.Reset();
+
+ mNotificationEngine->mGraphSolver.ClearDirty();
+
+ return err;
+}
+
+int TestTdm::BuildAndProcessNotify()
+{
+ bool isSubscriptionClean;
+ NotificationEngine::NotifyRequestBuilder notifyRequest;
+ NotificationRequest::Parser notify;
+ PacketBuffer *buf = NULL;
+ TLVWriter writer;
+ TLVReader reader;
+ TLVType dummyType1, dummyType2;
+ WEAVE_ERROR err = WEAVE_NO_ERROR;
+ bool neWriteInProgress = false;
+ uint32_t maxNotificationSize = 0;
+ uint32_t maxPayloadSize = 0;
+
+ maxNotificationSize = mSubHandler->GetMaxNotificationSize();
+
+ err = mSubHandler->mBinding->AllocateRightSizedBuffer(buf, maxNotificationSize, WDM_MIN_NOTIFICATION_SIZE, maxPayloadSize);
+ SuccessOrExit(err);
+
+ err = notifyRequest.Init(buf, &writer, mSubHandler, maxPayloadSize);
+ SuccessOrExit(err);
+
+ err = mNotificationEngine->BuildSingleNotifyRequestDataList(mSubHandler, notifyRequest, isSubscriptionClean, neWriteInProgress);
+ SuccessOrExit(err);
+
+ if (neWriteInProgress)
+ {
+ err = notifyRequest.MoveToState(NotificationEngine::kNotifyRequestBuilder_Idle);
+ SuccessOrExit(err);
+
+ reader.Init(buf);
+
+ err = reader.Next();
+ SuccessOrExit(err);
+
+ notify.Init(reader);
+
+ err = notify.CheckSchemaValidity();
+ SuccessOrExit(err);
+
+ // Enter the struct
+ err = reader.EnterContainer(dummyType1);
+ SuccessOrExit(err);
+
+ // SubscriptionId
+ err = reader.Next();
+ SuccessOrExit(err);
+
+ err = reader.Next();
+ SuccessOrExit(err);
+
+ VerifyOrExit(nl::Weave::TLV::kTLVType_Array == reader.GetType(), err = WEAVE_ERROR_WRONG_TLV_TYPE);
+
+ err = reader.EnterContainer(dummyType2);
+ SuccessOrExit(err);
+
+ err = mSubClient->ProcessDataList(reader);
+ SuccessOrExit(err);
+ }
+ else
+ {
+ WeaveLogDetail(DataManagement, "nothing has been written");
+ }
+
+exit:
+ if (buf) {
+ PacketBuffer::Free(buf);
+ }
+
+ return err;
+}
+
+WEAVE_ERROR TestTdm::AllocateBuffer(uint32_t desiredSize, uint32_t minSize)
+{
+ WEAVE_ERROR err = WEAVE_NO_ERROR;
+ uint32_t maxPayloadSize = 0;
+ PacketBuffer *buf = NULL;
+
+
+ err = mSubHandler->mBinding->AllocateRightSizedBuffer(buf, desiredSize, minSize, maxPayloadSize);
+ SuccessOrExit(err);
+
+exit:
+
+ if (buf)
+ {
+ PacketBuffer::Free(buf);
+ }
+
+ return err;
+}
+
+} // WeaveMakeManagedNamespaceIdentifier(DataManagement, kWeaveManagedNamespaceDesignation_Current)
+}
+}
+}
+
+TestTdm *gTestTdm;
+
+/**
+ * Set up the test suite.
+ */
+static int TestSetup(void *inContext)
+{
+ static TestTdm testTdm;
+ gTestTdm = &testTdm;
+
+ return testTdm.Setup();
+}
+
+/**
+ * Tear down the test suite.
+ */
+static int TestTeardown(void *inContext)
+{
+ return gTestTdm->Teardown();
+}
+
+static void TestTdmStatic_SingleLeafHandle(nlTestSuite *inSuite, void *inContext)
+{
+ gTestTdm->TestTdmStatic_SingleLeafHandle(inSuite);
+}
+
+/**
+ * Main
+ */
+int main(int argc, char *argv[])
+{
+#if WEAVE_SYSTEM_CONFIG_USE_LWIP
+ tcpip_init(NULL, NULL);
+#endif // WEAVE_SYSTEM_CONFIG_USE_LWIP
+
+ MockPlatform::gMockPlatformClocks.GetClock_RealTime = Private::GetClock_RealTime;
+ MockPlatform::gMockPlatformClocks.SetClock_RealTime = Private::SetClock_RealTime;
+
+ nlTestSuite theSuite = {
+ "weave-tdm",
+ &sTests[0],
+ TestSetup,
+ TestTeardown
+ };
+
+ // Generate machine-readable, comma-separated value (CSV) output.
+ nl_test_set_output_style(OUTPUT_CSV);
+
+ // Run test suit against one context
+ nlTestRunner(&theSuite, NULL);
+
+ return nlTestRunnerStats(&theSuite);
+}
diff --git a/src/test-apps/schema/nest/test/trait/TestHTrait.h b/src/test-apps/schema/nest/test/trait/TestHTrait.h
index c3badd4..d7a1d75 100644
--- a/src/test-apps/schema/nest/test/trait/TestHTrait.h
+++ b/src/test-apps/schema/nest/test/trait/TestHTrait.h
@@ -186,6 +186,10 @@
uint32_t db;
uint32_t dc;
+ bool operator ==(const StructDictionary& a) const {
+ return (memcmp(&a, this, sizeof(StructDictionary)) == 0);
+ }
+
static const nl::SchemaFieldDescriptor FieldSchema;
};