mgmt: redesign ControlCommand and its usage in nfd::Controller

Split parameter encoding and validation into a separate class so that
different commands can have different formats in the future. Moreover,
request parameters and response parameters may be of different types.

Lastly, change the hierarchy to CRTP and make everything static. All the
encoding and validation details are the same for every request/response
of a given command type, so it makes no sense to allocate a separate
ControlCommand instance for each individual request.

Change-Id: I56c16dc3e275aaa48608478aad002d448c0492cc
diff --git a/tests/unit/mgmt/nfd/control-command.t.cpp b/tests/unit/mgmt/nfd/control-command.t.cpp
index 2da5270..dc8cff0 100644
--- a/tests/unit/mgmt/nfd/control-command.t.cpp
+++ b/tests/unit/mgmt/nfd/control-command.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2023 Regents of the University of California.
+ * Copyright (c) 2013-2025 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -33,13 +33,14 @@
 
 BOOST_AUTO_TEST_CASE(FaceCreateRequest)
 {
-  FaceCreateCommand command;
+  using Command = FaceCreateCommand;
 
   // good with required fields only
   ControlParameters p1;
   p1.setUri("tcp4://192.0.2.1:6363");
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
-  BOOST_CHECK(Name("/PREFIX/faces/create").isPrefixOf(command.getRequestName("/PREFIX", p1)));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
+  Name n1 = Command::createRequest("/PREFIX", p1).getName();
+  BOOST_CHECK(Name("/PREFIX/faces/create").isPrefixOf(n1));
 
   // good with optional fields
   ControlParameters p2(p1);
@@ -50,31 +51,31 @@
     .setMtu(8192)
     .setFlags(0x3)
     .setMask(0x1);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
 
   // Uri is required
   ControlParameters p3;
-  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p3), ArgumentError);
 
   // Name is forbidden
   ControlParameters p4(p1);
   p4.setName("/example");
-  BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p4), ArgumentError);
 
   // Flags and Mask must be specified together
   ControlParameters p5(p1);
   p5.setFlags(0x3);
-  BOOST_CHECK_THROW(command.validateRequest(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p5), ArgumentError);
 
   // Flags and Mask must be specified together
   ControlParameters p6(p1);
   p6.setMask(0x1);
-  BOOST_CHECK_THROW(command.validateRequest(p6), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p6), ArgumentError);
 }
 
 BOOST_AUTO_TEST_CASE(FaceCreateResponse)
 {
-  FaceCreateCommand command;
+  using Command = FaceCreateCommand;
 
   // good
   ControlParameters p1;
@@ -86,45 +87,45 @@
     .setDefaultCongestionThreshold(12345)
     .setMtu(2048)
     .setFlags(0x3);
-  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  BOOST_CHECK_NO_THROW(Command::validateResponse(p1));
 
   // Name is forbidden
   ControlParameters p2(p1);
   p2.setName("/example");
-  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p2), ArgumentError);
 
   // Mask is forbidden
   ControlParameters p3(p1);
   p3.setMask(0x1);
-  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p3), ArgumentError);
 
   // FaceId must be valid
   ControlParameters p4(p1);
   p4.setFaceId(INVALID_FACE_ID);
-  BOOST_CHECK_THROW(command.validateResponse(p4), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p4), ArgumentError);
 
   // LocalUri is required
   ControlParameters p5(p1);
   p5.unsetLocalUri();
-  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p5), ArgumentError);
 }
 
 BOOST_AUTO_TEST_CASE(FaceUpdate)
 {
-  FaceUpdateCommand command;
+  using Command = FaceUpdateCommand;
 
   // FaceId must be valid
   ControlParameters p1;
   p1.setFaceId(0);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
   p1.setFaceId(INVALID_FACE_ID);
-  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p1), ArgumentError);
 
   // Persistency and Flags are required in response
   p1.setFaceId(1);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
-  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
-  command.applyDefaultsToRequest(p1);
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
+  BOOST_CHECK_THROW(Command::validateResponse(p1), ArgumentError);
+  Command::applyDefaultsToRequest(p1);
   BOOST_CHECK_EQUAL(p1.getFaceId(), 1);
 
   // Good request, bad response (Mask is forbidden but present)
@@ -135,86 +136,84 @@
     .setDefaultCongestionThreshold(54321)
     .setMtu(8192)
     .setFlagBit(BIT_LOCAL_FIELDS_ENABLED, false);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
