management: add Flags and Mask fields to faces/create and faces/update

refs #3731

Change-Id: I35fb84a875767649da0cb354be5e5c07de179198
diff --git a/src/encoding/nfd-constants.hpp b/src/encoding/nfd-constants.hpp
index 671bd2c..4c38bd9 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-2014 Regents of the University of California.
+ * Copyright (c) 2013-2016 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -64,6 +64,14 @@
 
 /** \ingroup management
  */
+enum FaceFlagBit {
+  /** \brief bit that controls whether local fields are enabled on a face
+   */
+  BIT_LOCAL_FIELDS_ENABLED = 0
+};
+
+/** \ingroup management
+ */
 enum LinkType : uint8_t {
   LINK_TYPE_NONE = std::numeric_limits<uint8_t>::max(),
   /** \brief link is point-to-point
diff --git a/src/encoding/tlv-nfd.hpp b/src/encoding/tlv-nfd.hpp
index 51cfc4f..0033f22 100644
--- a/src/encoding/tlv-nfd.hpp
+++ b/src/encoding/tlv-nfd.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2015 Regents of the University of California.
+ * Copyright (c) 2013-2016 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -39,6 +39,7 @@
   Origin              = 111,
   Cost                = 106,
   Flags               = 108,
+  Mask                = 112,
   Strategy            = 107,
   ExpirationPeriod    = 109,
 
diff --git a/src/management/nfd-control-command.cpp b/src/management/nfd-control-command.cpp
index 88db4f9..9853f9d 100644
--- a/src/management/nfd-control-command.cpp
+++ b/src/management/nfd-control-command.cpp
@@ -98,22 +98,37 @@
 {
   m_requestValidator
     .required(CONTROL_PARAMETER_URI)
-    .optional(CONTROL_PARAMETER_FACE_PERSISTENCY);
+    .optional(CONTROL_PARAMETER_FACE_PERSISTENCY)
+    .optional(CONTROL_PARAMETER_FLAGS)
+    .optional(CONTROL_PARAMETER_MASK);
   m_responseValidator
-    .required(CONTROL_PARAMETER_URI)
     .required(CONTROL_PARAMETER_FACE_ID)
-    .required(CONTROL_PARAMETER_FACE_PERSISTENCY);
+    .required(CONTROL_PARAMETER_FACE_PERSISTENCY)
+    .optional(CONTROL_PARAMETER_FLAGS)
+    .optional(CONTROL_PARAMETER_URI);
 }
 
 void
 FaceCreateCommand::applyDefaultsToRequest(ControlParameters& parameters) const
 {
+  parameters.setFaceId(0);
+
   if (!parameters.hasFacePersistency()) {
     parameters.setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERSISTENT);
   }
 }
 
 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);
@@ -127,12 +142,21 @@
   : ControlCommand("faces", "update")
 {
   m_requestValidator
-    .required(CONTROL_PARAMETER_FACE_ID)
-    .optional(CONTROL_PARAMETER_FACE_PERSISTENCY);
+    .optional(CONTROL_PARAMETER_FACE_ID)
+    .optional(CONTROL_PARAMETER_FACE_PERSISTENCY)
+    .optional(CONTROL_PARAMETER_FLAGS)
+    .optional(CONTROL_PARAMETER_MASK);
   m_responseValidator
-    .required(CONTROL_PARAMETER_FACE_ID)
-    .optional(CONTROL_PARAMETER_FACE_PERSISTENCY);
-  m_responseValidator = m_requestValidator;
+    .optional(CONTROL_PARAMETER_FACE_ID)
+    .optional(CONTROL_PARAMETER_FACE_PERSISTENCY)
+    .optional(CONTROL_PARAMETER_FLAGS)
+    .optional(CONTROL_PARAMETER_MASK);
+}
+
+void
+FaceUpdateCommand::applyDefaultsToRequest(ControlParameters& parameters) const
+{
+  parameters.setFaceId(0);
 }
 
 void
@@ -140,8 +164,8 @@
 {
   this->ControlCommand::validateRequest(parameters);
 
-  if (parameters.getFaceId() == 0) {
-    BOOST_THROW_EXCEPTION(ArgumentError("FaceId must not be zero"));
+  if (parameters.hasFlags() != parameters.hasMask()) {
+    BOOST_THROW_EXCEPTION(ArgumentError("Flags must be accompanied by Mask"));
   }
 }
 
diff --git a/src/management/nfd-control-command.hpp b/src/management/nfd-control-command.hpp
index dfe876b..2f1c4e0 100644
--- a/src/management/nfd-control-command.hpp
+++ b/src/management/nfd-control-command.hpp
@@ -148,6 +148,9 @@
   applyDefaultsToRequest(ControlParameters& parameters) const override;
 
   virtual void
+  validateRequest(const ControlParameters& parameters) const override;
+
+  virtual void
   validateResponse(const ControlParameters& parameters) const override;
 };
 
@@ -163,6 +166,9 @@
   FaceUpdateCommand();
 
   virtual void
+  applyDefaultsToRequest(ControlParameters& parameters) const override;
+
+  virtual void
   validateRequest(const ControlParameters& parameters) const override;
 
   virtual void
diff --git a/src/management/nfd-control-parameters.cpp b/src/management/nfd-control-parameters.cpp
index f94148b..f68ca26 100644
--- a/src/management/nfd-control-parameters.cpp
+++ b/src/management/nfd-control-parameters.cpp
@@ -61,6 +61,9 @@
   if (this->hasStrategy()) {
     totalLength += prependNestedBlock(encoder, tlv::nfd::Strategy, m_strategy);
   }
+  if (this->hasMask()) {
+    totalLength += prependNonNegativeIntegerBlock(encoder, tlv::nfd::Mask, m_mask);
+  }
   if (this->hasFlags()) {
     totalLength += prependNonNegativeIntegerBlock(encoder, tlv::nfd::Flags, m_flags);
   }
@@ -167,6 +170,12 @@
     m_flags = static_cast<uint64_t>(readNonNegativeInteger(*val));
   }
 
+  val = m_wire.find(tlv::nfd::Mask);
+  m_hasFields[CONTROL_PARAMETER_MASK] = val != m_wire.elements_end();
+  if (this->hasMask()) {
+    m_mask = static_cast<uint64_t>(readNonNegativeInteger(*val));
+  }
+
   val = m_wire.find(tlv::nfd::Strategy);
   m_hasFields[CONTROL_PARAMETER_STRATEGY] = val != m_wire.elements_end();
   if (this->hasStrategy()) {
@@ -192,6 +201,79 @@
   }
 }
 
+bool
+ControlParameters::hasFlagBit(size_t bit) const
+{
+  if (bit >= 64) {
+    BOOST_THROW_EXCEPTION(std::out_of_range("bit must be within range [0, 64)"));
+  }
+
+  if (!hasMask()) {
+    return false;
+  }
+
+  return getMask() & (1 << bit);
+}
+
+bool
+ControlParameters::getFlagBit(size_t bit) const
+{
+  if (bit >= 64) {
+    BOOST_THROW_EXCEPTION(std::out_of_range("bit must be within range [0, 64)"));
+  }
+
+  if (!hasFlags()) {
+    return false;
+  }
+
+  return getFlags() & (1 << bit);
+}
+
+ControlParameters&
+ControlParameters::setFlagBit(size_t bit, bool value, bool wantMask/* = true*/)
+{
+  if (bit >= 64) {
+    BOOST_THROW_EXCEPTION(std::out_of_range("bit must be within range [0, 64)"));
+  }
+
+  uint64_t flags = hasFlags() ? getFlags() : 0;
+  if (value) {
+    flags |= (1 << bit);
+  }
+  else {
+    flags &= ~(1 << bit);
+  }
+  setFlags(flags);
+
+  if (wantMask) {
+    uint64_t mask = hasMask() ? getMask() : 0;
+    mask |= (1 << bit);
+    setMask(mask);
+  }
+
+  return *this;
+}
+
+ControlParameters&
+ControlParameters::unsetFlagBit(size_t bit)
+{
+  if (bit >= 64) {
+    BOOST_THROW_EXCEPTION(std::out_of_range("bit must be within range [0, 64)"));
+  }
+
+  uint64_t mask = hasMask() ? getMask() : 0;
+  mask &= ~(1 << bit);
+  if (mask == 0) {
+    unsetMask();
+    unsetFlags();
+  }
+  else {
+    setMask(mask);
+  }
+
+  return *this;
+}
+
 std::ostream&
 operator<<(std::ostream& os, const ControlParameters& parameters)
 {
@@ -222,7 +304,15 @@
   }
 
   if (parameters.hasFlags()) {
-    os << "Flags: " << parameters.getFlags() << ", ";
+    std::ios_base::fmtflags osFlags = os.flags();
+    os << "Flags: " << std::showbase << std::hex << parameters.getFlags() << ", ";
+    os.flags(osFlags);
+  }
+
+  if (parameters.hasMask()) {
+    std::ios_base::fmtflags osFlags = os.flags();
+    os << "Mask: " << std::showbase << std::hex << parameters.getMask() << ", ";
+    os.flags(osFlags);
   }
 
   if (parameters.hasStrategy()) {
diff --git a/src/management/nfd-control-parameters.hpp b/src/management/nfd-control-parameters.hpp
index ed077a7..92f40b8 100644
--- a/src/management/nfd-control-parameters.hpp
+++ b/src/management/nfd-control-parameters.hpp
@@ -41,6 +41,7 @@
   CONTROL_PARAMETER_ORIGIN,
   CONTROL_PARAMETER_COST,
   CONTROL_PARAMETER_FLAGS,
+  CONTROL_PARAMETER_MASK,
   CONTROL_PARAMETER_STRATEGY,
   CONTROL_PARAMETER_EXPIRATION_PERIOD,
   CONTROL_PARAMETER_FACE_PERSISTENCY,
@@ -55,6 +56,7 @@
   "Origin",
   "Cost",
   "Flags",
+  "Mask",
   "Strategy",
   "ExpirationPeriod",
   "FacePersistency"
@@ -62,6 +64,7 @@
 
 /**
  * \ingroup management
+ * \deprecated use Flags+Mask fields instead
  */
 enum LocalControlFeature {
   LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID = 1,
@@ -69,10 +72,10 @@
 };
 
 /**
- * @ingroup management
- * @brief represents parameters in a ControlCommand request or response
- * @sa http://redmine.named-data.net/projects/nfd/wiki/ControlCommand#ControlParameters
- * @details This type is copyable because it's an abstraction of a TLV type.
+ * \ingroup management
+ * \brief represents parameters in a ControlCommand request or response
+ * \sa http://redmine.named-data.net/projects/nfd/wiki/ControlCommand#ControlParameters
+ * \details This type is copyable because it's an abstraction of a TLV type.
  */
 class ControlParameters : public ndn::mgmt::ControlParameters
 {
@@ -103,7 +106,6 @@
   wireDecode(const Block& wire) final;
 
 public: // getters & setters
-
   bool
   hasName() const
   {
@@ -194,12 +196,18 @@
     return *this;
   }
 
+  /**
+   * \deprecated use Flags+Mask fields instead
+   */
   bool
   hasLocalControlFeature() const
   {
     return m_hasFields[CONTROL_PARAMETER_LOCAL_CONTROL_FEATURE];
   }
 
+  /**
+   * \deprecated use Flags+Mask fields instead
+   */
   LocalControlFeature
   getLocalControlFeature() const
   {
@@ -207,6 +215,9 @@
     return m_localControlFeature;
   }
 
+  /**
+   * \deprecated use Flags+Mask fields instead
+   */
   ControlParameters&
   setLocalControlFeature(LocalControlFeature localControlFeature)
   {
@@ -216,6 +227,9 @@
     return *this;
   }
 
+  /**
+   * \deprecated use Flags+Mask fields instead
+   */
   ControlParameters&
   unsetLocalControlFeature()
   {
@@ -315,6 +329,36 @@
   }
 
   bool
+  hasMask() const
+  {
+    return m_hasFields[CONTROL_PARAMETER_MASK];
+  }
+
+  uint64_t
+  getMask() const
+  {
+    BOOST_ASSERT(this->hasMask());
+    return m_mask;
+  }
+
+  ControlParameters&
+  setMask(uint64_t mask)
+  {
+    m_wire.reset();
+    m_mask = mask;
+    m_hasFields[CONTROL_PARAMETER_MASK] = true;
+    return *this;
+  }
+
+  ControlParameters&
+  unsetMask()
+  {
+    m_wire.reset();
+    m_hasFields[CONTROL_PARAMETER_MASK] = false;
+    return *this;
+  }
+
+  bool
   hasStrategy() const
   {
     return m_hasFields[CONTROL_PARAMETER_STRATEGY];
@@ -410,6 +454,38 @@
     return m_hasFields;
   }
 
+public: // Flags and Mask helpers
+  /**
+   * \return whether bit is enabled in Mask
+   * \param bit bit position within range [0, 64) (least significant bit is 0)
+   */
+  bool
+  hasFlagBit(size_t bit) const;
+
+  /**
+   * \return bit at a position in Flags
+   * \param bit bit position within range [0, 64) (least significant bit is 0)
+   */
+  bool
+  getFlagBit(size_t bit) const;
+
+  /**
+   * \brief set a bit in Flags
+   * \param bit bit position within range [0, 64) (least significant bit is 0)
+   * \param value new value in Flags
+   * \param wantMask if true, enable the bit in Mask
+   */
+  ControlParameters&
+  setFlagBit(size_t bit, bool value, bool wantMask = true);
+
+  /**
+   * \brief disable a bit in Mask
+   * \param bit bit position within range [0, 64) (least significant bit is 0)
+   * \post If all bits are disabled, Flags and Mask fields are deleted.
+   */
+  ControlParameters&
+  unsetFlagBit(size_t bit);
+
 private: // fields
   std::vector<bool>   m_hasFields;
 
@@ -420,6 +496,7 @@
   uint64_t            m_origin;
   uint64_t            m_cost;
   uint64_t            m_flags;
+  uint64_t            m_mask;
   Name                m_strategy;
   time::milliseconds  m_expirationPeriod;
   FacePersistency     m_facePersistency;
diff --git a/tests/unit-tests/management/nfd-control-command.t.cpp b/tests/unit-tests/management/nfd-control-command.t.cpp
index 67696b2..95857d4 100644
--- a/tests/unit-tests/management/nfd-control-command.t.cpp
+++ b/tests/unit-tests/management/nfd-control-command.t.cpp
@@ -51,26 +51,40 @@
 
   ControlParameters p4;
   p4.setUri("tcp4://192.0.2.1")
-    .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+    .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT)
+    .setFlags(0x3)
+    .setMask(0x1);
   BOOST_CHECK_NO_THROW(command.validateRequest(p4));
 
+  ControlParameters p5;
+  p5.setUri("tcp4://192.0.2.1")
+    .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT)
+    .setFlags(0x1);
+  BOOST_CHECK_THROW(command.validateRequest(p5), ControlCommand::ArgumentError);
+
   p4.unsetFacePersistency();
   BOOST_CHECK_NO_THROW(command.validateRequest(p4));
   command.applyDefaultsToRequest(p4);
   BOOST_REQUIRE(p4.hasFacePersistency());
   BOOST_CHECK_EQUAL(p4.getFacePersistency(), ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
 
-  ControlParameters p5;
-  p5.setUri("tcp4://192.0.2.1")
-    .setFaceId(4)
-    .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
-  BOOST_CHECK_NO_THROW(command.validateResponse(p5));
-
   ControlParameters p6;
-  p6.setUri("tcp4://192.0.2.1:6363");
-  Name n6;
-  BOOST_CHECK_NO_THROW(n6 = command.getRequestName("/PREFIX", p6));
-  BOOST_CHECK(Name("ndn:/PREFIX/faces/create").isPrefixOf(n6));
+  p6.setFaceId(4)
+    .setUri("tcp4://192.0.2.1")
+    .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p6));
+
+  ControlParameters p7;
+  p7.setUri("tcp4://192.0.2.1:6363");
+  Name n7;
+  BOOST_CHECK_NO_THROW(n7 = command.getRequestName("/PREFIX", p7));
+  BOOST_CHECK(Name("ndn:/PREFIX/faces/create").isPrefixOf(n7));
+
+  ControlParameters p8;
+  p8.setFaceId(5)
+    .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERMANENT)
+    .setFlags(0x2);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p8));
 }
 
 BOOST_AUTO_TEST_CASE(FaceUpdate)
