mgmt: declare cs/config command

refs #4050

Change-Id: I779d425d0ca89750cf923c84009c19b37eecc9cf
diff --git a/src/encoding/nfd-constants.hpp b/src/encoding/nfd-constants.hpp
index 172c8c7..5951311 100644
--- a/src/encoding/nfd-constants.hpp
+++ b/src/encoding/nfd-constants.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2017 Regents of the University of California.
+ * Copyright (c) 2013-2018 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -85,6 +85,15 @@
 operator<<(std::ostream& os, FaceEventKind faceEventKind);
 
 /** \ingroup management
+ *  \brief CS enablement flags
+ *  \sa https://redmine.named-data.net/projects/nfd/wiki/CsMgmt#Update-config
+ */
+enum CsFlagBit {
+  BIT_CS_ENABLE_ADMIT = 0, ///< enables the CS to admit new Data
+  BIT_CS_ENABLE_SERVE = 1, ///< enables the CS to satisfy Interests using cached Data
+};
+
+/** \ingroup management
  */
 enum RouteOrigin : uint16_t {
   ROUTE_ORIGIN_NONE     = std::numeric_limits<uint16_t>::max(),
diff --git a/src/encoding/tlv-nfd.hpp b/src/encoding/tlv-nfd.hpp
index 5662470..0326024 100644
--- a/src/encoding/tlv-nfd.hpp
+++ b/src/encoding/tlv-nfd.hpp
@@ -37,6 +37,7 @@
   Uri                 = 114,
   Origin              = 111,
   Cost                = 106,
+  Capacity            = 131,
   Flags               = 108,
   Mask                = 112,
   Strategy            = 107,
diff --git a/src/mgmt/nfd/control-command.cpp b/src/mgmt/nfd/control-command.cpp
index 5ecf975..f85c870 100644
--- a/src/mgmt/nfd/control-command.cpp
+++ b/src/mgmt/nfd/control-command.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2013-2017 Regents of the University of California.
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -88,6 +88,12 @@
       BOOST_THROW_EXCEPTION(ArgumentError(CONTROL_PARAMETER_FIELD[i] + " is forbidden but present"));
     }
   }
+
+  if (m_optional[CONTROL_PARAMETER_FLAGS] && m_optional[CONTROL_PARAMETER_MASK]) {
+    if (parameters.hasFlags() != parameters.hasMask()) {
+      BOOST_THROW_EXCEPTION(ArgumentError("Flags must be accompanied by Mask"));
+    }
+  }
 }
 
 FaceCreateCommand::FaceCreateCommand()
@@ -116,16 +122,6 @@
 }
 
 void
-FaceCreateCommand::validateRequest(const ControlParameters& parameters) const
-{
-  this->ControlCommand::validateRequest(parameters);
-
-  if (parameters.hasFlags() != parameters.hasMask()) {
-    BOOST_THROW_EXCEPTION(ArgumentError("Flags must be accompanied by Mask"));
-  }
-}
-
-void
 FaceCreateCommand::validateResponse(const ControlParameters& parameters) const
 {
   this->ControlCommand::validateResponse(parameters);
@@ -158,16 +154,6 @@
 }
 
 void
-FaceUpdateCommand::validateRequest(const ControlParameters& parameters) const
-{
-  this->ControlCommand::validateRequest(parameters);
-
-  if (parameters.hasFlags() != parameters.hasMask()) {
-    BOOST_THROW_EXCEPTION(ArgumentError("Flags must be accompanied by Mask"));
-  }
-}
-
-void
 FaceUpdateCommand::validateResponse(const ControlParameters& parameters) const
 {
   this->ControlCommand::validateResponse(parameters);
@@ -264,6 +250,18 @@
   }
 }
 