-  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p2));
+  BOOST_CHECK_THROW(Command::validateResponse(p2), ArgumentError);
 
   // Flags without Mask (good response, bad request)
   p2.unsetMask();
-  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
-  BOOST_CHECK_NO_THROW(command.validateResponse(p2));
+  BOOST_CHECK_THROW(Command::validateRequest(p2), ArgumentError);
+  BOOST_CHECK_NO_THROW(Command::validateResponse(p2));
 
   // FaceId is optional in request
   p2.setFlagBit(BIT_LOCAL_FIELDS_ENABLED, false);
   p2.unsetFaceId();
-  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p2));
 
   // Name is forbidden
   ControlParameters p3;
   p3.setFaceId(1)
     .setName("/ndn/name");
-  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
-  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p3), ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p3), ArgumentError);
 
   // Uri is forbidden
   ControlParameters p4;
   p4.setFaceId(1)
     .setUri("tcp4://192.0.2.1");
-  BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
-  BOOST_CHECK_THROW(command.validateResponse(p4), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p4), ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p4), ArgumentError);
 
   // Empty request is valid, empty response is invalid
   ControlParameters p5;
-  BOOST_CHECK_NO_THROW(command.validateRequest(p5));
-  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p5));
+  BOOST_CHECK_THROW(Command::validateResponse(p5), ArgumentError);
   BOOST_CHECK(!p5.hasFaceId());
 
   // Default request, not valid response due to missing FacePersistency and Flags
-  command.applyDefaultsToRequest(p5);
+  Command::applyDefaultsToRequest(p5);
   BOOST_REQUIRE(p5.hasFaceId());
-  BOOST_CHECK_NO_THROW(command.validateRequest(p5));
-  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p5));
+  BOOST_CHECK_THROW(Command::validateResponse(p5), ArgumentError);
   BOOST_CHECK_EQUAL(p5.getFaceId(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(FaceDestroy)
 {
-  FaceDestroyCommand command;
+  using Command = FaceDestroyCommand;
 
   // Uri is forbidden
   ControlParameters p1;
   p1.setUri("tcp4://192.0.2.1")
     .setFaceId(4);
-  BOOST_CHECK_THROW(command.validateRequest(p1), ControlCommand::ArgumentError);
-  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p1), ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p1), ArgumentError);
 
   // FaceId must be valid
   ControlParameters p2;
   p2.setFaceId(INVALID_FACE_ID);
-  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
-  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p2), ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p2), ArgumentError);
 
   // Good request, good response
   ControlParameters p3;
   p3.setFaceId(6);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p3));
-  BOOST_CHECK_NO_THROW(command.validateResponse(p3));
-  Name n3;
-  BOOST_CHECK_NO_THROW(n3 = command.getRequestName("/PREFIX", p3));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p3));
+  BOOST_CHECK_NO_THROW(Command::validateResponse(p3));
+  Name n3 = Command::createRequest("/PREFIX", p3).getName();
   BOOST_CHECK(Name("ndn:/PREFIX/faces/destroy").isPrefixOf(n3));
 }
 
 BOOST_AUTO_TEST_CASE(FibAddNextHop)
 {
-  FibAddNextHopCommand command;
+  using Command = FibAddNextHopCommand;
 
   // Cost required in response
   ControlParameters p1;
   p1.setName("ndn:/")
     .setFaceId(22);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
-  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
-  Name n1;
-  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
+  BOOST_CHECK_THROW(Command::validateResponse(p1), ArgumentError);
+  Name n1 = Command::createRequest("/PREFIX", p1).getName();
   BOOST_CHECK(Name("ndn:/PREFIX/fib/add-nexthop").isPrefixOf(n1));
 
   // Good request, bad response (FaceId must be valid)
@@ -222,231 +221,229 @@
   p2.setName("ndn:/example")
     .setFaceId(0)
     .setCost(6);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p2));
   p2.setFaceId(INVALID_FACE_ID);
-  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p2), ArgumentError);
 
   // Default request
