management: nfd::ControlCommand

ControlCommand and its subclasses provide command-specific parameter validation.

refs #1397

Change-Id: Ieca08f5db530c14d1cd16ddd7af17e4ffe6eb344
diff --git a/src/management/nfd-control-command.hpp b/src/management/nfd-control-command.hpp
new file mode 100644
index 0000000..69407c0
--- /dev/null
+++ b/src/management/nfd-control-command.hpp
@@ -0,0 +1,386 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/**
+ * Copyright (C) 2013 Regents of the University of California.
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NDN_MANAGEMENT_NFD_CONTROL_COMMAND_HPP
+#define NDN_MANAGEMENT_NFD_CONTROL_COMMAND_HPP
+
+#include "nfd-control-parameters.hpp"
+#include "../util/command-interest-generator.hpp"
+
+namespace ndn {
+namespace nfd {
+
+/** \brief base class of NFD ControlCommand
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/ControlCommand
+ */
+class ControlCommand
+{
+public:
+  /** \brief represents an error in ControlParameters
+   */
+  class ArgumentError : public std::invalid_argument
+  {
+  public:
+    explicit
+    ArgumentError(const std::string& what)
+      : std::invalid_argument(what)
+    {
+    }
+  };
+
+  /** \return Name prefix of this ControlCommand
+   */
+  const Name&
+  getPrefix() const
+  {
+    return m_prefix;
+  }
+
+  /** \brief make a Command Interest from parameters
+   */
+  Interest
+  makeCommandInterest(const ControlParameters& parameters,
+                      CommandInterestGenerator& commandInterestGenerator) const
+  {
+    this->validateRequest(parameters);
+
+    Name name = m_prefix;
+    name.append(parameters.wireEncode());
+    Interest commandInterest(name);
+    commandInterestGenerator.generate(commandInterest);
+    return commandInterest;
+  }
+
+  /** \brief validate request parameters
+   *  \throw ArgumentError
+   */
+  virtual void
+  validateRequest(const ControlParameters& parameters) const
+  {
+    m_requestValidator.validate(parameters);
+  }
+
+  /** \brief apply default values to missing fields in request
+   */
+  virtual void
+  applyDefaultsToRequest(ControlParameters& parameters) const
+  {
+  }
+
+  /** \brief validate response parameters
+   *  \throw ArgumentError
+   */
+  virtual void
+  validateResponse(const ControlParameters& parameters) const
+  {
+    m_responseValidator.validate(parameters);
+  }
+
+  /** \brief apply default values to missing fields in response
+   */
+  virtual void
+  applyDefaultsToResponse(ControlParameters& parameters) const
+  {
+  }
+
+protected:
+  ControlCommand(const std::string& module, const std::string& verb)
+    : m_prefix("ndn:/localhost/nfd")
+  {
+    m_prefix.append(module).append(verb);
+  }
+
+  class FieldValidator
+  {
+  public:
+    FieldValidator()
+    {
+      m_required.resize(CONTROL_PARAMETER_UBOUND);
+      m_optional.resize(CONTROL_PARAMETER_UBOUND);
+    }
+
+    /** \brief declare a required field
+     */
+    FieldValidator&
+    required(ControlParameterField field)
+    {
+      m_required[field] = true;
+      return *this;
+    }
+
+    /** \brief declare an optional field
+     */
+    FieldValidator&
+    optional(ControlParameterField field)
+    {
+      m_optional[field] = true;
+      return *this;
+    }
+
+    /** \brief verify that all required fields are present,
+     *         and all present fields are either required or optional
+     *  \throw ArgumentError
+     */
+    void
+    validate(const ControlParameters& parameters) const
+    {
+      const std::vector<bool>& presentFields = parameters.getPresentFields();
+
+      for (size_t i = 0; i < CONTROL_PARAMETER_UBOUND; ++i) {
+        bool isPresent = presentFields[i];
+        if (m_required[i]) {
+          if (!isPresent) {
+            throw ArgumentError(CONTROL_PARAMETER_FIELD[i] + " is required but missing");
+          }
+        }
+        else if (isPresent && !m_optional[i]) {
+          throw ArgumentError(CONTROL_PARAMETER_FIELD[i] + " is forbidden but present");
+        }
+      }
+    }
+
+  private:
+    std::vector<bool> m_required;
+    std::vector<bool> m_optional;
+  };
+
+protected:
+  /** \brief FieldValidator for request ControlParameters
+   *
+   *  Constructor of subclass should populate this validator.
+   */
+  FieldValidator m_requestValidator;
+  /** \brief FieldValidator for response ControlParameters
+   *
+   *  Constructor of subclass should populate this validator.
+   */
+  FieldValidator m_responseValidator;
+
+private:
+  Name m_prefix;
+};
+
+
+/** \brief represents a faces/create command
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Create-a-face
+ */
+class FaceCreateCommand : public ControlCommand
+{
+public:
+  FaceCreateCommand()
+    : ControlCommand("faces", "create")
+  {
+    m_requestValidator
+      .required(CONTROL_PARAMETER_URI);
+    m_responseValidator
+      .required(CONTROL_PARAMETER_URI)
+      .required(CONTROL_PARAMETER_FACE_ID);
+  }
+
+  virtual void
+  validateResponse(const ControlParameters& parameters) const
+  {
+    this->ControlCommand::validateResponse(parameters);
+
+    if (parameters.getFaceId() == 0) {
+      throw ArgumentError("FaceId must not be zero");
+    }
+  }
+};
+
+
+/** \brief represents a faces/destroy command
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Destroy-a-face
+ */
+class FaceDestroyCommand : public ControlCommand
+{
+public:
+  FaceDestroyCommand()
+    : ControlCommand("faces", "destroy")
+  {
+    m_requestValidator
+      .required(CONTROL_PARAMETER_FACE_ID);
+    m_responseValidator = m_requestValidator;
+  }
+
+  virtual void
+  validateRequest(const ControlParameters& parameters) const
+  {
+    this->ControlCommand::validateRequest(parameters);
+
+    if (parameters.getFaceId() == 0) {
+      throw ArgumentError("FaceId must not be zero");
+    }
+  }
+
+  virtual void
+  validateResponse(const ControlParameters& parameters) const
+  {
+    this->validateRequest(parameters);
+  }
+};
+
+
+class FaceLocalControlCommand : public ControlCommand
+{
+protected:
+  explicit
+  FaceLocalControlCommand(const std::string& verb)
+    : ControlCommand("faces", verb)
+  {
+    m_requestValidator
+      .required(CONTROL_PARAMETER_LOCAL_CONTROL_FEATURE);
+    m_responseValidator = m_requestValidator;
+  }
+};
+
+
+/** \brief represents a faces/enable-local-control command
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Enable-a-LocalControlHeader-feature
+ */
+class FaceEnableLocalControlCommand : public FaceLocalControlCommand
+{
+public:
+  FaceEnableLocalControlCommand()
+    : FaceLocalControlCommand("enable-local-control")
+  {
+  }
+};
+
+
+/** \brief represents a faces/disable-local-control command
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Disable-a-LocalControlHeader-feature
+ */
+class FaceDisableLocalControlCommand : public FaceLocalControlCommand
+{
+public:
+  FaceDisableLocalControlCommand()
+    : FaceLocalControlCommand("disable-local-control")
+  {
+  }
+};
+
+
+/** \brief represents a fib/add-nexthop command
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/FibMgmt#Add-a-nexthop
+ */
+class FibAddNextHopCommand : public ControlCommand
+{
+public:
+  FibAddNextHopCommand()
+    : ControlCommand("fib", "add-nexthop")
+  {
+    m_requestValidator
+      .required(CONTROL_PARAMETER_NAME)
+      .required(CONTROL_PARAMETER_FACE_ID)
+      .optional(CONTROL_PARAMETER_COST);
+    m_responseValidator
+      .required(CONTROL_PARAMETER_NAME)
+      .required(CONTROL_PARAMETER_FACE_ID)
+      .required(CONTROL_PARAMETER_COST);
+  }
+
+  virtual void
+  applyDefaultsToRequest(ControlParameters& parameters) const
+  {
+    if (!parameters.hasCost()) {
+      parameters.setCost(0);
+    }
+  }
+
+  virtual void
+  validateResponse(const ControlParameters& parameters) const
+  {
+    this->ControlCommand::validateResponse(parameters);
+
+    if (parameters.getFaceId() == 0) {
+      throw ArgumentError("FaceId must not be zero");
+    }
+  }
+};
+
+
+/** \brief represents a fib/remove-nexthop command
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/FibMgmt#Remove-a-nexthop
+ */
+class FibRemoveNextHopCommand : public ControlCommand
+{
+public:
+  FibRemoveNextHopCommand()
+    : ControlCommand("fib", "remove-nexthop")
+  {
+    m_requestValidator
+      .required(CONTROL_PARAMETER_NAME)
+      .required(CONTROL_PARAMETER_FACE_ID);
+    m_responseValidator
+      .required(CONTROL_PARAMETER_NAME)
+      .required(CONTROL_PARAMETER_FACE_ID);
+  }
+
+  virtual void
+  validateResponse(const ControlParameters& parameters) const
+  {
+    this->ControlCommand::validateResponse(parameters);
+
+    if (parameters.getFaceId() == 0) {
+      throw ArgumentError("FaceId must not be zero");
+    }
+  }
+};
+
+
+/** \brief represents a strategy-choice/set command
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/StrategyChoice#Set-the-strategy-for-a-namespace
+ */
+class StrategyChoiceSetCommand : public ControlCommand
+{
+public:
+  StrategyChoiceSetCommand()
+    : ControlCommand("strategy-choice", "set")
+  {
+    m_requestValidator
+      .required(CONTROL_PARAMETER_NAME)
+      .required(CONTROL_PARAMETER_STRATEGY);
+    m_responseValidator = m_requestValidator;
+  }
+};
+
+
+/** \brief represents a strategy-choice/set command
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/StrategyChoice#Unset-the-strategy-for-a-namespace
+ */
+class StrategyChoiceUnsetCommand : public ControlCommand
+{
+public:
+  StrategyChoiceUnsetCommand()
+    : ControlCommand("strategy-choice", "unset")
+  {
+    m_requestValidator
+      .required(CONTROL_PARAMETER_NAME);
+    m_responseValidator = m_requestValidator;
+  }
+
+  virtual void
+  validateRequest(const ControlParameters& parameters) const
+  {
+    this->ControlCommand::validateRequest(parameters);
+
+    if (parameters.getName().size() == 0) {
+      throw ArgumentError("Name must not be ndn:/");
+    }
+  }
+
+  virtual void
+  validateResponse(const ControlParameters& parameters) const
+  {
+    this->validateRequest(parameters);
+  }
+};
+
+} // namespace nfd
+} // namespace ndn
+
+#endif // NDN_MANAGEMENT_NFD_CONTROL_COMMAND_HPP
diff --git a/src/management/nfd-control-parameters.hpp b/src/management/nfd-control-parameters.hpp
index b9e1d7b..836369b 100644
--- a/src/management/nfd-control-parameters.hpp
+++ b/src/management/nfd-control-parameters.hpp
@@ -18,6 +18,25 @@
 typedef ControlParameters FibManagementOptions;
 typedef ControlParameters StrategyChoiceOptions;
 