+CsConfigCommand::CsConfigCommand()
+  : ControlCommand("cs", "config")
+{
+  m_requestValidator
+    .optional(CONTROL_PARAMETER_CAPACITY)
+    .optional(CONTROL_PARAMETER_FLAGS)
+    .optional(CONTROL_PARAMETER_MASK);
+  m_responseValidator
+    .required(CONTROL_PARAMETER_CAPACITY)
+    .required(CONTROL_PARAMETER_FLAGS);
+}
+
 StrategyChoiceSetCommand::StrategyChoiceSetCommand()
   : ControlCommand("strategy-choice", "set")
 {
diff --git a/src/mgmt/nfd/control-command.hpp b/src/mgmt/nfd/control-command.hpp
index 97b0fa8..74f0bb1 100644
--- a/src/mgmt/nfd/control-command.hpp
+++ b/src/mgmt/nfd/control-command.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2013-2017 Regents of the University of California.
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -148,9 +148,6 @@
   applyDefaultsToRequest(ControlParameters& parameters) const override;
 
   void
-  validateRequest(const ControlParameters& parameters) const override;
-
-  void
   validateResponse(const ControlParameters& parameters) const override;
 };
 
@@ -168,9 +165,6 @@
   void
   applyDefaultsToRequest(ControlParameters& parameters) const override;
 
-  void
-  validateRequest(const ControlParameters& parameters) const override;
-
   /**
    * \note This can only validate ControlParameters in a success response.
    *       Failure responses should be validated with validateRequest.
@@ -236,6 +230,18 @@
 
 /**
  * \ingroup management
+ * \brief represents a cs/config command
+ * \sa https://redmine.named-data.net/projects/nfd/wiki/CsMgmt#Update-config
+ */
+class CsConfigCommand : public ControlCommand
+{
+public:
+  CsConfigCommand();
+};
+
+
+/**
+ * \ingroup management
  * \brief represents a strategy-choice/set command
  * \sa https://redmine.named-data.net/projects/nfd/wiki/StrategyChoice#Set-the-strategy-for-a-namespace
  */
diff --git a/src/mgmt/nfd/control-parameters.cpp b/src/mgmt/nfd/control-parameters.cpp
index 61fb823..a98e807 100644
--- a/src/mgmt/nfd/control-parameters.cpp
+++ b/src/mgmt/nfd/control-parameters.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2017 Regents of the University of California.
+ * Copyright (c) 2013-2018 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -68,6 +68,9 @@
   if (this->hasFlags()) {
     totalLength += prependNonNegativeIntegerBlock(encoder, tlv::nfd::Flags, m_flags);
   }
+  if (this->hasCapacity()) {
+    totalLength += prependNonNegativeIntegerBlock(encoder, tlv::nfd::Capacity, m_capacity);
+  }
   if (this->hasCost()) {
     totalLength += prependNonNegativeIntegerBlock(encoder, tlv::nfd::Cost, m_cost);
   }
@@ -156,6 +159,12 @@
     m_cost = readNonNegativeInteger(*val);
   }
 
+  val = m_wire.find(tlv::nfd::Capacity);
+  m_hasFields[CONTROL_PARAMETER_CAPACITY] = val != m_wire.elements_end();
+  if (this->hasCapacity()) {
+    m_capacity = readNonNegativeInteger(*val);
+  }
+
   val = m_wire.find(tlv::nfd::Flags);
   m_hasFields[CONTROL_PARAMETER_FLAGS] = val != m_wire.elements_end();
   if (this->hasFlags()) {
@@ -295,6 +304,10 @@
     os << "Cost: " << parameters.getCost() << ", ";
   }
 
+  if (parameters.hasCapacity()) {
+    os << "Capacity: " << parameters.getCapacity() << ", ";
+  }
+
   if (parameters.hasFlags()) {
     os << "Flags: " << AsHex{parameters.getFlags()} << ", ";
   }
diff --git a/src/mgmt/nfd/control-parameters.hpp b/src/mgmt/nfd/control-parameters.hpp
index 791813f..79b84e0 100644
--- a/src/mgmt/nfd/control-parameters.hpp
+++ b/src/mgmt/nfd/control-parameters.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2017 Regents of the University of California.
+ * Copyright (c) 2013-2018 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -40,6 +40,7 @@
   CONTROL_PARAMETER_LOCAL_URI,
   CONTROL_PARAMETER_ORIGIN,
   CONTROL_PARAMETER_COST,
+  CONTROL_PARAMETER_CAPACITY,
   CONTROL_PARAMETER_FLAGS,
   CONTROL_PARAMETER_MASK,
   CONTROL_PARAMETER_STRATEGY,
@@ -55,6 +56,7 @@
   "LocalUri",
   "Origin",
   "Cost",
+  "Capacity",
   "Flags",
   "Mask",
   "Strategy",
@@ -278,6 +280,36 @@
   }
 
   bool