-  command.applyDefaultsToRequest(p1);
+  Command::applyDefaultsToRequest(p1);
   BOOST_REQUIRE(p1.hasCost());
   BOOST_CHECK_EQUAL(p1.getCost(), 0);
 
   // FaceId optional in request
   p1.unsetFaceId();
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
-  command.applyDefaultsToRequest(p1);
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
+  Command::applyDefaultsToRequest(p1);
   BOOST_REQUIRE(p1.hasFaceId());
   BOOST_CHECK_EQUAL(p1.getFaceId(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(FibRemoveNextHop)
 {
-  FibRemoveNextHopCommand command;
+  using Command = FibRemoveNextHopCommand;
 
   // Good request, good response
   ControlParameters p1;
   p1.setName("ndn:/")
     .setFaceId(22);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
-  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
-  Name n1;
-  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
+  BOOST_CHECK_NO_THROW(Command::validateResponse(p1));
+  Name n1 = Command::createRequest("/PREFIX", p1).getName();
   BOOST_CHECK(Name("ndn:/PREFIX/fib/remove-nexthop").isPrefixOf(n1));
 
   // Good request, bad response (FaceId must be valid)
   ControlParameters p2;
   p2.setName("ndn:/example")
     .setFaceId(0);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p2));
   p2.setFaceId(INVALID_FACE_ID);
-  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p2), ArgumentError);
 
   // FaceId is optional in request
   p1.unsetFaceId();
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
-  command.applyDefaultsToRequest(p1);
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
+  Command::applyDefaultsToRequest(p1);
   BOOST_REQUIRE(p1.hasFaceId());
   BOOST_CHECK_EQUAL(p1.getFaceId(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(CsConfigRequest)
 {
-  CsConfigCommand command;
+  using Command = CsConfigCommand;
 
   // good empty request
   ControlParameters p1;
-  command.validateRequest(p1);
-  BOOST_CHECK(Name("/PREFIX/cs/config").isPrefixOf(command.getRequestName("/PREFIX", p1)));
+  Command::validateRequest(p1);
+  Name n1 = Command::createRequest("/PREFIX", p1).getName();
+  BOOST_CHECK(Name("/PREFIX/cs/config").isPrefixOf(n1));
 
   // 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);
+  Command::validateRequest(p2);
 
   // bad request: Flags but no Mask
   ControlParameters p3(p2);
   p3.unsetMask();
-  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p3), ArgumentError);
 
   // bad request: Mask but no Flags
   ControlParameters p4(p2);
   p4.unsetFlags();
-  BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p4), ArgumentError);
 
   // bad request: forbidden field
   ControlParameters p5(p2);
   p5.setName("/example");
-  BOOST_CHECK_THROW(command.validateRequest(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p5), ArgumentError);
 }
 
 BOOST_AUTO_TEST_CASE(CsConfigResponse)
 {
-  CsConfigCommand command;
+  using Command = CsConfigCommand;
 
   // bad empty response
   ControlParameters p1;
-  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p1), 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);
+  BOOST_CHECK_THROW(Command::validateResponse(p2), ArgumentError);
 
   // good response
   ControlParameters p3(p2);
   p3.unsetMask();
-  command.validateResponse(p3);
+  Command::validateResponse(p3);
 }
 
 BOOST_AUTO_TEST_CASE(CsEraseRequest)
 {
-  CsEraseCommand command;
+  using Command = CsEraseCommand;
 
   // good no-limit request
   ControlParameters p1;
   p1.setName("/u4LYPNU8Q");
-  command.validateRequest(p1);
-  BOOST_CHECK(Name("/PREFIX/cs/erase").isPrefixOf(command.getRequestName("/PREFIX", p1)));
+  Command::validateRequest(p1);
+  Name n1 = Command::createRequest("/PREFIX", p1).getName();
+  BOOST_CHECK(Name("/PREFIX/cs/erase").isPrefixOf(n1));
 
   // good limited request
   ControlParameters p2;
   p2.setName("/IMw1RaLF");
   p2.setCount(177);
-  command.validateRequest(p2);
+  Command::validateRequest(p2);
 
   // bad request: zero entry
   ControlParameters p3;
   p3.setName("/ahMID1jcib");
   p3.setCount(0);
-  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p3), ArgumentError);
 
   // bad request: forbidden field
   ControlParameters p4(p2);
   p4.setCapacity(278);
