Skip to content

Commit b6db541

Browse files
Address review comments
Remove DefaultInstance() and weak cluster init function and instead provide a DefaultThreadNetworkDirectoryServer sub-class that's easy to instantiate with the default storage implementation. Roll back in-memory state on persistent storage failure and add tests for this. Add documentation about ByteSpan lifetimes in OperationalDataset class. Add comments to MTRDemuxingStorage.mm
1 parent 63ac159 commit b6db541

9 files changed

+143
-84
lines changed

examples/network-manager-app/linux/main.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
#include <app/clusters/thread-network-directory-server/thread-network-directory-server.h>
2020
#include <app/clusters/wifi-network-management-server/wifi-network-management-server.h>
2121
#include <lib/core/CHIPSafeCasts.h>
22+
#include <lib/support/CodeUtils.h>
2223
#include <lib/support/Span.h>
2324

25+
#include <optional>
26+
2427
using namespace chip;
2528
using namespace chip::app;
2629
using namespace chip::app::Clusters;
@@ -33,6 +36,13 @@ ByteSpan ByteSpanFromCharSpan(CharSpan span)
3336
return ByteSpan(Uint8::from_const_char(span.data()), span.size());
3437
}
3538

39+
std::optional<DefaultThreadNetworkDirectoryServer> gThreadNetworkDirectoryServer;
40+
void emberAfThreadNetworkDirectoryClusterServerInitCallback(chip::EndpointId endpoint)
41+
{
42+
VerifyOrDie(!gThreadNetworkDirectoryServer.has_value());
43+
gThreadNetworkDirectoryServer.emplace(endpoint).Init();
44+
}
45+
3646
int main(int argc, char * argv[])
3747
{
3848
if (ChipLinuxAppInit(argc, argv) != 0)

src/app/chip_data_model.gni

-1
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,6 @@ template("chip_data_model") {
392392
]
393393
} else if (cluster == "thread-network-directory-server") {
394394
sources += [
395-
"${_app_root}/clusters/${cluster}/${cluster}-instance.cpp",
396395
"${_app_root}/clusters/${cluster}/${cluster}.cpp",
397396
"${_app_root}/clusters/${cluster}/${cluster}.h",
398397
"${_app_root}/clusters/${cluster}/DefaultThreadNetworkDirectoryStorage.cpp",

src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.cpp

+10-3
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,18 @@ CHIP_ERROR DefaultThreadNetworkDirectoryStorage::RemoveNetwork(const ExtendedPan
118118
VerifyOrReturnError(FindNetwork(exPanId, index), CHIP_ERROR_NOT_FOUND);
119119

120120
// Move subsequent elements down to fill the deleted slot
121-
for (mCount--; index < mCount; index++)
121+
index_t subsequent = --mCount - index;
122+
auto * element = &mExtendedPanIds[index];
123+
memmove(element, element + 1, subsequent * sizeof(*element));
124+
CHIP_ERROR err = StoreIndex();
125+
if (err != CHIP_NO_ERROR)
122126
{
123-
mExtendedPanIds[index] = mExtendedPanIds[index + 1];
127+
// Roll back the change to our in-memory state
128+
memmove(element + 1, element, subsequent * sizeof(*element));
129+
mExtendedPanIds[index] = exPanId;
130+
mCount++;
131+
return err;
124132
}
125-
ReturnErrorOnFailure(StoreIndex());
126133

127134
// Delete the dataset itself. Ignore errors since we successfully updated the index.
128135
StorageKeyName key = DefaultStorageKeyAllocator::ThreadNetworkDirectoryDataset(exPanId.AsNumber());

src/app/clusters/thread-network-directory-server/thread-network-directory-server-instance.cpp

-62
This file was deleted.

src/app/clusters/thread-network-directory-server/thread-network-directory-server.h

+18-11
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
#include <app-common/zap-generated/cluster-objects.h>
2121
#include <app/AttributeAccessInterface.h>
2222
#include <app/CommandHandlerInterface.h>
23+
#include <app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.h>
2324
#include <app/clusters/thread-network-directory-server/ThreadNetworkDirectoryStorage.h>
25+
#include <app/server/Server.h>
2426
#include <lib/core/CHIPError.h>
2527

2628
#include <optional>
@@ -32,17 +34,6 @@ namespace Clusters {
3234
class ThreadNetworkDirectoryServer : private AttributeAccessInterface, private CommandHandlerInterface
3335
{
3436
public:
35-
/*
36-
* Returns a default instance of this server cluster. Only available if there is
37-
* exactly one instance of this cluster defined in the application zap file.
38-
* Must not be called before the data model has been initialized.
39-
*
40-
* To support more than one cluster instance, or to customize how the cluster server
41-
* is instantiated, the application should override the callback function
42-
* emberAfThreadNetworkDirectoryClusterServerInitCallback(chip::EndpointId).
43-
*/
44-
static ThreadNetworkDirectoryServer & DefaultInstance();
45-
4637
ThreadNetworkDirectoryServer(EndpointId endpoint, ThreadNetworkDirectoryStorage & storage);
4738
~ThreadNetworkDirectoryServer();
4839

@@ -74,6 +65,22 @@ class ThreadNetworkDirectoryServer : private AttributeAccessInterface, private C
7465
ThreadNetworkDirectoryStorage & mStorage;
7566
};
7667

68+
/**
69+
* A ThreadNetworkDirectoryServer using DefaultThreadNetworkDirectoryStorage.
70+
*/
71+
class DefaultThreadNetworkDirectoryServer final : public ThreadNetworkDirectoryServer
72+
{
73+
public:
74+
DefaultThreadNetworkDirectoryServer(EndpointId endpoint,
75+
PersistentStorageDelegate & storage = Server::GetInstance().GetPersistentStorage()) :
76+
ThreadNetworkDirectoryServer(endpoint, mStorage),
77+
mStorage(storage)
78+
{}
79+
80+
private:
81+
DefaultThreadNetworkDirectoryStorage mStorage;
82+
};
83+
7784
} // namespace Clusters
7885
} // namespace app
7986
} // namespace chip

src/app/tests/TestDefaultThreadNetworkDirectoryStorage.cpp

+83-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include <app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.h>
1919
#include <lib/core/CHIPPersistentStorageDelegate.h>
20+
#include <lib/support/DefaultStorageKeyAllocator.h>
2021
#include <lib/support/TestPersistentStorageDelegate.h>
2122

2223
#include <cstdint>
@@ -79,7 +80,7 @@ TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestAddRemoveNetworks)
7980
const uint8_t datasetA[] = { 1 };
8081
const uint8_t updatedDatasetA[] = { 0x11, 0x11 };
8182
const ThreadNetworkDirectoryStorage::ExtendedPanId panB(UINT64_C(0xe475cafef00df00d));
82-
uint8_t datasetB[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };
83+
const uint8_t datasetB[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };
8384
uint8_t mutableBytes[ThreadNetworkDirectoryStorage::kMaxThreadDatasetLen];
8485

8586
// Add Pan A only
@@ -207,4 +208,85 @@ TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestStorageOverflow)
207208
EXPECT_EQ(storage.AddOrUpdateNetwork(ThreadNetworkDirectoryStorage::ExtendedPanId(0u), ByteSpan(dataset)), CHIP_NO_ERROR);
208209
}
209210

211+
TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestAddNetworkStorageFailure)
212+
{
213+
const ThreadNetworkDirectoryStorage::ExtendedPanId panA(UINT64_C(0xd00fd00fdeadbeef));
214+
const uint8_t datasetA[] = { 1 };
215+
const ThreadNetworkDirectoryStorage::ExtendedPanId panB(UINT64_C(0xe475cafef00df00d));
216+
uint8_t datasetB[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };
217+
218+
EXPECT_EQ(storage.AddOrUpdateNetwork(panA, ByteSpan(datasetA)), CHIP_NO_ERROR);
219+
220+
// Make the storage delegate return CHIP_ERROR_PERSISTED_STORAGE_FAILED for writes.
221+
// Attempting to add a network should fail and state should be unaffected.
222+
persistentStorageDelegate.SetRejectWrites(true);
223+
EXPECT_EQ(storage.AddOrUpdateNetwork(panB, ByteSpan(datasetB)), CHIP_ERROR_PERSISTED_STORAGE_FAILED);
224+
EXPECT_TRUE(storage.ContainsNetwork(panA));
225+
EXPECT_FALSE(storage.ContainsNetwork(panB));
226+
{
227+
auto * iterator = storage.IterateNetworkIds();
228+
EXPECT_EQ(iterator->Count(), 1u);
229+
iterator->Release();
230+
}
231+
}
232+
233+
TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestAddNetworkStorageWriteIndexFailure)
234+
{
235+
const ThreadNetworkDirectoryStorage::ExtendedPanId panA(UINT64_C(0xd00fd00fdeadbeef));
236+
const uint8_t datasetA[] = { 1 };
237+
const ThreadNetworkDirectoryStorage::ExtendedPanId panB(UINT64_C(0xe475cafef00df00d));
238+
uint8_t datasetB[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };
239+
240+
EXPECT_EQ(storage.AddOrUpdateNetwork(panA, ByteSpan(datasetA)), CHIP_NO_ERROR);
241+
242+
// White box test: Poison the index key specifically, so the dataset
243+
// can be written but the subsequent update of the index fails.
244+
// Attempting to add a network should fail and state should be unaffected.
245+
StorageKeyName indexKey = DefaultStorageKeyAllocator::ThreadNetworkDirectoryIndex();
246+
persistentStorageDelegate.AddPoisonKey(indexKey.KeyName());
247+
EXPECT_EQ(storage.AddOrUpdateNetwork(panB, ByteSpan(datasetB)), CHIP_ERROR_PERSISTED_STORAGE_FAILED);
248+
EXPECT_TRUE(storage.ContainsNetwork(panA));
249+
EXPECT_FALSE(storage.ContainsNetwork(panB));
250+
{
251+
auto * iterator = storage.IterateNetworkIds();
252+
EXPECT_EQ(iterator->Count(), 1u);
253+
iterator->Release();
254+
}
255+
}
256+
257+
TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestRemoveNetworkStorageFailure)
258+
{
259+
const ThreadNetworkDirectoryStorage::ExtendedPanId panA(UINT64_C(0xd00f0001));
260+
const ThreadNetworkDirectoryStorage::ExtendedPanId panB(UINT64_C(0xd00f0002));
261+
const ThreadNetworkDirectoryStorage::ExtendedPanId panC(UINT64_C(0xd00f0003));
262+
const uint8_t dataset[] = { 1 };
263+
264+
// Just in case the test is compiled with CHIP_CONFIG_MAX_FABRICS == 1
265+
bool canStorePanC = (storage.Capacity() >= 3);
266+
267+
// Add Pans A, B, and C
268+
EXPECT_EQ(storage.AddOrUpdateNetwork(panA, ByteSpan(dataset)), CHIP_NO_ERROR);
269+
EXPECT_EQ(storage.AddOrUpdateNetwork(panB, ByteSpan(dataset)), CHIP_NO_ERROR);
270+
if (canStorePanC)
271+
{
272+
EXPECT_EQ(storage.AddOrUpdateNetwork(panC, ByteSpan(dataset)), CHIP_NO_ERROR);
273+
}
274+
275+
// Make the storage delegate return CHIP_ERROR_PERSISTED_STORAGE_FAILED for writes.
276+
// Attempting to remove a network should fail and state should be unaffected.
277+
persistentStorageDelegate.SetRejectWrites(true);
278+
EXPECT_EQ(storage.RemoveNetwork(panA), CHIP_ERROR_PERSISTED_STORAGE_FAILED);
279+
EXPECT_TRUE(storage.ContainsNetwork(panA));
280+
EXPECT_TRUE(storage.ContainsNetwork(panB));
281+
if (canStorePanC)
282+
{
283+
EXPECT_TRUE(storage.ContainsNetwork(panC));
284+
}
285+
{
286+
auto * iterator = storage.IterateNetworkIds();
287+
EXPECT_EQ(iterator->Count(), canStorePanC ? 3u : 2u);
288+
iterator->Release();
289+
}
290+
}
291+
210292
} // namespace