+  hasCapacity() const
+  {
+    return m_hasFields[CONTROL_PARAMETER_CAPACITY];
+  }
+
+  uint64_t
+  getCapacity() const
+  {
+    BOOST_ASSERT(this->hasCapacity());
+    return m_capacity;
+  }
+
+  ControlParameters&
+  setCapacity(uint64_t capacity)
+  {
+    m_wire.reset();
+    m_capacity = capacity;
+    m_hasFields[CONTROL_PARAMETER_CAPACITY] = true;
+    return *this;
+  }
+
+  ControlParameters&
+  unsetCapacity()
+  {
+    m_wire.reset();
+    m_hasFields[CONTROL_PARAMETER_CAPACITY] = false;
+    return *this;
+  }
+
+  bool
   hasFlags() const
   {
     return m_hasFields[CONTROL_PARAMETER_FLAGS];
@@ -474,6 +506,7 @@
   std::string         m_localUri;
   RouteOrigin         m_origin;
   uint64_t            m_cost;
+  uint64_t            m_capacity;
   uint64_t            m_flags;
   uint64_t            m_mask;
   Name                m_strategy;
diff --git a/tests/unit-tests/mgmt/nfd/control-command.t.cpp b/tests/unit-tests/mgmt/nfd/control-command.t.cpp
index f204197..537e9ee 100644
--- a/tests/unit-tests/mgmt/nfd/control-command.t.cpp
+++ b/tests/unit-tests/mgmt/nfd/control-command.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2013-2017 Regents of the University of California.
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -240,6 +240,59 @@
   BOOST_CHECK_EQUAL(p1.getFaceId(), 0);
 }
 
+BOOST_AUTO_TEST_CASE(CsConfigRequest)
+{
+  CsConfigCommand command;
+
+  // good empty request
+  ControlParameters p1;
+  command.validateRequest(p1);
+  BOOST_CHECK(Name("/PREFIX/cs/config").isPrefixOf(command.getRequestName("/PREFIX", p1)));
+
+  // good full request
+  ControlParameters p2;
+  p2.setCapacity(1574);
+  p2.setFlagBit(BIT_CS_ENABLE_ADMIT, true);
+  p2.setFlagBit(BIT_CS_ENABLE_SERVE, true);
+  command.validateRequest(p2);
+
+  // bad request: Flags but no Mask
+  ControlParameters p3(p2);
+  p3.unsetMask();
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+
+  // bad request: Mask but no Flags
+  ControlParameters p4(p2);
+  p4.unsetFlags();
+  BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
+
+  // bad request: forbidden field
+  ControlParameters p5(p2);
+  p5.setName("/example");
+  BOOST_CHECK_THROW(command.validateRequest(p5), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(CsConfigResponse)
+{
+  CsConfigCommand command;
+
+  // bad empty response
+  ControlParameters p1;
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+
+  // bad response: Mask not allowed
+  ControlParameters p2;
+  p2.setCapacity(1574);
+  p2.setFlagBit(BIT_CS_ENABLE_ADMIT, true);
+  p2.setFlagBit(BIT_CS_ENABLE_SERVE, true);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  // good response
+  ControlParameters p3(p2);
+  p3.unsetMask();
+  command.validateResponse(p3);
+}
+
 BOOST_AUTO_TEST_CASE(StrategyChoiceSet)
 {
   StrategyChoiceSetCommand command;
diff --git a/tests/unit-tests/mgmt/nfd/control-parameters.t.cpp b/tests/unit-tests/mgmt/nfd/control-parameters.t.cpp
index 199ba3b..80e8134 100644
--- a/tests/unit-tests/mgmt/nfd/control-parameters.t.cpp
+++ b/tests/unit-tests/mgmt/nfd/control-parameters.t.cpp
@@ -31,103 +31,88 @@
 BOOST_AUTO_TEST_SUITE(Nfd)
 BOOST_AUTO_TEST_SUITE(TestControlParameters)
 
-BOOST_AUTO_TEST_CASE(FaceOptions)
+BOOST_AUTO_TEST_CASE(Fields)
 {
-  ControlParameters parameters;
-  parameters.setUri("tcp4://192.0.2.1:6363");
-  parameters.setLocalUri("dev://eth0");
-  parameters.setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
-  parameters.setFlags(1);
-  parameters.setMask(1);
-  Block wire = parameters.wireEncode();
+  ControlParameters input;
 
-  ControlParameters decoded(wire);
-  BOOST_CHECK_EQUAL(decoded.getUri(), "tcp4://192.0.2.1:6363");
-  BOOST_CHECK_EQUAL(decoded.getLocalUri(), "dev://eth0");
-  BOOST_CHECK_EQUAL(decoded.getFacePersistency(), ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
-  BOOST_CHECK_EQUAL(decoded.getFlags(), 1);
-  BOOST_CHECK_EQUAL(decoded.getMask(), 1);
-
+  ControlParameters decoded(input.wireEncode());
   BOOST_CHECK_EQUAL(decoded.hasName(), false);
   BOOST_CHECK_EQUAL(decoded.hasFaceId(), false);
-  BOOST_CHECK_EQUAL(decoded.hasOrigin(), false);
-  BOOST_CHECK_EQUAL(decoded.hasCost(), false);
-  BOOST_CHECK_EQUAL(decoded.hasStrategy(), false);
-  BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), false);
-}
-
-BOOST_AUTO_TEST_CASE(FibOptions)
-{
-  ControlParameters parameters;
-  parameters.setName("ndn:/example")
-            .setFaceId(4)
-            .setCost(555);
-
-  Block wire = parameters.wireEncode();
-
-  ControlParameters decoded(wire);
-  BOOST_CHECK_EQUAL(decoded.getName(), Name("ndn:/example"));
-  BOOST_CHECK_EQUAL(decoded.getFaceId(), 4);
-  BOOST_CHECK_EQUAL(decoded.getCost(), 555);
-
   BOOST_CHECK_EQUAL(decoded.hasUri(), false);
   BOOST_CHECK_EQUAL(decoded.hasLocalUri(), false);
   BOOST_CHECK_EQUAL(decoded.hasOrigin(), false);
+  BOOST_CHECK_EQUAL(decoded.hasCost(), false);
+  BOOST_CHECK_EQUAL(decoded.hasCapacity(), false);
   BOOST_CHECK_EQUAL(decoded.hasFlags(), false);
   BOOST_CHECK_EQUAL(decoded.hasMask(), false);
   BOOST_CHECK_EQUAL(decoded.hasStrategy(), false);
   BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), false);
   BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), false);