@@ -79,8 +93,8 @@
 
   ControlParameters p1;
   p1.setFaceId(0);
-  BOOST_CHECK_THROW(command.validateRequest(p1), ControlCommand::ArgumentError);
-  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
 
   p1.setFaceId(1);
   BOOST_CHECK_NO_THROW(command.validateRequest(p1));
@@ -88,26 +102,32 @@
 
   ControlParameters p2;
   p2.setFaceId(1)
-    .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+    .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT)
+    .setFlags(0x0)
+    .setMask(0x1);
   BOOST_CHECK_NO_THROW(command.validateRequest(p2));
   BOOST_CHECK_NO_THROW(command.validateResponse(p2));
 
+  p2.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+
+  p2.unsetMask();
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+
   ControlParameters p3;
-  p3.setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+  p3.setFaceId(1)
+    .setName("/ndn/name");
   BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
   BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
 
   ControlParameters p4;
   p4.setFaceId(1)
-    .setName("/ndn/name");
+    .setUri("tcp4://192.0.2.1");
   BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
   BOOST_CHECK_THROW(command.validateResponse(p4), ControlCommand::ArgumentError);
 
   ControlParameters p5;
-  p5.setFaceId(1)
-    .setUri("tcp4://192.0.2.1");
-  BOOST_CHECK_THROW(command.validateRequest(p5), ControlCommand::ArgumentError);
-  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p5));
 }
 
 BOOST_AUTO_TEST_CASE(FaceDestroy)