+enum ControlParameterField {
+  CONTROL_PARAMETER_NAME,
+  CONTROL_PARAMETER_FACE_ID,
+  CONTROL_PARAMETER_URI,
+  CONTROL_PARAMETER_LOCAL_CONTROL_FEATURE,
+  CONTROL_PARAMETER_COST,
+  CONTROL_PARAMETER_STRATEGY,
+  CONTROL_PARAMETER_UBOUND
+};
+
+const std::string CONTROL_PARAMETER_FIELD[CONTROL_PARAMETER_UBOUND] = {
+  "Name",
+  "FaceId",
+  "Uri",
+  "LocalControlFeature",
+  "Cost",
+  "Strategy",
+};
+
 enum LocalControlFeature {
   LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID = 1,
   LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID = 2
@@ -35,10 +54,14 @@
     }
   };
 
-  ControlParameters();
+  ControlParameters()
+    : m_hasFields(CONTROL_PARAMETER_UBOUND)
+  {
+  }
 
   explicit
   ControlParameters(const Block& block)
+    : m_hasFields(CONTROL_PARAMETER_UBOUND)
   {
     wireDecode(block);
   }
@@ -57,13 +80,13 @@
   bool
   hasName() const
   {
-    return m_hasName;
+    return m_hasFields[CONTROL_PARAMETER_NAME];
   }
 
   const Name&
   getName() const
   {
-    BOOST_ASSERT(m_hasName);
+    BOOST_ASSERT(this->hasName());
     return m_name;
   }
 