src/darwin/Framework/CHIP/MTRDemuxingStorage.mm

+2
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ static bool IsMemoryOnlyGlobalKey(NSString * key)
162162
// We do not expect to see the "g/icd/cic" key; that's only used for an ICD
163163
// that sends check-in messages.
164164

165+
// We do not expect to see the "g/tnd/*" Thread Network Directory keys.
166+
165167
return false;
166168
}
167169

src/lib/support/TestPersistentStorageDelegate.h

+10-4
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate
134134
*/
135135
virtual void AddPoisonKey(const std::string & key) { mPoisonKeys.insert(key); }
136136

137+
/**
138+
* Allows subsequent writes to be rejected for unit testing purposes.
139+
*/
140+
virtual void SetRejectWrites(bool rejectWrites) { mRejectWrites = rejectWrites; }
141+
137142
/**
138143
* @brief Clear all "poison keys"
139144
*
@@ -233,8 +238,8 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate
233238

234239
virtual CHIP_ERROR SyncSetKeyValueInternal(const char * key, const void * value, uint16_t size)
235240
{
236-
// Make sure poison keys are not accessed
237-
if (mPoisonKeys.find(std::string(key)) != mPoisonKeys.end())
241+
// Make sure writes are allowed and poison keys are not accessed
242+
if (mRejectWrites || mPoisonKeys.find(std::string(key)) != mPoisonKeys.end())
238243
{
239244
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
240245
}
@@ -259,8 +264,8 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate
259264

260265
virtual CHIP_ERROR SyncDeleteKeyValueInternal(const char * key)
261266
{
262-
// Make sure poison keys are not accessed
263-
if (mPoisonKeys.find(std::string(key)) != mPoisonKeys.end())
267+
// Make sure writes are allowed and poison keys are not accessed
268+
if (mRejectWrites || mPoisonKeys.find(std::string(key)) != mPoisonKeys.end())
264269
{
265270
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
266271
}
@@ -273,6 +278,7 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate
273278

274279
std::map<std::string, std::vector<uint8_t>> mStorage;
275280
std::set<std::string> mPoisonKeys;
281+
bool mRejectWrites = false;
276282
LoggingLevel mLoggingLevel = LoggingLevel::kDisabled;
277283
};
278284

src/lib/support/ThreadOperationalDataset.h

+10-2
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ class OperationalDataset
121121
* This method returns a const ByteSpan to the extended PAN ID in the dataset.
122122
* This can be used to pass the extended PAN ID to a cluster command without the use of external memory.
123123
*
124+
* Note: The returned span points into storage managed by this class,
125+
* and must not be dereferenced beyond the lifetime of this object.
126+
*
124127
* @param[out] span A reference to receive the location of the extended PAN ID.
125128
*
126129
* @retval CHIP_NO_ERROR Successfully retrieved the extended PAN ID.
@@ -256,6 +259,10 @@ class OperationalDataset
256259

257260
/**
258261
* Returns ByteSpan pointing to the channel mask within the dataset.
262+
*
263+
* Note: The returned span points into storage managed by this class,
264+
* and must not be dereferenced beyond the lifetime of this object.
265+
*
259266
* @retval CHIP_NO_ERROR on success.
260267
* @retval CHIP_ERROR_TLV_TAG_NOT_FOUND if the channel mask is not present in the dataset.
261268
* @retval CHIP_ERROR_INVALID_TLV_ELEMENT if the TLV element is invalid.
@@ -264,13 +271,15 @@ class OperationalDataset
264271

265272
/**
266273
* This method sets the channel mask within the dataset.
274+
*
267275
* @retval CHIP_NO_ERROR on success.
268276
* @retval CHIP_ERROR_NO_MEMORY if there is insufficient space within the dataset.
269277
*/
270278
CHIP_ERROR SetChannelMask(ByteSpan aChannelMask);
271279

272280
/**
273281
* Retrieves the security policy from the dataset.
282+
*
274283
* @retval CHIP_NO_ERROR on success.
275284
* @retval CHIP_ERROR_TLV_TAG_NOT_FOUND if no security policy is present in the dataset.
276285
* @retval CHIP_ERROR_INVALID_TLV_ELEMENT if the TLV element is invalid.
@@ -279,6 +288,7 @@ class OperationalDataset
279288

280289
/**
281290
* This method sets the security policy within the dataset.
291+
*
282292
* @retval CHIP_NO_ERROR on success.
283293
* @retval CHIP_ERROR_NO_MEMORY if there is insufficient space within the dataset.
284294
*/
@@ -291,7 +301,6 @@ class OperationalDataset
291301

292302
/**
293303
* This method checks if the dataset is ready for creating Thread network.
294-
*
295304
*/
296305
bool IsCommissioned(void) const;
297306

@@ -304,7 +313,6 @@ class OperationalDataset
304313
* This method checks whether @p aData is formatted as ThreadTLVs.
305314
*
306315
* @note This method doesn't verify ThreadTLV values are valid.
307-
*
308316
*/
309317
static bool IsValid(ByteSpan aData);
310318

0 commit comments

Comments
 (0)