diff --git a/tests/unit-tests/management/nfd-control-parameters.t.cpp b/tests/unit-tests/management/nfd-control-parameters.t.cpp
index 5a3a864..e12d675 100644
--- a/tests/unit-tests/management/nfd-control-parameters.t.cpp
+++ b/tests/unit-tests/management/nfd-control-parameters.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2015 Regents of the University of California.
+ * Copyright (c) 2013-2016 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -34,18 +34,21 @@
   ControlParameters parameters;
   parameters.setUri("tcp4://192.0.2.1:6363");
   parameters.setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+  parameters.setFlags(1);
+  parameters.setMask(1);
   Block wire = parameters.wireEncode();
 
   ControlParameters decoded(wire);
   BOOST_CHECK_EQUAL(decoded.getUri(), "tcp4://192.0.2.1:6363");
   BOOST_CHECK_EQUAL(decoded.getFacePersistency(), ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(decoded.getFlags(), 1);
+  BOOST_CHECK_EQUAL(decoded.getMask(), 1);
 
   BOOST_CHECK_EQUAL(decoded.hasName(), false);
   BOOST_CHECK_EQUAL(decoded.hasFaceId(), false);
   BOOST_CHECK_EQUAL(decoded.hasLocalControlFeature(), false);
   BOOST_CHECK_EQUAL(decoded.hasOrigin(), false);
   BOOST_CHECK_EQUAL(decoded.hasCost(), false);
-  BOOST_CHECK_EQUAL(decoded.hasFlags(), false);
   BOOST_CHECK_EQUAL(decoded.hasStrategy(), false);
   BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), false);
 }