@@ -72,7 +95,7 @@
   {
     m_wire.reset();
     m_name = name;
-    m_hasName = true;
+    m_hasFields[CONTROL_PARAMETER_NAME] = true;
     return *this;
   }
 
@@ -80,20 +103,20 @@
   unsetName()
   {
     m_wire.reset();
-    m_hasName = false;
+    m_hasFields[CONTROL_PARAMETER_NAME] = false;
     return *this;
   }
 
   bool
   hasFaceId() const
   {
-    return m_hasFaceId;
+    return m_hasFields[CONTROL_PARAMETER_FACE_ID];
   }
 
   uint64_t
   getFaceId() const
   {
-    BOOST_ASSERT(m_hasFaceId);
+    BOOST_ASSERT(this->hasFaceId());
     return m_faceId;
   }
 
@@ -102,7 +125,7 @@
   {
     m_wire.reset();
     m_faceId = faceId;
-    m_hasFaceId = true;
+    m_hasFields[CONTROL_PARAMETER_FACE_ID] = true;
     return *this;
   }
 
@@ -110,20 +133,20 @@
   unsetFaceId()
   {
     m_wire.reset();
-    m_hasFaceId = false;
+    m_hasFields[CONTROL_PARAMETER_FACE_ID] = false;
     return *this;
   }
 
   bool
   hasUri() const
   {
-    return m_hasUri;
+    return m_hasFields[CONTROL_PARAMETER_URI];
   }
 
   const std::string&
   getUri() const
   {
-    BOOST_ASSERT(m_hasUri);
+    BOOST_ASSERT(this->hasUri());
     return m_uri;
   }
 
