text: /*
   *
   *    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
      { }
  };
  
  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)
  
  static WEAVE_ERROR ValidatePath(TLVReader &aReader,
                                  const uint64_t &aTag,
                                  uint32_t aProfileId,
                                  const uint64_t &aInstanceId,
                                  uint32_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 (uint32_t 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;
  }
  
  /*
   * 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[])
  {
      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) ||
          !ResolveWeaveNetworkOptions(TOOL_NAME, gWeaveNodeOptions, gNetworkOptions))
      {
          exit(EXIT_FAILURE);
      }
  
      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();
      }
  }
  *
  */
  