mgmt: support rib/announce control command

This commit adds support for a new control command type,
RibAnnounceCommand. A new command format class is also added,
ApplicationParametersCommandFormat, to support encoding the
request information in the ApplicationParameters element of
the interest instead of using ControlParameters.

Refs: #4650
Change-Id: I351d3852062a80349fb67d7f18db4c883551835c
diff --git a/ndn-cxx/mgmt/nfd/control-command.cpp b/ndn-cxx/mgmt/nfd/control-command.cpp
index 31ec2df..6e90f84 100644
--- a/ndn-cxx/mgmt/nfd/control-command.cpp
+++ b/ndn-cxx/mgmt/nfd/control-command.cpp
@@ -347,4 +347,42 @@
   }
 }
 
+void
+RibAnnounceParameters::wireDecode(const Block& wire)
+{
+  wire.parse();
+  auto it = wire.find(tlv::Data);
+  if (it == wire.elements_end()) {
+    NDN_THROW(Error("Missing prefix announcement parameter"));
+  }
+  m_prefixAnn = PrefixAnnouncement(Data(*it));
+}
+
+Block
+RibAnnounceParameters::wireEncode() const
+{
+  if (!m_prefixAnn.getData()) {
+    NDN_THROW(Error("Prefix announcement must be signed"));
+  }
+  return m_prefixAnn.getData()->wireEncode();
+}
+
+const RibAnnounceCommand::RequestFormat RibAnnounceCommand::s_requestFormat;
+const RibAnnounceCommand::ResponseFormat RibAnnounceCommand::s_responseFormat =
+    RibRegisterCommand::s_responseFormat;
+
+void
+RibAnnounceCommand::validateRequestImpl(const RibAnnounceParameters& parameters)
+{
+  if (!parameters.getPrefixAnnouncement().getData()) {
+    NDN_THROW(ArgumentError("Prefix announcement must be signed"));
+  }
+}
+
+void
+RibAnnounceCommand::validateResponseImpl(const ControlParameters& parameters)
+{
+  RibRegisterCommand::validateResponseImpl(parameters);
+}
+
 } // namespace ndn::nfd
diff --git a/ndn-cxx/mgmt/nfd/control-command.hpp b/ndn-cxx/mgmt/nfd/control-command.hpp
index a67e28e..a6784c7 100644
--- a/ndn-cxx/mgmt/nfd/control-command.hpp
+++ b/ndn-cxx/mgmt/nfd/control-command.hpp
@@ -24,6 +24,7 @@
 
 #include "ndn-cxx/interest.hpp"
 #include "ndn-cxx/mgmt/nfd/control-parameters.hpp"
+#include "ndn-cxx/prefix-announcement.hpp"
 
 #include <bitset>
 
@@ -103,9 +104,52 @@
 
 /**
  * \ingroup management
+ * \brief Implements decoding, encoding, and validation of control command parameters carried
+ *        in the ApplicationParameters of the request (Interest packet).
+ * \note This format is applicable to control command requests only.
+ */
+template<typename PT>
+class ApplicationParametersCommandFormat
+{
+public:
+  using ParametersType = PT;
+
+  /**
+   * \brief Does nothing.
+   */
+  void
+  validate(const ParametersType&) const
+  {
+  }
+
+  /**
+   * \brief Extract the parameters from the request \p interest.
+   */
+  static shared_ptr<ParametersType>
+  decode(const Interest& interest, size_t prefixLen = 0)
+  {
+    auto params = make_shared<ParametersType>();
+    params->wireDecode(interest.getApplicationParameters());
+    return params;
+  }
+
+  /**
+   * \brief Serialize the parameters into the request \p interest.
+   */
+  static void
+  encode(Interest& interest, const ParametersType& params)
+  {
+    interest.setApplicationParameters(params.wireEncode());
+  }
+};
+
+
+/**
+ * \ingroup management
  * \brief Base class for all NFD control commands.
  * \tparam RequestFormatType  A class type that will handle the encoding and validation of the request
- *                            parameters. Only ControlParametersCommandFormat is supported for now.
+ *                            parameters. The type can be ApplicationParametersCommandFormat or
+ *                            ControlParametersCommandFormat.
  * \tparam ResponseFormatType A class type that will handle the encoding and validation of the response
  *                            parameters. Only ControlParametersCommandFormat is supported for now.
  * \sa https://redmine.named-data.net/projects/nfd/wiki/ControlCommand
@@ -384,6 +428,7 @@
 class RibRegisterCommand : public ControlCommand<RibRegisterCommand>
 {
   NDN_CXX_CONTROL_COMMAND("rib", "register");
+  friend class RibAnnounceCommand;
 
   static void
   applyDefaultsToRequestImpl(ControlParameters& parameters);
@@ -409,6 +454,61 @@
   validateResponseImpl(const ControlParameters& parameters);
 };
 
+
+/**
+ * \ingroup management
+ * \brief Request parameters for `rib/announce` command.
+ */
+class RibAnnounceParameters final : public mgmt::ControlParametersBase
+{
+public:
+  class Error : public tlv::Error
+  {
+  public:
+    using tlv::Error::Error;
+  };
+
+  const PrefixAnnouncement&
+  getPrefixAnnouncement() const
+  {
+    return m_prefixAnn;
+  }
+
+  RibAnnounceParameters&
+  setPrefixAnnouncement(const PrefixAnnouncement& pa)
+  {
+    m_prefixAnn = pa;
+    return *this;
+  }
+
+  void
+  wireDecode(const Block& wire) final;
+
+  Block
+  wireEncode() const final;
+
+private:
+  PrefixAnnouncement m_prefixAnn;
+};
+
+
+/**
+ * \ingroup management
+ * \brief Represents a `rib/announce` command.
+ * \sa https://redmine.named-data.net/projects/nfd/wiki/PrefixAnnouncement
+ */
+class RibAnnounceCommand : public ControlCommand<RibAnnounceCommand,
+                                                 ApplicationParametersCommandFormat<RibAnnounceParameters>>
+{
+  NDN_CXX_CONTROL_COMMAND("rib", "announce");
+
+  static void
+  validateRequestImpl(const RibAnnounceParameters& parameters);
+
+  static void
+  validateResponseImpl(const ControlParameters& parameters);
+};
+
 } // namespace ndn::nfd
 
 #endif // NDN_CXX_MGMT_NFD_CONTROL_COMMAND_HPP