-  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p3), ArgumentError);
 }
 
 BOOST_AUTO_TEST_CASE(CsEraseResponse)
 {
-  CsEraseCommand command;
+  using Command = CsEraseCommand;
 
   // good normal response
   ControlParameters p1;
   p1.setName("/TwiIwCdR");
   p1.setCount(1);
-  command.validateResponse(p1);
+  Command::validateResponse(p1);
 
   // good limit exceeded request
   ControlParameters p2;
   p2.setName("/NMsiy44pr");
   p2.setCapacity(360);
   p2.setCount(360);
-  command.validateResponse(p2);
+  Command::validateResponse(p2);
 
   // good zero-entry response
   ControlParameters p3;
   p3.setName("/5f1LRPh1L");
   p3.setCount(0);
-  command.validateResponse(p3);
+  Command::validateResponse(p3);
 
   // bad request: missing Count
   ControlParameters p4(p1);
   p4.unsetCount();
-  BOOST_CHECK_THROW(command.validateResponse(p4), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p4), ArgumentError);
 
   // bad request: zero capacity
   ControlParameters p5(p1);
   p5.setCapacity(0);
-  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p5), ArgumentError);
 }
 
 BOOST_AUTO_TEST_CASE(StrategyChoiceSet)
 {
-  StrategyChoiceSetCommand command;
+  using Command = StrategyChoiceSetCommand;
 
   // Good request, good response
   ControlParameters p1;
   p1.setName("ndn:/")
     .setStrategy("ndn:/strategy/P");
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
-  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
-  Name n1;
-  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
+  BOOST_CHECK_NO_THROW(Command::validateResponse(p1));
+  Name n1 = Command::createRequest("/PREFIX", p1).getName();
   BOOST_CHECK(Name("ndn:/PREFIX/strategy-choice/set").isPrefixOf(n1));
 
   // Strategy is required in both requests and responses
   ControlParameters p2;
   p2.setName("ndn:/example");
-  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
-  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p2), ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p2), ArgumentError);
 }
 
 BOOST_AUTO_TEST_CASE(StrategyChoiceUnset)
 {
-  StrategyChoiceUnsetCommand command;
+  using Command = StrategyChoiceUnsetCommand;
 
   // Good request, good response
   ControlParameters p1;
   p1.setName("ndn:/example");
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
-  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
-  Name n1;
-  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
+  BOOST_CHECK_NO_THROW(Command::validateResponse(p1));
+  Name n1 = Command::createRequest("/PREFIX", p1).getName();
   BOOST_CHECK(Name("ndn:/PREFIX/strategy-choice/unset").isPrefixOf(n1));
 
   // Strategy is forbidden
   ControlParameters p2;
   p2.setName("ndn:/example")
     .setStrategy("ndn:/strategy/P");
-  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
-  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p2), ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p2), ArgumentError);
 
   // Name must have at least one component
   ControlParameters p3;
   p3.setName("ndn:/");
-  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
-  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateRequest(p3), ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p3), ArgumentError);
 }
 
 BOOST_AUTO_TEST_CASE(RibRegister)
 {
-  RibRegisterCommand command;
+  using Command = RibRegisterCommand;
 
   // Good request, response missing many fields
   ControlParameters p1;
   p1.setName("ndn:/");
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
-  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
-  Name n1;
-  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
+  BOOST_CHECK_THROW(Command::validateResponse(p1), ArgumentError);
+  Name n1 = Command::createRequest("/PREFIX", p1).getName();
   BOOST_CHECK(Name("ndn:/PREFIX/rib/register").isPrefixOf(n1));
 
   // Default request
-  command.applyDefaultsToRequest(p1);
+  Command::applyDefaultsToRequest(p1);
   BOOST_REQUIRE(p1.hasOrigin());
   BOOST_CHECK_EQUAL(p1.getOrigin(), ROUTE_ORIGIN_APP);
   BOOST_REQUIRE(p1.hasCost());
@@ -460,25 +457,24 @@
   p2.setName("ndn:/example")
     .setFaceId(2)
     .setCost(6);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
-  command.applyDefaultsToRequest(p2);
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p2));
+  Command::applyDefaultsToRequest(p2);
   BOOST_CHECK_EQUAL(p2.hasExpirationPeriod(), false);
