mgmt+tools: rework FacePersistency handling in faces/create and faces/update
1. enable changing persistency in faces/update command
2. reject faces/create request if the face already exists
3. nfdc changes to support the above
Change-Id: I659906da846608a42a768f08fb110ceee1a947a7
refs: #3232
diff --git a/tests/daemon/mgmt/face-manager-command-fixture.cpp b/tests/daemon/mgmt/face-manager-command-fixture.cpp
index 989ff7b..8eecf9e 100644
--- a/tests/daemon/mgmt/face-manager-command-fixture.cpp
+++ b/tests/daemon/mgmt/face-manager-command-fixture.cpp
@@ -85,6 +85,24 @@
}
}
+const Face*
+FaceManagerCommandNode::findFaceByUri(const std::string& uri) const
+{
+ for (const auto& face : faceTable) {
+ if (face.getRemoteUri().toString() == uri) {
+ return &face;
+ }
+ }
+ return nullptr;
+}
+
+FaceId
+FaceManagerCommandNode::findFaceIdByUri(const std::string& uri) const
+{
+ auto face = findFaceByUri(uri);
+ return face != nullptr ? face->getId() : face::INVALID_FACEID;
+}
+
FaceManagerCommandFixture::FaceManagerCommandFixture()
: node1(m_keyChain, 16363)
, node2(m_keyChain, 26363)
diff --git a/tests/daemon/mgmt/face-manager-command-fixture.hpp b/tests/daemon/mgmt/face-manager-command-fixture.hpp
index 0bde982..bf376f5 100644
--- a/tests/daemon/mgmt/face-manager-command-fixture.hpp
+++ b/tests/daemon/mgmt/face-manager-command-fixture.hpp
@@ -45,7 +45,14 @@
~FaceManagerCommandNode();
public:
- ndn::util::DummyClientFace face; ///< internal client face used by management
+ const Face*
+ findFaceByUri(const std::string& uri) const;
+
+ FaceId
+ findFaceIdByUri(const std::string& uri) const;
+
+public:
+ ndn::util::DummyClientFace face;
ndn::mgmt::Dispatcher dispatcher;
shared_ptr<CommandAuthenticator> authenticator;
diff --git a/tests/daemon/mgmt/face-manager-create-face.t.cpp b/tests/daemon/mgmt/face-manager-create-face.t.cpp
index ed89388..5cfe952 100644
--- a/tests/daemon/mgmt/face-manager-create-face.t.cpp
+++ b/tests/daemon/mgmt/face-manager-create-face.t.cpp
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
- * Copyright (c) 2014-2016, Regents of the University of California,
+ * Copyright (c) 2014-2017, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -23,6 +23,7 @@
* NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "mgmt/face-manager.hpp"
#include "face-manager-command-fixture.hpp"
#include "nfd-manager-common-fixture.hpp"
@@ -206,8 +207,8 @@
ControlResponse actual(response.getContent().blockFromValue());
ControlResponse expected(CreateResult().getExpected());
- BOOST_CHECK_EQUAL(expected.getCode(), actual.getCode());
BOOST_TEST_MESSAGE(actual.getText());
+ BOOST_CHECK_EQUAL(expected.getCode(), actual.getCode());
if (actual.getBody().hasWire()) {
ControlParameters expectedParams(FaceType().getParameters());
@@ -249,25 +250,16 @@
BOOST_CHECK(hasCallbackFired);
}
-
-typedef mpl::vector<// mpl::pair<mpl::pair<TcpFacePersistent, TcpFacePermanent>, TcpFacePermanent>, // no need to check now
- // mpl::pair<mpl::pair<TcpFacePermanent, TcpFacePersistent>, TcpFacePersistent>, // no need to check now
- mpl::pair<mpl::pair<UdpFacePersistent, UdpFacePermanent>, UdpFacePermanent>,
- mpl::pair<mpl::pair<UdpFacePermanent, UdpFacePersistent>, UdpFacePermanent>> FaceTransitions;
-
-
-BOOST_FIXTURE_TEST_CASE_TEMPLATE(ExistingFace, T, FaceTransitions, FaceManagerCommandFixture)
+BOOST_FIXTURE_TEST_CASE(ExistingFace, FaceManagerCommandFixture)
{
- typedef typename T::first::first FaceType1;
- typedef typename T::first::second FaceType2;
- typedef typename T::second FinalFaceType;
+ using FaceType = UdpFacePersistent;
{
// create face
Name commandName("/localhost/nfd/faces");
commandName.append("create");
- commandName.append(FaceType1().getParameters().wireEncode());
+ commandName.append(FaceType().getParameters().wireEncode());
auto command = makeInterest(commandName);
m_keyChain.sign(*command);
@@ -275,116 +267,12 @@
this->advanceClocks(time::milliseconds(1), 5);
}
- //
- {
- // re-create face (= change face persistency)
-
- Name commandName("/localhost/nfd/faces");
- commandName.append("create");
- commandName.append(FaceType2().getParameters().wireEncode());
- auto command = makeInterest(commandName);
- m_keyChain.sign(*command);
-
- bool hasCallbackFired = false;
- this->node1.face.onSendData.connect([this, command, &hasCallbackFired] (const Data& response) {
- if (!command->getName().isPrefixOf(response.getName())) {
- return;
- }
-
- ControlResponse actual(response.getContent().blockFromValue());
- BOOST_REQUIRE_EQUAL(actual.getCode(), 200);
- BOOST_TEST_MESSAGE(actual.getText());
-
- ControlParameters expectedParams(FinalFaceType().getParameters());
- ControlParameters actualParams(actual.getBody());
- BOOST_REQUIRE(!actualParams.hasUri()); // Uri parameter is only included on command failure
- BOOST_CHECK_EQUAL(expectedParams.getFacePersistency(), actualParams.getFacePersistency());
-
- hasCallbackFired = true;
- });
-
- this->node1.face.receive(*command);
- this->advanceClocks(time::milliseconds(1), 5);
-
- BOOST_CHECK(hasCallbackFired);
- }
-}
-
-
-class UdpFace
-{
-public:
- ControlParameters
- getParameters()
- {
- return ControlParameters()
- .setUri("udp4://127.0.0.1:16363")
- .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
- }
-};
-
-
-// Note that the transitions from on-demand TcpFace are intentionally not tested.
-// On-demand TcpFace has a remote endpoint with a randomized port number. Normal face
-// creation operations will not need to create a face toward a remote port not listened by
-// a channel.
-
-typedef mpl::vector<mpl::pair<UdpFace, UdpFacePersistent>,
- mpl::pair<UdpFace, UdpFacePermanent>> OnDemandFaceTransitions;
-
-// need a slightly different logic to test transitions from OnDemand state
-BOOST_FIXTURE_TEST_CASE_TEMPLATE(ExistingFaceOnDemand, T, OnDemandFaceTransitions, FaceManagerCommandFixture)
-{
- typedef typename T::first OtherNodeFace;
- typedef typename T::second FaceType;
+ // find the created face
+ auto foundFace = this->node1.findFaceByUri(FaceType().getParameters().getUri());
+ BOOST_REQUIRE(foundFace != nullptr);
{
- // create on-demand face
-
- Name commandName("/localhost/nfd/faces");
- commandName.append("create");
- commandName.append(OtherNodeFace().getParameters().wireEncode());
- auto command = makeInterest(commandName);
- m_keyChain.sign(*command);
-
- ndn::util::signal::ScopedConnection connection =
- this->node2.face.onSendData.connect([this, command] (const Data& response) {
- if (!command->getName().isPrefixOf(response.getName())) {
- return;
- }
-
- ControlResponse controlResponse(response.getContent().blockFromValue());
- BOOST_REQUIRE_EQUAL(controlResponse.getText(), "OK");
- BOOST_REQUIRE_EQUAL(controlResponse.getCode(), 200);
- BOOST_REQUIRE(!ControlParameters(controlResponse.getBody()).hasUri());
- uint64_t faceId = ControlParameters(controlResponse.getBody()).getFaceId();
- auto face = this->node2.faceTable.get(static_cast<FaceId>(faceId));
-
- // to force creation of on-demand face
- auto dummyInterest = make_shared<Interest>("/hello/world");
- face->sendInterest(*dummyInterest);
- });
-
- this->node2.face.receive(*command);
- this->advanceClocks(time::milliseconds(1), 5); // let node2 process command and send Interest
- std::this_thread::sleep_for(std::chrono::milliseconds(100)); // allow wallclock time for socket IO
- this->advanceClocks(time::milliseconds(1), 5); // let node1 accept Interest and create on-demand face
- }
-
- // make sure there is on-demand face
- FaceUri onDemandFaceUri(FaceType().getParameters().getUri());
- const Face* foundFace = nullptr;
- for (const Face& face : this->node1.faceTable) {
- if (face.getRemoteUri() == onDemandFaceUri) {
- foundFace = &face;
- break;
- }
- }
- BOOST_REQUIRE_MESSAGE(foundFace != nullptr, "on-demand face is not created");
-
- //
- {
- // re-create face (= change face persistency)
+ // re-create face
Name commandName("/localhost/nfd/faces");
commandName.append("create");
@@ -400,14 +288,12 @@
}
ControlResponse actual(response.getContent().blockFromValue());
- BOOST_REQUIRE_EQUAL(actual.getCode(), 200);
+ BOOST_REQUIRE_EQUAL(actual.getCode(), 409);
- ControlParameters expectedParams(FaceType().getParameters());
ControlParameters actualParams(actual.getBody());
- BOOST_REQUIRE(!actualParams.hasUri());
- BOOST_CHECK_EQUAL(actualParams.getFacePersistency(), expectedParams.getFacePersistency());
- BOOST_CHECK_EQUAL(actualParams.getFaceId(), foundFace->getId());
- BOOST_CHECK_EQUAL(foundFace->getPersistency(), expectedParams.getFacePersistency());
+ BOOST_CHECK_EQUAL(foundFace->getId(), actualParams.getFaceId());
+ BOOST_CHECK_EQUAL(foundFace->getRemoteUri().toString(), actualParams.getUri());
+ BOOST_CHECK_EQUAL(foundFace->getPersistency(), actualParams.getFacePersistency());
hasCallbackFired = true;
});
diff --git a/tests/daemon/mgmt/face-manager-update-face.t.cpp b/tests/daemon/mgmt/face-manager-update-face.t.cpp
index df1f97f..6757622 100644
--- a/tests/daemon/mgmt/face-manager-update-face.t.cpp
+++ b/tests/daemon/mgmt/face-manager-update-face.t.cpp
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
- * Copyright (c) 2014-2016, Regents of the University of California,
+ * Copyright (c) 2014-2017, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -23,10 +23,15 @@
* NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "mgmt/face-manager.hpp"
+#include "face/generic-link-service.hpp"
#include "face-manager-command-fixture.hpp"
#include "nfd-manager-common-fixture.hpp"
+
#include <ndn-cxx/lp/tags.hpp>
+#include <thread>
+
namespace nfd {
namespace tests {
@@ -56,36 +61,54 @@
params.setFacePersistency(persistency);
params.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, enableLocalFields);
+ createFace(params);
+ }
+
+ void
+ createFace(const ControlParameters& createParams,
+ bool isForOnDemandFace = false)
+ {
Name commandName("/localhost/nfd/faces/create");
- commandName.append(params.wireEncode());
+ commandName.append(createParams.wireEncode());
auto command = makeInterest(commandName);
m_keyChain.sign(*command);
+ // if this creation if for on-demand face then create it on node2
+ FaceManagerCommandNode& target = isForOnDemandFace ? this->node2 : this->node1;
+
bool hasCallbackFired = false;
- signal::ScopedConnection connection = this->node1.face.onSendData.connect(
- [this, command, &hasCallbackFired] (const Data& response) {
+ signal::ScopedConnection connection = target.face.onSendData.connect(
+ [&, command, isForOnDemandFace, this] (const Data& response) {
if (!command->getName().isPrefixOf(response.getName())) {
return;
}
ControlResponse create(response.getContent().blockFromValue());
BOOST_REQUIRE_EQUAL(create.getCode(), 200);
+ BOOST_REQUIRE(create.getBody().hasWire());
- if (create.getBody().hasWire()) {
- ControlParameters faceParams(create.getBody());
- BOOST_REQUIRE(faceParams.hasFaceId());
- this->faceId = faceParams.getFaceId();
- }
- else {
- BOOST_FAIL("Face creation failed");
- }
+ ControlParameters faceParams(create.getBody());
+ BOOST_REQUIRE(faceParams.hasFaceId());
+ this->faceId = faceParams.getFaceId();
hasCallbackFired = true;
+
+ if (isForOnDemandFace) {
+ auto face = target.faceTable.get(static_cast<FaceId>(this->faceId));
+
+ // to force creation of on-demand face
+ face->sendInterest(*make_shared<Interest>("/hello/world"));
+ }
});
- this->node1.face.receive(*command);
+ target.face.receive(*command);
this->advanceClocks(time::milliseconds(1), 5);
+ if (isForOnDemandFace) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(100)); // allow wallclock time for socket IO
+ this->advanceClocks(time::milliseconds(1), 5); // let node1 accept Interest and create on-demand face
+ }
+
BOOST_REQUIRE(hasCallbackFired);
}
@@ -176,29 +199,63 @@
});
}
-// TODO #3232: Expected failure until FacePersistency updating implemented
-BOOST_AUTO_TEST_CASE(UpdatePersistency)
+template<bool CAN_CHANGE_PERSISTENCY>
+class UpdatePersistencyDummyTransport : public face::Transport
{
- createFace();
+public:
+ UpdatePersistencyDummyTransport()
+ {
+ this->setPersistency(ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+ }
- ControlParameters requestParams;
- requestParams.setFaceId(faceId);
- requestParams.setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+protected:
+ bool
+ canChangePersistencyToImpl(ndn::nfd::FacePersistency newPersistency) const override
+ {
+ return CAN_CHANGE_PERSISTENCY;
+ }
- updateFace(requestParams, false, [] (const ControlResponse& actual) {
- ControlResponse expected(409, "Invalid fields specified");
- BOOST_CHECK_EQUAL(actual.getCode(), expected.getCode());
- BOOST_TEST_MESSAGE(actual.getText());
+ void
+ doClose() override
+ {
+ }
- if (actual.getBody().hasWire()) {
- ControlParameters actualParams(actual.getBody());
+private:
+ void
+ doSend(face::Transport::Packet&& packet) override
+ {
+ }
+};
- BOOST_REQUIRE(actualParams.hasFacePersistency());
- BOOST_CHECK_EQUAL(actualParams.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERMANENT);
- }
- else {
- BOOST_ERROR("Response does not contain ControlParameters");
- }
+namespace mpl = boost::mpl;
+
+using UpdatePersistencyTests =
+ mpl::vector<mpl::pair<UpdatePersistencyDummyTransport<true>, CommandSuccess>,
+ mpl::pair<UpdatePersistencyDummyTransport<false>, CommandFailure<409>>>;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(UpdatePersistency, T, UpdatePersistencyTests, FaceManagerUpdateFixture)
+{
+ using TransportType = typename T::first;
+ using ResultType = typename T::second;
+
+ auto face = make_shared<face::Face>(make_unique<face::GenericLinkService>(),
+ make_unique<TransportType>());
+ this->node1.faceTable.add(face);
+
+ auto parameters = ControlParameters()
+ .setFaceId(face->getId())
+ .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+
+ updateFace(parameters, false, [] (const ControlResponse& actual) {
+ BOOST_TEST_MESSAGE(actual.getText());
+ BOOST_CHECK_EQUAL(actual.getCode(), ResultType::getExpected().getCode());
+
+ // the response for either 200 or 409 will have a content body
+ BOOST_REQUIRE(actual.getBody().hasWire());
+
+ ControlParameters resp;
+ resp.wireDecode(actual.getBody());
+ BOOST_CHECK_EQUAL(resp.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERMANENT);
});
}
@@ -413,8 +470,6 @@
}
};
-namespace mpl = boost::mpl;
-
typedef mpl::vector<mpl::pair<TcpLocalFieldsEnable, CommandSuccess>,
mpl::pair<TcpLocalFieldsDisable, CommandSuccess>,
mpl::pair<UdpLocalFieldsEnable, CommandFailure<409>>,
diff --git a/tests/tools/nfdc/face-module.t.cpp b/tests/tools/nfdc/face-module.t.cpp
index 81ea707..5b44a5b 100644
--- a/tests/tools/nfdc/face-module.t.cpp
+++ b/tests/tools/nfdc/face-module.t.cpp
@@ -105,9 +105,25 @@
BOOST_AUTO_TEST_SUITE_END() // ShowCommand
-BOOST_FIXTURE_TEST_SUITE(CreateCommand, ExecuteCommandFixture)
+class ExecuteFaceCreateCommandFixture : public ExecuteCommandFixture
+{
+protected:
+ void
+ respond409(FacePersistency persistency)
+ {
+ MOCK_NFD_MGMT_REQUIRE_LAST_COMMAND_IS("/localhost/nfd/faces/create");
+ ControlParameters body;
+ body.setFaceId(1172)
+ .setUri("udp4://100.77.30.65:6363")
+ .setFacePersistency(persistency)
+ .setFlags(0);
+ this->failCommand(409, "conflict-409", body);
+ }
+};
-BOOST_AUTO_TEST_CASE(Normal)
+BOOST_FIXTURE_TEST_SUITE(CreateCommand, ExecuteFaceCreateCommandFixture)
+
+BOOST_AUTO_TEST_CASE(Creating)
{
this->processInterest = [this] (const Interest& interest) {
ControlParameters req = MOCK_NFD_MGMT_REQUIRE_LAST_COMMAND_IS("/localhost/nfd/faces/create");
@@ -128,7 +144,64 @@
BOOST_CHECK(err.is_empty());
}
-BOOST_AUTO_TEST_CASE(Error)
+BOOST_AUTO_TEST_CASE(UpgradingPersistency)
+{
+ bool hasUpdateCommand = false;
+ this->processInterest = [this, &hasUpdateCommand] (const Interest& interest) {
+ if (this->getCommand("/localhost/nfd/faces/create")) {
+ this->respond409(FacePersistency::FACE_PERSISTENCY_ON_DEMAND);
+ return;
+ }
+
+ ControlParameters req = MOCK_NFD_MGMT_REQUIRE_LAST_COMMAND_IS("/localhost/nfd/faces/update");
+ hasUpdateCommand = true;
+ BOOST_REQUIRE(req.hasFaceId());
+ BOOST_CHECK_EQUAL(req.getFaceId(), 1172);
+ BOOST_REQUIRE(req.hasFacePersistency());
+ BOOST_CHECK_EQUAL(req.getFacePersistency(), FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+ BOOST_CHECK(!req.hasFlags());
+
+ ControlParameters resp;
+ resp.setFaceId(1172)
+ .setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERSISTENT)
+ .setFlags(0);
+ this->succeedCommand(resp);
+ };
+
+ this->execute("face create udp://100.77.30.65");
+ BOOST_CHECK(hasUpdateCommand);
+ BOOST_CHECK_EQUAL(exitCode, 0);
+ BOOST_CHECK(out.is_equal("face-updated id=1172 remote=udp4://100.77.30.65:6363 persistency=persistent\n"));
+ BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(NotDowngradingPersistency)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ this->respond409(FacePersistency::FACE_PERSISTENCY_PERMANENT);
+ // no command other than faces/create is expected
+ };
+
+ this->execute("face create udp://100.77.30.65");
+ BOOST_CHECK_EQUAL(exitCode, 0);
+ BOOST_CHECK(out.is_equal("face-exists id=1172 remote=udp4://100.77.30.65:6363 persistency=permanent\n"));
+ BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(SamePersistency)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ this->respond409(FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+ // no command other than faces/create is expected
+ };
+
+ this->execute("face create udp://100.77.30.65");
+ BOOST_CHECK_EQUAL(exitCode, 0);
+ BOOST_CHECK(out.is_equal("face-exists id=1172 remote=udp4://100.77.30.65:6363 persistency=persistent\n"));
+ BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(ErrorCreate)
{
this->processInterest = nullptr; // no response
@@ -138,6 +211,40 @@
BOOST_CHECK(err.is_equal("Error 10060 when creating face: request timed out\n"));
}
+BOOST_AUTO_TEST_CASE(ErrorConflict)
+{
+ // Current NFD will not report a 409-conflict with a different remote FaceUri, but this is
+ // allowed by FaceMgmt protocol and nfdc should not attempt to upgrade persistency in this case.
+
+ this->processInterest = [this] (const Interest& interest) {
+ // conflict with udp4://100.77.30.65:6363
+ this->respond409(FacePersistency::FACE_PERSISTENCY_ON_DEMAND);
+ };
+
+ this->execute("face create udp://20.53.73.45");
+ BOOST_CHECK_EQUAL(exitCode, 1);
+ BOOST_CHECK(out.is_empty());
+ BOOST_CHECK(err.is_equal("Error 409 when creating face: conflict-409\n"));
+}
+
+BOOST_AUTO_TEST_CASE(ErrorUpdate)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ if (this->getCommand("/localhost/nfd/faces/create")) {
+ this->respond409(FacePersistency::FACE_PERSISTENCY_ON_DEMAND);
+ return;
+ }
+
+ MOCK_NFD_MGMT_REQUIRE_LAST_COMMAND_IS("/localhost/nfd/faces/update");
+ // no response to faces/update
+ };
+
+ this->execute("face create udp://100.77.30.65");
+ BOOST_CHECK_EQUAL(exitCode, 1);
+ BOOST_CHECK(out.is_empty());
+ BOOST_CHECK(err.is_equal("Error 10060 when upgrading face persistency: request timed out\n"));
+}
+
BOOST_AUTO_TEST_SUITE_END() // CreateCommand
BOOST_FIXTURE_TEST_SUITE(DestroyCommand, ExecuteCommandFixture)
diff --git a/tests/tools/nfdc/mock-nfd-mgmt-fixture.hpp b/tests/tools/nfdc/mock-nfd-mgmt-fixture.hpp
index 4d55849..485b030 100644
--- a/tests/tools/nfdc/mock-nfd-mgmt-fixture.hpp
+++ b/tests/tools/nfdc/mock-nfd-mgmt-fixture.hpp
@@ -63,7 +63,7 @@
* \return command parameters
*/
ndn::optional<ControlParameters>
- getCommand(const Name& expectedPrefix)
+ getCommand(const Name& expectedPrefix) const
{
if (face.sentInterests.empty() ||
!expectedPrefix.isPrefixOf(face.sentInterests.back().getName())) {
@@ -93,6 +93,12 @@
this->sendCommandReply({code, text});
}
+ void
+ failCommand(uint32_t code, const std::string& text, const ControlParameters& resp)
+ {
+ this->sendCommandReply(ndn::nfd::ControlResponse(code, text).setBody(resp.wireEncode()));
+ }
+
protected: // StatusDataset
/** \brief send an empty dataset in reply to StatusDataset request
* \param prefix dataset prefix without version and segment