@@ -89,6 +92,7 @@
   BOOST_CHECK_EQUAL(decoded.hasLocalControlFeature(), false);
   BOOST_CHECK_EQUAL(decoded.hasOrigin(), 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);
@@ -112,6 +116,7 @@
   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);
 }
@@ -138,10 +143,128 @@
 
   BOOST_CHECK_EQUAL(decoded.hasUri(), false);
   BOOST_CHECK_EQUAL(decoded.hasLocalControlFeature(), false);
+  BOOST_CHECK_EQUAL(decoded.hasMask(), false);
   BOOST_CHECK_EQUAL(decoded.hasStrategy(), false);
   BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), false);
 }
 
+BOOST_AUTO_TEST_CASE(FlagsAndMask)
+{
+  ControlParameters p;
+
+  BOOST_CHECK(!p.hasFlags());
+  BOOST_CHECK(!p.hasMask());
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(!p.getFlagBit(0));
+
+  // Set bit 2 to true (change Mask)
+  p.setFlagBit(2, true);
+  // 2^2 = 4
+  BOOST_CHECK_EQUAL(p.getFlags(), 4);
+  BOOST_CHECK_EQUAL(p.getMask(), 4);
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+  BOOST_CHECK(!p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 3 to true (no change to Mask)
+  p.setFlagBit(3, true, false);
+  // 2^3 + 2^2 = 12
+  BOOST_CHECK_EQUAL(p.getFlags(), 12);
+  // 2^2 = 4
+  BOOST_CHECK_EQUAL(p.getMask(), 4);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+
+  // Set bit 1 to false (change Mask)
+  p.setFlagBit(1, false);
+  // 2^3 + 2^2 = 12
+  BOOST_CHECK_EQUAL(p.getFlags(), 12);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 2 to false (change Mask)
+  p.setFlagBit(2, false);
+  // 2^3 = 8
+  BOOST_CHECK_EQUAL(p.getFlags(), 8);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 0 to true (change Mask)
+  p.setFlagBit(0, true);
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 + 2^0 = 7
+  BOOST_CHECK_EQUAL(p.getMask(), 7);
+  BOOST_CHECK(p.hasFlagBit(0));
+  BOOST_CHECK(p.getFlagBit(0));
+
+  // Unset bit 0
+  p.unsetFlagBit(0);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(p.getFlagBit(0));
+
+  // Unset bit 3 (already unset in Mask, so no change)
+  p.unsetFlagBit(3);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+
+  // Unset bit 2
+  p.unsetFlagBit(2);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^1 = 2
+  BOOST_CHECK_EQUAL(p.getMask(), 2);
+  BOOST_CHECK(!p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+
+  // Unset bit 1
+  // Flags and Mask fields will be deleted as Mask is now 0
+  p.unsetFlagBit(1);
+  BOOST_CHECK(!p.hasFlags());
+  BOOST_CHECK(!p.hasMask());
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(!p.getFlagBit(3));
+  BOOST_CHECK(!p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+  BOOST_CHECK(!p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(!p.getFlagBit(0));
+
+  BOOST_CHECK_THROW(p.setFlagBit(64, true), std::out_of_range);
+  BOOST_CHECK_THROW(p.unsetFlagBit(64), std::out_of_range);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests