Make chip-tool subscription shutdown commands easier to use. (#21750)

* Removes redundant "subscription" from the command names, since it's part of
  the "cluster" already.
* Removes the need to specify fabric-index (which was ambiguous anyway, in terms
  of whether it was the fabric-index on the server or the client), deriving
  fabric information from the command's identity (--commissioner-name) instead.
* Adds a shutdown-all command.
* Adds various documentation bits.

Fixes https://github.com/project-chip/connectedhomeip/issues/21724
diff --git a/examples/chip-tool/commands/clusters/SubscriptionsCommands.h b/examples/chip-tool/commands/clusters/SubscriptionsCommands.h
index c5c5b46..9626070 100644
--- a/examples/chip-tool/commands/clusters/SubscriptionsCommands.h
+++ b/examples/chip-tool/commands/clusters/SubscriptionsCommands.h
@@ -26,7 +26,8 @@
 class ShutdownSubscription : public CHIPCommand
 {
 public:
-    ShutdownSubscription(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("shutdown-subscription", credsIssuerConfig)
+    ShutdownSubscription(CredentialIssuerCommands * credsIssuerConfig) :
+        CHIPCommand("shutdown-one", credsIssuerConfig, "Shut down a single subscription, identified by its subscription id.")
     {
         AddArgument("subscription-id", 0, UINT64_MAX, &mSubscriptionId);
     }
@@ -44,39 +45,58 @@
     chip::SubscriptionId mSubscriptionId;
 };
 
-class ShutdownSubscriptions : public CHIPCommand
+class ShutdownSubscriptionsForNode : public CHIPCommand
 {
 public:
-    ShutdownSubscriptions(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("shutdown-subscriptions", credsIssuerConfig)
+    ShutdownSubscriptionsForNode(CredentialIssuerCommands * credsIssuerConfig) :
+        CHIPCommand("shutdown-all-for-node", credsIssuerConfig, "Shut down all subscriptions targeting a given node.")
     {
-        AddArgument("fabric-index", 0, UINT64_MAX, &mFabricIndex);
-        AddArgument("node-id", 0, UINT64_MAX, &mNodeId);
+        AddArgument("node-id", 0, UINT64_MAX, &mNodeId,
+                    "The node id, scoped to the commissioner name the command is running under.");
     }
 
     /////////// CHIPCommand Interface /////////
     CHIP_ERROR RunCommand() override
     {
-        CHIP_ERROR err = CHIP_NO_ERROR;
+        chip::app::InteractionModelEngine::GetInstance()->ShutdownSubscriptions(CurrentCommissioner().GetFabricIndex(), mNodeId);
 
-        chip::app::InteractionModelEngine::GetInstance()->ShutdownSubscriptions(mFabricIndex, mNodeId);
-
-        SetCommandExitStatus(err);
+        SetCommandExitStatus(CHIP_NO_ERROR);
         return CHIP_NO_ERROR;
     }
     chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); }
 
 private:
-    chip::FabricIndex mFabricIndex;
     chip::NodeId mNodeId;
 };
 
+class ShutdownAllSubscriptions : public CHIPCommand
+{
+public:
+    ShutdownAllSubscriptions(CredentialIssuerCommands * credsIssuerConfig) :
+        CHIPCommand("shutdown-all", credsIssuerConfig, "Shut down all subscriptions to all nodes.")
+    {}
+
+    /////////// CHIPCommand Interface /////////
+    CHIP_ERROR RunCommand() override
+    {
+        chip::app::InteractionModelEngine::GetInstance()->ShutdownAllSubscriptions();
+
+        SetCommandExitStatus(CHIP_NO_ERROR);
+        return CHIP_NO_ERROR;
+    }
+    chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); }
+
+private:
+};
+
 void registerClusterSubscriptions(Commands & commands, CredentialIssuerCommands * credsIssuerConfig)
 {
     const char * clusterName = "Subscriptions";
 
     commands_list clusterCommands = {
-        make_unique<ShutdownSubscription>(credsIssuerConfig),  //
-        make_unique<ShutdownSubscriptions>(credsIssuerConfig), //
+        make_unique<ShutdownSubscription>(credsIssuerConfig),         //
+        make_unique<ShutdownSubscriptionsForNode>(credsIssuerConfig), //
+        make_unique<ShutdownAllSubscriptions>(credsIssuerConfig),     //
     };
 
     commands.Register(clusterName, clusterCommands);
diff --git a/examples/chip-tool/commands/common/CHIPCommand.h b/examples/chip-tool/commands/common/CHIPCommand.h
index 02a30eb..47bcc78 100644
--- a/examples/chip-tool/commands/common/CHIPCommand.h
+++ b/examples/chip-tool/commands/common/CHIPCommand.h
@@ -59,15 +59,15 @@
     static constexpr uint16_t kMaxGroupsPerFabric    = 5;
     static constexpr uint16_t kMaxGroupKeysPerFabric = 8;
 
-    CHIPCommand(const char * commandName, CredentialIssuerCommands * credIssuerCmds) :
-        Command(commandName), mCredIssuerCmds(credIssuerCmds)
+    CHIPCommand(const char * commandName, CredentialIssuerCommands * credIssuerCmds, const char * helpText = nullptr) :
+        Command(commandName, helpText), mCredIssuerCmds(credIssuerCmds)
     {
         AddArgument("paa-trust-store-path", &mPaaTrustStorePath,
                     "Path to directory holding PAA certificate information.  Can be absolute or relative to the current working "
                     "directory.");
-        AddArgument(
-            "commissioner-name", &mCommissionerName,
-            "Name of fabric to use. Valid values are \"alpha\", \"beta\", \"gamma\", and integers greater than or equal to 4.");
+        AddArgument("commissioner-name", &mCommissionerName,
+                    "Name of fabric to use. Valid values are \"alpha\", \"beta\", \"gamma\", and integers greater than or equal to "
+                    "4.  The default if not specified is \"alpha\".");
         AddArgument("commissioner-nodeid", 0, UINT64_MAX, &mCommissionerNodeId,
                     "The node id to use for chip-tool.  If not provided, kTestControllerNodeId (112233, 0x1B669) will be used.");
 #if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED
diff --git a/examples/chip-tool/commands/common/Command.h b/examples/chip-tool/commands/common/Command.h
index 2a7411c..96de494 100644
--- a/examples/chip-tool/commands/common/Command.h
+++ b/examples/chip-tool/commands/common/Command.h
@@ -105,10 +105,11 @@
         ::chip::Inet::InterfaceId interfaceId;
     };
 
-    Command(const char * commandName) : mName(commandName) {}
+    Command(const char * commandName, const char * helpText = nullptr) : mName(commandName), mHelpText(helpText) {}
     virtual ~Command() {}
 
     const char * GetName(void) const { return mName; }
+    const char * GetHelpText() const { return mHelpText; }
     const char * GetAttribute(void) const;
     const char * GetEvent(void) const;
     const char * GetArgumentName(size_t index) const;
@@ -271,7 +272,8 @@
      */
     size_t AddArgumentToList(Argument && argument);
 
-    const char * mName  = nullptr;
-    bool mIsInteractive = false;
+    const char * mName     = nullptr;
+    const char * mHelpText = nullptr;
+    bool mIsInteractive    = false;
     std::vector<Argument> mArgs;
 };