-}
 
-BOOST_AUTO_TEST_CASE(StrategyChoiceOptions)
-{
-  ControlParameters parameters;
-  parameters.setName("ndn:/")
-            .setStrategy("ndn:/strategy/A");
+  input.setName("/name");
+  input.setFaceId(2634);
+  input.setUri("udp4://192.0.2.1:6363");
+  input.setLocalUri("udp4://192.0.2.2:6363");
+  input.setOrigin(ROUTE_ORIGIN_NLSR);
+  input.setCost(1388);
+  input.setCapacity(2632);
+  input.setFlags(0xAFC4);
+  input.setMask(0xF7A1);
+  input.setStrategy("/strategy-name");
+  input.setExpirationPeriod(1800000_ms);
+  input.setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERSISTENT);
 
-  Block wire = parameters.wireEncode();
+  decoded.wireDecode(input.wireEncode());
+  BOOST_CHECK_EQUAL(decoded.hasName(), true);
+  BOOST_CHECK_EQUAL(decoded.hasFaceId(), true);
+  BOOST_CHECK_EQUAL(decoded.hasUri(), true);
+  BOOST_CHECK_EQUAL(decoded.hasLocalUri(), true);
+  BOOST_CHECK_EQUAL(decoded.hasOrigin(), true);
+  BOOST_CHECK_EQUAL(decoded.hasCost(), true);
+  BOOST_CHECK_EQUAL(decoded.hasCapacity(), true);
+  BOOST_CHECK_EQUAL(decoded.hasFlags(), true);
+  BOOST_CHECK_EQUAL(decoded.hasMask(), true);
+  BOOST_CHECK_EQUAL(decoded.hasStrategy(), true);
+  BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), true);
+  BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), true);
 