@@ -132,7 +155,7 @@
   {
     m_wire.reset();
     m_uri = uri;
-    m_hasUri = true;
+    m_hasFields[CONTROL_PARAMETER_URI] = true;
     return *this;
   }
 
@@ -140,20 +163,20 @@
   unsetUri()
   {
     m_wire.reset();
-    m_hasUri = false;
+    m_hasFields[CONTROL_PARAMETER_URI] = false;
     return *this;
   }
 
   bool
   hasLocalControlFeature() const
   {
-    return m_hasLocalControlFeature;
+    return m_hasFields[CONTROL_PARAMETER_LOCAL_CONTROL_FEATURE];
   }
 
   LocalControlFeature
   getLocalControlFeature() const
   {
-    BOOST_ASSERT(m_hasLocalControlFeature);
+    BOOST_ASSERT(this->hasLocalControlFeature());
     return m_localControlFeature;
   }
 
@@ -162,7 +185,7 @@
   {
     m_wire.reset();
     m_localControlFeature = localControlFeature;
-    m_hasLocalControlFeature = true;
+    m_hasFields[CONTROL_PARAMETER_LOCAL_CONTROL_FEATURE] = true;
     return *this;
   }
 
@@ -170,20 +193,20 @@
   unsetLocalControlFeature()
   {
     m_wire.reset();
-    m_hasLocalControlFeature = false;
+    m_hasFields[CONTROL_PARAMETER_LOCAL_CONTROL_FEATURE] = false;
     return *this;
   }
 
   bool
   hasCost() const
   {
-    return m_hasCost;
+    return m_hasFields[CONTROL_PARAMETER_COST];
   }
 
   uint64_t
   getCost() const
   {
-    BOOST_ASSERT(m_hasCost);
+    BOOST_ASSERT(this->hasCost());
     return m_cost;
   }
 
@@ -192,7 +215,7 @@
   {
     m_wire.reset();
     m_cost = cost;
-    m_hasCost = true;
+    m_hasFields[CONTROL_PARAMETER_COST] = true;
     return *this;
   }
 
@@ -200,20 +223,20 @@
   unsetCost()
   {
     m_wire.reset();
-    m_hasCost = false;
+    m_hasFields[CONTROL_PARAMETER_COST] = false;
     return *this;
   }
 
   bool
   hasStrategy() const
   {
-    return m_hasStrategy;
+    return m_hasFields[CONTROL_PARAMETER_STRATEGY];
   }
 
   const Name&
   getStrategy() const
   {
-    BOOST_ASSERT(m_hasStrategy);
+    BOOST_ASSERT(this->hasStrategy());
     return m_strategy;
   }
 
@@ -222,7 +245,7 @@
   {
     m_wire.reset();
     m_strategy = strategy;
-    m_hasStrategy = true;
+    m_hasFields[CONTROL_PARAMETER_STRATEGY] = true;
     return *this;
   }
 
@@ -230,11 +253,19 @@
   unsetStrategy()
   {
     m_wire.reset();
-    m_hasStrategy = false;
+    m_hasFields[CONTROL_PARAMETER_STRATEGY] = false;
     return *this;
   }
 
+  const std::vector<bool>&
+  getPresentFields() const
+  {
+    return m_hasFields;
+  }
+
 private: // fields
+  std::vector<bool>   m_hasFields;
+
   Name                m_name;
   uint64_t            m_faceId;
   std::string         m_uri;
@@ -242,56 +273,38 @@
   uint64_t            m_cost;
   Name                m_strategy;
 
