[srp-server] simplify sub-type services (#9208)

This commit updates and simplifies how `Srp::Server` stores the
sub-type services. The earlier implementation treated each sub-type
as its own service, which was tracked by a `Service` object. This
design allowed sub-type services to be registered and deleted
individually. However, the latest SRP RFC draft requires SRP servers
to treat updates to a service and all its sub-types as atomic. This
means that when a service and its sub-types are being updated, the
SRP Update message must contain the entirety of information about
that service and its sub-types. As a result of this change, we can
simplify the server implementation by tracking sub-types as an array
of sub-type names in the `Service` object.

This commit also updates the way the server commits a received SRP
update info into its existing data. Previously, the server would try
to merge the new information into existing `Host` and `Service`
objects. However, this could be inefficient, as it would require
moving heap-allocated items like "txt data" and/or array of host IPv6
addresses from one object to another. The new `CommitSrpUpdate()`
implementation now starts from the new `Host` object that is
constructed as the received SRP Update message from the client is
parsed. Any previously registered services that are not present in
the new `Host` object are then moved into it.

This commit adds new public OT APIs for retrieving the sub-types
associated with a service and making it easier to iterate over the
list of registered services by a host. Due to fundamental changes in
how services are stored by the `Srp::Server` class, some existing
public `otSrpServer` APIs for filtering and iterating over services
are being removed.
diff --git a/include/openthread/instance.h b/include/openthread/instance.h
index a92a6b5..90366cd 100644
--- a/include/openthread/instance.h
+++ b/include/openthread/instance.h
@@ -53,7 +53,7 @@
  * @note This number versions both OpenThread platform and user APIs.
  *
  */
-#define OPENTHREAD_API_VERSION (342)
+#define OPENTHREAD_API_VERSION (343)
 
 /**
  * @addtogroup api-instance
diff --git a/include/openthread/srp_server.h b/include/openthread/srp_server.h
index 4a37b46..8f45b08 100644
--- a/include/openthread/srp_server.h
+++ b/include/openthread/srp_server.h
@@ -74,64 +74,6 @@
 typedef uint32_t otSrpServerServiceUpdateId;
 
 /**
- * The service flag type to indicate which services to include or exclude when searching in (or iterating over) the
- * list of SRP services.
- *
- * This is a combination of bit-flags. The specific bit-flags are defined in the enumeration `OT_SRP_SERVER_FLAG_*`.
- *
- */
-typedef uint8_t otSrpServerServiceFlags;
-
-enum
-{
-    OT_SRP_SERVER_SERVICE_FLAG_BASE_TYPE = 1 << 0, ///< Include base services (not a sub-type).
-    OT_SRP_SERVER_SERVICE_FLAG_SUB_TYPE  = 1 << 1, ///< Include sub-type services.
-    OT_SRP_SERVER_SERVICE_FLAG_ACTIVE    = 1 << 2, ///< Include active (not deleted) services.
-    OT_SRP_SERVER_SERVICE_FLAG_DELETED   = 1 << 3, ///< Include deleted services.
-};
-
-enum
-{
-    /**
-     * This constant defines an `otSrpServerServiceFlags` combination accepting any service (base/sub-type,
-     * active/deleted).
-     *
-     */
-    OT_SRP_SERVER_FLAGS_ANY_SERVICE = (OT_SRP_SERVER_SERVICE_FLAG_BASE_TYPE | OT_SRP_SERVER_SERVICE_FLAG_SUB_TYPE |
-                                       OT_SRP_SERVER_SERVICE_FLAG_ACTIVE | OT_SRP_SERVER_SERVICE_FLAG_DELETED),
-
-    /**
-     * This constant defines an `otSrpServerServiceFlags` combination accepting base service only.
-     *
-     */
-    OT_SRP_SERVER_FLAGS_BASE_TYPE_SERVICE_ONLY =
-        (OT_SRP_SERVER_SERVICE_FLAG_BASE_TYPE | OT_SRP_SERVER_SERVICE_FLAG_ACTIVE | OT_SRP_SERVER_SERVICE_FLAG_DELETED),
-
-    /**
-     * This constant defines an `otSrpServerServiceFlags` combination accepting sub-type service only.
-     *
-     */
-    OT_SRP_SERVER_FLAGS_SUB_TYPE_SERVICE_ONLY =
-        (OT_SRP_SERVER_SERVICE_FLAG_SUB_TYPE | OT_SRP_SERVER_SERVICE_FLAG_ACTIVE | OT_SRP_SERVER_SERVICE_FLAG_DELETED),
-
-    /**
-     * This constant defines an `otSrpServerServiceFlags` combination accepting any active service (not deleted).
-     *
-     */
-    OT_SRP_SERVER_FLAGS_ANY_TYPE_ACTIVE_SERVICE =
-        (OT_SRP_SERVER_SERVICE_FLAG_BASE_TYPE | OT_SRP_SERVER_SERVICE_FLAG_SUB_TYPE |
-         OT_SRP_SERVER_SERVICE_FLAG_ACTIVE),
-
-    /**
-     * This constant defines an `otSrpServerServiceFlags` combination accepting any deleted service.
-     *
-     */
-    OT_SRP_SERVER_FLAGS_ANY_TYPE_DELETED_SERVICE =
-        (OT_SRP_SERVER_SERVICE_FLAG_BASE_TYPE | OT_SRP_SERVER_SERVICE_FLAG_SUB_TYPE |
-         OT_SRP_SERVER_SERVICE_FLAG_ACTIVE),
-};
-
-/**
  * Represents the state of the SRP server.
  *
  */
@@ -506,6 +448,21 @@
 const char *otSrpServerHostGetFullName(const otSrpServerHost *aHost);
 
 /**
+ * Indicates whether the host matches a given host name.
+ *
+ * DNS name matches are performed using a case-insensitive string comparison (i.e., "Abc" and "aBc" are considered to
+ * be the same).
+ *
+ * @param[in]  aHost       A pointer to the SRP service host.
+ * @param[in]  aFullName   A full host name.
+ *
+ * @retval  TRUE   If host matches the host name.
+ * @retval  FALSE  If host does not match the host name.
+ *
+ */
+bool otSrpServerHostMatchesFullName(const otSrpServerHost *aHost, const char *aFullName);
+
+/**
  * Returns the addresses of given host.
  *
  * @param[in]   aHost          A pointer to the SRP service host.
@@ -526,10 +483,7 @@
 void otSrpServerHostGetLeaseInfo(const otSrpServerHost *aHost, otSrpServerLeaseInfo *aLeaseInfo);
 
 /**
- * Returns the next service (excluding any sub-type services) of given host.
- *
- * @note This function is being deprecated and will be removed. `otSrpServerHostFindNextService()` can be used
- *       instead.
+ * Returns the next service of given host.
  *
  * @param[in]  aHost     A pointer to the SRP service host.
  * @param[in]  aService  A pointer to current SRP service instance; use NULL to get the first service.
@@ -541,44 +495,6 @@
                                                         const otSrpServerService *aService);
 
 /**
- * Finds the next matching service on the host.
- *
- * The combination of flags and service and instance names enables iterating over the full list of services and/or a
- * subset of them matching certain conditions, or finding a specific service.
- *
- * To iterate over all services of a host:
- *   service = otSrpServerHostFindNextService(host, service, OT_SRP_SERVER_FLAGS_ANY_SERVICE, NULL, NULL);
- *
- * To iterate over base services only (exclude sub-types):
- *   service = otSrpServerHostFindNextService(host, service, OT_SRP_SERVER_FLAGS_BASE_TYPE_SERVICE_ONLY, NULL, NULL);
- *
- * To iterate over sub-types of a specific instance name `instanceName`:
- *   service = otSrpServerHostFindNextService(host, service, OT_SRP_SERVER_FLAGS_SUB_TYPE_SERVICE_ONLY, NULL,
- *                                            instanceName);
- *
- * To find a specific service with service name `serviceName` and service instance name `instanceName`:
- *   service = otSrpServerHostFindNextService(host, NULL, OT_SRP_SERVER_FLAGS_ANY_SERVICE, serviceName, instanceName);
- *
- * To find the base type service with a given service instance name `instanceName`:
- *   service = otSrpServerHostFindNextService(host, NULL, OT_SRP_SERVER_FLAGS_BASE_TYPE_SERVICE_ONLY, NULL,
- *                                            instanceName);
- *
- * @param[in] aHost          A pointer to the SRP service host (MUST NOT be NULL).
- * @param[in] aPrevService   A pointer to the previous service or NULL to start from the beginning of the list.
- * @param[in] aFlags         Flags indicating which services to include (base/sub-type, active/deleted).
- * @param[in] aServiceName   The service name to match. Set to NULL to accept any name.
- * @param[in] aInstanceName  The service instance name to match. Set to NULL to accept any name.
- *
- * @returns  A pointer to the next matching service or NULL if no matching service could be found.
- *
- */
-const otSrpServerService *otSrpServerHostFindNextService(const otSrpServerHost    *aHost,
-                                                         const otSrpServerService *aPrevService,
-                                                         otSrpServerServiceFlags   aFlags,
-                                                         const char               *aServiceName,
-                                                         const char               *aInstanceName);
-
-/**
  * Indicates whether or not the SRP service has been deleted.
  *
  * A SRP service can be deleted but retains its name for future uses.
@@ -593,29 +509,6 @@
 bool otSrpServerServiceIsDeleted(const otSrpServerService *aService);
 
 /**
- * Indicates whether or not the SRP service is sub-type.
- *
- * @param[in]  aService  A pointer to the SRP service.
- *
- * @returns  TRUE if the service is a sub-type, FALSE if not.
- *
- */
-bool otSrpServerServiceIsSubType(const otSrpServerService *aService);
-
-/**
- * Returns the full service instance name of the service.
- *
- * @note This function is being deprecated and will be removed. `otSrpServerServiceGetInstanceName()` can be used
- *       instead.
- *
- * @param[in]  aService  A pointer to the SRP service.
- *
- * @returns  A pointer to the null-terminated service instance name string.
- *
- */
-const char *otSrpServerServiceGetFullName(const otSrpServerService *aService);
-
-/**
  * Returns the full service instance name of the service.
  *
  * @param[in]  aService  A pointer to the SRP service.
@@ -626,6 +519,31 @@
 const char *otSrpServerServiceGetInstanceName(const otSrpServerService *aService);
 
 /**
+ * Indicates whether this service matches a given service instance name.
+ *
+ * DNS name matches are performed using a case-insensitive string comparison (i.e., "Abc" and "aBc" are considered to
+ * be the same).
+ *
+ * @param[in]  aService       A pointer to the SRP service.
+ * @param[in]  aInstanceName  The service instance name.
+ *
+ * @retval  TRUE   If service matches the service instance name.
+ * @retval  FALSE  If service does not match the service instance name.
+ *
+ */
+bool otSrpServerServiceMatchesInstanceName(const otSrpServerService *aService, const char *aInstanceName);
+
+/**
+ * Returns the service instance label (first label in instance name) of the service.
+ *
+ * @param[in]  aService  A pointer to the SRP service.
+ *
+ * @returns  A pointer to the null-terminated service instance label string..
+ *
+ */
+const char *otSrpServerServiceGetInstanceLabel(const otSrpServerService *aService);
+
+/**
  * Returns the full service name of the service.
  *
  * @param[in]  aService  A pointer to the SRP service.
@@ -636,27 +554,74 @@
 const char *otSrpServerServiceGetServiceName(const otSrpServerService *aService);
 
 /**
- * Gets the sub-type label from service name.
+ * Indicates whether this service matches a given service name.
  *
- * Is intended to be used when the @p aService is a sub-type, i.e., `otSrpServerServiceIsSubType()` for
- * the service returns TRUE. If it is not a sub-type this function returns `OT_ERROR_INVALID_ARGS`.
+ * DNS name matches are performed using a case-insensitive string comparison (i.e., "Abc" and "aBc" are considered to
+ * be the same).
  *
- * The full service name for a sub-type service follows "<sub-label>._sub.<service-labels>.<domain>.". This function
- * copies the `<sub-label>` into the @p aLabel buffer.
+ * @param[in]  aService       A pointer to the SRP service.
+ * @param[in]  aServiceName  The service  name.
  *
- * The @p aLabel is ensured to always be null-terminated after returning even in case of failure.
- *
- * @param[in]  aService           A pointer to the SRP service.
- * @param[out] aLabel             A pointer to a buffer to copy the sub-type label name into.
- * @param[in]  aMaxSize           Maximum size of @p aLabel buffer.
- *
- * @retval OT_ERROR_NONE          @p aLabel was updated successfully.
- * @retval OT_ERROR_NO_BUFS       The sub-type label could not fit in @p aLabel buffer (number of chars from label
- *                                that could fit are copied in @p aLabel ensuring it is null-terminated).
- * @retval OT_ERROR_INVALID_ARGS  SRP service is not a sub-type.
+ * @retval  TRUE   If service matches the service name.
+ * @retval  FALSE  If service does not match the service name.
  *
  */
-otError otSrpServerServiceGetServiceSubTypeLabel(const otSrpServerService *aService, char *aLabel, uint8_t aMaxSize);
+bool otSrpServerServiceMatchesServiceName(const otSrpServerService *aService, const char *aServiceName);
+
+/**
+ * Gets the number of sub-types of the service.
+ *
+ * @param[in]  aService  A pointer to the SRP service.
+ *
+ * @returns The number of sub-types of @p aService.
+ *
+ */
+uint16_t otSrpServerServiceGetNumberOfSubTypes(const otSrpServerService *aService);
+
+/**
+ * Gets the sub-type service name (full name) of the service at a given index
+ *
+ * The full service name for a sub-type service follows "<sub-label>._sub.<service-labels>.<domain>.".
+ *
+ * @param[in]  aService  A pointer to the SRP service.
+ * @param[in] aIndex     The index to get.
+ *
+ * @returns A pointer to sub-type service name at @p aIndex, or `NULL` if no sub-type at this index.
+ *
+ */
+const char *otSrpServerServiceGetSubTypeServiceNameAt(const otSrpServerService *aService, uint16_t aIndex);
+
+/**
+ * Indicates whether or not the service has a given sub-type.
+ *
+ * DNS name matches are performed using a case-insensitive string comparison (i.e., "Abc" and "aBc" are considered to
+ * be the same).
+ *
+ * @param[in] aService             A pointer to the SRP service.
+ * @param[in] aSubTypeServiceName  The sub-type service name (full name) to check.
+ *
+ * @retval TRUE   Service contains the sub-type @p aSubTypeServiceName.
+ * @retval FALSE  Service does not contain the sub-type @p aSubTypeServiceName.
+ *
+ */
+bool otSrpServerServiceHasSubTypeServiceName(const otSrpServerService *aService, const char *aSubTypeServiceName);
+
+/**
+ * Parses a sub-type service name (full name) and extracts the sub-type label.
+ *
+ * The full service name for a sub-type service follows "<sub-label>._sub.<service-labels>.<domain>.".
+ *
+ * @param[in]  aSubTypeServiceName  A sub-type service name (full name).
+ * @param[out] aLabel               A pointer to a buffer to copy the extracted sub-type label.
+ * @param[in]  aLabelSize           Maximum size of @p aLabel buffer.
+ *
+ * @retval OT_ERROR_NONE          Name was successfully parsed and @p aLabel was updated.
+ * @retval OT_ERROR_NO_BUFS       The sub-type label could not fit in @p aLabel buffer (number of chars from label
+ *                                that could fit are copied in @p aLabel ensuring it is null-terminated).
+ * @retval OT_ERROR_INVALID_ARGS  @p aSubTypeServiceName is not a valid sub-type format.
+ *
+ */
+otError otSrpServerParseSubTypeServiceName(const char *aSubTypeServiceName, char *aLabel, uint8_t aLabelSize);
 
 /**
  * Returns the port of the service instance.
diff --git a/src/cli/cli_srp_server.cpp b/src/cli/cli_srp_server.cpp
index 50281ad..5a96d5f 100644
--- a/src/cli/cli_srp_server.cpp
+++ b/src/cli/cli_srp_server.cpp
@@ -270,9 +270,6 @@
 
 template <> otError SrpServer::Process<Cmd("service")>(Arg aArgs[])
 {
-    static constexpr char *kAnyServiceName  = nullptr;
-    static constexpr char *kAnyInstanceName = nullptr;
-
     otError                error = OT_ERROR_NONE;
     const otSrpServerHost *host  = nullptr;
 
@@ -282,18 +279,15 @@
     {
         const otSrpServerService *service = nullptr;
 
-        while ((service = otSrpServerHostFindNextService(host, service, OT_SRP_SERVER_FLAGS_BASE_TYPE_SERVICE_ONLY,
-                                                         kAnyServiceName, kAnyInstanceName)) != nullptr)
+        while ((service = otSrpServerHostGetNextService(host, service)) != nullptr)
         {
-            bool                      isDeleted    = otSrpServerServiceIsDeleted(service);
-            const char               *instanceName = otSrpServerServiceGetInstanceName(service);
-            const otSrpServerService *subService   = nullptr;
-            const uint8_t            *txtData;
-            uint16_t                  txtDataLength;
-            bool                      hasSubType = false;
-            otSrpServerLeaseInfo      leaseInfo;
+            bool                 isDeleted = otSrpServerServiceIsDeleted(service);
+            const uint8_t       *txtData;
+            uint16_t             txtDataLength;
+            bool                 hasSubType = false;
+            otSrpServerLeaseInfo leaseInfo;
 
-            OutputLine("%s", instanceName);
+            OutputLine("%s", otSrpServerServiceGetInstanceName(service));
             OutputLine(kIndentSize, "deleted: %s", isDeleted ? "true" : "false");
 
             if (isDeleted)
@@ -305,13 +299,17 @@
 
             OutputFormat(kIndentSize, "subtypes: ");
 
-            while ((subService = otSrpServerHostFindNextService(
-                        host, subService, (OT_SRP_SERVER_SERVICE_FLAG_SUB_TYPE | OT_SRP_SERVER_SERVICE_FLAG_ACTIVE),
-                        kAnyServiceName, instanceName)) != nullptr)
+            for (uint16_t index = 0;; index++)
             {
-                char subLabel[OT_DNS_MAX_LABEL_SIZE];
+                char        subLabel[OT_DNS_MAX_LABEL_SIZE];
+                const char *subTypeName = otSrpServerServiceGetSubTypeServiceNameAt(service, index);
 
-                IgnoreError(otSrpServerServiceGetServiceSubTypeLabel(subService, subLabel, sizeof(subLabel)));
+                if (subTypeName == nullptr)
+                {
+                    break;
+                }
+
+                IgnoreError(otSrpServerParseSubTypeServiceName(subTypeName, subLabel, sizeof(subLabel)));
                 OutputFormat("%s%s", hasSubType ? "," : "", subLabel);
                 hasSubType = true;
             }
diff --git a/src/core/api/srp_server_api.cpp b/src/core/api/srp_server_api.cpp
index a6a3ca7..4a0044b 100644
--- a/src/core/api/srp_server_api.cpp
+++ b/src/core/api/srp_server_api.cpp
@@ -139,6 +139,11 @@
 
 const char *otSrpServerHostGetFullName(const otSrpServerHost *aHost) { return AsCoreType(aHost).GetFullName(); }
 
+bool otSrpServerHostMatchesFullName(const otSrpServerHost *aHost, const char *aFullName)
+{
+    return AsCoreType(aHost).Matches(aFullName);
+}
+
 const otIp6Address *otSrpServerHostGetAddresses(const otSrpServerHost *aHost, uint8_t *aAddressesNum)
 {
     return AsCoreType(aHost).GetAddresses(*aAddressesNum);
@@ -154,30 +159,24 @@
 const otSrpServerService *otSrpServerHostGetNextService(const otSrpServerHost    *aHost,
                                                         const otSrpServerService *aService)
 {
-    return AsCoreType(aHost).FindNextService(AsCoreTypePtr(aService), Srp::Server::kFlagsBaseTypeServiceOnly);
-}
-
-const otSrpServerService *otSrpServerHostFindNextService(const otSrpServerHost    *aHost,
-                                                         const otSrpServerService *aPrevService,
-                                                         otSrpServerServiceFlags   aFlags,
-                                                         const char               *aServiceName,
-                                                         const char               *aInstanceName)
-{
-    return AsCoreType(aHost).FindNextService(AsCoreTypePtr(aPrevService), aFlags, aServiceName, aInstanceName);
+    return AsCoreType(aHost).GetNextService(AsCoreTypePtr(aService));
 }
 
 bool otSrpServerServiceIsDeleted(const otSrpServerService *aService) { return AsCoreType(aService).IsDeleted(); }
 
-bool otSrpServerServiceIsSubType(const otSrpServerService *aService) { return AsCoreType(aService).IsSubType(); }
-
-const char *otSrpServerServiceGetFullName(const otSrpServerService *aService)
+const char *otSrpServerServiceGetInstanceName(const otSrpServerService *aService)
 {
     return AsCoreType(aService).GetInstanceName();
 }
 
-const char *otSrpServerServiceGetInstanceName(const otSrpServerService *aService)
+bool otSrpServerServiceMatchesInstanceName(const otSrpServerService *aService, const char *aInstanceName)
 {
-    return AsCoreType(aService).GetInstanceName();
+    return AsCoreType(aService).MatchesInstanceName(aInstanceName);
+}
+
+const char *otSrpServerServiceGetInstanceLabel(const otSrpServerService *aService)
+{
+    return AsCoreType(aService).GetInstanceLabel();
 }
 
 const char *otSrpServerServiceGetServiceName(const otSrpServerService *aService)
@@ -185,9 +184,29 @@
     return AsCoreType(aService).GetServiceName();
 }
 
-otError otSrpServerServiceGetServiceSubTypeLabel(const otSrpServerService *aService, char *aLabel, uint8_t aMaxSize)
+bool otSrpServerServiceMatchesServiceName(const otSrpServerService *aService, const char *aServiceName)
 {
-    return AsCoreType(aService).GetServiceSubTypeLabel(aLabel, aMaxSize);
+    return AsCoreType(aService).MatchesServiceName(aServiceName);
+}
+
+uint16_t otSrpServerServiceGetNumberOfSubTypes(const otSrpServerService *aService)
+{
+    return AsCoreType(aService).GetNumberOfSubTypes();
+}
+
+const char *otSrpServerServiceGetSubTypeServiceNameAt(const otSrpServerService *aService, uint16_t aIndex)
+{
+    return AsCoreType(aService).GetSubTypeServiceNameAt(aIndex);
+}
+
+bool otSrpServerServiceHasSubTypeServiceName(const otSrpServerService *aService, const char *aSubTypeServiceName)
+{
+    return AsCoreType(aService).HasSubTypeServiceName(aSubTypeServiceName);
+}
+
+otError otSrpServerParseSubTypeServiceName(const char *aSubTypeServiceName, char *aLabel, uint8_t aLabelSize)
+{
+    return Srp::Server::Service::ParseSubTypeServiceName(aSubTypeServiceName, aLabel, aLabelSize);
 }
 
 uint16_t otSrpServerServiceGetPort(const otSrpServerService *aService) { return AsCoreType(aService).GetPort(); }
diff --git a/src/core/net/dnssd_server.cpp b/src/core/net/dnssd_server.cpp
index 478e4d8..19f5c86 100644
--- a/src/core/net/dnssd_server.cpp
+++ b/src/core/net/dnssd_server.cpp
@@ -754,31 +754,46 @@
                                               NameCompressInfo &aCompressInfo,
                                               bool              aAdditional)
 {
-    Error                    error    = kErrorNone;
-    const Srp::Server::Host *host     = nullptr;
-    TimeMilli                now      = TimerMilli::GetNow();
-    uint16_t                 qtype    = aQuestion.GetType();
-    Header::Response         response = Header::kResponseNameError;
+    Error            error    = kErrorNone;
+    TimeMilli        now      = TimerMilli::GetNow();
+    uint16_t         qtype    = aQuestion.GetType();
+    Header::Response response = Header::kResponseNameError;
 
-    while ((host = GetNextSrpHost(host)) != nullptr)
+    for (const Srp::Server::Host &host : Get<Srp::Server>().GetHosts())
     {
         bool        needAdditionalAaaaRecord = false;
-        const char *hostName                 = host->GetFullName();
+        const char *hostName                 = host.GetFullName();
+
+        if (host.IsDeleted())
+        {
+            continue;
+        }
 
         // Handle PTR/SRV/TXT query
         if (qtype == ResourceRecord::kTypePtr || qtype == ResourceRecord::kTypeSrv || qtype == ResourceRecord::kTypeTxt)
         {
-            const Srp::Server::Service *service = nullptr;
-
-            while ((service = GetNextSrpService(*host, service)) != nullptr)
+            for (const Srp::Server::Service &service : host.GetServices())
             {
-                uint32_t    instanceTtl         = TimeMilli::MsecToSec(service->GetExpireTime() - TimerMilli::GetNow());
-                const char *instanceName        = service->GetInstanceName();
-                bool        serviceNameMatched  = service->MatchesServiceName(aName);
-                bool        instanceNameMatched = service->MatchesInstanceName(aName);
-                bool        ptrQueryMatched     = qtype == ResourceRecord::kTypePtr && serviceNameMatched;
-                bool        srvQueryMatched     = qtype == ResourceRecord::kTypeSrv && instanceNameMatched;
-                bool        txtQueryMatched     = qtype == ResourceRecord::kTypeTxt && instanceNameMatched;
+                uint32_t    instanceTtl;
+                const char *instanceName;
+                bool        serviceNameMatched;
+                bool        instanceNameMatched;
+                bool        ptrQueryMatched;
+                bool        srvQueryMatched;
+                bool        txtQueryMatched;
+
+                if (service.IsDeleted())
+                {
+                    continue;
+                }
+
+                instanceTtl         = TimeMilli::MsecToSec(service.GetExpireTime() - TimerMilli::GetNow());
+                instanceName        = service.GetInstanceName();
+                serviceNameMatched  = service.MatchesServiceName(aName) || service.HasSubTypeServiceName(aName);
+                instanceNameMatched = service.MatchesInstanceName(aName);
+                ptrQueryMatched     = qtype == ResourceRecord::kTypePtr && serviceNameMatched;
+                srvQueryMatched     = qtype == ResourceRecord::kTypeSrv && instanceNameMatched;
+                txtQueryMatched     = qtype == ResourceRecord::kTypeTxt && instanceNameMatched;
 
                 if (ptrQueryMatched || srvQueryMatched)
                 {
@@ -798,8 +813,8 @@
                      !HasQuestion(aResponseHeader, aResponseMessage, instanceName, ResourceRecord::kTypeSrv)))
                 {
                     SuccessOrExit(error = AppendSrvRecord(aResponseMessage, instanceName, hostName, instanceTtl,
-                                                          service->GetPriority(), service->GetWeight(),
-                                                          service->GetPort(), aCompressInfo));
+                                                          service.GetPriority(), service.GetWeight(), service.GetPort(),
+                                                          aCompressInfo));
                     IncResourceRecordCount(aResponseHeader, aAdditional);
                     response = Header::kResponseSuccess;
                 }
@@ -808,8 +823,8 @@
                     (aAdditional && ptrQueryMatched &&
                      !HasQuestion(aResponseHeader, aResponseMessage, instanceName, ResourceRecord::kTypeTxt)))
                 {
-                    SuccessOrExit(error = AppendTxtRecord(aResponseMessage, instanceName, service->GetTxtData(),
-                                                          service->GetTxtDataLength(), instanceTtl, aCompressInfo));
+                    SuccessOrExit(error = AppendTxtRecord(aResponseMessage, instanceName, service.GetTxtData(),
+                                                          service.GetTxtDataLength(), instanceTtl, aCompressInfo));
                     IncResourceRecordCount(aResponseHeader, aAdditional);
                     response = Header::kResponseSuccess;
                 }
@@ -817,13 +832,13 @@
         }
 
         // Handle AAAA query
-        if ((!aAdditional && qtype == ResourceRecord::kTypeAaaa && host->Matches(aName)) ||
+        if ((!aAdditional && qtype == ResourceRecord::kTypeAaaa && host.Matches(aName)) ||
             (aAdditional && needAdditionalAaaaRecord &&
              !HasQuestion(aResponseHeader, aResponseMessage, hostName, ResourceRecord::kTypeAaaa)))
         {
             uint8_t             addrNum;
-            const Ip6::Address *addrs   = host->GetAddresses(addrNum);
-            uint32_t            hostTtl = TimeMilli::MsecToSec(host->GetExpireTime() - now);
+            const Ip6::Address *addrs   = host.GetAddresses(addrNum);
+            uint32_t            hostTtl = TimeMilli::MsecToSec(host.GetExpireTime() - now);
 
             for (uint8_t i = 0; i < addrNum; i++)
             {
@@ -839,23 +854,6 @@
     return error == kErrorNone ? response : Header::kResponseServerFailure;
 }
 
-const Srp::Server::Host *Server::GetNextSrpHost(const Srp::Server::Host *aHost)
-{
-    const Srp::Server::Host *host = Get<Srp::Server>().GetNextHost(aHost);
-
-    while (host != nullptr && host->IsDeleted())
-    {
-        host = Get<Srp::Server>().GetNextHost(host);
-    }
-
-    return host;
-}
-
-const Srp::Server::Service *Server::GetNextSrpService(const Srp::Server::Host    &aHost,
-                                                      const Srp::Server::Service *aService)
-{
-    return aHost.FindNextService(aService, Srp::Server::kFlagsAnyTypeActiveService);
-}
 #endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
 
 Error Server::ResolveByQueryCallbacks(Header                 &aResponseHeader,
diff --git a/src/core/net/dnssd_server.hpp b/src/core/net/dnssd_server.hpp
index 88c7376..5991e99 100644
--- a/src/core/net/dnssd_server.hpp
+++ b/src/core/net/dnssd_server.hpp
@@ -485,18 +485,15 @@
                                          const Ip6::MessageInfo &aMessageInfo,
                                          Ip6::Udp::Socket       &aSocket);
 #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
-    Header::Response                   ResolveBySrp(Header                   &aResponseHeader,
-                                                    Message                  &aResponseMessage,
-                                                    Server::NameCompressInfo &aCompressInfo);
-    Header::Response                   ResolveQuestionBySrp(const char       *aName,
-                                                            const Question   &aQuestion,
-                                                            Header           &aResponseHeader,
-                                                            Message          &aResponseMessage,
-                                                            NameCompressInfo &aCompressInfo,
-                                                            bool              aAdditional);
-    const Srp::Server::Host           *GetNextSrpHost(const Srp::Server::Host *aHost);
-    static const Srp::Server::Service *GetNextSrpService(const Srp::Server::Host    &aHost,
-                                                         const Srp::Server::Service *aService);
+    Header::Response ResolveBySrp(Header                   &aResponseHeader,
+                                  Message                  &aResponseMessage,
+                                  Server::NameCompressInfo &aCompressInfo);
+    Header::Response ResolveQuestionBySrp(const char       *aName,
+                                          const Question   &aQuestion,
+                                          Header           &aResponseHeader,
+                                          Message          &aResponseMessage,
+                                          NameCompressInfo &aCompressInfo,
+                                          bool              aAdditional);
 #endif
 
 #if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE
diff --git a/src/core/net/srp_server.cpp b/src/core/net/srp_server.cpp
index 9699d84..e908f68 100644
--- a/src/core/net/srp_server.cpp
+++ b/src/core/net/srp_server.cpp
@@ -308,15 +308,6 @@
     return (aHost == nullptr) ? mHosts.GetHead() : aHost->GetNext();
 }
 
-// This method adds a SRP service host and takes ownership of it.
-// The caller MUST make sure that there is no existing host with the same hostname.
-void Server::AddHost(Host &aHost)
-{
-    LogInfo("Add new host %s", aHost.GetFullName());
-
-    OT_ASSERT(mHosts.FindMatching(aHost.GetFullName()) == nullptr);
-    IgnoreError(mHosts.Add(aHost));
-}
 void Server::RemoveHost(Host *aHost, RetainName aRetainName, NotifyMode aNotifyServiceHandler)
 {
     VerifyOrExit(aHost != nullptr);
@@ -376,7 +367,7 @@
 
         for (const Host &host : mHosts)
         {
-            if (host.HasServiceInstance(service.GetInstanceName()) &&
+            if (host.HasService(service.GetInstanceName()) &&
                 aHost.GetKeyRecord()->GetKey() != host.GetKeyRecord()->GetKey())
             {
                 LogWarn("Name conflict: service name %s has already been allocated", service.GetInstanceName());
@@ -442,15 +433,18 @@
                              const TtlConfig         &aTtlConfig,
                              const LeaseConfig       &aLeaseConfig)
 {
-    Host    *existingHost;
-    uint32_t grantedTtl;
+    Host    *existingHost    = nullptr;
+    uint32_t grantedTtl      = 0;
     uint32_t hostLease       = 0;
     uint32_t hostKeyLease    = 0;
     uint32_t grantedLease    = 0;
     uint32_t grantedKeyLease = 0;
-    bool     shouldFreeHost  = true;
 
-    SuccessOrExit(aError);
+    if (aError != kErrorNone)
+    {
+        aHost.Free();
+        ExitNow();
+    }
 
     hostLease       = aHost.GetLease();
     hostKeyLease    = aHost.GetKeyLease();
@@ -458,66 +452,86 @@
     grantedKeyLease = aHost.ShouldUseShortLeaseOption() ? grantedLease : aLeaseConfig.GrantKeyLease(hostKeyLease);
     grantedTtl      = aTtlConfig.GrantTtl(grantedLease, aHost.GetTtl());
 
+    existingHost = mHosts.RemoveMatching(aHost.GetFullName());
+
+    LogInfo("Committing update for %s host %s", (existingHost != nullptr) ? "existing" : "new", aHost.GetFullName());
+    LogInfo("    Granted lease:%lu, key-lease:%lu, ttl:%lu", ToUlong(grantedLease), ToUlong(grantedKeyLease),
+            ToUlong(grantedTtl));
+
     aHost.SetLease(grantedLease);
     aHost.SetKeyLease(grantedKeyLease);
     aHost.SetTtl(grantedTtl);
 
+    if (grantedKeyLease == 0)
+    {
+        VerifyOrExit(existingHost != nullptr);
+        LogInfo("Fully remove host %s", aHost.GetFullName());
+        ExitNow();
+    }
+
+    mHosts.Push(aHost);
+
     for (Service &service : aHost.mServices)
     {
-        service.mDescription->mLease    = grantedLease;
-        service.mDescription->mKeyLease = grantedKeyLease;
-        service.mDescription->mTtl      = grantedTtl;
-    }
+        service.mLease       = grantedLease;
+        service.mKeyLease    = grantedKeyLease;
+        service.mTtl         = grantedTtl;
+        service.mIsCommitted = true;
 
-    existingHost = mHosts.FindMatching(aHost.GetFullName());
-
-    if (aHost.GetLease() == 0)
-    {
-        if (aHost.GetKeyLease() == 0)
+#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
         {
-            LogInfo("Remove key of host %s", aHost.GetFullName());
-            RemoveHost(existingHost, kDeleteName, kDoNotNotifyServiceHandler);
-        }
-        else if (existingHost != nullptr)
-        {
-            existingHost->SetKeyLease(aHost.GetKeyLease());
-            RemoveHost(existingHost, kRetainName, kDoNotNotifyServiceHandler);
+            Service::Action action = Service::kAddNew;
 
-            for (Service &service : existingHost->mServices)
+            if (service.mIsDeleted)
             {
-                existingHost->RemoveService(&service, kRetainName, kDoNotNotifyServiceHandler);
+                action = Service::kRemoveButRetainName;
             }
-        }
-    }
-    else if (existingHost != nullptr)
-    {
-        SuccessOrExit(aError = existingHost->MergeServicesAndResourcesFrom(aHost));
-    }
-    else
-    {
-        AddHost(aHost);
-        shouldFreeHost = false;
+            else if ((existingHost != nullptr) && existingHost->HasService(service.GetInstanceName()))
+            {
+                action = Service::kUpdateExisting;
+            }
 
-        for (Service &service : aHost.GetServices())
-        {
-            service.mIsCommitted = true;
-            service.Log(Service::kAddNew);
-        }
-
-#if OPENTHREAD_CONFIG_SRP_SERVER_PORT_SWITCH_ENABLE
-        if (!mHasRegisteredAnyService && (mAddressMode == kAddressModeUnicast))
-        {
-            Settings::SrpServerInfo info;
-
-            mHasRegisteredAnyService = true;
-            info.SetPort(GetSocket().mSockName.mPort);
-            IgnoreError(Get<Settings>().Save(info));
+            service.Log(action);
         }
 #endif
     }
 
-    // Re-schedule the lease timer.
-    HandleLeaseTimer();
+    if (existingHost != nullptr)
+    {
+        // Move any existing service that is not included in the new
+        // update into `aHost`.
+
+        Service *existingService;
+
+        while ((existingService = existingHost->mServices.Pop()) != nullptr)
+        {
+            if (!aHost.HasService(existingService->GetInstanceName()))
+            {
+                aHost.AddService(*existingService);
+                existingService->Log(Service::kKeepUnchanged);
+            }
+            else
+            {
+                existingService->Free();
+            }
+        }
+    }
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_PORT_SWITCH_ENABLE
+    if (!mHasRegisteredAnyService && (mAddressMode == kAddressModeUnicast))
+    {
+        Settings::SrpServerInfo info;
+
+        mHasRegisteredAnyService = true;
+        info.SetPort(GetSocket().mSockName.mPort);
+        IgnoreError(Get<Settings>().Save(info));
+    }
+#endif
+
+    if (!aHost.IsDeleted())
+    {
+        mLeaseTimer.FireAtIfEarlier(Min(aHost.GetExpireTime(), aHost.GetKeyExpireTime()));
+    }
 
 exit:
     if (aMessageInfo != nullptr)
@@ -532,9 +546,9 @@
         }
     }
 
-    if (shouldFreeHost)
+    if (existingHost != nullptr)
     {
-        aHost.Free();
+        existingHost->Free();
     }
 }
 
@@ -746,8 +760,6 @@
     // Parse lease time and validate signature.
     SuccessOrExit(error = ProcessAdditionalSection(host, aMessage, aMetadata));
 
-    SuccessOrExit(error = ValidateServiceSubTypes(*host, aMetadata));
-
     HandleUpdate(*host, aMetadata);
 
 exit:
@@ -846,7 +858,7 @@
 
             // A "Delete All RRsets from a name" RR can only apply to a Service or Host Description.
 
-            if (!aHost.HasServiceInstance(name))
+            if (!aHost.HasService(name))
             {
                 // If host name is already set to a different name, `SetFullName()`
                 // will return `kErrorFailed`.
@@ -924,6 +936,7 @@
         const char                     *subServiceName;
         Service                        *service;
         bool                            isSubType;
+        bool                            isDelete;
 
         SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, serviceName, sizeof(serviceName)));
         VerifyOrExit(Dns::Name::IsSubDomainOf(serviceName, GetDomain()), error = kErrorSecurity);
@@ -944,11 +957,12 @@
                                                     instanceServiceName, sizeof(instanceServiceName)));
         instanceName.Append("%s.%s", instanceLabel, instanceServiceName);
 
-        VerifyOrExit(ptrRecord.GetClass() == Dns::ResourceRecord::kClassNone ||
-                         ptrRecord.GetClass() == aMetadata.mDnsZone.GetClass(),
-                     error = kErrorFailed);
+        // Class None indicates "Delete an RR from an RRset".
+        isDelete = (ptrRecord.GetClass() == Dns::ResourceRecord::kClassNone);
 
-        // Check if the `serviceName` is a subtype with the name
+        VerifyOrExit(isDelete || ptrRecord.GetClass() == aMetadata.mDnsZone.GetClass(), error = kErrorParse);
+
+        // Check if the `serviceName` is a sub-type with name
         // format: "<sub-label>._sub.<service-labels>.<domain>."
 
         subServiceName = StringFind(serviceName, kServiceSubTypeLabel, kStringCaseInsensitiveMatch);
@@ -965,22 +979,61 @@
         VerifyOrExit(Dns::Name::IsSubDomainOf(instanceName.AsCString(), isSubType ? subServiceName : serviceName),
                      error = kErrorFailed);
 
-        // Ensure the same service does not exist already.
-        VerifyOrExit(aHost.FindService(serviceName, instanceName.AsCString()) == nullptr, error = kErrorFailed);
+        // Find a matching existing service or allocate a new one.
 
-        service =
-            aHost.AddNewService(serviceName, instanceName.AsCString(), instanceLabel, isSubType, aMetadata.mRxTime);
-        VerifyOrExit(service != nullptr, error = kErrorNoBufs);
+        service = aHost.FindService(instanceName.AsCString());
 
-        // This RR is a "Delete an RR from an RRset" update when the CLASS is NONE.
-        service->mIsDeleted = (ptrRecord.GetClass() == Dns::ResourceRecord::kClassNone);
+        if (service == nullptr)
+        {
+            service = aHost.AddNewService(instanceName.AsCString(), instanceLabel, aMetadata.mRxTime);
+            VerifyOrExit(service != nullptr, error = kErrorNoBufs);
+        }
 
-        if (!service->mIsDeleted)
+        if (isSubType)
+        {
+            VerifyOrExit(!service->HasSubTypeServiceName(serviceName), error = kErrorFailed);
+
+            // Ignore a sub-type service delete.
+
+            if (!isDelete)
+            {
+                Heap::String *newSubTypeLabel = service->mSubTypes.PushBack();
+
+                VerifyOrExit(newSubTypeLabel != nullptr, error = kErrorNoBufs);
+                SuccessOrExit(error = newSubTypeLabel->Set(serviceName));
+            }
+        }
+        else
+        {
+            // Processed PTR record is the base service (not a
+            // sub-type). `mServiceName` is only set when base
+            // service is processed.
+
+            VerifyOrExit(service->mServiceName.IsNull(), error = kErrorFailed);
+            SuccessOrExit(error = service->mServiceName.Set(serviceName));
+            service->mIsDeleted = isDelete;
+        }
+
+        if (!isDelete)
         {
             SuccessOrExit(error = aHost.ProcessTtl(ptrRecord.GetTtl()));
         }
     }
 
+    // Verify that for all services, a PTR record was processed for
+    // the base service (`mServiceName` is set), and for a deleted
+    // service, no PTR record was seen adding a sub-type.
+
+    for (const Service &service : aHost.mServices)
+    {
+        VerifyOrExit(!service.mServiceName.IsNull(), error = kErrorParse);
+
+        if (service.mIsDeleted)
+        {
+            VerifyOrExit(service.mSubTypes.GetLength() == 0, error = kErrorParse);
+        }
+    }
+
 exit:
     if (error != kErrorNone)
     {
@@ -999,9 +1052,9 @@
 
     for (uint16_t numRecords = aMetadata.mDnsHeader.GetUpdateRecordCount(); numRecords > 0; numRecords--)
     {
-        RetainPtr<Service::Description> desc;
-        char                            name[Dns::Name::kMaxNameSize];
-        Dns::ResourceRecord             record;
+        char                name[Dns::Name::kMaxNameSize];
+        Dns::ResourceRecord record;
+        Service            *service;
 
         SuccessOrExit(error = Dns::Name::ReadName(aMessage, offset, name, sizeof(name)));
         SuccessOrExit(error = aMessage.Read(offset, record));
@@ -1011,12 +1064,12 @@
             // Delete All RRsets from a name.
             VerifyOrExit(IsValidDeleteAllRecord(record), error = kErrorFailed);
 
-            desc = aHost.FindServiceDescription(name);
+            service = aHost.FindService(name);
 
-            if (desc != nullptr)
+            if (service != nullptr)
             {
-                desc->ClearResources();
-                desc->mUpdateTime = aMetadata.mRxTime;
+                VerifyOrExit(!service->mParsedDeleteAllRrset);
+                service->mParsedDeleteAllRrset = true;
             }
 
             offset += record.GetSize();
@@ -1040,16 +1093,16 @@
             VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = kErrorSecurity);
             VerifyOrExit(aHost.Matches(hostName), error = kErrorFailed);
 
-            desc = aHost.FindServiceDescription(name);
-            VerifyOrExit(desc != nullptr, error = kErrorFailed);
+            service = aHost.FindService(name);
+            VerifyOrExit(service != nullptr, error = kErrorFailed);
 
-            // Make sure that this is the first SRV RR for this service description
-            VerifyOrExit(desc->mPort == 0, error = kErrorFailed);
-            desc->mTtl        = srvRecord.GetTtl();
-            desc->mPriority   = srvRecord.GetPriority();
-            desc->mWeight     = srvRecord.GetWeight();
-            desc->mPort       = srvRecord.GetPort();
-            desc->mUpdateTime = aMetadata.mRxTime;
+            VerifyOrExit(!service->mParsedSrv, error = kErrorParse);
+            service->mParsedSrv = true;
+
+            service->mTtl      = srvRecord.GetTtl();
+            service->mPriority = srvRecord.GetPriority();
+            service->mWeight   = srvRecord.GetWeight();
+            service->mPort     = srvRecord.GetPort();
         }
         else if (record.GetType() == Dns::ResourceRecord::kTypeTxt)
         {
@@ -1057,11 +1110,13 @@
 
             SuccessOrExit(error = aHost.ProcessTtl(record.GetTtl()));
 
-            desc = aHost.FindServiceDescription(name);
-            VerifyOrExit(desc != nullptr, error = kErrorFailed);
+            service = aHost.FindService(name);
+            VerifyOrExit(service != nullptr, error = kErrorFailed);
+
+            service->mParsedTxt = true;
 
             offset += sizeof(record);
-            SuccessOrExit(error = desc->SetTxtDataFromMessage(aMessage, offset, record.GetLength()));
+            SuccessOrExit(error = service->SetTxtDataFromMessage(aMessage, offset, record.GetLength()));
             offset += record.GetLength();
         }
         else
@@ -1070,20 +1125,15 @@
         }
     }
 
-    // Verify that all service descriptions on `aHost` are updated. Note
-    // that `mUpdateTime` on a new `Service::Description` is set to
-    // `GetNow().GetDistantPast()`.
-
-    for (Service &service : aHost.mServices)
+    for (const Service &service : aHost.mServices)
     {
-        VerifyOrExit(service.mDescription->mUpdateTime == aMetadata.mRxTime, error = kErrorFailed);
+        VerifyOrExit(service.mParsedDeleteAllRrset, error = kErrorFailed);
+        VerifyOrExit(service.mParsedSrv == service.mParsedTxt, error = kErrorFailed);
 
-        // Check that either both `mPort` and `mTxtData` are set
-        // (i.e., we saw both SRV and TXT record) or both are default
-        // (cleared) value (i.e., we saw neither of them).
-
-        VerifyOrExit((service.mDescription->mPort == 0) == service.mDescription->mTxtData.IsNull(),
-                     error = kErrorFailed);
+        if (!service.mIsDeleted)
+        {
+            VerifyOrExit(service.mParsedSrv, error = kErrorFailed);
+        }
     }
 
     aMetadata.mOffset = offset;
@@ -1235,68 +1285,6 @@
     return error;
 }
 
-Error Server::ValidateServiceSubTypes(Host &aHost, const MessageMetadata &aMetadata)
-{
-    Error error = kErrorNone;
-    Host *existingHost;
-
-    // Verify that there is a matching base type service for all
-    // sub-type services in `aHost` (which is from the received
-    // and parsed SRP Update message).
-
-    for (const Service &service : aHost.GetServices())
-    {
-        if (service.IsSubType() && (aHost.FindBaseService(service.GetInstanceName()) == nullptr))
-        {
-#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_WARN)
-            char subLabel[Dns::Name::kMaxLabelSize];
-
-            IgnoreError(service.GetServiceSubTypeLabel(subLabel, sizeof(subLabel)));
-            LogWarn("Message contains instance %s with subtype %s without base type", service.GetInstanceName(),
-                    subLabel);
-#endif
-
-            ExitNow(error = kErrorParse);
-        }
-    }
-
-    // SRP server must treat the update instructions for a service type
-    // and all its sub-types as atomic, i.e., when a service and its
-    // sub-types are being updated, whatever information appears in the
-    // SRP Update is the entirety of information about that service and
-    // its sub-types. Any previously registered sub-type that does not
-    // appear in a new SRP Update, must be removed.
-    //
-    // We go though the list of registered services for the same host
-    // and if the base service is included in the new SRP Update
-    // message, we add any previously registered service sub-type that
-    // does not appear in new Update message as "deleted".
-
-    existingHost = mHosts.FindMatching(aHost.GetFullName());
-    VerifyOrExit(existingHost != nullptr);
-
-    for (const Service &baseService : existingHost->GetServices())
-    {
-        if (baseService.IsSubType() || (aHost.FindBaseService(baseService.GetInstanceName()) == nullptr))
-        {
-            continue;
-        }
-
-        for (const Service &subService : existingHost->GetServices())
-        {
-            if (!subService.IsSubType() || !subService.MatchesInstanceName(baseService.GetInstanceName()))
-            {
-                continue;
-            }
-
-            SuccessOrExit(error = aHost.AddCopyOfServiceAsDeletedIfNotPresent(subService, aMetadata.mRxTime));
-        }
-    }
-
-exit:
-    return error;
-}
-
 void Server::HandleUpdate(Host &aHost, const MessageMetadata &aMetadata)
 {
     Error error = kErrorNone;
@@ -1315,14 +1303,21 @@
     // when removing a host. We copy and append any missing services to
     // `aHost` from the `existingHost` and mark them as deleted.
 
-    for (Service &service : existingHost->mServices)
+    for (const Service &existingService : existingHost->mServices)
     {
-        if (service.mIsDeleted)
+        Service *service;
+
+        if (existingService.mIsDeleted || aHost.HasService(existingService.GetInstanceName()))
         {
             continue;
         }
 
-        SuccessOrExit(error = aHost.AddCopyOfServiceAsDeletedIfNotPresent(service, aMetadata.mRxTime));
+        service = aHost.AddNewService(existingService.GetInstanceName(), existingService.GetInstanceLabel(),
+                                      aMetadata.mRxTime);
+        VerifyOrExit(service != nullptr, error = kErrorNoBufs);
+
+        SuccessOrExit(error = service->mServiceName.Set(existingService.GetServiceName()));
+        service->mIsDeleted = true;
     }
 
 exit:
@@ -1337,7 +1332,7 @@
         uint8_t             numAddrs;
         const Ip6::Address *addrs;
 
-        LogInfo("Processed DNS update info");
+        LogInfo("Processed SRP update info");
         LogInfo("    Host:%s", aHost.GetFullName());
         LogInfo("    Lease:%lu, key-lease:%lu, ttl:%lu", ToUlong(aHost.GetLease()), ToUlong(aHost.GetKeyLease()),
                 ToUlong(aHost.GetTtl()));
@@ -1358,19 +1353,22 @@
             }
         }
 
-        for (const Service &service : aHost.GetServices())
+        for (const Service &service : aHost.mServices)
         {
-            char subLabel[Dns::Name::kMaxLabelSize];
+            LogInfo("    %s service '%s'", service.IsDeleted() ? "Deleting" : "Adding", service.GetInstanceName());
 
-            IgnoreError(service.GetServiceSubTypeLabel(subLabel, sizeof(subLabel)));
+            for (const Heap::String &subType : service.mSubTypes)
+            {
+                char label[Dns::Name::kMaxLabelSize];
 
-            LogInfo("    %s service '%s'%s%s", service.IsDeleted() ? "Deleting" : "Adding", service.GetInstanceName(),
-                    service.IsSubType() ? " subtype:" : "", subLabel);
+                IgnoreError(Service::ParseSubTypeServiceName(subType.AsCString(), label, sizeof(label)));
+                LogInfo("      sub-type: %s", label);
+            }
         }
     }
     else
     {
-        LogInfo("Error %s processing received DNS update", ErrorToString(aError));
+        LogInfo("Error %s processing received SRP update", ErrorToString(aError));
     }
 #endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
 
@@ -1705,43 +1703,63 @@
 //---------------------------------------------------------------------------------------------------------------------
 // Server::Service
 
-Error Server::Service::Init(const char *aServiceName, Description &aDescription, bool aIsSubType, TimeMilli aUpdateTime)
+Error Server::Service::Init(const char *aInstanceName, const char *aInstanceLabel, Host &aHost, TimeMilli aUpdateTime)
 {
-    mDescription.Reset(&aDescription);
+    Error error;
+
     mNext        = nullptr;
+    mHost        = &aHost;
+    mPriority    = 0;
+    mWeight      = 0;
+    mTtl         = 0;
+    mPort        = 0;
+    mLease       = 0;
+    mKeyLease    = 0;
     mUpdateTime  = aUpdateTime;
     mIsDeleted   = false;
-    mIsSubType   = aIsSubType;
     mIsCommitted = false;
 
-    return mServiceName.Set(aServiceName);
+    mParsedDeleteAllRrset = false;
+    mParsedSrv            = false;
+    mParsedTxt            = false;
+
+    SuccessOrExit(error = mInstanceLabel.Set(aInstanceLabel));
+    error = mInstanceName.Set(aInstanceName);
+
+exit:
+    return error;
 }
 
-Error Server::Service::GetServiceSubTypeLabel(char *aLabel, uint8_t aMaxSize) const
+const char *Server::Service::GetSubTypeServiceNameAt(uint16_t aIndex) const
 {
-    Error       error       = kErrorNone;
-    const char *serviceName = GetServiceName();
-    const char *subServiceName;
+    const Heap::String *subType = mSubTypes.At(aIndex);
+
+    return (subType == nullptr) ? nullptr : subType->AsCString();
+}
+
+Error Server::Service::ParseSubTypeServiceName(const char *aSubTypeServiceName, char *aLabel, uint8_t aLabelSize)
+{
+    Error       error = kErrorNone;
+    const char *subPos;
     uint8_t     labelLength;
 
-    memset(aLabel, 0, aMaxSize);
+    aLabel[0] = kNullChar;
 
-    VerifyOrExit(IsSubType(), error = kErrorInvalidArgs);
+    subPos = StringFind(aSubTypeServiceName, kServiceSubTypeLabel, kStringCaseInsensitiveMatch);
+    VerifyOrExit(subPos != nullptr, error = kErrorInvalidArgs);
 
-    subServiceName = StringFind(serviceName, kServiceSubTypeLabel, kStringCaseInsensitiveMatch);
-    OT_ASSERT(subServiceName != nullptr);
-
-    if (subServiceName - serviceName < aMaxSize)
+    if (subPos - aSubTypeServiceName < aLabelSize)
     {
-        labelLength = static_cast<uint8_t>(subServiceName - serviceName);
+        labelLength = static_cast<uint8_t>(subPos - aSubTypeServiceName);
     }
     else
     {
-        labelLength = aMaxSize - 1;
+        labelLength = aLabelSize - 1;
         error       = kErrorNoBufs;
     }
 
-    memcpy(aLabel, serviceName, labelLength);
+    memcpy(aLabel, aSubTypeServiceName, labelLength);
+    aLabel[labelLength] = kNullChar;
 
 exit:
     return error;
@@ -1752,13 +1770,10 @@
     OT_ASSERT(!mIsDeleted);
     OT_ASSERT(!GetHost().IsDeleted());
 
-    return mUpdateTime + Time::SecToMsec(mDescription->mLease);
+    return mUpdateTime + Time::SecToMsec(mLease);
 }
 
-TimeMilli Server::Service::GetKeyExpireTime(void) const
-{
-    return mUpdateTime + Time::SecToMsec(mDescription->mKeyLease);
-}
+TimeMilli Server::Service::GetKeyExpireTime(void) const { return mUpdateTime + Time::SecToMsec(mKeyLease); }
 
 void Server::Service::GetLeaseInfo(LeaseInfo &aLeaseInfo) const
 {
@@ -1781,42 +1796,32 @@
     }
 }
 
-bool Server::Service::MatchesInstanceName(const char *aInstanceName) const
-{
-    return StringMatch(mDescription->mInstanceName.AsCString(), aInstanceName, kStringCaseInsensitiveMatch);
-}
+bool Server::Service::MatchesInstanceName(const char *aInstanceName) const { return Matches(aInstanceName); }
 
 bool Server::Service::MatchesServiceName(const char *aServiceName) const
 {
     return StringMatch(mServiceName.AsCString(), aServiceName, kStringCaseInsensitiveMatch);
 }
 
-bool Server::Service::MatchesFlags(Flags aFlags) const
+bool Server::Service::Matches(const char *aInstanceName) const
 {
-    bool matches = false;
+    return StringMatch(mInstanceName.AsCString(), aInstanceName, kStringCaseInsensitiveMatch);
+}
 
-    if (IsSubType())
+bool Server::Service::HasSubTypeServiceName(const char *aSubTypeServiceName) const
+{
+    bool has = false;
+
+    for (const Heap::String &subType : mSubTypes)
     {
-        VerifyOrExit(aFlags & kFlagSubType);
-    }
-    else
-    {
-        VerifyOrExit(aFlags & kFlagBaseType);
+        if (StringMatch(subType.AsCString(), aSubTypeServiceName, kStringCaseInsensitiveMatch))
+        {
+            has = true;
+            break;
+        }
     }
 
-    if (IsDeleted())
-    {
-        VerifyOrExit(aFlags & kFlagDeleted);
-    }
-    else
-    {
-        VerifyOrExit(aFlags & kFlagActive);
-    }
-
-    matches = true;
-
-exit:
-    return matches;
+    return has;
 }
 
 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
@@ -1825,20 +1830,20 @@
     static const char *const kActionStrings[] = {
         "Add new",                   // (0) kAddNew
         "Update existing",           // (1) kUpdateExisting
-        "Remove but retain name of", // (2) kRemoveButRetainName
-        "Fully remove",              // (3) kFullyRemove
-        "LEASE expired for",         // (4) kLeaseExpired
-        "KEY LEASE expired for",     // (5) kKeyLeaseExpired
+        "Keep unchanged",            // (2) kKeepUnchanged
+        "Remove but retain name of", // (3) kRemoveButRetainName
+        "Fully remove",              // (4) kFullyRemove
+        "LEASE expired for",         // (5) kLeaseExpired
+        "KEY LEASE expired for",     // (6) kKeyLeaseExpired
     };
 
-    char subLabel[Dns::Name::kMaxLabelSize];
-
     static_assert(0 == kAddNew, "kAddNew value is incorrect");
     static_assert(1 == kUpdateExisting, "kUpdateExisting value is incorrect");
-    static_assert(2 == kRemoveButRetainName, "kRemoveButRetainName value is incorrect");
-    static_assert(3 == kFullyRemove, "kFullyRemove value is incorrect");
-    static_assert(4 == kLeaseExpired, "kLeaseExpired value is incorrect");
-    static_assert(5 == kKeyLeaseExpired, "kKeyLeaseExpired value is incorrect");
+    static_assert(2 == kKeepUnchanged, "kKeepUnchanged value is incorrect");
+    static_assert(3 == kRemoveButRetainName, "kRemoveButRetainName value is incorrect");
+    static_assert(4 == kFullyRemove, "kFullyRemove value is incorrect");
+    static_assert(5 == kLeaseExpired, "kLeaseExpired value is incorrect");
+    static_assert(6 == kKeyLeaseExpired, "kKeyLeaseExpired value is incorrect");
 
     // We only log if the `Service` is marked as committed. This
     // ensures that temporary `Service` entries associated with a
@@ -1847,67 +1852,22 @@
 
     if (mIsCommitted)
     {
-        IgnoreError(GetServiceSubTypeLabel(subLabel, sizeof(subLabel)));
+        LogInfo("%s service '%s'", kActionStrings[aAction], GetInstanceName());
 
-        LogInfo("%s service '%s'%s%s", kActionStrings[aAction], GetInstanceName(), IsSubType() ? " subtype:" : "",
-                subLabel);
+        for (const Heap::String &subType : mSubTypes)
+        {
+            char label[Dns::Name::kMaxLabelSize];
+
+            IgnoreError(ParseSubTypeServiceName(subType.AsCString(), label, sizeof(label)));
+            LogInfo("  sub-type: %s", subType.AsCString());
+        }
     }
 }
 #else
 void Server::Service::Log(Action) const {}
 #endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
 
-//---------------------------------------------------------------------------------------------------------------------
-// Server::Service::Description
-
-Error Server::Service::Description::Init(const char *aInstanceName, const char *aInstanceLabel, Host &aHost)
-{
-    Error error;
-
-    mNext       = nullptr;
-    mHost       = &aHost;
-    mPriority   = 0;
-    mWeight     = 0;
-    mTtl        = 0;
-    mPort       = 0;
-    mLease      = 0;
-    mKeyLease   = 0;
-    mUpdateTime = TimerMilli::GetNow().GetDistantPast();
-    mTxtData.Free();
-
-    SuccessOrExit(error = mInstanceLabel.Set(aInstanceLabel));
-    error = mInstanceName.Set(aInstanceName);
-
-exit:
-    return error;
-}
-
-bool Server::Service::Description::Matches(const char *aInstanceName) const
-{
-    return StringMatch(mInstanceName.AsCString(), aInstanceName, kStringCaseInsensitiveMatch);
-}
-
-void Server::Service::Description::ClearResources(void)
-{
-    mPort = 0;
-    mTxtData.Free();
-}
-
-void Server::Service::Description::TakeResourcesFrom(Description &aDescription)
-{
-    mTxtData.SetFrom(static_cast<Heap::Data &&>(aDescription.mTxtData));
-
-    mPriority = aDescription.mPriority;
-    mWeight   = aDescription.mWeight;
-    mPort     = aDescription.mPort;
-
-    mTtl        = aDescription.mTtl;
-    mLease      = aDescription.mLease;
-    mKeyLease   = aDescription.mKeyLease;
-    mUpdateTime = TimerMilli::GetNow();
-}
-
-Error Server::Service::Description::SetTxtDataFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength)
+Error Server::Service::SetTxtDataFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength)
 {
     Error error;
 
@@ -2022,64 +1982,28 @@
     return error;
 }
 
-const Server::Service *Server::Host::FindNextService(const Service *aPrevService,
-                                                     Service::Flags aFlags,
-                                                     const char    *aServiceName,
-                                                     const char    *aInstanceName) const
+const Server::Service *Server::Host::GetNextService(const Service *aPrevService) const
 {
-    const Service *service = (aPrevService == nullptr) ? GetServices().GetHead() : aPrevService->GetNext();
-
-    for (; service != nullptr; service = service->GetNext())
-    {
-        if (!service->MatchesFlags(aFlags))
-        {
-            continue;
-        }
-
-        if ((aServiceName != nullptr) && !service->MatchesServiceName(aServiceName))
-        {
-            continue;
-        }
-
-        if ((aInstanceName != nullptr) && !service->MatchesInstanceName(aInstanceName))
-        {
-            continue;
-        }
-
-        break;
-    }
-
-    return service;
+    return (aPrevService == nullptr) ? mServices.GetHead() : aPrevService->GetNext();
 }
 
-Server::Service *Server::Host::AddNewService(const char *aServiceName,
-                                             const char *aInstanceName,
+Server::Service *Server::Host::AddNewService(const char *aInstanceName,
                                              const char *aInstanceLabel,
-                                             bool        aIsSubType,
                                              TimeMilli   aUpdateTime)
 {
-    Service                        *service = nullptr;
-    RetainPtr<Service::Description> desc(FindServiceDescription(aInstanceName));
+    Service *service = Service::AllocateAndInit(aInstanceName, aInstanceLabel, *this, aUpdateTime);
 
-    if (desc == nullptr)
-    {
-        desc.Reset(Service::Description::AllocateAndInit(aInstanceName, aInstanceLabel, *this));
-        VerifyOrExit(desc != nullptr);
-    }
-
-    service = Service::AllocateAndInit(aServiceName, *desc, aIsSubType, aUpdateTime);
     VerifyOrExit(service != nullptr);
-
-    mServices.Push(*service);
+    AddService(*service);
 
 exit:
     return service;
 }
 
-Server::Service *Server::Host::AddNewService(const Service &aService, TimeMilli aUpdateTime)
+void Server::Host::AddService(Service &aService)
 {
-    return AddNewService(aService.GetServiceName(), aService.GetInstanceName(), aService.GetInstanceLabel(),
-                         aService.IsSubType(), aUpdateTime);
+    aService.mHost = this;
+    mServices.Push(aService);
 }
 
 void Server::Host::RemoveService(Service *aService, RetainName aRetainName, NotifyMode aNotifyServiceHandler)
@@ -2115,24 +2039,6 @@
     return;
 }
 
-Error Server::Host::AddCopyOfServiceAsDeletedIfNotPresent(const Service &aService, TimeMilli aUpdateTime)
-{
-    Error    error = kErrorNone;
-    Service *newService;
-
-    VerifyOrExit(FindService(aService.GetServiceName(), aService.GetInstanceName()) == nullptr);
-
-    newService = AddNewService(aService, aUpdateTime);
-
-    VerifyOrExit(newService != nullptr, error = kErrorNoBufs);
-
-    newService->mDescription->mUpdateTime = aUpdateTime;
-    newService->mIsDeleted                = true;
-
-exit:
-    return error;
-}
-
 void Server::Host::FreeAllServices(void)
 {
     while (!mServices.IsEmpty())
@@ -2143,106 +2049,14 @@
 
 void Server::Host::ClearResources(void) { mAddresses.Free(); }
 
-Error Server::Host::MergeServicesAndResourcesFrom(Host &aHost)
+Server::Service *Server::Host::FindService(const char *aInstanceName) { return mServices.FindMatching(aInstanceName); }
+
+const Server::Service *Server::Host::FindService(const char *aInstanceName) const
 {
-    // This method merges services, service descriptions, and other
-    // resources from another `aHost` into current host. It can
-    // possibly take ownership of some items from `aHost`.
-
-    Error error = kErrorNone;
-
-    LogInfo("Update host %s", GetFullName());
-
-    mAddresses.TakeFrom(static_cast<Heap::Array<Ip6::Address> &&>(aHost.mAddresses));
-    mKeyRecord  = aHost.mKeyRecord;
-    mTtl        = aHost.mTtl;
-    mLease      = aHost.mLease;
-    mKeyLease   = aHost.mKeyLease;
-    mUpdateTime = TimerMilli::GetNow();
-
-    for (Service &service : aHost.mServices)
-    {
-        Service *existingService = FindService(service.GetServiceName(), service.GetInstanceName());
-        Service *newService;
-
-        if (service.mIsDeleted)
-        {
-            // `RemoveService()` does nothing if `existingService` is `nullptr`.
-            RemoveService(existingService, kRetainName, kDoNotNotifyServiceHandler);
-            continue;
-        }
-
-        // Add/Merge `service` into the existing service or a allocate a new one
-
-        newService = (existingService != nullptr) ? existingService : AddNewService(service, service.GetUpdateTime());
-
-        VerifyOrExit(newService != nullptr, error = kErrorNoBufs);
-
-        newService->mIsDeleted   = false;
-        newService->mIsCommitted = true;
-        newService->mUpdateTime  = TimerMilli::GetNow();
-
-        if (!service.mIsSubType)
-        {
-            // (1) Service description is shared across a base type and all its subtypes.
-            // (2) `TakeResourcesFrom()` releases resources pinned to its argument.
-            // Therefore, make sure the function is called only for the base type.
-            newService->mDescription->TakeResourcesFrom(*service.mDescription);
-        }
-
-        newService->Log((existingService != nullptr) ? Service::kUpdateExisting : Service::kAddNew);
-    }
-
-exit:
-    return error;
+    return mServices.FindMatching(aInstanceName);
 }
 
-bool Server::Host::HasServiceInstance(const char *aInstanceName) const
-{
-    return (FindServiceDescription(aInstanceName) != nullptr);
-}
-
-const RetainPtr<Server::Service::Description> Server::Host::FindServiceDescription(const char *aInstanceName) const
-{
-    const Service::Description *desc = nullptr;
-
-    for (const Service &service : mServices)
-    {
-        if (service.mDescription->Matches(aInstanceName))
-        {
-            desc = service.mDescription.Get();
-            break;
-        }
-    }
-
-    return RetainPtr<Service::Description>(AsNonConst(desc));
-}
-
-RetainPtr<Server::Service::Description> Server::Host::FindServiceDescription(const char *aInstanceName)
-{
-    return AsNonConst(AsConst(this)->FindServiceDescription(aInstanceName));
-}
-
-const Server::Service *Server::Host::FindService(const char *aServiceName, const char *aInstanceName) const
-{
-    return FindNextService(/* aPrevService */ nullptr, kFlagsAnyService, aServiceName, aInstanceName);
-}
-
-Server::Service *Server::Host::FindService(const char *aServiceName, const char *aInstanceName)
-{
-    return AsNonConst(AsConst(this)->FindService(aServiceName, aInstanceName));
-}
-
-const Server::Service *Server::Host::FindBaseService(const char *aInstanceName) const
-{
-    return FindNextService(/*a PrevService */ nullptr, kFlagsBaseTypeServiceOnly, /* aServiceName */ nullptr,
-                           aInstanceName);
-}
-
-Server::Service *Server::Host::FindBaseService(const char *aInstanceName)
-{
-    return AsNonConst(AsConst(this)->FindBaseService(aInstanceName));
-}
+bool Server::Host::HasService(const char *aInstanceName) const { return mServices.ContainsMatching(aInstanceName); }
 
 Error Server::Host::AddIp6Address(const Ip6::Address &aIp6Address)
 {
diff --git a/src/core/net/srp_server.hpp b/src/core/net/srp_server.hpp
index ba5665b..25d1f75 100644
--- a/src/core/net/srp_server.hpp
+++ b/src/core/net/srp_server.hpp
@@ -188,37 +188,6 @@
 
     public:
         /**
-         * Represents the flags which indicates which services to include or exclude when searching in (or
-         * iterating over) the list of SRP services.
-         *
-         */
-        typedef otSrpServerServiceFlags Flags;
-
-        /**
-         * This `Flags` constant indicates to include base services (not a sub-type).
-         *
-         */
-        static constexpr Flags kFlagBaseType = OT_SRP_SERVER_SERVICE_FLAG_BASE_TYPE;
-
-        /**
-         * This `Flags` constant indicates to include sub-type services.
-         *
-         */
-        static constexpr Flags kFlagSubType = OT_SRP_SERVER_SERVICE_FLAG_SUB_TYPE;
-
-        /**
-         * This `Flags` constant indicates to include active (not deleted) services.
-         *
-         */
-        static constexpr Flags kFlagActive = OT_SRP_SERVER_SERVICE_FLAG_ACTIVE;
-
-        /**
-         * This `Flags` constant indicates to include deleted services.
-         *
-         */
-        static constexpr Flags kFlagDeleted = OT_SRP_SERVER_SERVICE_FLAG_DELETED;
-
-        /**
          * Tells if the SRP service has been deleted.
          *
          * A SRP service can be deleted but retains its name for future uses.
@@ -231,21 +200,12 @@
         bool IsDeleted(void) const { return mIsDeleted; }
 
         /**
-         * Indicates whether the SRP service is a sub-type.
-         *
-         * @retval TRUE    If the service is a sub-type.
-         * @retval FALSE   If the service is not a sub-type.
-         *
-         */
-        bool IsSubType(void) const { return mIsSubType; }
-
-        /**
          * Gets the full service instance name of the service.
          *
          * @returns  A pointer service instance name (as a null-terminated C string).
          *
          */
-        const char *GetInstanceName(void) const { return mDescription->GetInstanceName(); }
+        const char *GetInstanceName(void) const { return mInstanceName.AsCString(); }
 
         /**
          * Gets the service instance label of the service.
@@ -253,7 +213,7 @@
          * @returns  A pointer service instance label (as a null-terminated C string).
          *
          */
-        const char *GetInstanceLabel(void) const { return mDescription->GetInstanceLabel(); }
+        const char *GetInstanceLabel(void) const { return mInstanceLabel.AsCString(); }
 
         /**
          * Gets the full service name of the service.
@@ -264,23 +224,52 @@
         const char *GetServiceName(void) const { return mServiceName.AsCString(); }
 
         /**
-         * Gets the sub-type label from service name.
+         * Gets number of sub-types of this service.
          *
-         * The full service name for a sub-type service follows "<sub-label>._sub.<service-labels>.<domain>.". This
-         * method copies the `<sub-label>` into the @p aLabel buffer.
-         *
-         * The @p aLabel is ensured to always be null-terminated after returning even in case of failure.
-         *
-         * @param[out] aLabel        A pointer to a buffer to copy the sub-type label name.
-         * @param[in]  aMaxSize      Maximum size of @p aLabel buffer.
-         *
-         * @retval kErrorNone         @p aLabel was updated successfully.
-         * @retval kErrorNoBufs       The sub-type label could not fit in @p aLabel buffer (number of chars from label
-         *                            that could fit are copied in @p aLabel ensuring it is null-terminated).
-         * @retval kErrorInvalidArgs  SRP service is not a sub-type.
+         * @returns The number of sub-types.
          *
          */
-        Error GetServiceSubTypeLabel(char *aLabel, uint8_t aMaxSize) const;
+        uint16_t GetNumberOfSubTypes(void) const { return mSubTypes.GetLength(); }
+
+        /**
+         * Gets the sub-type service name (full name) at a given index.
+         *
+         * The full service name for a sub-type service follows "<sub-label>._sub.<service-labels>.<domain>.".
+         *
+         * @param[in] aIndex   The index to get.
+         *
+         * @returns A pointer to sub-type service name at @p aIndex, or `nullptr` if none at this index.
+         *
+         */
+        const char *GetSubTypeServiceNameAt(uint16_t aIndex) const;
+
+        /**
+         * Indicates whether or not service has a given sub-type.
+         *
+         * @param[in] aSubTypeServiceName  The sub-type service name (full name).
+         *
+         * @retval TRUE   Service contains the sub-type @p aSubTypeServiceName.
+         * @retval FALSE  Service does not contain the sub-type @p aSubTypeServiceName.
+         *
+         */
+        bool HasSubTypeServiceName(const char *aSubTypeServiceName) const;
+
+        /**
+         * Parses a sub-type service name (full name) and extracts the sub-type label.
+         *
+         * The full service name for a sub-type service follows "<sub-label>._sub.<service-labels>.<domain>.".
+         *
+         * @param[in]  aSubTypeServiceName  A sub-type service name (full name).
+         * @param[out] aLabel               A pointer to a buffer to copy the extracted sub-type label.
+         * @param[in]  aLabelSize           Maximum size of @p aLabel buffer.
+         *
+         * @retval kErrorNone         Name was successfully parsed and @p aLabel was updated.
+         * @retval kErrorNoBufs       The sub-type label could not fit in @p aLabel buffer (number of chars from label
+         *                            that could fit are copied in @p aLabel ensuring it is null-terminated).
+         * @retval kErrorInvalidArgs  @p aSubTypeServiceName is not a valid sub-type format.
+         *
+         */
+        static Error ParseSubTypeServiceName(const char *aSubTypeServiceName, char *aLabel, uint8_t aLabelSize);
 
         /**
          * Returns the TTL of the service instance.
@@ -288,7 +277,7 @@
          * @returns The TTL of the service instance.
          *
          */
-        uint32_t GetTtl(void) const { return mDescription->mTtl; }
+        uint32_t GetTtl(void) const { return mTtl; }
 
         /**
          * Returns the port of the service instance.
@@ -296,7 +285,7 @@
          * @returns  The port of the service.
          *
          */
-        uint16_t GetPort(void) const { return mDescription->mPort; }
+        uint16_t GetPort(void) const { return mPort; }
 
         /**
          * Returns the weight of the service instance.
@@ -304,7 +293,7 @@
          * @returns  The weight of the service.
          *
          */
-        uint16_t GetWeight(void) const { return mDescription->mWeight; }
+        uint16_t GetWeight(void) const { return mWeight; }
 
         /**
          * Returns the priority of the service instance.
@@ -312,7 +301,7 @@
          * @returns  The priority of the service.
          *
          */
-        uint16_t GetPriority(void) const { return mDescription->mPriority; }
+        uint16_t GetPriority(void) const { return mPriority; }
 
         /**
          * Returns the TXT record data of the service instance.
@@ -320,7 +309,7 @@
          * @returns A pointer to the buffer containing the TXT record data.
          *
          */
-        const uint8_t *GetTxtData(void) const { return mDescription->mTxtData.GetBytes(); }
+        const uint8_t *GetTxtData(void) const { return mTxtData.GetBytes(); }
 
         /**
          * Returns the TXT record data length of the service instance.
@@ -328,7 +317,7 @@
          * @return The TXT record data length (number of bytes in buffer returned from `GetTxtData()`).
          *
          */
-        uint16_t GetTxtDataLength(void) const { return mDescription->mTxtData.GetLength(); }
+        uint16_t GetTxtDataLength(void) const { return mTxtData.GetLength(); }
 
         /**
          * Returns the host which the service instance reside on.
@@ -336,7 +325,7 @@
          * @returns  A reference to the host instance.
          *
          */
-        const Host &GetHost(void) const { return *mDescription->mHost; }
+        const Host &GetHost(void) const { return *mHost; }
 
         /**
          * Returns the LEASE time of the service.
@@ -344,7 +333,7 @@
          * @returns  The LEASE time in seconds.
          *
          */
-        uint32_t GetLease(void) const { return mDescription->mLease; }
+        uint32_t GetLease(void) const { return mLease; }
 
         /**
          * Returns the KEY-LEASE time of the key of the service.
@@ -352,7 +341,7 @@
          * @returns  The KEY-LEASE time in seconds.
          *
          */
-        uint32_t GetKeyLease(void) const { return mDescription->mKeyLease; }
+        uint32_t GetKeyLease(void) const { return mKeyLease; }
 
         /**
          * Returns the expire time (in milliseconds) of the service.
@@ -402,55 +391,41 @@
         bool MatchesServiceName(const char *aServiceName) const;
 
     private:
-        struct Description : public LinkedListEntry<Description>,
-                             public Heap::Allocatable<Description>,
-                             public RetainCountable,
-                             private NonCopyable
-        {
-            Error       Init(const char *aInstanceName, const char *aInstanceLabel, Host &aHost);
-            const char *GetInstanceName(void) const { return mInstanceName.AsCString(); }
-            const char *GetInstanceLabel(void) const { return mInstanceLabel.AsCString(); }
-            bool        Matches(const char *aInstanceName) const;
-            void        ClearResources(void);
-            void        TakeResourcesFrom(Description &aDescription);
-            Error       SetTxtDataFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength);
-
-            Description *mNext;
-            Heap::String mInstanceName;
-            Heap::String mInstanceLabel;
-            Host        *mHost;
-            Heap::Data   mTxtData;
-            uint16_t     mPriority;
-            uint16_t     mWeight;
-            uint16_t     mPort;
-            uint32_t     mTtl;      // The TTL in seconds.
-            uint32_t     mLease;    // The LEASE time in seconds.
-            uint32_t     mKeyLease; // The KEY-LEASE time in seconds.
-            TimeMilli    mUpdateTime;
-        };
-
         enum Action : uint8_t
         {
             kAddNew,
             kUpdateExisting,
+            kKeepUnchanged,
             kRemoveButRetainName,
             kFullyRemove,
             kLeaseExpired,
             kKeyLeaseExpired,
         };
 
-        Error Init(const char *aServiceName, Description &aDescription, bool aIsSubType, TimeMilli aUpdateTime);
-        bool  MatchesFlags(Flags aFlags) const;
-        const TimeMilli &GetUpdateTime(void) const { return mUpdateTime; }
-        void             Log(Action aAction) const;
+        Error Init(const char *aInstanceName, const char *aInstanceLabel, Host &aHost, TimeMilli aUpdateTime);
+        Error SetTxtDataFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength);
+        bool  Matches(const char *aInstanceName) const;
+        void  Log(Action aAction) const;
 
-        Heap::String           mServiceName;
-        RetainPtr<Description> mDescription;
-        Service               *mNext;
-        TimeMilli              mUpdateTime;
-        bool                   mIsDeleted : 1;
-        bool                   mIsSubType : 1;
-        bool                   mIsCommitted : 1;
+        Service                  *mNext;
+        Heap::String              mInstanceName;
+        Heap::String              mInstanceLabel;
+        Heap::String              mServiceName;
+        Heap::Array<Heap::String> mSubTypes;
+        Host                     *mHost;
+        Heap::Data                mTxtData;
+        uint16_t                  mPriority;
+        uint16_t                  mWeight;
+        uint16_t                  mPort;
+        uint32_t                  mTtl;      // In seconds
+        uint32_t                  mLease;    // In seconds
+        uint32_t                  mKeyLease; // In seconds
+        TimeMilli                 mUpdateTime;
+        bool                      mIsDeleted : 1;
+        bool                      mIsCommitted : 1;
+        bool                      mParsedDeleteAllRrset : 1;
+        bool                      mParsedSrv : 1;
+        bool                      mParsedTxt : 1;
     };
 
     /**
@@ -568,21 +543,15 @@
          */
         const LinkedList<Service> &GetServices(void) const { return mServices; }
 
-        /**
-         * Finds the next matching service on the host.
+        /*
+         * Returns the next service.
          *
          * @param[in] aPrevService   A pointer to the previous service or `nullptr` to start from beginning of the list.
-         * @param[in] aFlags         Flags indicating which services to include (base/sub-type, active/deleted).
-         * @param[in] aServiceName   The service name to match. Set to `nullptr` to accept any name.
-         * @param[in] aInstanceName  The service instance name to match. Set to `nullptr` to accept any name.
          *
-         * @returns  A pointer to the next matching service or `nullptr` if no matching service could be found.
+         * @returns  A pointer to the next service or `nullptr` if no more services can be found.
          *
          */
-        const Service *FindNextService(const Service *aPrevService,
-                                       Service::Flags aFlags        = kFlagsAnyService,
-                                       const char    *aServiceName  = nullptr,
-                                       const char    *aInstanceName = nullptr) const;
+        const Service *GetNextService(const Service *aPrevService) const;
 
         /**
          * Tells whether the host matches a given full name.
@@ -607,26 +576,15 @@
         bool  ShouldUseShortLeaseOption(void) const { return mUseShortLeaseOption; }
         Error ProcessTtl(uint32_t aTtl);
 
-        LinkedList<Service> &GetServices(void) { return mServices; }
-        Service             *AddNewService(const char *aServiceName,
-                                           const char *aInstanceName,
-                                           const char *aInstanceLabel,
-                                           bool        aIsSubType,
-                                           TimeMilli   aUpdateTime);
-        Service             *AddNewService(const Service &aService, TimeMilli aUpdateTime);
-        void                 RemoveService(Service *aService, RetainName aRetainName, NotifyMode aNotifyServiceHandler);
-        Error                AddCopyOfServiceAsDeletedIfNotPresent(const Service &aService, TimeMilli aUpdateTime);
-        void                 FreeAllServices(void);
-        void                 ClearResources(void);
-        Error                MergeServicesAndResourcesFrom(Host &aHost);
-        Error                AddIp6Address(const Ip6::Address &aIp6Address);
-        bool                 HasServiceInstance(const char *aInstanceName) const;
-        RetainPtr<Service::Description>       FindServiceDescription(const char *aInstanceName);
-        const RetainPtr<Service::Description> FindServiceDescription(const char *aInstanceName) const;
-        Service                              *FindService(const char *aServiceName, const char *aInstanceName);
-        const Service                        *FindService(const char *aServiceName, const char *aInstanceName) const;
-        Service                              *FindBaseService(const char *aInstanceName);
-        const Service                        *FindBaseService(const char *aInstanceName) const;
+        Service       *AddNewService(const char *aInstanceName, const char *aInstanceLabel, TimeMilli aUpdateTime);
+        void           AddService(Service &aService);
+        void           RemoveService(Service *aService, RetainName aRetainName, NotifyMode aNotifyServiceHandler);
+        bool           HasService(const char *aInstanceName) const;
+        Service       *FindService(const char *aInstanceName);
+        const Service *FindService(const char *aInstanceName) const;
+        void           FreeAllServices(void);
+        void           ClearResources(void);
+        Error          AddIp6Address(const Ip6::Address &aIp6Address);
 
         Host                     *mNext;
         Heap::String              mFullName;
@@ -685,36 +643,6 @@
     };
 
     /**
-     * This constant defines a `Service::Flags` combination accepting any service (base/sub-type, active/deleted).
-     *
-     */
-    static constexpr Service::Flags kFlagsAnyService = OT_SRP_SERVER_FLAGS_ANY_SERVICE;
-
-    /**
-     * This constant defines a `Service::Flags` combination accepting base services only.
-     *
-     */
-    static constexpr Service::Flags kFlagsBaseTypeServiceOnly = OT_SRP_SERVER_FLAGS_BASE_TYPE_SERVICE_ONLY;
-
-    /**
-     * This constant defines a `Service::Flags` combination accepting sub-type services only.
-     *
-     */
-    static constexpr Service::Flags kFlagsSubTypeServiceOnly = OT_SRP_SERVER_FLAGS_SUB_TYPE_SERVICE_ONLY;
-
-    /**
-     * This constant defines a `Service::Flags` combination accepting any active services (not deleted).
-     *
-     */
-    static constexpr Service::Flags kFlagsAnyTypeActiveService = OT_SRP_SERVER_FLAGS_ANY_TYPE_ACTIVE_SERVICE;
-
-    /**
-     * This constant defines a `Service::Flags` combination accepting any deleted services.
-     *
-     */
-    static constexpr Service::Flags kFlagsAnyTypeDeletedService = OT_SRP_SERVER_FLAGS_ANY_TYPE_DELETED_SERVICE;
-
-    /**
      * Initializes the SRP server object.
      *
      * @param[in]  aInstance  A reference to the OpenThread instance.
@@ -902,6 +830,14 @@
     Error SetLeaseConfig(const LeaseConfig &aLeaseConfig);
 
     /**
+     * Returns the `Host` linked list.
+     *
+     * @returns The `Host` linked list.
+     *
+     */
+    const LinkedList<Host> &GetHosts(void) const { return mHosts; }
+
+    /**
      * Returns the next registered SRP host.
      *
      * @param[in]  aHost  The current SRP host; use `nullptr` to get the first SRP host.
@@ -1037,7 +973,6 @@
                           uint16_t                      aSigRdataOffset,
                           uint16_t                      aSigRdataLength,
                           const char                   *aSignerName) const;
-    Error ValidateServiceSubTypes(Host &aHost, const MessageMetadata &aMetadata);
     Error ProcessZoneSection(const Message &aMessage, MessageMetadata &aMetadata) const;
     Error ProcessHostDescriptionInstruction(Host                  &aHost,
                                             const Message         &aMessage,
@@ -1050,7 +985,6 @@
     static bool IsValidDeleteAllRecord(const Dns::ResourceRecord &aRecord);
 
     void        HandleUpdate(Host &aHost, const MessageMetadata &aMetadata);
-    void        AddHost(Host &aHost);
     void        RemoveHost(Host *aHost, RetainName aRetainName, NotifyMode aNotifyServiceHandler);
     bool        HasNameConflictsWith(Host &aHost) const;
     void        SendResponse(const Dns::UpdateHeader    &aHeader,