blob: 8748f225de60e82aa8cd7c86f0a6b1649df83eff [file] [log] [blame]
/*
*
* Copyright (c) 2014-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 an application that is intended to be a
* comprehensive tester for all aspects of Weave Data Management
* (WDM) that one can test in standalone mode. As such, it tests
* data structures, subsystems and networked behaviors. The
* latter requires a mock device acting as a WDM server and using
* the same test profile.
*
*/
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <assert.h>
// This code tests the legacy version of Weave Data Management
#define WEAVE_CONFIG_DATA_MANAGEMENT_NAMESPACE kWeaveManagedNamespace_Legacy
#include "ToolCommon.h"
#include <Weave/Profiles/ProfileCommon.h>
#include <Weave/Profiles/data-management/DataManagement.h>
#include <Weave/Profiles/data-management/ProfileDatabase.h>
#include "TestProfile.h"
using namespace ::nl::Inet;
using namespace ::nl::Weave;
using namespace ::nl::Weave::TLV;
using namespace ::nl::Weave::Profiles;
using namespace ::nl::Weave::Profiles::Common;
using namespace ::nl::Weave::Profiles::DataManagement;
using namespace ::nl::Weave::Profiles::StatusReporting;
#define TOOL_NAME "TestDataManagement"
static bool HandleOption(const char *progName, OptionSet *optSet, int id, const char *name, const char *arg);
static bool HandleNonOptionArgs(const char *progName, int argc, char *argv[]);
static void HandleConnectionReceived(WeaveMessageLayer *msgLayer, WeaveConnection *con);
static void HandleSecureSessionEstablished(WeaveSecurityManager *sm, WeaveConnection *con, void *reqState, uint16_t sessionKeyId, uint64_t peerNodeId, uint8_t encType);
static void HandleSecureSessionError(WeaveSecurityManager *sm, WeaveConnection *con, void *reqState, WEAVE_ERROR localErr, uint64_t peerNodeId, StatusReport *statusReport);
static void HandleConnectionClosed(WeaveConnection *con, WEAVE_ERROR conErr);
// static void HandleConnectionComplete(WeaveConnection *con, WEAVE_ERROR conErr);
uint64_t DestNodeId;
IPAddress DestAddr = IPAddress::Any; // TODO BUG: This isn't used anywhere, despite being settable from the command-line.
bool Debug = false;
/*
* in some circumstances we may want to use multiple clients and then
* we have to count them to know when we're done.
*/
uint8_t gClientCount = 1;
uint8_t gClientCounter = 0;
int gCyclingCnt = 32;
static OptionDef gToolOptionDefs[] =
{
{ "dest-addr", kArgumentRequired, 'D' },
{ "cycling-cnt", kArgumentRequired, 'c' },
#if WEAVE_CONFIG_SECURITY_TEST_MODE
{ "allow-dups", kNoArgument, 'A' },
#endif
{ NULL }
};
static const char *gToolOptionHelp =
" -D, --dest-addr <dest-node-ip-addr>\n"
" Send weave messages to a specific IPv4/IPv6 address rather than one\n"
" derived the destination node id.\n"
"\n"
" -c, --cycling-cnt <num>"
" The count of the cycling test\n"
"\n"
#if WEAVE_CONFIG_SECURITY_TEST_MODE
" -A, --allow-dups\n"
" Allow reception of duplicate messages.\n"
"\n"
#endif
"";
static OptionSet gToolOptions =
{
HandleOption,
gToolOptionDefs,
"GENERAL OPTIONS",
gToolOptionHelp
};
static HelpOptions gHelpOptions(
TOOL_NAME,
"Usage: " TOOL_NAME " [<options...>] [<dest-node-id>]\n",
WEAVE_VERSION_STRING "\n" WEAVE_TOOL_COPYRIGHT
);
static OptionSet *gToolOptionSets[] =
{
&gToolOptions,
&gNetworkOptions,
&gWeaveNodeOptions,
&gFaultInjectionOptions,
&gHelpOptions,
NULL
};
#define EnterTest(_name_, _errName_) \
WEAVE_ERROR _errName_ = ( \
printf("\n\n" \
_name_ \
"---\n"), \
WEAVE_NO_ERROR \
)
#define DriveTest(_done_) \
do { \
while (!(_done_)) \
{ \
struct timeval sleepTime; \
sleepTime.tv_sec = 0; \
sleepTime.tv_usec = 100000; \
ServiceNetwork(sleepTime); \
} \
} while (0)
#define ExitTest(_err_) \
do { \
if ((_err_) == WEAVE_NO_ERROR) \
printf("Success\n"); \
else \
{ \
printf("error: %s\n", ErrorStr(_err_)); \
exit(-1); \
} \
} while (0)
WEAVE_ERROR ValidatePath(TLVReader &aReader,
const uint64_t &aTag,
uint32_t aProfileId,
const uint64_t &aInstanceId,
uint16_t aPathLen,
...)
{
WEAVE_ERROR err;
va_list pathTags;
TLVType pathContainer;
TLVType profileContainer;
uint32_t profileId;
uint64_t instanceId = kInstanceIdNotSpecified;
// This following line must be executed before the execution flow could possibly reach the exit label,
// for we call va_end at there unconditionally.
va_start(pathTags, aPathLen);
VerifyOrExit((aReader.GetType() == kTLVType_Path), err = WEAVE_ERROR_WRONG_TLV_TYPE);
err = ValidateWDMTag(aTag, aReader);
SuccessOrExit(err);
err = aReader.EnterContainer(pathContainer);
SuccessOrExit(err);
/*
* the first element of a path under WDM should be a structure
* with 2 elements, one of which (the instance) is optional.
*/
err = aReader.Next();
SuccessOrExit(err);
VerifyOrExit(aReader.GetType() == kTLVType_Structure, err = WEAVE_ERROR_UNEXPECTED_TLV_ELEMENT);
err = ValidateWDMTag(kTag_WDMPathProfile, aReader);
SuccessOrExit(err);
/*
* check the path profile and instance
*/
err = aReader.EnterContainer(profileContainer);
SuccessOrExit(err);
/*
* the first element here should be a profile ID
*/
err = aReader.Next();
SuccessOrExit(err);
err = ValidateWDMTag(kTag_WDMPathProfileId, aReader);
SuccessOrExit(err);
err = aReader.Get(profileId);
SuccessOrExit(err);
VerifyOrExit(profileId == aProfileId, err = WEAVE_ERROR_INVALID_PROFILE_ID);
/*
* and the second may be an instance
*/
err = aReader.Next();
if (err != WEAVE_END_OF_TLV)
{
err = ValidateWDMTag(kTag_WDMPathProfileInstance, aReader);
SuccessOrExit(err);
err = aReader.Get(instanceId);
SuccessOrExit(err);
}
VerifyOrExit(instanceId == aInstanceId, err = WEAVE_ERROR_INCORRECT_STATE);
err = aReader.ExitContainer(profileContainer);
SuccessOrExit(err);
// now, the residual path elements, if any
for (int i = 0; i < aPathLen; i++)
{
err = aReader.Next();
SuccessOrExit(err);
if (err == WEAVE_NO_ERROR)
{
if (aReader.GetTag() != va_arg(pathTags, uint64_t))
{
err = WEAVE_ERROR_INVALID_TLV_TAG;
break;
}
}
else
{
err = WEAVE_ERROR_TLV_UNDERRUN;
break;
}
}
/*
* again, don't hide the error that caused us to exit behind
* the error you might get trying to exit the conmtainer.
*/
if (err == WEAVE_NO_ERROR)
err = aReader.ExitContainer(pathContainer);
else
aReader.ExitContainer(pathContainer);
exit:
va_end(pathTags);
return err;
}
/*
* one day we might want to include the other form of instance ID
* in testing. for now just leave this as a placeholder.
*/
WEAVE_ERROR ValidatePath(nl::Weave::TLV::TLVReader &aReader,
const uint64_t &aTag,
uint32_t aProfileId,
uint32_t aInstanceIdLen,
uint8_t *aInstanceId,
uint16_t aPathLen,
...)
{
return WEAVE_NO_ERROR;
}
/*
* in order to use the "new improved" data management, we have to create
* a sub-class of the WDM client and supply the relevant methods as follows.
*/
class DMTestClient :
public DMClient
{
public:
WEAVE_ERROR ViewConfirm(const uint64_t &aResponderId, StatusReport &aStatus, uint16_t aTxnId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
printf("processing: <view confirm - non-success status>\n");
Done = true;
return err;
}
WEAVE_ERROR ViewConfirm(const uint64_t &aResponderId, ReferencedTLVData &aDataList, uint16_t aTxnId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
printf("processing: <view confirm - success status>\n");
err = mDatabase.Store(aDataList);
if (err != WEAVE_NO_ERROR)
printf("<view confirm> error: %x\n", err);
Done = true;
return err;
}
WEAVE_ERROR UpdateConfirm(const uint64_t &aResponderId, StatusReport &aStatus, uint16_t aTxnId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
if (aStatus.success())
printf("processing: <update confirm - success!>\n");
else
printf("processing: <update confirm - non-success status>\n");
Done = true;
return err;
}
void IncompleteIndication(const uint64_t &aPeerNodeId, StatusReport &aReport)
{
printf("processing: <incomplete indication>\n");
}
#if WEAVE_CONFIG_WDM_ALLOW_CLIENT_SUBSCRIPTION
WEAVE_ERROR SubscribeConfirm(const uint64_t &aResponderId, StatusReport &aStatus, uint16_t aTxnId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
printf("processing: <subscribe confirm - non-success status>\n");
gClientCounter++;
if (gClientCounter == gClientCount)
Done = true;
return err;
}
WEAVE_ERROR SubscribeConfirm(const uint64_t &aResponderId,
const TopicIdentifier &aTopicId,
uint16_t aTxnId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
printf("processing: <subscribe confirm - success status, no data list>\n");
Done = true;
return err;
}
WEAVE_ERROR SubscribeConfirm(const uint64_t &aResponderId,
const TopicIdentifier &aTopicId,
ReferencedTLVData &aDataList,
uint16_t aTxnId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
printf("processing: <subscribe confirm - success status + data list>\n");
// and install the data
err = mDatabase.Store(aDataList);
SuccessOrExit(err);
gClientCounter++;
if (gClientCounter == gClientCount)
Done = true;
exit:
if (err != WEAVE_NO_ERROR)
printf("<subscribe confirm> error: %s\n", ErrorStr(err));
return err;
}
WEAVE_ERROR UnsubscribeIndication(const uint64_t &aPublisherId, const TopicIdentifier &aTopicId, StatusReport &aReport)
{
printf("processing: <unsubscribe indication 0x%" PRIx64 ", 0x%" PRIx64 ">\n", aPublisherId, aTopicId);
return WEAVE_NO_ERROR;
}
WEAVE_ERROR CancelSubscriptionConfirm(const uint64_t &aResponderId, const TopicIdentifier &aTopicId, StatusReport &aStatus, uint16_t aTxnId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
printf("processing: <cancel subscription confirm>\n");
if (aStatus.success())
printf("status == success\n");
else
printf("non-success status: <%x, %x>\n", aStatus.mProfileId, aStatus.mStatusCode);
gClientCounter++;
if (gClientCounter == gClientCount)
Done = true;
return err;
}
WEAVE_ERROR NotifyIndication(const TopicIdentifier &aTopicId, ReferencedTLVData &aDataList)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
printf("processing: <notify indication>\n");
err = mDatabase.Store(aDataList);
gClientCounter++;
if (gClientCounter == gClientCount)
Done = true;
return err;
}
#endif // WEAVE_CONFIG_WDM_ALLOW_CLIENT_SUBSCRIPTION
/*
* the client comes with a profile database
*/
TestProfileDB mDatabase;
};
/*
* and we need a test rig, which is basically a place to hang tests, all
* of which are run from the client side.
*/
class DMClientTester
{
public:
DMClientTester(uint8_t aTransport)
{
mTransport = aTransport;
}
/*
* test cases
*/
void Test_InitialState()
{
DMTestClient client;
EnterTest("InitialState", err);
assert(client.mDatabase.mTestData.mVersion == 0);
assert(client.mDatabase.mTestData.mIntegerItem == 0);
ExitTest(err);
}
void Test_DBStore()
{
DMTestClient client;
EnterTest("DBStore", err);
uint8_t buf[kTestBufferSize];
TLVWriter writer;
ReferencedTLVData dataList;
// write a data list to store (including new version)
writer.Init(buf, kTestBufferSize);
err = StartDataList(writer);
SuccessOrExit(err);
err = StartDataListElement(writer);
SuccessOrExit(err);
err = EncodePath(writer,
ContextTag(kTag_WDMDataListElementPath),
kWeaveProfile_Test,
kInstanceIdNotSpecified,
1,
ContextTag(kTag_IntegerItem));
SuccessOrExit(err);
err = writer.Put(ContextTag(kTag_WDMDataListElementVersion), (uint64_t)1); // increment version
SuccessOrExit(err);
err = writer.Put(ContextTag(kTag_WDMDataListElementData), 1); // write 1
SuccessOrExit(err);
err = EndDataListElement(writer);
SuccessOrExit(err);
err = EndList(writer);
SuccessOrExit(err);
// now store it and confirm results
err = dataList.init(writer.GetLengthWritten(), kTestBufferSize, buf);
SuccessOrExit(err);
err = client.mDatabase.Store(dataList);
SuccessOrExit(err);
assert(client.mDatabase.mTestData.mVersion == 1); // check for 1
assert(client.mDatabase.mTestData.mIntegerItem == 1); // check for 1
exit:
ExitTest(err);
}
void Test_DBRetrieve()
{
DMTestClient client;
EnterTest("DBRetrieve", err);
uint8_t pathBuf[kTestBufferSize];
TLVWriter writer;
ReferencedTLVData pathList;
uint8_t dataBuf[kTestBufferSize];
ReferencedTLVData dataList;
TLVReader dataListRdr;
TLVReader pathRdr;
uint64_t version;
uint32_t item;
// write a path list to extract a data item
writer.Init(pathBuf, kTestBufferSize);
err = StartPathList(writer);
SuccessOrExit(err);
err = EncodePath(writer, AnonymousTag, kWeaveProfile_Test, kInstanceIdNotSpecified, 1, ContextTag(kTag_IntegerItem));
SuccessOrExit(err);
err = EndList(writer);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
err = pathList.init(writer.GetLengthWritten(), kTestBufferSize, pathBuf);
SuccessOrExit(err);
// set up the data list to receive data
err = dataList.init(0, kTestBufferSize, dataBuf);
SuccessOrExit(err);
// now do the retrieve
err = client.mDatabase.Retrieve(pathList, dataList);
SuccessOrExit(err);
// validate the data list
OpenDataList(dataList, dataListRdr);
SuccessOrExit(err);
err = dataListRdr.Next();
SuccessOrExit(err);
err = OpenDataListElement(dataListRdr, pathRdr, version);
SuccessOrExit(err);
err = ValidatePath(pathRdr,
ContextTag(kTag_WDMDataListElementPath),
kWeaveProfile_Test,
kInstanceIdNotSpecified,
1,
ContextTag(kTag_IntegerItem));
SuccessOrExit(err);
assert(version == client.mDatabase.mTestData.mVersion); // check version
assert(CheckWDMTag(kTag_WDMDataListElementData, dataListRdr)); // check data tag
assert(dataListRdr.GetType() == kTLVType_UnsignedInteger); // check data type
err = dataListRdr.Get(item);
SuccessOrExit(err);
assert(item == client.mDatabase.mTestData.mIntegerItem); // check data
err = CloseDataListElement(dataListRdr);
SuccessOrExit(err);
err = CloseList(dataListRdr);
SuccessOrExit(err);
exit:
ExitTest(err);
}
void Test_DBRetrieveAndStore()
{
DMTestClient client;
EnterTest("DBRetrieveAndStore", err);
uint8_t pathBuf[kTestBufferSize];
TLVWriter writer;
ReferencedTLVData pathList;
uint8_t dataBuf[kTestBufferSize];
ReferencedTLVData dataList;
// check inital state
assert(client.mDatabase.mTestData.mVersion == 0);
assert(client.mDatabase.mTestData.mIntegerItem == 0);
// write a path list to extract the whole bucket
writer.Init(pathBuf, kTestBufferSize);
err = StartPathList(writer);
SuccessOrExit(err);
err = EncodePath(writer, AnonymousTag, kWeaveProfile_Test, kInstanceIdNotSpecified, 0);
SuccessOrExit(err);
err = EndList(writer);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
err = pathList.init(writer.GetLengthWritten(), kTestBufferSize, pathBuf);
SuccessOrExit(err);
// set up the data list to receive data
err = dataList.init(0, kTestBufferSize, dataBuf);
SuccessOrExit(err);
// now do the retrieve
err = client.mDatabase.Retrieve(pathList, dataList);
SuccessOrExit(err);
/*
* OK, so here's what we do now. we've got a data list capturing the
* original state. change the state and then verify that we can set it
* back by applying the data list.
*/
client.mDatabase.mTestData.mVersion = 1;
client.mDatabase.mTestData.mIntegerItem = 1;
err = client.mDatabase.Store(dataList);
SuccessOrExit(err);
assert(client.mDatabase.mTestData.mVersion == 0);
assert(client.mDatabase.mTestData.mIntegerItem == 0);
exit:
ExitTest(err);
}
void Test_View(void)
{
DMTestClient client;
uint8_t pathBuf[kTestBufferSize];
TLVWriter writer;
ReferencedTLVData pathList;
EnterTest("DBView", err);
// set up the client
err = client.Init(&ExchangeMgr);
SuccessOrExit(err);
err = client.BindRequest(DestNodeId, mTransport);
SuccessOrExit(err);
// now, make a path list
writer.Init(pathBuf, kTestBufferSize);
err = StartPathList(writer);
SuccessOrExit(err);
// first path
err = EncodePath(writer, AnonymousTag, kWeaveProfile_Test, kInstanceIdNotSpecified, 0);
SuccessOrExit(err);
// second path
err = EncodePath(writer, AnonymousTag, kWeaveProfile_Test, kInstanceIdNotSpecified, 0);
SuccessOrExit(err);
err = EndList(writer);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
err = pathList.init(writer.GetLengthWritten(), kTestBufferSize, pathBuf);
SuccessOrExit(err);
/*
* do this a bunch of times to test resource management
*/
for (int i = 0; i < gCyclingCnt; i++)
{
err = client.ViewRequest(pathList, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
// now drive
Done = false;
DriveTest(Done);
}
err = client.ViewRequest(DestNodeId, pathList, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
// now drive
Done = false;
DriveTest(Done);
exit:
ExitTest(err);
}
void Test_Update(void)
{
DMTestClient client;
uint8_t pathBuf[kTestBufferSize];
TLVWriter writer;
ReferencedTLVData pathList;
uint8_t dataBuf[kTestBufferSize];
ReferencedTLVData dataList;
EnterTest("DBUpdate", err);
// set up the client
err = client.Init(&ExchangeMgr);
SuccessOrExit(err);
err = client.BindRequest(DestNodeId, mTransport);
SuccessOrExit(err);
// now, make a path list
writer.Init(pathBuf, kTestBufferSize);
err = StartPathList(writer);
SuccessOrExit(err);
// first path
err = EncodePath(writer, AnonymousTag, kWeaveProfile_Test, kInstanceIdNotSpecified, 1, ContextTag(kTag_IntegerItem));
SuccessOrExit(err);
// second path
err = EncodePath(writer, AnonymousTag, kWeaveProfile_Test, kInstanceIdNotSpecified, 1, ContextTag(kTag_IntegerItem));
SuccessOrExit(err);
err = EndList(writer);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
err = pathList.init(writer.GetLengthWritten(), kTestBufferSize, pathBuf);
SuccessOrExit(err);
for (uint16_t i = 0; i < gCyclingCnt; i++)
{
uint64_t currentVersion = client.mDatabase.mTestData.mVersion;
client.mDatabase.mTestData.mIntegerItem = i;
// use path list to extract a data list
err = dataList.init(0, kTestBufferSize, dataBuf);
SuccessOrExit(err);
err = client.mDatabase.Retrieve(pathList, dataList);
SuccessOrExit(err);
// send an update
#if WEAVE_CONFIG_WDM_ALLOW_CLIENT_LEGACY_MESSAGE_TYPES
// it we're allowing old message types, send some
if (i % 3 == 0)
{
err = client.UpdateRequest(dataList, 1, kDefaultDMResponseTimeout, true);
SuccessOrExit(err);
}
else if (i % 3 == 1)
{
err = client.UpdateRequest(dataList, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
}
else if (i % 3 == 2)
{
err = client.UpdateRequest(DestNodeId, dataList, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
}
#else
err = client.UpdateRequest(dataList, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
#endif // WEAVE_CONFIG_WDM_ALLOW_CLIENT_LEGACY_MESSAGE_TYPES
// now drive
Done = false;
DriveTest(Done);
// OK, now send a view
err = client.ViewRequest(pathList, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
// and again...
Done = false;
DriveTest(Done);
// now check the results
assert(client.mDatabase.mTestData.mIntegerItem == i);
assert(client.mDatabase.mTestData.mVersion > currentVersion);
}
exit:
ExitTest(err);
}
#if WEAVE_CONFIG_WDM_ALLOW_CLIENT_SUBSCRIPTION
void Test_CancelNonSubscription(void)
{
DMTestClient client;
gClientCount = 1;
EnterTest("CancelNonSubscription", err);
// set up the client with subscription enabled
err = client.Init(&ExchangeMgr);
SuccessOrExit(err);
err = client.BindRequest(DestNodeId, mTransport);
SuccessOrExit(err);
for (uint16_t i = 0; i < gCyclingCnt; i++)
{
/*
* insert the subscription so there'll be something there.
*/
err = DMClient::sNotifier.InstallSubscription(kTopicIdNotSpecified, kTestTopic, DestNodeId, &client);
SuccessOrExit(err);
// now cancel
err = client.CancelSubscriptionRequest(kTestTopic, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
// drive
Done = false;
gClientCounter = 0;
DriveTest(Done);
assert(!client.HasSubscription(kTestTopic));
}
exit:
ExitTest(err);
}
void Test_SubscribeToTopic(void)
{
DMTestClient client;
uint8_t pathBuf[kTestBufferSize];
TLVWriter writer;
ReferencedTLVData pathList;
gClientCount = 1;
EnterTest("SubscribeToTopic", err);
// set up the client with subscription enabled
err = client.Init(&ExchangeMgr);
SuccessOrExit(err);
err = client.BindRequest(DestNodeId, mTransport);
SuccessOrExit(err);
writer.Init(pathBuf, kTestBufferSize);
err = StartPathList(writer);
SuccessOrExit(err);
// first path
err = EncodePath(writer, AnonymousTag, kWeaveProfile_Test, kInstanceIdNotSpecified, 1, ContextTag(kTag_IntegerItem));
SuccessOrExit(err);
err = EndList(writer);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
err = pathList.init(writer.GetLengthWritten(), kTestBufferSize, pathBuf);
SuccessOrExit(err);
err = client.SubscribeRequest(DestNodeId, pathList, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
err = client.CancelSubscriptionRequest(kTestTopic, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
Done = false;
gClientCounter = 0;
DriveTest(Done);
assert(!client.HasSubscription(kTestTopic));
err = client.SubscribeRequest(pathList, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
err = client.CancelSubscriptionRequest(kTestTopic, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
Done = false;
gClientCounter = 0;
DriveTest(Done);
assert(!client.HasSubscription(kTestTopic));
err = client.SubscribeRequest(DestNodeId, kTestTopic, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
Done = false;
gClientCounter = 0;
DriveTest(Done);
assert(client.HasSubscription(kTestTopic));
err = client.CancelSubscriptionRequest(kTestTopic, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
Done = false;
gClientCounter = 0;
DriveTest(Done);
assert(!client.HasSubscription(kTestTopic));
err = client.CancelTransactionRequest(1, err);
SuccessOrExit(err);
for (uint16_t i = 0; i < gCyclingCnt; i++)
{
err = client.SubscribeRequest(kTestTopic, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
// now drive
Done = false;
gClientCounter = 0;
DriveTest(Done);
assert(client.HasSubscription(kTestTopic));
// now wait for a notification
Done = false;
gClientCounter = 0;
DriveTest(Done);
// now cancel
err = client.CancelSubscriptionRequest(kTestTopic, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
// drive s'more
Done = false;
gClientCounter = 0;
DriveTest(Done);
assert(!client.HasSubscription(kTestTopic));
}
exit:
ExitTest(err);
}
void Test_MultipleClients(void)
{
DMTestClient client1;
DMTestClient client2;
gClientCount = 2;
EnterTest("MultipleClients", err);
// set up the client with subscription enabled
err = client1.Init(&ExchangeMgr);
SuccessOrExit(err);
err = client2.Init(&ExchangeMgr);
SuccessOrExit(err);
err = client1.BindRequest(DestNodeId, mTransport);
SuccessOrExit(err);
err = client2.BindRequest(DestNodeId, mTransport);
SuccessOrExit(err);
for (uint16_t i = 0; i < gCyclingCnt; i++)
{
err = client1.SubscribeRequest(kTestTopic, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
Done = false;
gClientCounter = 0;
DriveTest(Done);
err = client2.SubscribeRequest(kTestTopic, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
// now drive
Done = false;
gClientCounter = 0;
DriveTest(Done);
assert(client1.HasSubscription(kTestTopic));
assert(client2.HasSubscription(kTestTopic));
// now wait for a notification
Done = false;
gClientCounter = 0;
DriveTest(Done);
// now cancel
err = client1.CancelSubscriptionRequest(kTestTopic, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
err = client2.CancelSubscriptionRequest(kTestTopic, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
// drive s'more
Done = false;
gClientCounter = 0;
DriveTest(Done);
assert(!client1.HasSubscription(kTestTopic));
assert(!client2.HasSubscription(kTestTopic));
}
exit:
ExitTest(err);
}
#endif // WEAVE_CONFIG_WDM_ALLOW_CLIENT_SUBSCRIPTION
void Test_CloseConnection(void)
{
DMTestClient client;
uint8_t pathBuf[kTestBufferSize];
TLVWriter writer;
ReferencedTLVData pathList;
EnterTest("CloseConnection", err);
// set up the client
err = client.Init(&ExchangeMgr);
SuccessOrExit(err);
err = client.BindRequest(DestNodeId, kTransport_TCP);
SuccessOrExit(err);
// now, make a "special" path list
writer.Init(pathBuf, kTestBufferSize);
err = StartPathList(writer);
SuccessOrExit(err);
err = EncodePath(writer, AnonymousTag, kWeaveProfile_Fail, kFailureInstance_CloseConnection, 0);
SuccessOrExit(err);
err = EndList(writer);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
err = pathList.init(writer.GetLengthWritten(), kTestBufferSize, pathBuf);
SuccessOrExit(err);
/*
* do this a bunch of times to test resource management
*/
for (int i = 0; i < gCyclingCnt; i++)
{
err = client.ViewRequest(pathList, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
// now drive
Done = false;
DriveTest(Done);
}
exit:
ExitTest(err);
}
void Test_NoResponse(void)
{
DMTestClient client;
uint8_t pathBuf[kTestBufferSize];
TLVWriter writer;
ReferencedTLVData pathList;
EnterTest("NoResponse", err);
// set up the client
err = client.Init(&ExchangeMgr);
SuccessOrExit(err);
err = client.BindRequest(DestNodeId, mTransport);
SuccessOrExit(err);
// now, make a "special" path list
writer.Init(pathBuf, kTestBufferSize);
err = StartPathList(writer);
SuccessOrExit(err);
err = EncodePath(writer, AnonymousTag, kWeaveProfile_Fail, kFailureInstance_NoResponse, 0);
SuccessOrExit(err);
err = EndList(writer);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
err = pathList.init(writer.GetLengthWritten(), kTestBufferSize, pathBuf);
SuccessOrExit(err);
/*
* do this a bunch of times to test resource management
*/
for (int i = 0; i < gCyclingCnt; i++)
{
err = client.ViewRequest(pathList, 1, kDefaultDMResponseTimeout);
SuccessOrExit(err);
// now drive
Done = false;
DriveTest(Done);
}
exit:
ExitTest(err);
}
static void Run(void)
{
printf("Running WDM client tests---\n");
/*
* check the profile database operaiton
*/
{
DMClientTester tester(kTransport_UDP);
tester.Test_InitialState();
}
{
DMClientTester tester(kTransport_UDP);
tester.Test_DBStore();
}
{
DMClientTester tester(kTransport_UDP);
tester.Test_DBRetrieve();
}
{
DMClientTester tester(kTransport_UDP);
tester.Test_DBRetrieveAndStore();
}
/*
* then the client notifier stuff
*/
/*
* and the server subscription table
*/
/*
* OK, now test the stuff that actually requires communication
*/
// test view
{
DMClientTester tester(kTransport_UDP);
tester.Test_View();
}
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
{
DMClientTester tester(kTransport_WRMP);
tester.Test_View();
}
#endif
// and using TCP
{
DMClientTester tester(kTransport_TCP);
tester.Test_View();
}
// test update
{
DMClientTester tester(kTransport_UDP);
tester.Test_Update();
}
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
{
DMClientTester tester(kTransport_WRMP);
tester.Test_Update();
}
#endif
{
DMClientTester tester(kTransport_TCP);
tester.Test_Update();
}
#if WEAVE_CONFIG_WDM_ALLOW_CLIENT_SUBSCRIPTION
// cancel a non-existent subscription
{
DMClientTester tester(kTransport_UDP);
tester.Test_CancelNonSubscription();
}
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
{
DMClientTester tester(kTransport_WRMP);
tester.Test_CancelNonSubscription();
}
#endif
{
DMClientTester tester(kTransport_TCP);
tester.Test_CancelNonSubscription();
}
// now try establishing a topic subscription
{
DMClientTester tester(kTransport_UDP);
tester.Test_SubscribeToTopic();
}
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
{
DMClientTester tester(kTransport_WRMP);
tester.Test_SubscribeToTopic();
}
#endif
{
DMClientTester tester(kTransport_TCP);
tester.Test_SubscribeToTopic();
}
// do the same with multiple clients
{
DMClientTester tester(kTransport_UDP);
tester.Test_MultipleClients();
}
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
{
DMClientTester tester(kTransport_WRMP);
tester.Test_MultipleClients();
}
#endif
{
DMClientTester tester(kTransport_TCP);
tester.Test_MultipleClients();
}
#endif // WEAVE_CONFIG_WDM_ALLOW_CLIENT_SUBSCRIPTION
// these are negative tests
{
DMClientTester tester(kTransport_TCP);
tester.Test_CloseConnection();
}
{
DMClientTester tester(kTransport_UDP);
tester.Test_NoResponse();
}
{
DMClientTester tester(kTransport_TCP);
tester.Test_NoResponse();
}
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
{
DMClientTester tester(kTransport_WRMP);
tester.Test_NoResponse();
}
#endif
}
// we need to keep the transport around since the DME one is protected
uint8_t mTransport;
};
int main(int argc, char *argv[])
{
gWeaveNodeOptions.FabricId = 0;
gWeaveNodeOptions.LocalNodeId = 0;
if (argc == 1)
{
gHelpOptions.PrintBriefUsage(stderr);
exit(EXIT_FAILURE);
}
if (!ParseArgsFromEnvVar(TOOL_NAME, TOOL_OPTIONS_ENV_VAR_NAME, gToolOptionSets, NULL, true) ||
!ParseArgs(TOOL_NAME, argc, argv, gToolOptionSets, HandleNonOptionArgs))
{
exit(EXIT_FAILURE);
}
if (gNetworkOptions.LocalIPv6Addr != IPAddress::Any)
{
if (!gNetworkOptions.LocalIPv6Addr.IsIPv6ULA())
{
printf("ERROR: Local address must be an IPv6 ULA\n");
exit(EXIT_FAILURE);
}
gWeaveNodeOptions.FabricId = gNetworkOptions.LocalIPv6Addr.GlobalId();
gWeaveNodeOptions.LocalNodeId = IPv6InterfaceIdToWeaveNodeId(gNetworkOptions.LocalIPv6Addr.InterfaceId());
gWeaveNodeOptions.SubnetId = gNetworkOptions.LocalIPv6Addr.Subnet();
}
// Default LocalNodeId to 1 if not set explicitly, or by means of setting the node address.
if (gWeaveNodeOptions.LocalNodeId == 0)
gWeaveNodeOptions.LocalNodeId = 1;
InitSystemLayer();
InitNetwork();
InitWeaveStack(false, true);
// Arrange to get called for various activity in the message layer.
MessageLayer.OnConnectionReceived = HandleConnectionReceived;
MessageLayer.OnReceiveError = HandleMessageReceiveError;
MessageLayer.OnAcceptError = HandleAcceptConnectionError;
SecurityMgr.OnSessionEstablished = HandleSecureSessionEstablished;
SecurityMgr.OnSessionError = HandleSecureSessionError;
if (DestAddr == IPAddress::Any)
DestAddr = FabricState.SelectNodeAddress(DestNodeId);
PrintNodeConfig();
DMClientTester::Run();
printf("WDM Test is Completed!\n");
ShutdownWeaveStack();
ShutdownNetwork();
ShutdownSystemLayer();
return EXIT_SUCCESS;
}
bool HandleOption(const char *progName, OptionSet *optSet, int id, const char *name, const char *arg)
{
switch (id)
{
case 'D':
if (!ParseIPAddress(arg, DestAddr))
{
PrintArgError("%s: Invalid value specified for destination IP address: %s\n", progName, arg);
return false;
}
break;
case 'c':
if (!ParseInt(arg, gCyclingCnt))
{
PrintArgError("%s: Invalid value specified for cycling count: %s\n", progName, arg);
return false;
}
break;
default:
PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", progName, name);
return false;
}
return true;
}
bool HandleNonOptionArgs(const char *progName, int argc, char *argv[])
{
if (argc > 0)
{
if (argc > 1)
{
PrintArgError("%s: Unexpected argument: %s\n", progName, argv[1]);
return false;
}
if (!ParseNodeId(argv[0], DestNodeId))
{
PrintArgError("%s: Invalid value specified for destination node-id: %s\n", progName, argv[0]);
return false;
}
}
return true;
}
void HandleConnectionReceived(WeaveMessageLayer *msgLayer, WeaveConnection *con)
{
char ipAddrStr[64];
con->PeerAddr.ToString(ipAddrStr, sizeof(ipAddrStr));
printf("Connection received from node 0x%" PRIx64 " (%s)\n", con->PeerNodeId, ipAddrStr);
con->OnConnectionClosed = HandleConnectionClosed;
}
void HandleSecureSessionEstablished(WeaveSecurityManager *sm, WeaveConnection *con, void *reqState, uint16_t sessionKeyId, uint64_t peerNodeId, uint8_t encType)
{
char ipAddrStr[64] = "";
if (con != NULL)
con->PeerAddr.ToString(ipAddrStr, sizeof(ipAddrStr));
printf("Secure session established with node 0x%" PRIx64 " (%s)\n", peerNodeId, ipAddrStr);
}
void HandleSecureSessionError(WeaveSecurityManager *sm, WeaveConnection *con, void *reqState, WEAVE_ERROR localErr, uint64_t peerNodeId, StatusReport *statusReport)
{
char ipAddrStr[64] = "";
if (con != NULL)
{
con->PeerAddr.ToString(ipAddrStr, sizeof(ipAddrStr));
con->Close();
}
if (localErr == WEAVE_ERROR_STATUS_REPORT_RECEIVED && statusReport != NULL)
printf("FAILED to establish secure session with node 0x%" PRIx64 " (%s): %s\n", peerNodeId, ipAddrStr, nl::StatusReportStr(statusReport->mProfileId, statusReport->mStatusCode));
else
printf("FAILED to establish secure session with node 0x%" PRIx64 " (%s): %s\n", peerNodeId, ipAddrStr, ErrorStr(localErr));
}
void HandleConnectionClosed(WeaveConnection *con, WEAVE_ERROR conErr)
{
char ipAddrStr[64];
// Note: by contract con MUST NOT be NULL, so it is used without the check.
con->PeerAddr.ToString(ipAddrStr, sizeof(ipAddrStr));
if (conErr == WEAVE_NO_ERROR)
printf("Connection closed to node 0x%" PRIx64 " (%s)\n", con->PeerNodeId, ipAddrStr);
else
printf("Connection ABORTED to node 0x%" PRIx64 " (%s): %s\n", con->PeerNodeId, ipAddrStr, ErrorStr(conErr));
con->Close();
}
/*
*
* we may have need of this some day but for now we're not using it and it's
* generating warnings to that effect.
*
void HandleConnectionComplete(WeaveConnection *con, WEAVE_ERROR err)
{
char ipAddrStr[64];
con->PeerAddr.ToString(ipAddrStr, sizeof(ipAddrStr));
if (err == WEAVE_NO_ERROR)
{
printf("Connection established to %s\n", ipAddrStr);
ExchangeMgr.AllowUnsolicitedMessages(con);
}
else
{
printf("Failed to establish connection to %s: %s\n", ipAddrStr, ErrorStr(err));
con->Close();
}
}
*
*/