-  ControlParameters decoded(wire);
-  BOOST_CHECK_EQUAL(decoded.getName(), Name("ndn:/"));
-  BOOST_CHECK_EQUAL(decoded.getStrategy(), Name("ndn:/strategy/A"));
-
-  BOOST_CHECK_EQUAL(decoded.hasFaceId(), false);
-  BOOST_CHECK_EQUAL(decoded.hasUri(), false);
-  BOOST_CHECK_EQUAL(decoded.hasLocalUri(), false);
-  BOOST_CHECK_EQUAL(decoded.hasOrigin(), false);
-  BOOST_CHECK_EQUAL(decoded.hasCost(), false);
-  BOOST_CHECK_EQUAL(decoded.hasFlags(), false);
-  BOOST_CHECK_EQUAL(decoded.hasMask(), false);
-  BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), false);
-  BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), false);
-}
-
-BOOST_AUTO_TEST_CASE(RibOptions)
-{
-  ControlParameters parameters;
-  parameters.setName("ndn:/example")
-            .setFaceId(4)
-            .setOrigin(ROUTE_ORIGIN_NLSR)
-            .setCost(6)
-            .setFlags(0x01)
-            .setExpirationPeriod(30_min);
-
-  Block wire = parameters.wireEncode();
-
-  ControlParameters decoded(wire);
-  BOOST_CHECK_EQUAL(decoded.getName(), Name("ndn:/example"));
-  BOOST_CHECK_EQUAL(decoded.getFaceId(), 4);
+  BOOST_CHECK_EQUAL(decoded.getName(), "/name");
+  BOOST_CHECK_EQUAL(decoded.getFaceId(), 2634);
+  BOOST_CHECK_EQUAL(decoded.getUri(), "udp4://192.0.2.1:6363");
+  BOOST_CHECK_EQUAL(decoded.getLocalUri(), "udp4://192.0.2.2:6363");
   BOOST_CHECK_EQUAL(decoded.getOrigin(), ROUTE_ORIGIN_NLSR);
-  BOOST_CHECK_EQUAL(decoded.getCost(), 6);
-  BOOST_CHECK_EQUAL(decoded.getFlags(), 0x01);
+  BOOST_CHECK_EQUAL(decoded.getCost(), 1388);
+  BOOST_CHECK_EQUAL(decoded.getCapacity(), 2632);
+  BOOST_CHECK_EQUAL(decoded.getFlags(), 0xAFC4);
+  BOOST_CHECK_EQUAL(decoded.getMask(), 0xF7A1);
+  BOOST_CHECK_EQUAL(decoded.getStrategy(), "/strategy-name");
   BOOST_CHECK_EQUAL(decoded.getExpirationPeriod(), 1800000_ms);
+  BOOST_CHECK_EQUAL(decoded.getFacePersistency(), FacePersistency::FACE_PERSISTENCY_PERSISTENT);
 
-  BOOST_CHECK_EQUAL(decoded.hasUri(), false);
-  BOOST_CHECK_EQUAL(decoded.hasLocalUri(), false);
-  BOOST_CHECK_EQUAL(decoded.hasMask(), false);
-  BOOST_CHECK_EQUAL(decoded.hasStrategy(), false);
-  BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), false);
+  input.unsetName();
+  input.unsetFaceId();
+  input.unsetUri();
+  input.unsetLocalUri();
+  input.unsetOrigin();
+  input.unsetCost();
+  input.unsetCapacity();
+  input.unsetFlags();
+  input.unsetMask();
+  input.unsetStrategy();
+  input.unsetExpirationPeriod();
+  input.unsetFacePersistency();
+  BOOST_CHECK_EQUAL(input.hasName(), false);
+  BOOST_CHECK_EQUAL(input.hasFaceId(), false);
+  BOOST_CHECK_EQUAL(input.hasUri(), false);
+  BOOST_CHECK_EQUAL(input.hasLocalUri(), false);
+  BOOST_CHECK_EQUAL(input.hasOrigin(), false);
+  BOOST_CHECK_EQUAL(input.hasCost(), false);
+  BOOST_CHECK_EQUAL(input.hasCapacity(), false);
+  BOOST_CHECK_EQUAL(input.hasFlags(), false);
+  BOOST_CHECK_EQUAL(input.hasMask(), false);
+  BOOST_CHECK_EQUAL(input.hasStrategy(), false);
+  BOOST_CHECK_EQUAL(input.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(input.hasFacePersistency(), false);
 }
 
 BOOST_AUTO_TEST_CASE(FlagsAndMask)