diff --git a/tests/unit/mgmt/dispatcher.t.cpp b/tests/unit/mgmt/dispatcher.t.cpp
index 8caedc2..6379003 100644
--- a/tests/unit/mgmt/dispatcher.t.cpp
+++ b/tests/unit/mgmt/dispatcher.t.cpp
@@ -232,6 +232,7 @@
   dispatcher.addControlCommand<nfd::StrategyChoiceUnsetCommand>(makeAcceptAllAuthorization(), handler);
   dispatcher.addControlCommand<nfd::RibRegisterCommand>(makeAcceptAllAuthorization(), handler);
   dispatcher.addControlCommand<nfd::RibUnregisterCommand>(makeAcceptAllAuthorization(), handler);
+  dispatcher.addControlCommand<nfd::RibAnnounceCommand>(makeAcceptAllAuthorization(), handler);
 
   BOOST_CHECK_THROW(dispatcher.addControlCommand<nfd::CsConfigCommand>(makeAcceptAllAuthorization(),
                                                                        [] (auto&&...) {}),
diff --git a/tests/unit/mgmt/nfd/control-command.t.cpp b/tests/unit/mgmt/nfd/control-command.t.cpp
index dc8cff0..8aa7f50 100644
--- a/tests/unit/mgmt/nfd/control-command.t.cpp
+++ b/tests/unit/mgmt/nfd/control-command.t.cpp
@@ -22,6 +22,7 @@
 #include "ndn-cxx/mgmt/nfd/control-command.hpp"
 
 #include "tests/boost-test.hpp"
+#include "tests/key-chain-fixture.hpp"
 
 namespace ndn::tests {
 
@@ -492,6 +493,51 @@
   BOOST_CHECK_THROW(Command::validateResponse(p2), ArgumentError);
 }
 
+BOOST_FIXTURE_TEST_CASE(RibAnnounce, KeyChainFixture)
+{
+  using Command = RibAnnounceCommand;
+
+  // Good request
+  PrefixAnnouncement pa1;
+  pa1.setAnnouncedName("ndn:/");
+  pa1.setExpiration(1_min);
+  pa1.toData(m_keyChain);
+  RibAnnounceParameters p1;
+  p1.setPrefixAnnouncement(pa1);
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
+  Name n1 = Command::createRequest("/PREFIX", p1).getName();
+  BOOST_CHECK(Name("ndn:/PREFIX/rib/announce").isPrefixOf(n1));
+
+  // Good response
+  ControlParameters p2;
+  p2.setName("ndn:/")
+    .setFaceId(22)
+    .setOrigin(ndn::nfd::ROUTE_ORIGIN_PREFIXANN)
+    .setCost(2048)
+    .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)
+    .setExpirationPeriod(1_min);
+  BOOST_CHECK_NO_THROW(Command::validateResponse(p2));
+
+  // Bad request (PrefixAnnouncement must be signed)
+  PrefixAnnouncement pa2;
+  pa2.setAnnouncedName("ndn:/");
+  pa2.setExpiration(1_min);
+  RibAnnounceParameters p3;
+  BOOST_CHECK_THROW(Command::validateRequest(p3), ArgumentError);
+  p3.setPrefixAnnouncement(pa2);
+  BOOST_CHECK_THROW(Command::validateRequest(p3), ArgumentError);
+
+  // Bad response (FaceId must be valid)
+  ControlParameters p4;
+  p4.setName("ndn:/")
+    .setFaceId(0)
+    .setOrigin(ndn::nfd::ROUTE_ORIGIN_PREFIXANN)
+    .setCost(2048)
+    .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)
+    .setExpirationPeriod(1_min);
+  BOOST_CHECK_THROW(Command::validateResponse(p4), ArgumentError);
+}
+
 BOOST_AUTO_TEST_SUITE_END() // TestControlCommand
 BOOST_AUTO_TEST_SUITE_END() // Nfd
 BOOST_AUTO_TEST_SUITE_END() // Mgmt
diff --git a/tests/unit/mgmt/nfd/controller.t.cpp b/tests/unit/mgmt/nfd/controller.t.cpp
index 8be9d23..9fad4ec 100644
--- a/tests/unit/mgmt/nfd/controller.t.cpp
+++ b/tests/unit/mgmt/nfd/controller.t.cpp
@@ -140,11 +140,15 @@
 
 BOOST_AUTO_TEST_CASE(InvalidRequest)
 {
-  ControlParameters parameters;
-  parameters.setName("ndn:/should-not-have-this-field");
+  ControlParameters p1;
+  p1.setName("/should-not-have-this-field");
   // Uri is missing
+  BOOST_CHECK_THROW(controller.start<FaceCreateCommand>(p1, succeedCallback, commandFailCallback),
+                    ArgumentError);
 
-  BOOST_CHECK_THROW(controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback),
+  RibAnnounceParameters p2;
+  // PrefixAnnouncement not signed
+  BOOST_CHECK_THROW(controller.start<RibAnnounceCommand>(p2, succeedCallback, commandFailCallback),
                     ArgumentError);
 }