-  BOOST_CHECK_NO_THROW(command.validateResponse(p2));
+  BOOST_CHECK_NO_THROW(Command::validateResponse(p2));
 }
 
 BOOST_AUTO_TEST_CASE(RibUnregister)
 {
-  RibUnregisterCommand command;
+  using Command = RibUnregisterCommand;
 
   // Good request, good response
   ControlParameters p1;
   p1.setName("ndn:/")
     .setFaceId(22)
     .setOrigin(ROUTE_ORIGIN_STATIC);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
-  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
-  Name n1;
-  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p1));
+  BOOST_CHECK_NO_THROW(Command::validateResponse(p1));
+  Name n1 = Command::createRequest("/PREFIX", p1).getName();
   BOOST_CHECK(Name("ndn:/PREFIX/rib/unregister").isPrefixOf(n1));
 
   // Good request, bad response (FaceId must be valid)
@@ -486,14 +482,14 @@
   p2.setName("ndn:/example")
     .setFaceId(0)
     .setOrigin(ROUTE_ORIGIN_APP);
-  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p2));
   p2.setFaceId(INVALID_FACE_ID);
-  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(Command::validateResponse(p2), ArgumentError);
 
   // FaceId is optional in request, required in response
   p2.unsetFaceId();
-  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
-  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_NO_THROW(Command::validateRequest(p2));
+  BOOST_CHECK_THROW(Command::validateResponse(p2), ArgumentError);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestControlCommand
diff --git a/tests/unit/mgmt/nfd/controller.t.cpp b/tests/unit/mgmt/nfd/controller.t.cpp
index 6760d5e..8be9d23 100644
--- a/tests/unit/mgmt/nfd/controller.t.cpp
+++ b/tests/unit/mgmt/nfd/controller.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2023 Regents of the University of California.
+ * Copyright (c) 2013-2025 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -20,6 +20,7 @@
  */
 
 #include "ndn-cxx/mgmt/nfd/controller.hpp"
+#include "ndn-cxx/mgmt/nfd/control-command.hpp"
 #include "ndn-cxx/mgmt/nfd/control-response.hpp"
 
 #include "tests/test-common.hpp"
@@ -87,8 +88,7 @@
   // 6 components: /localhost/nfd/faces/create/<parameters>/params-sha256=...
   BOOST_REQUIRE_EQUAL(requestInterest.getName().size(), 6);
   ControlParameters requestParams(requestInterest.getName()[4].blockFromValue());
-  FaceCreateCommand command;
-  BOOST_CHECK_NO_THROW(command.validateRequest(requestParams));
+  BOOST_CHECK_NO_THROW(FaceCreateCommand::validateRequest(requestParams));
   BOOST_CHECK_EQUAL(requestParams.getUri(), parameters.getUri());
 
   ControlParameters responseBody = makeFaceCreateResponse();
@@ -131,12 +131,11 @@
   this->advanceClocks(1_ms);
 
   BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
-  const Interest& requestInterest = face.sentInterests[0];
+  const Interest& request = face.sentInterests[0];
 
-  FaceCreateCommand command;
-  BOOST_CHECK(Name("/localhop/net/example/router1/nfd/rib/register").isPrefixOf(requestInterest.getName()));
-  BOOST_CHECK(requestInterest.isSigned());
-  BOOST_CHECK(requestInterest.isParametersDigestValid());
+  BOOST_CHECK(Name("/localhop/net/example/router1/nfd/rib/register").isPrefixOf(request.getName()));
+  BOOST_CHECK(request.isSigned());
+  BOOST_CHECK(request.isParametersDigestValid());
 }
 
 BOOST_AUTO_TEST_CASE(InvalidRequest)
@@ -146,7 +145,7 @@
   // Uri is missing
 
   BOOST_CHECK_THROW(controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback),
-                    ControlCommand::ArgumentError);
+                    ArgumentError);
 }
 
 BOOST_AUTO_TEST_CASE(ValidationFailure)
diff --git a/tests/unit/util/dummy-client-face.t.cpp b/tests/unit/util/dummy-client-face.t.cpp
index a946858..8f40f4c 100644
--- a/tests/unit/util/dummy-client-face.t.cpp
+++ b/tests/unit/util/dummy-client-face.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2024 Regents of the University of California.
+ * Copyright (c) 2013-2025 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -20,6 +20,7 @@
  */
 
 #include "ndn-cxx/util/dummy-client-face.hpp"
+#include "ndn-cxx/mgmt/nfd/control-command.hpp"
 #include "ndn-cxx/mgmt/nfd/controller.hpp"
 
 #include "tests/test-common.hpp"