diff --git a/examples/chip-tool/commands/common/Commands.cpp b/examples/chip-tool/commands/common/Commands.cpp
index 16e188c..333138b 100644
--- a/examples/chip-tool/commands/common/Commands.cpp
+++ b/examples/chip-tool/commands/common/Commands.cpp
@@ -350,6 +350,11 @@
     }
     fprintf(stderr, "  %s %s %s\n", executable.c_str(), clusterName.c_str(), arguments.c_str());
 
+    if (command->GetHelpText())
+    {
+        fprintf(stderr, "\n%s\n", command->GetHelpText());
+    }
+
     if (description.size() > 0)
     {
         fprintf(stderr, "%s\n", description.c_str());
diff --git a/examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.cpp b/examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.cpp
index 3a39954..c992736 100644
--- a/examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.cpp
+++ b/examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.cpp
@@ -83,3 +83,39 @@
 {
     Shutdown();
 }
+
+chip::Controller::DeviceCommissioner & CHIPCommand::CurrentCommissioner()
+{
+    auto item = mCommissioners.find(GetIdentity());
+    return *item->second;
+}
+
+constexpr chip::FabricId kIdentityOtherFabricId = 4;
+std::map<std::string, std::unique_ptr<chip::Controller::DeviceCommissioner>> CHIPCommand::mCommissioners;
+
+std::string CHIPCommand::GetIdentity()
+{
+    std::string name = mCommissionerName.HasValue() ? mCommissionerName.Value() : kIdentityAlpha;
+    if (name.compare(kIdentityAlpha) != 0 && name.compare(kIdentityBeta) != 0 && name.compare(kIdentityGamma) != 0 &&
+        name.compare(kIdentityNull) != 0)
+    {
+        chip::FabricId fabricId = strtoull(name.c_str(), nullptr, 0);
+        if (fabricId >= kIdentityOtherFabricId)
+        {
+            // normalize name since it is used in persistent storage
+
+            char s[24];
+            sprintf(s, "%" PRIu64, fabricId);
+
+            name = s;
+        }
+        else
+        {
+            ChipLogError(chipTool, "Unknown commissioner name: %s. Supported names are [%s, %s, %s, 4, 5...]", name.c_str(),
+                         kIdentityAlpha, kIdentityBeta, kIdentityGamma);
+            chipDie();
+        }
+    }
+
+    return name;
+}
diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp
index 4232601..745d992 100644
--- a/src/app/InteractionModelEngine.cpp
+++ b/src/app/InteractionModelEngine.cpp
@@ -279,6 +279,17 @@
     }
 }
 
+void InteractionModelEngine::ShutdownAllSubscriptions()
+{
+    for (auto * readClient = mpActiveReadClientList; readClient != nullptr; readClient = readClient->GetNextClient())
+    {
+        if (readClient->IsSubscriptionType())
+        {
+            readClient->Close(CHIP_NO_ERROR);
+        }
+    }
+}
+
 void InteractionModelEngine::OnDone(CommandHandler & apCommandObj)
 {
     mCommandHandlerObjs.ReleaseObject(&apCommandObj);
diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h
index 9f21baf..6dffea8 100644
--- a/src/app/InteractionModelEngine.h
+++ b/src/app/InteractionModelEngine.h
@@ -139,6 +139,11 @@
     void ShutdownSubscriptions(FabricIndex aFabricIndex, NodeId aPeerNodeId);
 
     /**
+     * Tears down all active subscriptions.
+     */
+    void ShutdownAllSubscriptions();
+
+    /**
      * Expire active transactions and release related objects for the given fabric index.
      * This is used for releasing transactions that won't be closed when a fabric is removed.
      */