-  bool m_hasName;
-  bool m_hasFaceId;
-  bool m_hasUri;
-  bool m_hasLocalControlFeature;
-  bool m_hasCost;
-  bool m_hasStrategy;
-
 private:
   mutable Block m_wire;
 };
 
 
-inline
-ControlParameters::ControlParameters()
-  : m_hasName(false)
-  , m_hasFaceId(false)
-  , m_hasUri(false)
-  , m_hasLocalControlFeature(false)
-  , m_hasCost(false)
-  , m_hasStrategy(false)
-{
-}
-
 template<bool T>
 inline size_t
 ControlParameters::wireEncode(EncodingImpl<T>& encoder) const
 {
   size_t totalLength = 0;
 
-  if (m_hasStrategy) {
+  if (this->hasStrategy()) {
     totalLength += prependNestedBlock(encoder, tlv::nfd::Strategy, m_strategy);
   }
-  if (m_hasCost) {
+  if (this->hasCost()) {
     totalLength += prependNonNegativeIntegerBlock(encoder, tlv::nfd::Cost, m_cost);
   }
-  if (m_hasLocalControlFeature) {
+  if (this->hasLocalControlFeature()) {
     totalLength += prependNonNegativeIntegerBlock(encoder,
                    tlv::nfd::LocalControlFeature, m_localControlFeature);
   }
-  if (m_hasUri) {
+  if (this->hasUri()) {
     size_t valLength = encoder.prependByteArray(
                        reinterpret_cast<const uint8_t*>(m_uri.c_str()), m_uri.size());
     totalLength += valLength;
     totalLength += encoder.prependVarNumber(valLength);
     totalLength += encoder.prependVarNumber(tlv::nfd::Uri);
   }
-  if (m_hasFaceId) {
+  if (this->hasFaceId()) {
     totalLength += prependNonNegativeIntegerBlock(encoder, tlv::nfd::FaceId, m_faceId);
   }
-  if (m_hasName) {
+  if (this->hasName()) {
     totalLength += m_name.wireEncode(encoder);
   }
 
@@ -327,38 +340,38 @@
   Block::element_const_iterator val;
 
   val = m_wire.find(Tlv::Name);
-  m_hasName = val != m_wire.elements_end();
-  if (m_hasName) {
+  m_hasFields[CONTROL_PARAMETER_NAME] = val != m_wire.elements_end();
+  if (this->hasName()) {
     m_name.wireDecode(*val);
   }
 
   val = m_wire.find(tlv::nfd::FaceId);
-  m_hasFaceId = val != m_wire.elements_end();
-  if (m_hasFaceId) {
+  m_hasFields[CONTROL_PARAMETER_FACE_ID] = val != m_wire.elements_end();
+  if (this->hasFaceId()) {
     m_faceId = static_cast<uint64_t>(readNonNegativeInteger(*val));
   }
 
   val = m_wire.find(tlv::nfd::Uri);
-  m_hasUri = val != m_wire.elements_end();
-  if (m_hasUri) {
+  m_hasFields[CONTROL_PARAMETER_URI] = val != m_wire.elements_end();
+  if (this->hasUri()) {
     m_uri.assign(reinterpret_cast<const char*>(val->value()), val->value_size());
   }
 
   val = m_wire.find(tlv::nfd::LocalControlFeature);
-  m_hasLocalControlFeature = val != m_wire.elements_end();
-  if (m_hasLocalControlFeature) {
+  m_hasFields[CONTROL_PARAMETER_LOCAL_CONTROL_FEATURE] = val != m_wire.elements_end();
+  if (this->hasLocalControlFeature()) {
     m_localControlFeature = static_cast<LocalControlFeature>(readNonNegativeInteger(*val));
   }
 
   val = m_wire.find(tlv::nfd::Cost);
-  m_hasCost = val != m_wire.elements_end();
-  if (m_hasCost) {
+  m_hasFields[CONTROL_PARAMETER_COST] = val != m_wire.elements_end();
+  if (this->hasCost()) {
     m_cost = static_cast<uint64_t>(readNonNegativeInteger(*val));
   }
 
   val = m_wire.find(tlv::nfd::Strategy);
-  m_hasStrategy = val != m_wire.elements_end();
-  if (m_hasStrategy) {
+  m_hasFields[CONTROL_PARAMETER_STRATEGY] = val != m_wire.elements_end();
+  if (this->hasStrategy()) {
     val->parse();
     if (val->elements().empty()) {
       throw Error("expecting Strategy/Name");